Skip to content

Commit

Permalink
Adding support for reading editorconfigs
Browse files Browse the repository at this point in the history
closes #630
  • Loading branch information
belav committed Jul 14, 2023
1 parent 65fbc9d commit df793ca
Show file tree
Hide file tree
Showing 18 changed files with 861 additions and 294 deletions.
2 changes: 2 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
<PackageVersion Include="BenchmarkDotNet.Annotations" Version="0.13.5" />
<PackageVersion Include="CliWrap" Version="3.3.3" />
<PackageVersion Include="DiffEngine" Version="6.5.7" />
<PackageVersion Include="editorconfig" Version="0.14.0" />
<PackageVersion Include="FluentAssertions" Version="5.10.3" />
<PackageVersion Include="Glob" Version="1.1.9" />
<PackageVersion Include="Ignore" Version="0.1.48" />
<PackageVersion Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="5.0.1" />
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="4.6.0" />
Expand Down
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
1 change: 1 addition & 0 deletions Src/CSharpier.Cli/CSharpier.Cli.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<PublicKey>002400000480000094000000060200000024000052534131000400000100010049d266ea1aeae09c0abfce28b8728314d4e4807126ee8bc56155a7ddc765997ed3522908b469ae133fc49ef0bfa957df36082c1c2e0ec8cdc05a4ca4dbd4e1bea6c17fc1008555e15af13a8fc871a04ffc38f5e60e6203bfaf01d16a2a283b90572ade79135801c1675bf38b7a5a60ec8353069796eb53a26ffdddc9ee1273be</PublicKey>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Glob" />
<PackageReference Include="Ignore" />
<PackageReference Include="Microsoft.Extensions.Logging" />
<PackageReference Include="System.CommandLine" />
Expand Down
76 changes: 27 additions & 49 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,31 @@ CancellationToken cancellationToken

for (var x = 0; x < commandLineOptions.DirectoryOrFilePaths.Length; x++)
{
var directoryOrFile = commandLineOptions.DirectoryOrFilePaths[x].Replace("\\", "/");
var originalDirectoryOrFile = commandLineOptions.OriginalDirectoryOrFilePaths[
x
].Replace("\\", "/");

var (ignoreFile, printerOptions) = await GetIgnoreFileAndPrinterOptions(
directoryOrFile,
var directoryOrFilePath = commandLineOptions.DirectoryOrFilePaths[x];
var isFile = fileSystem.File.Exists(directoryOrFilePath);
var isDirectory = fileSystem.Directory.Exists(directoryOrFilePath);
var directoryName = isFile
? fileSystem.Path.GetDirectoryName(directoryOrFilePath)
: isDirectory
? directoryOrFilePath
: string.Empty;
// TODO 1 if single file don't look in subtree? just look for the one for this file
var optionsProvider = await OptionsProvider.Create(
directoryName,
commandLineOptions.ConfigPath,
fileSystem,
logger,
cancellationToken
);

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

var formattingCache = await FormattingCacheFactory.InitializeAsync(
commandLineOptions,
printerOptions,
optionsProvider,
fileSystem,
cancellationToken
);
Expand All @@ -159,9 +168,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,11 +191,11 @@ await FormatPhysicalFile(
);
}

if (fileSystem.File.Exists(directoryOrFile))
if (isFile)
{
await FormatFile(directoryOrFile, originalDirectoryOrFile);
}
else if (fileSystem.Directory.Exists(directoryOrFile))
else if (isDirectory)
{
if (
!commandLineOptions.NoMSBuildCheck
Expand Down Expand Up @@ -232,38 +242,6 @@ await FormatPhysicalFile(
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 List<Section> Sections { get; init; }
public bool IsRoot { get; init; }
}
78 changes: 78 additions & 0 deletions Src/CSharpier.Cli/EditorConfig/ConfigFileParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using System.IO.Abstractions;
using System.Text.RegularExpressions;

namespace CSharpier.Cli.EditorConfig;

internal static class ConfigFileParser
{
private static readonly Regex SectionRegex = new(@"^\s*\[(([^#;]|\\#|\\;)+)\]\s*([#;].*)?$");
private static readonly Regex CommentRegex = new(@"^\s*[#;]");
private static readonly Regex PropertyRegex =
new(@"^\s*([\w\.\-_]+)\s*[=:]\s*(.*?)\s*([#;].*)?$");

private static readonly HashSet<string> KnownProperties =
new(
new[] { "indent_style", "indent_size", "tab_width", "max_line_length", "root", },
StringComparer.OrdinalIgnoreCase
);

public static ConfigFile Parse(string filePath, IFileSystem fileSystem)
{
var lines = fileSystem.File.ReadLines(filePath);

var isRoot = false;
var propertiesBySection = new Dictionary<string, Dictionary<string, string?>>();
var sectionName = string.Empty;
foreach (var line in lines)
{
if (string.IsNullOrWhiteSpace(line) || CommentRegex.IsMatch(line))
{
continue;
}

var propertyMatches = PropertyRegex.Matches(line);
if (propertyMatches.Count > 0)
{
var key = propertyMatches[0].Groups[1].Value.Trim().ToLowerInvariant();
var value = propertyMatches[0].Groups[2].Value.Trim();

if (KnownProperties.Contains(key))
{
value = value.ToLowerInvariant();
}

if (sectionName is "")
{
if (key == "root" && bool.TryParse(value, out var parsedValue))
{
isRoot = parsedValue;
}
}
else
{
propertiesBySection[sectionName][key] = value;
}
}
else
{
var sectionMatches = SectionRegex.Matches(line);
if (sectionMatches.Count <= 0)
{
continue;
}

sectionName = sectionMatches[0].Groups[1].Value;
propertiesBySection[sectionName] = new Dictionary<string, string?>();
}
}

var directory = fileSystem.Path.GetDirectoryName(filePath);
return new ConfigFile
{
IsRoot = isRoot,
Sections = propertiesBySection
.Select(o => new Section(o.Key, directory, o.Value))
.ToList()
};
}
}
69 changes: 69 additions & 0 deletions Src/CSharpier.Cli/EditorConfig/EditorConfigParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using System.IO.Abstractions;

namespace CSharpier.Cli.EditorConfig;

internal static class EditorConfigParser
{
public static List<EditorConfigSections> GetAllForDirectory(
string directoryName,
IFileSystem fileSystem
)
{
if (directoryName is "")
{
return new List<EditorConfigSections>();
}

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

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

private static List<Section> FindSections(string filePath, IFileSystem fileSystem)
{
var editorConfigFiles = ParseConfigFiles(
fileSystem.Path.GetDirectoryName(filePath),
fileSystem
)
.Reverse()
.ToList();
return editorConfigFiles.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 df793ca

Please sign in to comment.