• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kysect / Configuin / 9133870919

17 May 2024 08:25PM UTC coverage: 82.046% (+0.4%) from 81.686%
9133870919

push

github

FrediKats
Fix styledoc generating for case when some values is missed

254 of 376 branches covered (67.55%)

Branch coverage included in aggregate %.

14 of 14 new or added lines in 4 files covered. (100.0%)

8 existing lines in 1 file now uncovered.

1839 of 2175 relevant lines covered (84.55%)

649.59 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.63
/Sources/Kysect.Configuin.MsLearn/MsLearnDocumentationParser.cs
1
using Kysect.CommonLib.BaseTypes.Extensions;
2
using Kysect.Configuin.Common;
3
using Kysect.Configuin.Markdown.Documents;
4
using Kysect.Configuin.Markdown.Tables;
5
using Kysect.Configuin.Markdown.Tables.Models;
6
using Kysect.Configuin.Markdown.TextExtractor;
7
using Kysect.Configuin.MsLearn.Models;
8
using Kysect.Configuin.MsLearn.Tables;
9
using Kysect.Configuin.MsLearn.Tables.Models;
10
using Kysect.Configuin.RoslynModels;
11
using Markdig.Extensions.Tables;
12
using Markdig.Syntax;
13
using Microsoft.Extensions.Logging;
14

15
namespace Kysect.Configuin.MsLearn;
16

17
public class MsLearnDocumentationParser : IMsLearnDocumentationParser
18
{
19
    private readonly ILogger _logger;
20

21
    private readonly MarkdownTableParser _markdownTableParser;
22
    private readonly MsLearnTableParser _msLearnTableParser;
23
    private readonly IMarkdownTextExtractor _textExtractor;
24
    private readonly MsLearnDocumentationPreprocessor _documentationPreprocessor;
25

26
    public MsLearnDocumentationParser(IMarkdownTextExtractor textExtractor, ILogger logger)
27
    {
28
        _textExtractor = textExtractor;
11✔
29
        _logger = logger;
11✔
30

31
        _markdownTableParser = new MarkdownTableParser(textExtractor);
11✔
32
        _msLearnTableParser = new MsLearnTableParser();
11✔
33
        _documentationPreprocessor = new MsLearnDocumentationPreprocessor();
11✔
34
    }
11✔
35

36
    public RoslynRules Parse(MsLearnDocumentationRawInfo rawInfo)
37
    {
38
        ArgumentNullException.ThrowIfNull(rawInfo);
2✔
39

40
        rawInfo = _documentationPreprocessor.Process(rawInfo);
2✔
41

42
        _logger.LogInformation("Parsing roslyn rules from MS Learn");
2✔
43

44
        var roslynQualityRules = rawInfo.QualityRuleFileContents.SelectMany(ParseQualityRules).ToList();
2✔
45
        var roslynStyleRules = rawInfo.StyleRuleFileContents.Select(ParseStyleRules).ToList();
2✔
46

47
        roslynStyleRules = ParseIde0055FormatOptions(roslynStyleRules, rawInfo);
2✔
48

49
        return new RoslynRules(roslynQualityRules, roslynStyleRules);
2✔
50
    }
51

52
    private List<RoslynStyleRuleGroup> ParseIde0055FormatOptions(
53
        List<RoslynStyleRuleGroup> roslynStyleRules,
54
        MsLearnDocumentationRawInfo rawInfo)
55
    {
56
        _logger.LogDebug("Parsing IDE0055 options");
2✔
57

58
        int ruleIde0055Index = roslynStyleRules.FindIndex(r => r.Rules.First().RuleId.Equals(RoslynRuleId.Parse("IDE0055")));
192✔
59
        if (ruleIde0055Index == -1)
2!
60
        {
UNCOV
61
            _logger.LogWarning("Rule IDE0055 was not found. Cannot add format options.");
×
UNCOV
62
            return roslynStyleRules;
×
63
        }
64

65
        _logger.LogDebug("Parse dotnet format options");
2✔
66
        IReadOnlyCollection<RoslynStyleRuleOption> dotnetFormattingOptions = ParseAdditionalFormattingOptions(rawInfo.DotnetFormattingOptionsContent);
2✔
67
        _logger.LogDebug("Parse C# format options");
2✔
68
        IReadOnlyCollection<RoslynStyleRuleOption> sharpFormattingOptions = ParseAdditionalFormattingOptions(rawInfo.SharpFormattingOptionsContent);
2✔
69

70
        var options = new List<RoslynStyleRuleOption>();
2✔
71
        options.AddRange(roslynStyleRules[ruleIde0055Index].Options);
2✔
72
        options.AddRange(dotnetFormattingOptions);
2✔
73
        options.AddRange(sharpFormattingOptions);
2✔
74

75
        roslynStyleRules[ruleIde0055Index] = roslynStyleRules[ruleIde0055Index] with
2✔
76
        {
2✔
77
            Options = options
2✔
78
        };
2✔
79

80
        return roslynStyleRules;
2✔
81
    }
82

83
    public RoslynStyleRuleGroup ParseStyleRules(string info)
84
    {
85
        info.ThrowIfNull();
195✔
86

87
        info = _documentationPreprocessor.Process(info);
195✔
88

89
        MarkdownDocument markdownDocument = MarkdownDocumentExtensions.CreateFromString(info);
195✔
90
        IReadOnlyCollection<MarkdownHeadedBlock> markdownHeadedBlocks = markdownDocument.SplitByHeaders(_textExtractor);
195✔
91

92
        if (markdownHeadedBlocks.Count == 0)
195!
UNCOV
93
            throw new ConfiguinException("Style rule markdown file does not contains any heading blocks. Cannot parse description");
×
94

95
        var ruleDescriptionTables = markdownHeadedBlocks
195✔
96
            .First()
195✔
97
            .Content
195✔
98
            .OfType<Table>()
195✔
99
            .ToList();
195✔
100

101
        if (ruleDescriptionTables.Count == 0)
195!
UNCOV
102
            throw new ConfiguinException($"Style rule description block does not contains child table.");
×
103

104
        var roslynStyleRuleInformationTables = ruleDescriptionTables
195✔
105
            .Select(ParseInformationTable)
195✔
106
            .ToList();
195✔
107

108
        string overviewText = GetStyleOverviewText(markdownHeadedBlocks);
195✔
109
        string? csharpCodeSample = FindIdeExample(markdownHeadedBlocks);
195✔
110

111
        IReadOnlyCollection<RoslynStyleRuleOption> roslynStyleRuleOptions = ParseOptions(markdownHeadedBlocks);
195✔
112

113
        var rules = roslynStyleRuleInformationTables
195✔
114
            .Select(ConvertToRule)
195✔
115
            .ToList();
195✔
116

117
        return new RoslynStyleRuleGroup(rules, roslynStyleRuleOptions, overviewText, csharpCodeSample);
195✔
118
    }
119

120
    private RoslynStyleRuleInformationTable ParseInformationTable(Table tableBlock)
121
    {
122
        MarkdownTableContent markdownTableContent = _markdownTableParser.ParseToSimpleContent(tableBlock);
216✔
123
        MsLearnPropertyValueDescriptionTable table = _msLearnTableParser.Parse(markdownTableContent);
216✔
124
        return RoslynStyleRuleInformationTable.Create(table);
216✔
125
    }
126

127
    private RoslynStyleRule ConvertToRule(RoslynStyleRuleInformationTable roslynStyleRuleInformationTable)
128
    {
129
        return new RoslynStyleRule(
216✔
130
            roslynStyleRuleInformationTable.RuleId,
216✔
131
            roslynStyleRuleInformationTable.Title,
216✔
132
            roslynStyleRuleInformationTable.Category);
216✔
133
    }
134

135
    public IReadOnlyCollection<RoslynQualityRule> ParseQualityRules(string info)
136
    {
137
        MarkdownDocument markdownDocument = MarkdownDocumentExtensions.CreateFromString(info);
630✔
138
        IReadOnlyCollection<MarkdownHeadedBlock> markdownHeadedBlocks = markdownDocument.SplitByHeaders(_textExtractor);
630✔
139

140
        if (markdownHeadedBlocks.Count == 0)
630!
UNCOV
141
            throw new ConfiguinException("Style rule markdown file does not contains any heading blocks. Cannot parse description");
×
142

143
        MarkdownHeadedBlock markdownHeadedBlock = markdownHeadedBlocks.First();
630✔
144
        IReadOnlyCollection<Table> contentBlocks = markdownHeadedBlock.Content.OfType<Table>().ToList();
630✔
145
        if (contentBlocks.Count != 1)
630!
UNCOV
146
            throw new ConfiguinException($"Style rule description block contains unexpected child count. Expected 1, but was {contentBlocks.Count}");
×
147

148
        Table tableBlock = contentBlocks.Single();
630✔
149
        MarkdownTableContent markdownTableContent = _markdownTableParser.ParseToSimpleContent(tableBlock);
630✔
150
        MsLearnPropertyValueDescriptionTable table = _msLearnTableParser.Parse(markdownTableContent);
630✔
151

152
        MsLearnPropertyValueDescriptionTableRow ruleId = table.GetSingleValue("Rule ID");
630✔
153
        MsLearnPropertyValueDescriptionTableRow title = table.GetSingleValue("Title");
630✔
154
        MsLearnPropertyValueDescriptionTableRow category = table.GetSingleValue("Category");
630✔
155
        // TODO: add this fields to model
156
        MsLearnPropertyValueDescriptionTableRow breakingChanges = table.GetSingleValue("Fix is breaking or non-breaking");
630✔
157
        // TODO: remove hardcoded dotnet version
158
        // TODO: docs contains both .NET7 and .NET8 =_=
159
        //MsLearnPropertyValueDescriptionTableRow isDefault = table.GetSingleValue("Enabled by default in .NET 8");
160

161
        IReadOnlyCollection<RoslynRuleId> ruleIds = RoslynRuleIdRange.Parse(ruleId.Value).Enumerate().ToList();
630✔
162

163
        string description = ParseCaRuleDescription(markdownHeadedBlocks);
630✔
164

165
        return ruleIds
630✔
166
            .Select(id => new RoslynQualityRule(
636✔
167
                id,
636✔
168
                title.Value,
636✔
169
                category.Value,
636✔
170
                description))
636✔
171
            .ToList();
630✔
172
    }
173

174
    public IReadOnlyCollection<RoslynStyleRuleOption> ParseAdditionalFormattingOptions(string dotnetFormattingFileContent)
175
    {
176
        MarkdownDocument markdownDocument = MarkdownDocumentExtensions.CreateFromString(dotnetFormattingFileContent);
6✔
177
        IReadOnlyCollection<MarkdownHeadedBlock> markdownHeadedBlocks = markdownDocument.SplitByHeaders(_textExtractor);
6✔
178
        return ParseOptions(markdownHeadedBlocks);
6✔
179
    }
180

181
    private string ParseCaRuleDescription(IReadOnlyCollection<MarkdownHeadedBlock> markdownHeadedBlocks)
182
    {
183
        MarkdownHeadedBlock? headedBlock = markdownHeadedBlocks.FirstOrDefault(b => b.HeaderText == "Rule description");
2,520✔
184
        if (headedBlock is null)
630!
UNCOV
185
            throw new ConfiguinException("Quality rule page does not contains Rule description block.");
×
186

187
        return ConvertBlockToText(headedBlock);
630✔
188
    }
189

190
    private string GetStyleOverviewText(IReadOnlyCollection<MarkdownHeadedBlock> markdownHeadedBlocks)
191
    {
192
        MarkdownHeadedBlock? overviewBlock = markdownHeadedBlocks.FirstOrDefault(h => h.HeaderText == "Overview");
587✔
193
        if (overviewBlock is null)
195✔
194
        {
195
            // TODO: Rule IDE0055 does not contains this block
196
            //throw new ConfiguinException("Style rule page does not contains Overview block.");
197

198
            _logger.LogWarning("Skip overview parsing for IDE0055");
2✔
199
            return string.Empty;
2✔
200
        }
201

202
        return ConvertBlockToText(overviewBlock);
193✔
203
    }
204

205
    private string? FindIdeExample(IReadOnlyCollection<MarkdownHeadedBlock> markdownHeadedBlocks)
206
    {
207
        MarkdownHeadedBlock? exampleBlock = markdownHeadedBlocks.FirstOrDefault(h => h.HeaderText == "Example");
1,272✔
208
        if (exampleBlock is null)
195✔
209
            return null;
108✔
210

211
        return TryExtractCsharpCodeBlock(exampleBlock);
87✔
212
    }
213

214
    private IReadOnlyCollection<RoslynStyleRuleOption> ParseOptions(IReadOnlyCollection<MarkdownHeadedBlock> markdownHeadedBlocks)
215
    {
216
        return markdownHeadedBlocks
201✔
217
            .Where(HeaderForOption)
201✔
218
            .Select(ParseOption)
201✔
219
            .ToList();
201✔
220
    }
221

222
    private bool HeaderForOption(MarkdownHeadedBlock markdownHeadedBlock)
223
    {
224
        // TODO: do it in better way?
225
        string headerText = markdownHeadedBlock.HeaderText;
1,391✔
226

227
        return headerText.StartsWith("dotnet_")
1,391✔
228
               || headerText.StartsWith("csharp_")
1,391✔
229
               // IDE0073
1,391✔
230
               || headerText == "file_header_template";
1,391✔
231
    }
232

233
    private RoslynStyleRuleOption ParseOption(MarkdownHeadedBlock optionBlock)
234
    {
235
        var tables = optionBlock.Content.OfType<Table>().ToList();
286✔
236
        if (tables.Count != 1)
286!
UNCOV
237
            throw new ConfiguinException($"Unexpected table count in option block {optionBlock.HeaderText}");
×
238

239
        MarkdownTableContent markdownTableContent = _markdownTableParser.ParseToSimpleContent(tables.Single());
286✔
240
        MsLearnPropertyValueDescriptionTable table = _msLearnTableParser.Parse(markdownTableContent);
286✔
241

242
        string? csharpCodeSample = TryExtractCsharpCodeBlock(optionBlock);
286✔
243

244
        MsLearnPropertyValueDescriptionTableRow optionName = table.GetSingleValue("Option name");
286✔
245
        IReadOnlyList<MsLearnPropertyValueDescriptionTableRow> optionValues = table.FindValues("Option values");
286✔
246
        MsLearnPropertyValueDescriptionTableRow? defaultValue = table.FindValues("Default option value").SingleOrDefault();
286✔
247

248
        return new RoslynStyleRuleOption(
286✔
249
            optionName.Value,
286✔
250
            optionValues.Select(v => new RoslynStyleRuleOptionValue(v.Value, v.Description)).ToList(),
621✔
251
            defaultValue?.Value,
286✔
252
            csharpCodeSample);
286✔
253
    }
254

255
    private string? TryExtractCsharpCodeBlock(MarkdownHeadedBlock block)
256
    {
257
        FencedCodeBlock? codeBlock = block.Content
373✔
258
            .OfType<FencedCodeBlock>()
373✔
259
            .FirstOrDefault(cb => cb.Info == "csharp");
700✔
260

261
        if (codeBlock is null)
373✔
262
            return null;
50✔
263

264
        return _textExtractor.ExtractText(codeBlock);
323✔
265
    }
266

267
    private string ConvertBlockToText(MarkdownHeadedBlock block)
268
    {
269
        return block
823✔
270
            .Content
823✔
271
            .Select(_textExtractor.ExtractText)
823✔
272
            .Aggregate((a, b) => $"{a}{Environment.NewLine}{b}");
1,573✔
273
    }
274
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc