From 270783a7f4da6923d54e920835d1ead2e1717fe8 Mon Sep 17 00:00:00 2001 From: Bela VanderVoort Date: Fri, 24 Jan 2025 12:56:07 -0600 Subject: [PATCH 1/3] Working on supporting passing .editorconfig via configPath closes #1456 --- Src/CSharpier.Cli.Tests/CliTests.cs | 23 +++++++++++ Src/CSharpier.Cli/Options/OptionsProvider.cs | 40 ++++++++++++++----- .../CommandLineFormatterTests.cs | 32 +++++++++++++++ 3 files changed, 86 insertions(+), 9 deletions(-) diff --git a/Src/CSharpier.Cli.Tests/CliTests.cs b/Src/CSharpier.Cli.Tests/CliTests.cs index d19e3f8e9..51a108cb1 100644 --- a/Src/CSharpier.Cli.Tests/CliTests.cs +++ b/Src/CSharpier.Cli.Tests/CliTests.cs @@ -133,6 +133,29 @@ public async Task Should_Support_Config_Path() result.Should().Be("var myVariable =\n someLongValue;\n"); } + [Test] + public async Task Should_Support_Config_Path_With_Editorconfig() + { + const string fileContent = "var myVariable = someLongValue;"; + var fileName = "TooWide.cs"; + await this.WriteFileAsync(fileName, fileContent); + await this.WriteFileAsync( + "config/.editorconfig", + """ + [*] + max_line_length = 10" + """ + ); + + await new CsharpierProcess() + .WithArguments("--config-path config/.editorconfig . ") + .ExecuteAsync(); + + var result = await this.ReadAllTextAsync(fileName); + + result.Should().Be("var myVariable =\n someLongValue;\n"); + } + [Test] public async Task Should_Return_Error_When_No_DirectoryOrFile_And_Not_Piping_StdIn() { diff --git a/Src/CSharpier.Cli/Options/OptionsProvider.cs b/Src/CSharpier.Cli/Options/OptionsProvider.cs index 00728a368..ea32b5ae7 100644 --- a/Src/CSharpier.Cli/Options/OptionsProvider.cs +++ b/Src/CSharpier.Cli/Options/OptionsProvider.cs @@ -37,11 +37,21 @@ public static async Task Create( bool limitConfigSearch = false ) { - var specifiedConfigFile = configPath is not null - ? ConfigFileParser.Create(configPath, fileSystem, logger) + var filename = configPath is not null ? Path.GetFileName(configPath) : null; + var csharpierConfigPath = configPath; + string? editorConfigPath = null; + + if (configPath is not null && Path.GetFileName(configPath) == ".editorconfig") + { + csharpierConfigPath = null; + editorConfigPath = configPath; + } + + var specifiedConfigFile = csharpierConfigPath is not null + ? ConfigFileParser.Create(csharpierConfigPath, fileSystem, logger) : null; - var csharpierConfigs = configPath is null + var csharpierConfigs = csharpierConfigPath is null ? ConfigFileParser.FindForDirectoryName( directoryName, fileSystem, @@ -56,12 +66,24 @@ public static async Task Create( try { - editorConfigSections = EditorConfigParser.FindForDirectoryName( - directoryName, - fileSystem, - limitConfigSearch, - ignoreFile - ); + // TODO #1456 can probably add tests to OptionsProviderTests instead of the other two spots + // TODO #1456 because there is a specified file, we should always be using that one + // kind of like above it passes a "specifiedConfigFile" + // right how this just uses the specified one to get the list, but then later + // the files that we format don't exist in the same directory so aren't formatted with it + editorConfigSections = editorConfigPath is null + ? EditorConfigParser.FindForDirectoryName( + directoryName, + fileSystem, + limitConfigSearch, + ignoreFile + ) + : EditorConfigParser.FindForDirectoryName( + Path.GetDirectoryName(editorConfigPath)!, + fileSystem, + true, + ignoreFile + ); } catch (Exception ex) { diff --git a/Src/CSharpier.Tests/CommandLineFormatterTests.cs b/Src/CSharpier.Tests/CommandLineFormatterTests.cs index c489514b0..8f54ec50e 100644 --- a/Src/CSharpier.Tests/CommandLineFormatterTests.cs +++ b/Src/CSharpier.Tests/CommandLineFormatterTests.cs @@ -716,6 +716,36 @@ public void Empty_Config_Files_Should_Log_Warning(string configFileName) .Be($"Warning The configuration file at {configPath} was empty."); } + [Test] + public void Should_Support_Config_Path() + { + var context = new TestContext(); + var configPath = context.WhenAFileExists("config/.csharpierrc", "printWidth: 10"); + context.WhenAFileExists("file1.cs", "var myVariable = someLongValue;"); + + this.Format(context, configPath: configPath); + + context.GetFileContent("file1.cs").Should().Be("var myVariable =\n someLongValue;\n"); + } + + [Test] + public void Should_Support_Config_Path_With_Editor_Config() + { + var context = new TestContext(); + var configPath = context.WhenAFileExists( + "config/.editorconfig", + """ + [*] + max_line_length = 10" + """ + ); + var fileName = context.WhenAFileExists("file1.cs", "var myVariable = someLongValue;"); + + this.Format(context, configPath: configPath); + + context.GetFileContent(fileName).Should().Be("var myVariable =\n someLongValue;\n"); + } + private FormatResult Format( TestContext context, bool skipWrite = false, @@ -724,6 +754,7 @@ private FormatResult Format( bool includeGenerated = false, bool compilationErrorsAsWarnings = false, string? standardInFileContents = null, + string? configPath = null, params string[] directoryOrFilePaths ) { @@ -746,6 +777,7 @@ params string[] directoryOrFilePaths .Format( new CommandLineOptions { + ConfigPath = configPath, DirectoryOrFilePaths = directoryOrFilePaths, OriginalDirectoryOrFilePaths = originalDirectoryOrFilePaths, SkipWrite = skipWrite, From 1322d0432752dd314dddf6b226a254f645066f7a Mon Sep 17 00:00:00 2001 From: Bela VanderVoort Date: Sat, 25 Jan 2025 09:41:37 -0600 Subject: [PATCH 2/3] Finishing up editorconfig support on configPath --- Src/CSharpier.Cli.Tests/CliTests.cs | 2 +- .../EditorConfig/EditorConfigSections.cs | 6 +++-- Src/CSharpier.Cli/EditorConfig/Globber.cs | 10 +++++++-- Src/CSharpier.Cli/EditorConfig/Section.cs | 7 ++++-- Src/CSharpier.Cli/Options/OptionsProvider.cs | 22 ++++++++++--------- .../CommandLineFormatterTests.cs | 2 +- docs/CLI.md | 3 +++ 7 files changed, 34 insertions(+), 18 deletions(-) diff --git a/Src/CSharpier.Cli.Tests/CliTests.cs b/Src/CSharpier.Cli.Tests/CliTests.cs index 51a108cb1..6d59d93a6 100644 --- a/Src/CSharpier.Cli.Tests/CliTests.cs +++ b/Src/CSharpier.Cli.Tests/CliTests.cs @@ -143,7 +143,7 @@ await this.WriteFileAsync( "config/.editorconfig", """ [*] - max_line_length = 10" + max_line_length = 10 """ ); diff --git a/Src/CSharpier.Cli/EditorConfig/EditorConfigSections.cs b/Src/CSharpier.Cli/EditorConfig/EditorConfigSections.cs index ddb82d51d..aab29c9cf 100644 --- a/Src/CSharpier.Cli/EditorConfig/EditorConfigSections.cs +++ b/Src/CSharpier.Cli/EditorConfig/EditorConfigSections.cs @@ -9,9 +9,11 @@ internal class EditorConfigSections public required string DirectoryName { get; init; } public required IReadOnlyCollection
SectionsIncludingParentFiles { get; init; } - public PrinterOptions? ConvertToPrinterOptions(string filePath) + public PrinterOptions? ConvertToPrinterOptions(string filePath, bool ignoreDirectory) { - var sections = this.SectionsIncludingParentFiles.Where(o => o.IsMatch(filePath)).ToList(); + var sections = this + .SectionsIncludingParentFiles.Where(o => o.IsMatch(filePath, ignoreDirectory)) + .ToList(); var resolvedConfiguration = new ResolvedConfiguration(sections); var formatter = diff --git a/Src/CSharpier.Cli/EditorConfig/Globber.cs b/Src/CSharpier.Cli/EditorConfig/Globber.cs index d8fe6709b..7edb928fe 100644 --- a/Src/CSharpier.Cli/EditorConfig/Globber.cs +++ b/Src/CSharpier.Cli/EditorConfig/Globber.cs @@ -10,13 +10,13 @@ internal static class Globber AllowSingleBraceSets = true, }; - public static GlobMatcher Create(string files, string directory) + public static GlobMatcher Create(string files, string? directory) { var pattern = FixGlob(files, directory); return GlobMatcher.Create(pattern, globOptions); } - private static string FixGlob(string glob, string directory) + private static string FixGlob(string glob, string? directory) { glob = glob.IndexOf('/') switch { @@ -24,6 +24,12 @@ private static string FixGlob(string glob, string directory) 0 => glob[1..], _ => glob, }; + + if (directory is null) + { + return glob; + } + directory = directory.Replace(@"\", "/"); if (!directory.EndsWith("/")) { diff --git a/Src/CSharpier.Cli/EditorConfig/Section.cs b/Src/CSharpier.Cli/EditorConfig/Section.cs index 96017dff3..e8f708831 100644 --- a/Src/CSharpier.Cli/EditorConfig/Section.cs +++ b/Src/CSharpier.Cli/EditorConfig/Section.cs @@ -5,6 +5,7 @@ namespace CSharpier.Cli.EditorConfig; internal class Section(SectionData section, string directory) { private readonly GlobMatcher matcher = Globber.Create(section.SectionName, directory); + private readonly GlobMatcher noDirectoryMatcher = Globber.Create(section.SectionName, null); public string? IndentStyle { get; } = section.Keys["indent_style"]; public string? IndentSize { get; } = section.Keys["indent_size"]; @@ -13,8 +14,10 @@ internal class Section(SectionData section, string directory) public string? EndOfLine { get; } = section.Keys["end_of_line"]; public string? Formatter { get; } = section.Keys["csharpier_formatter"]; - public bool IsMatch(string fileName) + public bool IsMatch(string fileName, bool ignoreDirectory) { - return this.matcher.IsMatch(fileName); + return ignoreDirectory + ? this.noDirectoryMatcher.IsMatch(fileName) + : this.matcher.IsMatch(fileName); } } diff --git a/Src/CSharpier.Cli/Options/OptionsProvider.cs b/Src/CSharpier.Cli/Options/OptionsProvider.cs index ea32b5ae7..ec3fe0dac 100644 --- a/Src/CSharpier.Cli/Options/OptionsProvider.cs +++ b/Src/CSharpier.Cli/Options/OptionsProvider.cs @@ -11,6 +11,7 @@ internal class OptionsProvider private readonly List csharpierConfigs; private readonly IgnoreFile ignoreFile; private readonly ConfigurationFileOptions? specifiedConfigFile; + private readonly bool hasSpecificEditorConfig; private readonly IFileSystem fileSystem; private OptionsProvider( @@ -18,6 +19,7 @@ private OptionsProvider( List csharpierConfigs, IgnoreFile ignoreFile, ConfigurationFileOptions? specifiedPrinterOptions, + bool hasSpecificEditorConfig, IFileSystem fileSystem ) { @@ -25,6 +27,7 @@ IFileSystem fileSystem this.csharpierConfigs = csharpierConfigs; this.ignoreFile = ignoreFile; this.specifiedConfigFile = specifiedPrinterOptions; + this.hasSpecificEditorConfig = hasSpecificEditorConfig; this.fileSystem = fileSystem; } @@ -37,7 +40,6 @@ public static async Task Create( bool limitConfigSearch = false ) { - var filename = configPath is not null ? Path.GetFileName(configPath) : null; var csharpierConfigPath = configPath; string? editorConfigPath = null; @@ -66,11 +68,6 @@ public static async Task Create( try { - // TODO #1456 can probably add tests to OptionsProviderTests instead of the other two spots - // TODO #1456 because there is a specified file, we should always be using that one - // kind of like above it passes a "specifiedConfigFile" - // right how this just uses the specified one to get the list, but then later - // the files that we format don't exist in the same directory so aren't formatted with it editorConfigSections = editorConfigPath is null ? EditorConfigParser.FindForDirectoryName( directoryName, @@ -99,6 +96,7 @@ public static async Task Create( csharpierConfigs, ignoreFile, specifiedConfigFile, + hasSpecificEditorConfig: editorConfigPath is not null, fileSystem ); } @@ -110,6 +108,11 @@ public static async Task Create( return this.specifiedConfigFile.ConvertToPrinterOptions(filePath); } + if (this.hasSpecificEditorConfig) + { + return this.editorConfigs.First().ConvertToPrinterOptions(filePath, true); + } + var directoryName = this.fileSystem.Path.GetDirectoryName(filePath); ArgumentNullException.ThrowIfNull(directoryName); @@ -128,8 +131,7 @@ public static async Task Create( if (resolvedEditorConfig is not null) { - DebugLogger.Log("has editorconfig"); - return resolvedEditorConfig.ConvertToPrinterOptions(filePath); + return resolvedEditorConfig.ConvertToPrinterOptions(filePath, false); } if (filePath.EndsWith(".cs") || filePath.EndsWith(".csx")) @@ -151,8 +153,8 @@ public string Serialize() new { specified = this.specifiedConfigFile, - csharpierConfigs = this.csharpierConfigs, - editorConfigs = this.editorConfigs, + this.csharpierConfigs, + this.editorConfigs, } ); } diff --git a/Src/CSharpier.Tests/CommandLineFormatterTests.cs b/Src/CSharpier.Tests/CommandLineFormatterTests.cs index 8f54ec50e..458fad587 100644 --- a/Src/CSharpier.Tests/CommandLineFormatterTests.cs +++ b/Src/CSharpier.Tests/CommandLineFormatterTests.cs @@ -736,7 +736,7 @@ public void Should_Support_Config_Path_With_Editor_Config() "config/.editorconfig", """ [*] - max_line_length = 10" + max_line_length = 10 """ ); var fileName = context.WhenAFileExists("file1.cs", "var myVariable = someLongValue;"); diff --git a/docs/CLI.md b/docs/CLI.md index f2c18c207..71ccbe5aa 100644 --- a/docs/CLI.md +++ b/docs/CLI.md @@ -153,4 +153,7 @@ dotnet csharpier . --config-path "./config/.csharpierrc" # also supports any name for the config file dotnet csharpier . --config-path "./config/csharpier.yaml" + +# as of 0.31.0 supports .editorconfig +dotnet csharpier . --config-path "./config/.editorconfig" ``` From a83ff0abd63cac685b0fc559f91bee84ce8f87de Mon Sep 17 00:00:00 2001 From: Bela VanderVoort Date: Sun, 26 Jan 2025 10:44:23 -0600 Subject: [PATCH 3/3] Fixing build --- Src/CSharpier.Cli.Tests/EditorConfig/SectionTests.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Src/CSharpier.Cli.Tests/EditorConfig/SectionTests.cs b/Src/CSharpier.Cli.Tests/EditorConfig/SectionTests.cs index 5cf8729d1..d69a32302 100644 --- a/Src/CSharpier.Cli.Tests/EditorConfig/SectionTests.cs +++ b/Src/CSharpier.Cli.Tests/EditorConfig/SectionTests.cs @@ -9,28 +9,28 @@ namespace CSharpier.Cli.Tests.EditorConfig; public class SectionTests { [Test] - public void Test1() + public void BasicWildcardGlob() { var path = "/test/test.cs"; - var result = new Section(new SectionData("*.cs"), "/test").IsMatch(path); + var result = new Section(new SectionData("*.cs"), "/test").IsMatch(path, false); result.Should().BeTrue(); } [Test] - public void Test2() + public void BasicGroupGlob() { var path = "/test/test.cs"; - var result = new Section(new SectionData("*.{cs}"), "/test").IsMatch(path); + var result = new Section(new SectionData("*.{cs}"), "/test").IsMatch(path, false); result.Should().BeTrue(); } [Test] - public void Test3() + public void GroupWithTwoOptionsGlob() { var path = "/test/test.cs"; - var result = new Section(new SectionData("*.{csx,cs}"), "/test").IsMatch(path); + var result = new Section(new SectionData("*.{csx,cs}"), "/test").IsMatch(path, false); result.Should().BeTrue(); }