diff --git a/Readme.md b/Readme.md index 36782ac..7fd8785 100644 --- a/Readme.md +++ b/Readme.md @@ -152,3 +152,29 @@ Some features: - Options saved near the rule - Not parsed rules don't change - Comments are saved and moved with the rule + +#### Grouping by category + +Command 'format' support flag `--group-ca` that allows to group rules by category. For example: + +``` +# with --group-ca false +# Autogenerated values +[*.cs] +### CA ### +dotnet_diagnostic.CA1016.severity = none +dotnet_diagnostic.CA1027.severity = none +dotnet_diagnostic.CA1501.severity = none +dotnet_diagnostic.CA1835.severity = none + +# with --group-ca true +# Autogenerated values +[*.cs] +### CA Design ### +dotnet_diagnostic.CA1016.severity = none +dotnet_diagnostic.CA1027.severity = none +### CA Maintainability ### +dotnet_diagnostic.CA1501.severity = none +### CA Performance ### +dotnet_diagnostic.CA1835.severity = none +``` diff --git a/Sources/Kysect.Configuin.Console/Commands/FormatEditorconfigCommand.cs b/Sources/Kysect.Configuin.Console/Commands/FormatEditorconfigCommand.cs index 426cba8..b9b7e47 100644 --- a/Sources/Kysect.Configuin.Console/Commands/FormatEditorconfigCommand.cs +++ b/Sources/Kysect.Configuin.Console/Commands/FormatEditorconfigCommand.cs @@ -25,6 +25,11 @@ public sealed class Settings : CommandSettings [Description("Path to cloned MS Learn repository.")] [CommandOption("-d|--documentation")] public string? MsLearnRepositoryPath { get; init; } + + [Description("Group CA rules by category")] + [CommandOption("--group-ca")] + [DefaultValue(true)] + public bool GroupQualityRulesByCategory { get; init; } } public override int Execute(CommandContext context, Settings settings) @@ -38,7 +43,7 @@ public override int Execute(CommandContext context, Settings settings) MsLearnDocumentationRawInfo msLearnDocumentationRawInfo = repositoryPathReader.Provide(settings.MsLearnRepositoryPath); RoslynRules roslynRules = msLearnDocumentationParser.Parse(msLearnDocumentationRawInfo); EditorConfigDocument editorConfigDocument = editorConfigDocumentParser.Parse(editorConfigContentLines); - EditorConfigDocument formattedDocument = editorConfigFormatter.FormatAccordingToRuleDefinitions(editorConfigDocument, roslynRules); + EditorConfigDocument formattedDocument = editorConfigFormatter.FormatAccordingToRuleDefinitions(editorConfigDocument, roslynRules, settings.GroupQualityRulesByCategory); File.WriteAllText(settings.EditorConfigPath, formattedDocument.ToFullString()); return 0; diff --git a/Sources/Kysect.Configuin.EditorConfig/Formatter/EditorConfigFormatter.cs b/Sources/Kysect.Configuin.EditorConfig/Formatter/EditorConfigFormatter.cs index 31d89da..a66392d 100644 --- a/Sources/Kysect.Configuin.EditorConfig/Formatter/EditorConfigFormatter.cs +++ b/Sources/Kysect.Configuin.EditorConfig/Formatter/EditorConfigFormatter.cs @@ -27,14 +27,14 @@ public EditorConfigDocument Format(EditorConfigDocument value) if (nodesForRemoving.IsEmpty()) return value; - EditorConfigCategoryNode autoGeneratedSection = CreateAutoGeneratedCategory(styleRuleNodesForMoving, qualityRuleNodesForMoving); + EditorConfigCategoryNode autoGeneratedSection = CreateAutoGeneratedCategory(styleRuleNodesForMoving, [new RoslynQualityRuleFormattedSection("CA", qualityRuleNodesForMoving)]); return value .RemoveNodes(nodesForRemoving) .AddChild(autoGeneratedSection); } - public EditorConfigDocument FormatAccordingToRuleDefinitions(EditorConfigDocument document, RoslynRules rules) + public EditorConfigDocument FormatAccordingToRuleDefinitions(EditorConfigDocument document, RoslynRules rules, bool groupQualityRulesByCategory = false) { rules.ThrowIfNull(); @@ -80,26 +80,54 @@ public EditorConfigDocument FormatAccordingToRuleDefinitions(EditorConfigDocumen } } - List selectedQualityRuleNodes = new List(); - foreach (RoslynQualityRule qualityRule in rules.QualityRules) + List formattedSections = new List(); + if (groupQualityRulesByCategory) { - EditorConfigPropertyNode? editorConfigPropertyNode = TryFindSeverityNode(propertyNodes, qualityRule.RuleId); - if (editorConfigPropertyNode is null) - continue; + foreach (IGrouping categoryRules in rules.QualityRules.GroupBy(r => r.Category)) + { + List selectedQualityRuleNodes = new List(); + foreach (RoslynQualityRule qualityRule in categoryRules) + { + EditorConfigPropertyNode? editorConfigPropertyNode = TryFindSeverityNode(propertyNodes, qualityRule.RuleId); + if (editorConfigPropertyNode is null) + continue; + + selectedQualityRuleNodes.Add(editorConfigPropertyNode); + } + + if (selectedQualityRuleNodes.Any()) + formattedSections.Add(new RoslynQualityRuleFormattedSection($"CA {categoryRules.Key}", selectedQualityRuleNodes)); + } + } + else + { + List selectedQualityRuleNodes = new List(); + foreach (RoslynQualityRule qualityRule in rules.QualityRules) + { + EditorConfigPropertyNode? editorConfigPropertyNode = TryFindSeverityNode(propertyNodes, qualityRule.RuleId); + if (editorConfigPropertyNode is null) + continue; - selectedQualityRuleNodes.Add(editorConfigPropertyNode); + selectedQualityRuleNodes.Add(editorConfigPropertyNode); + } + if (selectedQualityRuleNodes.Any()) + formattedSections.Add(new RoslynQualityRuleFormattedSection("CA", selectedQualityRuleNodes)); } nodesForRemoving.AddRange(selectedStyleRuleNodes); - nodesForRemoving.AddRange(selectedQualityRuleNodes); - EditorConfigCategoryNode autoGeneratedSection = CreateAutoGeneratedCategory(selectedStyleRuleNodes, selectedQualityRuleNodes); + nodesForRemoving.AddRange(formattedSections.SelectMany(s => s.SelectedQualityRuleNodes)); + EditorConfigCategoryNode autoGeneratedSection = CreateAutoGeneratedCategory(selectedStyleRuleNodes, formattedSections); return document .RemoveNodes(nodesForRemoving) .AddChild(autoGeneratedSection); } - private EditorConfigCategoryNode CreateAutoGeneratedCategory(IReadOnlyCollection styleRuleNodesForMoving, IReadOnlyCollection qualityRuleNodesForMoving) + public record RoslynQualityRuleFormattedSection(string Title, IReadOnlyCollection SelectedQualityRuleNodes); + + private EditorConfigCategoryNode CreateAutoGeneratedCategory( + IReadOnlyCollection styleRuleNodesForMoving, + IReadOnlyCollection qualityRuleNodesForMoving) { var autoGeneratedSection = new EditorConfigCategoryNode("*.cs", [], ["# Autogenerated values"], null); @@ -113,12 +141,14 @@ private EditorConfigCategoryNode CreateAutoGeneratedCategory(IReadOnlyCollection autoGeneratedSection = autoGeneratedSection.AddChild(styleRuleSection); } - if (qualityRuleNodesForMoving.Any()) + foreach (RoslynQualityRuleFormattedSection roslynQualityRuleFormattedSection in qualityRuleNodesForMoving) { - var qualitySection = new EditorConfigDocumentSectionNode("### CA ###"); - foreach (EditorConfigPropertyNode qualityRule in qualityRuleNodesForMoving) - qualitySection = qualitySection.AddChild(qualityRule); + if (roslynQualityRuleFormattedSection.SelectedQualityRuleNodes.IsEmpty()) + continue; + var qualitySection = new EditorConfigDocumentSectionNode($"### {roslynQualityRuleFormattedSection.Title} ###"); + foreach (EditorConfigPropertyNode qualityRule in roslynQualityRuleFormattedSection.SelectedQualityRuleNodes) + qualitySection = qualitySection.AddChild(qualityRule); autoGeneratedSection = autoGeneratedSection.AddChild(qualitySection); } diff --git a/Sources/Kysect.Configuin.Tests/EditorConfig/EditorConfigFormatterTests.cs b/Sources/Kysect.Configuin.Tests/EditorConfig/EditorConfigFormatterTests.cs index b7d6953..6319824 100644 --- a/Sources/Kysect.Configuin.Tests/EditorConfig/EditorConfigFormatterTests.cs +++ b/Sources/Kysect.Configuin.Tests/EditorConfig/EditorConfigFormatterTests.cs @@ -101,7 +101,38 @@ public void Format_NamingRule_ReturnOrderedLinesWithHeader() RoslynRules roslynRules = _msLearnDocumentationParser.Parse(msLearnDocumentationRawInfo); EditorConfigDocument editorConfigDocument = _parser.Parse(input); - EditorConfigDocument formattedDocument = _formatter.FormatAccordingToRuleDefinitions(editorConfigDocument, roslynRules); + EditorConfigDocument formattedDocument = _formatter.FormatAccordingToRuleDefinitions(editorConfigDocument, roslynRules, false); + + formattedDocument.ToFullString().Should().Be(expected); + } + + [Fact] + public void Format_QualityRuleByCategory_ReturnGroupedByCategory() + { + var input = """ + dotnet_diagnostic.CA1016.severity = none + dotnet_diagnostic.CA1027.severity = none + dotnet_diagnostic.CA1501.severity = none + dotnet_diagnostic.CA1835.severity = none + """; + + var expected = """ + # Autogenerated values + [*.cs] + ### CA Design ### + dotnet_diagnostic.CA1016.severity = none + dotnet_diagnostic.CA1027.severity = none + ### CA Maintainability ### + dotnet_diagnostic.CA1501.severity = none + ### CA Performance ### + dotnet_diagnostic.CA1835.severity = none + """; + + MsLearnDocumentationRawInfo msLearnDocumentationRawInfo = _repositoryPathReader.Provide(Constants.GetPathToMsDocsRoot()); + RoslynRules roslynRules = _msLearnDocumentationParser.Parse(msLearnDocumentationRawInfo); + + EditorConfigDocument editorConfigDocument = _parser.Parse(input); + EditorConfigDocument formattedDocument = _formatter.FormatAccordingToRuleDefinitions(editorConfigDocument, roslynRules, true); formattedDocument.ToFullString().Should().Be(expected); } @@ -119,7 +150,7 @@ public void FormatAccordingToRuleDefinitions_Sample_ReturnExpectedFormatterDocum RoslynRules roslynRules = _msLearnDocumentationParser.Parse(msLearnDocumentationRawInfo); EditorConfigDocument editorConfigDocument = _parser.Parse(input); - EditorConfigDocument formattedDocument = _formatter.FormatAccordingToRuleDefinitions(editorConfigDocument, roslynRules); + EditorConfigDocument formattedDocument = _formatter.FormatAccordingToRuleDefinitions(editorConfigDocument, roslynRules, false); // TODO: Do smth with this =_= formattedDocument.ToFullString()