Skip to content

Commit

Permalink
Adding support for editorconfig (#927)
Browse files Browse the repository at this point in the history
* Adding support for reading editorconfigs

closes #630

* Some self review

* Fixing some issues. Switching to strong name signed glob

* Finishing up code changes

* Some self review

* Fixing tests when run on linux

* And more issues fixed

* Fixing last linux issue

* Use ini-parser for editorconfig. Clean up a couple things

* get these sorted

---------

Co-authored-by: Lasath Fernando <devel@lasath.org>
  • Loading branch information
belav and shocklateboy92 authored Sep 1, 2023
1 parent c1bd5db commit e3c5fdd
Show file tree
Hide file tree
Showing 24 changed files with 1,026 additions and 369 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,4 @@ Src/CSharpier.VSCode/.idea/prettier.xml

.idea/.idea.CSharpier/.idea/riderMarkupCache.xml
/Src/CSharpier.Benchmarks/BenchmarkDotNet.Artifacts/
/Src/CSharpier.Tests/TestResults
2 changes: 2 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
<PackageVersion Include="BenchmarkDotNet.Annotations" Version="0.13.5" />
<PackageVersion Include="CliWrap" Version="3.3.3" />
<PackageVersion Include="DiffEngine" Version="6.5.7" />
<PackageVersion Include="DotNet.Glob" Version="3.1.3" />
<PackageVersion Include="FluentAssertions" Version="5.10.3" />
<PackageVersion Include="GitHubActionsTestLogger" Version="2.3.2" />
<PackageVersion Include="Ignore" Version="0.1.48" />
<PackageVersion Include="ini-parser-netstandard" Version="2.5.2" />
<PackageVersion Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="5.0.1" />
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="4.6.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.6.0" />
Expand Down
2 changes: 1 addition & 1 deletion Scripts/RunLinuxTests.ps1
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# running this seems to screw up the nuget restore, but provides a way to figure out why a test is failing on linux while working on windows.
# you have to run this from the root, IE powershell ./Scripts/RunLinuxTests.ps1
# also a lot of these tests fail due to line endings in your local files being \r\n but the writeLine using \n
docker run --rm -v ${pwd}:/app -e "NormalizeLineEndings=1" -w /app/tests mcr.microsoft.com/dotnet/sdk:6.0 dotnet test /app/Src/CSharpier.Tests/CSharpier.Tests.csproj --logger:trx
docker run --rm -v ${pwd}:/app -e "NormalizeLineEndings=1" -w /app/tests mcr.microsoft.com/dotnet/sdk:7.0 dotnet test /app/Src/CSharpier.Tests/CSharpier.Tests.csproj --logger:trx

# gross way to run csharpier against the csharpier-repos
#docker run --rm -v ${pwd}:/app -e "NormalizeLineEndings=1" -w /app mcr.microsoft.com/dotnet/sdk:5.0 dotnet ./csharpier/Src/CSharpier/bin/Debug/net6.0/dotnet-csharpier.dll csharpier-repos --skip-write
1 change: 1 addition & 0 deletions Src/CSharpier.Cli.Tests/CliTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ public async Task Should_Format_Basic_File(string lineEnding)

var result = await new CsharpierProcess().WithArguments("BasicFile.cs").ExecuteAsync();

result.ErrorOutput.Should().BeNullOrEmpty();
result.Output.Should().StartWith("Formatted 1 files in ");
result.ExitCode.Should().Be(0);
(await this.ReadAllTextAsync("BasicFile.cs")).Should().Be(formattedContent);
Expand Down
2 changes: 2 additions & 0 deletions Src/CSharpier.Cli/CSharpier.Cli.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
<PublicKey>002400000480000094000000060200000024000052534131000400000100010049d266ea1aeae09c0abfce28b8728314d4e4807126ee8bc56155a7ddc765997ed3522908b469ae133fc49ef0bfa957df36082c1c2e0ec8cdc05a4ca4dbd4e1bea6c17fc1008555e15af13a8fc871a04ffc38f5e60e6203bfaf01d16a2a283b90572ade79135801c1675bf38b7a5a60ec8353069796eb53a26ffdddc9ee1273be</PublicKey>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DotNet.Glob" />
<PackageReference Include="Ignore" />
<PackageReference Include="ini-parser-netstandard" />
<PackageReference Include="Microsoft.Extensions.Logging" />
<PackageReference Include="System.CommandLine" />
<PackageReference Include="System.IO.Abstractions" />
Expand Down
99 changes: 40 additions & 59 deletions Src/CSharpier.Cli/CommandLineFormatter.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
using System.Text;
using CSharpier.Cli.Options;
using System.Diagnostics;
using System.IO.Abstractions;
using CSharpier.Utilities;
using Microsoft.Extensions.Logging;

namespace CSharpier.Cli;

using System.Text;

internal static class CommandLineFormatter
{
public static async Task<int> Format(
Expand All @@ -31,8 +31,8 @@ CancellationToken cancellationToken
console.InputEncoding
);

var (ignoreFile, printerOptions) = await GetIgnoreFileAndPrinterOptions(
filePath,
var optionsProvider = await OptionsProvider.Create(
fileSystem.Path.GetDirectoryName(filePath),
commandLineOptions.ConfigPath,
fileSystem,
logger,
Expand All @@ -41,7 +41,7 @@ CancellationToken cancellationToken

if (
!GeneratedCodeUtilities.IsGeneratedCodeFile(filePath)
&& !ignoreFile.IsIgnored(filePath)
&& !optionsProvider.IsIgnored(filePath)
)
{
var fileIssueLogger = new FileIssueLogger(
Expand All @@ -54,7 +54,7 @@ await PerformFormattingSteps(
new StdOutFormattedFileWriter(console),
commandLineFormatterResult,
fileIssueLogger,
printerOptions,
optionsProvider.GetPrinterOptionsFor(filePath),
commandLineOptions,
FormattingCacheFactory.NullCache,
cancellationToken
Expand Down Expand Up @@ -129,22 +129,37 @@ CancellationToken cancellationToken

for (var x = 0; x < commandLineOptions.DirectoryOrFilePaths.Length; x++)
{
var directoryOrFile = commandLineOptions.DirectoryOrFilePaths[x].Replace("\\", "/");
var originalDirectoryOrFile = commandLineOptions.OriginalDirectoryOrFilePaths[
x
].Replace("\\", "/");
var directoryOrFilePath = commandLineOptions.DirectoryOrFilePaths[x].Replace("\\", "/");
var isFile = fileSystem.File.Exists(directoryOrFilePath);
var isDirectory = fileSystem.Directory.Exists(directoryOrFilePath);

var (ignoreFile, printerOptions) = await GetIgnoreFileAndPrinterOptions(
directoryOrFile,
if (!isFile && !isDirectory)
{
console.WriteErrorLine(
"There was no file or directory found at " + directoryOrFilePath
);
return 1;
}

var directoryName = isFile
? fileSystem.Path.GetDirectoryName(directoryOrFilePath)
: directoryOrFilePath;

var optionsProvider = await OptionsProvider.Create(
directoryName,
commandLineOptions.ConfigPath,
fileSystem,
logger,
cancellationToken
);

var originalDirectoryOrFile = commandLineOptions.OriginalDirectoryOrFilePaths[
x
].Replace("\\", "/");

var formattingCache = await FormattingCacheFactory.InitializeAsync(
commandLineOptions,
printerOptions,
optionsProvider,
fileSystem,
cancellationToken
);
Expand All @@ -159,9 +174,10 @@ CancellationToken cancellationToken

async Task FormatFile(string actualFilePath, string originalFilePath)
{
var printerOptions = optionsProvider.GetPrinterOptionsFor(actualFilePath);
if (
GeneratedCodeUtilities.IsGeneratedCodeFile(actualFilePath)
|| ignoreFile.IsIgnored(actualFilePath)
|| optionsProvider.IsIgnored(actualFilePath)
)
{
return;
Expand All @@ -181,28 +197,32 @@ await FormatPhysicalFile(
);
}

if (fileSystem.File.Exists(directoryOrFile))
if (isFile)
{
await FormatFile(directoryOrFile, originalDirectoryOrFile);
await FormatFile(directoryOrFilePath, originalDirectoryOrFile);
}
else if (fileSystem.Directory.Exists(directoryOrFile))
else if (isDirectory)
{
if (
!commandLineOptions.NoMSBuildCheck
&& HasMismatchedCliAndMsBuildVersions.Check(directoryOrFile, fileSystem, logger)
&& HasMismatchedCliAndMsBuildVersions.Check(
directoryOrFilePath,
fileSystem,
logger
)
)
{
return 1;
}

var tasks = fileSystem.Directory
.EnumerateFiles(directoryOrFile, "*.cs", SearchOption.AllDirectories)
.EnumerateFiles(directoryOrFilePath, "*.cs", SearchOption.AllDirectories)
.Select(o =>
{
var normalizedPath = o.Replace("\\", "/");
return FormatFile(
normalizedPath,
normalizedPath.Replace(directoryOrFile, originalDirectoryOrFile)
normalizedPath.Replace(directoryOrFilePath, originalDirectoryOrFile)
);
})
.ToArray();
Expand All @@ -218,52 +238,13 @@ await FormatPhysicalFile(
}
}
}
else
{
console.WriteErrorLine(
"There was no file or directory found at " + directoryOrFile
);
return 1;
}

await formattingCache.ResolveAsync(cancellationToken);
}

return 0;
}

private static async Task<(IgnoreFile, PrinterOptions)> GetIgnoreFileAndPrinterOptions(
string directoryOrFile,
string? configPath,
IFileSystem fileSystem,
ILogger logger,
CancellationToken cancellationToken
)
{
var isDirectory = fileSystem.Directory.Exists(directoryOrFile);

var baseDirectoryPath = isDirectory
? directoryOrFile
: fileSystem.Path.GetDirectoryName(directoryOrFile);

var ignoreFile = await IgnoreFile.Create(
baseDirectoryPath,
fileSystem,
logger,
cancellationToken
);

var printerOptions = configPath is null
? ConfigurationFileOptions.FindPrinterOptionsForDirectory(
baseDirectoryPath,
fileSystem,
logger
)
: ConfigurationFileOptions.CreatePrinterOptionsFromPath(configPath, fileSystem, logger);

return (ignoreFile, printerOptions);
}

private static async Task FormatPhysicalFile(
string actualFilePath,
string originalFilePath,
Expand Down
7 changes: 7 additions & 0 deletions Src/CSharpier.Cli/EditorConfig/ConfigFile.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace CSharpier.Cli.EditorConfig;

internal class ConfigFile
{
public required IReadOnlyCollection<Section> Sections { get; init; }
public bool IsRoot { get; init; }
}
24 changes: 24 additions & 0 deletions Src/CSharpier.Cli/EditorConfig/ConfigFileParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System.IO.Abstractions;
using IniParser;

namespace CSharpier.Cli.EditorConfig;

internal static class ConfigFileParser
{
public static ConfigFile Parse(string filePath, IFileSystem fileSystem)
{
var parser = new FileIniDataParser();

using var stream = fileSystem.File.OpenRead(filePath);
using var streamReader = new StreamReader(stream);
var configData = parser.ReadData(streamReader);

var directory = fileSystem.Path.GetDirectoryName(filePath);
var sections = new List<Section>();
foreach (var section in configData.Sections)
{
sections.Add(new Section(section, directory));
}
return new ConfigFile { IsRoot = configData.Global["root"] == "true", Sections = sections };
}
}
84 changes: 84 additions & 0 deletions Src/CSharpier.Cli/EditorConfig/EditorConfigParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using System.IO.Abstractions;

namespace CSharpier.Cli.EditorConfig;

internal static class EditorConfigParser
{
/// <summary>Finds all configs above the given directory as well as within the subtree of this directory</summary>
public static List<EditorConfigSections> FindForDirectoryName(
string directoryName,
IFileSystem fileSystem
)
{
if (directoryName is "")
{
return new List<EditorConfigSections>();
}

var directoryInfo = fileSystem.DirectoryInfo.FromDirectoryName(directoryName);
var editorConfigFiles = directoryInfo
.EnumerateFiles(".editorconfig", SearchOption.AllDirectories)
.ToList();

// already found any in this directory above
directoryInfo = directoryInfo.Parent;

while (directoryInfo is not null)
{
var file = fileSystem.FileInfo.FromFileName(
fileSystem.Path.Combine(directoryInfo.FullName, ".editorconfig")
);
if (file.Exists)
{
editorConfigFiles.Add(file);
}

directoryInfo = directoryInfo.Parent;
}

return editorConfigFiles
.Select(
o =>
new EditorConfigSections
{
DirectoryName = fileSystem.Path.GetDirectoryName(o.FullName),
SectionsIncludingParentFiles = FindSections(o.FullName, fileSystem)
}
)
.OrderByDescending(o => o.DirectoryName.Length)
.ToList();
}

private static List<Section> FindSections(string filePath, IFileSystem fileSystem)
{
return ParseConfigFiles(fileSystem.Path.GetDirectoryName(filePath), fileSystem)
.Reverse()
.SelectMany(configFile => configFile.Sections)
.ToList();
}

private static IEnumerable<ConfigFile> ParseConfigFiles(
string directoryPath,
IFileSystem fileSystem
)
{
var directory = fileSystem.DirectoryInfo.FromDirectoryName(directoryPath);
while (directory != null)
{
var potentialPath = fileSystem.Path.Combine(directory.FullName, ".editorconfig");
if (fileSystem.File.Exists(potentialPath))
{
var configFile = ConfigFileParser.Parse(potentialPath, fileSystem);

DebugLogger.Log(potentialPath);
yield return configFile;
if (configFile.IsRoot)
{
yield break;
}
}

directory = directory.Parent;
}
}
}
Loading

0 comments on commit e3c5fdd

Please sign in to comment.