From 67b6405274c95a36b3f07391092c7b35b55534bc Mon Sep 17 00:00:00 2001 From: Fredi Kats Date: Fri, 8 Sep 2023 19:34:26 +0400 Subject: [PATCH] Implement parsing for IDE* examples --- .../Markdown/MarkdownCodeStyleFormatter.cs | 11 +++++- .../MsLearnDocumentationParser.cs | 38 +++++++++++++------ .../RoslynRuleModels/RoslynStyleRule.cs | 4 +- .../MarkdownCodeStyleFormatterTests.cs | 36 ++++++++++++++++++ .../MsLearnDocumentationParserTests.cs | 17 ++++++++- .../WellKnownRoslynRuleDefinitions.cs | 24 +++++++++++- 6 files changed, 112 insertions(+), 18 deletions(-) diff --git a/Sources/Kysect.Configuin.Core/CodeStyleGeneration/Markdown/MarkdownCodeStyleFormatter.cs b/Sources/Kysect.Configuin.Core/CodeStyleGeneration/Markdown/MarkdownCodeStyleFormatter.cs index ae1b66c..dabe9a4 100644 --- a/Sources/Kysect.Configuin.Core/CodeStyleGeneration/Markdown/MarkdownCodeStyleFormatter.cs +++ b/Sources/Kysect.Configuin.Core/CodeStyleGeneration/Markdown/MarkdownCodeStyleFormatter.cs @@ -43,14 +43,21 @@ public string FormatStyleRule(CodeStyleRoslynStyleRuleConfiguration rule) builder.AddText($"Severity: {rule.Severity}"); builder.AddEmptyLine(); builder.AddText(rule.Rule.Overview); - builder.AddEmptyLine(); + if (rule.Rule.Example is not null) + { + builder.AddEmptyLine(); + builder.AddCode(rule.Rule.Example); + } foreach (CodeStyleRoslynOptionConfiguration optionConfiguration in rule.Options) { - builder.AddH3($"{optionConfiguration.Option.Name} = {optionConfiguration.SelectedValue}"); builder.AddEmptyLine(); + builder.AddH3($"{optionConfiguration.Option.Name} = {optionConfiguration.SelectedValue}"); if (optionConfiguration.Option.CsharpCodeSample is not null) + { + builder.AddEmptyLine(); builder.AddCode(optionConfiguration.Option.CsharpCodeSample); + } } return builder.Build(); diff --git a/Sources/Kysect.Configuin.Core/MsLearnDocumentation/MsLearnDocumentationParser.cs b/Sources/Kysect.Configuin.Core/MsLearnDocumentation/MsLearnDocumentationParser.cs index 07a6707..a57150e 100644 --- a/Sources/Kysect.Configuin.Core/MsLearnDocumentation/MsLearnDocumentationParser.cs +++ b/Sources/Kysect.Configuin.Core/MsLearnDocumentation/MsLearnDocumentationParser.cs @@ -65,10 +65,12 @@ public IReadOnlyCollection ParseStyleRules(string info) .ToList(); string overviewText = GetStyleOverviewText(markdownHeadedBlocks); + string? csharpCodeSample = FindIdeExample(markdownHeadedBlocks); + IReadOnlyCollection roslynStyleRuleOptions = ParseOptions(markdownHeadedBlocks); return roslynStyleRuleInformationTables - .Select(table => ConvertToRule(table, overviewText, roslynStyleRuleOptions)) + .Select(table => ConvertToRule(table, overviewText, csharpCodeSample, roslynStyleRuleOptions)) .ToList(); } @@ -101,6 +103,7 @@ private RoslynStyleRuleInformationTable ParseInformationTable(Table tableBlock) private RoslynStyleRule ConvertToRule( RoslynStyleRuleInformationTable roslynStyleRuleInformationTable, string overviewText, + string? example, IReadOnlyCollection roslynStyleRuleOptions) { return new RoslynStyleRule( @@ -108,8 +111,7 @@ private RoslynStyleRule ConvertToRule( roslynStyleRuleInformationTable.Title, roslynStyleRuleInformationTable.Category, overviewText, - // TODO: #39 support this - example: string.Empty, + example: example, roslynStyleRuleOptions); } @@ -186,6 +188,15 @@ private string GetStyleOverviewText(IReadOnlyCollection mar return overviewText; } + private string? FindIdeExample(IReadOnlyCollection markdownHeadedBlocks) + { + MarkdownHeadedBlock? exampleBlock = markdownHeadedBlocks.FirstOrDefault(h => h.HeaderText == "Example"); + if (exampleBlock is null) + return null; + + return TryExtractCsharpCodeBlock(exampleBlock); + } + private IReadOnlyCollection ParseOptions(IReadOnlyCollection markdownHeadedBlocks) { return markdownHeadedBlocks @@ -214,14 +225,7 @@ private RoslynStyleRuleOption ParseOption(MarkdownHeadedBlock optionBlock) MarkdownTableContent markdownTableContent = _markdownTableParser.ParseToSimpleContent(tables.Single()); MsLearnPropertyValueDescriptionTable table = _msLearnTableParser.Parse(markdownTableContent); - var codeBlocks = optionBlock.Content.OfType().ToList(); - CodeBlock? csharpCodeBlock = codeBlocks - .OfType() - .FirstOrDefault(cb => cb.Info == "csharp"); - - string? csharpCodeSample = csharpCodeBlock is null - ? null - : _textExtractor.ExtractText(csharpCodeBlock); + string? csharpCodeSample = TryExtractCsharpCodeBlock(optionBlock); MsLearnPropertyValueDescriptionTableRow optionName = table.GetSingleValue("Option name"); IReadOnlyList optionValues = table.FindValues("Option values"); @@ -233,4 +237,16 @@ private RoslynStyleRuleOption ParseOption(MarkdownHeadedBlock optionBlock) defaultValue?.Value, csharpCodeSample); } + + private string? TryExtractCsharpCodeBlock(MarkdownHeadedBlock block) + { + FencedCodeBlock? codeBlock = block.Content + .OfType() + .FirstOrDefault(cb => cb.Info == "csharp"); + + if (codeBlock is null) + return null; + + return _textExtractor.ExtractText(codeBlock); + } } \ No newline at end of file diff --git a/Sources/Kysect.Configuin.Core/RoslynRuleModels/RoslynStyleRule.cs b/Sources/Kysect.Configuin.Core/RoslynRuleModels/RoslynStyleRule.cs index 25007bb..156489e 100644 --- a/Sources/Kysect.Configuin.Core/RoslynRuleModels/RoslynStyleRule.cs +++ b/Sources/Kysect.Configuin.Core/RoslynRuleModels/RoslynStyleRule.cs @@ -6,10 +6,10 @@ public class RoslynStyleRule public string Title { get; } public string Category { get; } public string Overview { get; } - public string Example { get; } + public string? Example { get; } public IReadOnlyCollection Options { get; } - public RoslynStyleRule(RoslynRuleId ruleId, string title, string category, string overview, string example, IReadOnlyCollection options) + public RoslynStyleRule(RoslynRuleId ruleId, string title, string category, string overview, string? example, IReadOnlyCollection options) { RuleId = ruleId; Title = title; diff --git a/Sources/Kysect.Configuin.Tests/CodeStyleGeneration/MarkdownCodeStyleFormatterTests.cs b/Sources/Kysect.Configuin.Tests/CodeStyleGeneration/MarkdownCodeStyleFormatterTests.cs index 418dfc9..c73306d 100644 --- a/Sources/Kysect.Configuin.Tests/CodeStyleGeneration/MarkdownCodeStyleFormatterTests.cs +++ b/Sources/Kysect.Configuin.Tests/CodeStyleGeneration/MarkdownCodeStyleFormatterTests.cs @@ -12,6 +12,42 @@ public class MarkdownCodeStyleFormatterTests { private readonly MarkdownCodeStyleFormatter _formatter = new MarkdownCodeStyleFormatter(TestLogger.ProviderForTests()); + [Test] + public void FormatStyleRule_ForIDE0001_ReturnExpected() + { + const string expected = """ + ## Simplify name (IDE0001) + + Severity: Warning + + This rule concerns the use of simplified type names in declarations and executable code, when possible. You can remove unnecessary name qualification to simplify code and improve readability. + + ```csharp + using System.IO; + class C + { + // IDE0001: 'System.IO.FileInfo' can be simplified to 'FileInfo' + System.IO.FileInfo file; + + // Fixed code + FileInfo file; + } + ``` + + """; + + RoslynStyleRule ide0040 = WellKnownRoslynRuleDefinitions.IDE0001(); + + var styleRoslynStyleRuleConfiguration = new CodeStyleRoslynStyleRuleConfiguration( + ide0040, + RoslynRuleSeverity.Warning, + Options: Array.Empty()); + + string formatterRule = _formatter.FormatStyleRule(styleRoslynStyleRuleConfiguration); + + formatterRule.Should().Be(expected); + } + [Test] public void FormatStyleRule_ForIDE0040_ReturnExpected() { diff --git a/Sources/Kysect.Configuin.Tests/MsLearnDocumentation/MsLearnDocumentationParserTests.cs b/Sources/Kysect.Configuin.Tests/MsLearnDocumentation/MsLearnDocumentationParserTests.cs index 67f6695..fcfa301 100644 --- a/Sources/Kysect.Configuin.Tests/MsLearnDocumentation/MsLearnDocumentationParserTests.cs +++ b/Sources/Kysect.Configuin.Tests/MsLearnDocumentation/MsLearnDocumentationParserTests.cs @@ -14,6 +14,19 @@ public class MsLearnDocumentationParserTests private readonly MsLearnDocumentationParser _parser = new MsLearnDocumentationParser(TestImplementations.GetTextExtractor(), TestLogger.ProviderForTests()); + [Test] + public void ParseStyleRule_IDE0001_ReturnExpectedResult() + { + string fileText = GetIdeDescription("ide0001.md"); + + RoslynStyleRule expected = WellKnownRoslynRuleDefinitions.IDE0001(); + + IReadOnlyCollection roslynStyleRules = _parser.ParseStyleRules(fileText); + + roslynStyleRules.Should().HaveCount(1) + .And.Subject.ElementAt(0).Should().BeEquivalentTo(expected); + } + [Test] public void ParseStyleRule_IDE0040_ReturnExpectedResult() { @@ -110,7 +123,7 @@ These two rules define whether or not you prefer the use of this (C#) and Me. (V "Remove this or Me qualification", "Style", overview, - string.Empty, + null, options); var ide0009 = new RoslynStyleRule( @@ -118,7 +131,7 @@ These two rules define whether or not you prefer the use of this (C#) and Me. (V "Add this or Me qualification", "Style", overview, - string.Empty, + null, options); IReadOnlyCollection roslynStyleRules = _parser.ParseStyleRules(fileText); diff --git a/Sources/Kysect.Configuin.Tests/Resources/WellKnownRoslynRuleDefinitions.cs b/Sources/Kysect.Configuin.Tests/Resources/WellKnownRoslynRuleDefinitions.cs index f02185a..9e3653d 100644 --- a/Sources/Kysect.Configuin.Tests/Resources/WellKnownRoslynRuleDefinitions.cs +++ b/Sources/Kysect.Configuin.Tests/Resources/WellKnownRoslynRuleDefinitions.cs @@ -4,6 +4,28 @@ namespace Kysect.Configuin.Tests.Resources; public static class WellKnownRoslynRuleDefinitions { + public static RoslynStyleRule IDE0001() + { + return new RoslynStyleRule( + ruleId: RoslynRuleId.Parse("IDE0001"), + title: "Simplify name", + category: "Style", + overview: "This rule concerns the use of simplified type names in declarations and executable code, when possible. You can remove unnecessary name qualification to simplify code and improve readability.", + example: """ + using System.IO; + class C + { + // IDE0001: 'System.IO.FileInfo' can be simplified to 'FileInfo' + System.IO.FileInfo file; + + // Fixed code + FileInfo file; + } + """, + options: Array.Empty()); + } + + public static RoslynStyleRule IDE0040() { string codeSample = """ @@ -40,7 +62,7 @@ class MyClass "Add accessibility modifiers", "Style", "This style rule concerns requiring accessibility modifiers in declarations.", - string.Empty, + null, new[] { expectedOption }); }