Skip to content

Commit a450a90

Browse files
Merge pull request #157 from atc-net/feature/Fix-all-provider-recollect
fix: Ensure all providers's recollect works agains the new gidhub pag…
2 parents 4b07eb6 + 3b7a876 commit a450a90

File tree

11 files changed

+154
-36
lines changed

11 files changed

+154
-36
lines changed
Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,15 @@
1-
global using System;
2-
global using System.Collections.Generic;
31
global using System.Collections.ObjectModel;
42
global using System.Diagnostics;
53
global using System.Diagnostics.CodeAnalysis;
6-
global using System.IO;
7-
global using System.Linq;
84
global using System.Net;
95
global using System.Text.Encodings.Web;
106
global using System.Text.Json;
117
global using System.Text.Json.Serialization;
12-
global using System.Threading.Tasks;
138
global using System.Xml;
149

1510
global using Atc.CodingRules.AnalyzerProviders.Models;
1611
global using Atc.CodingRules.AnalyzerProviders.Providers;
17-
12+
global using Atc.Serialization;
1813
global using HtmlAgilityPack;
1914

2015
global using Microsoft.Extensions.Logging;

src/Atc.CodingRules.AnalyzerProviders/Providers/AsyncFixerProvider.cs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public AsyncFixerProvider(
1818
protected override AnalyzerProviderBaseRuleData CreateData()
1919
=> new(Name);
2020

21+
[SuppressMessage("Design", "MA0051:Method is too long", Justification = "OK.")]
2122
protected override async Task ReCollect(
2223
AnalyzerProviderBaseRuleData data)
2324
{
@@ -26,6 +27,43 @@ protected override async Task ReCollect(
2627
var web = new HtmlWeb();
2728
var htmlDoc = await web.LoadFromWebAsync(DocumentationLink!.AbsoluteUri);
2829

30+
var embeddedNode = htmlDoc.DocumentNode.SelectSingleNode("//script[@data-target='react-app.embeddedData']");
31+
if (embeddedNode is not null)
32+
{
33+
var dynamicJson = new DynamicJson(embeddedNode.InnerText);
34+
if (dynamicJson.GetValue("payload.blob.headerInfo.toc") is List<object> tocObjects)
35+
{
36+
foreach (var tocObject in tocObjects)
37+
{
38+
if (tocObject is not Dictionary<string, object> tocItem)
39+
{
40+
continue;
41+
}
42+
43+
if (!tocItem.ContainsKey("text"))
44+
{
45+
continue;
46+
}
47+
48+
var sa = tocItem["text"]
49+
.ToString()!
50+
.Split(':', StringSplitOptions.RemoveEmptyEntries);
51+
52+
var code = sa[0].Trim();
53+
var title = sa[1].Trim();
54+
55+
data.Rules.Add(
56+
new Rule(
57+
code,
58+
title,
59+
link: string.Empty,
60+
description: title));
61+
}
62+
63+
return;
64+
}
65+
}
66+
2967
var headers3 = htmlDoc.DocumentNode.SelectNodes("//h3").ToList();
3068

3169
foreach (var item in headers3)

src/Atc.CodingRules.AnalyzerProviders/Providers/MeziantouProvider.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,16 @@ protected override async Task ReCollect(
2929

3030
var web = new HtmlWeb();
3131
var htmlDoc = await web.LoadFromWebAsync(DocumentationLink!.AbsoluteUri).ConfigureAwait(false);
32+
33+
var embeddedNode = htmlDoc.DocumentNode.SelectSingleNode("//script[@data-target='react-app.embeddedData']");
34+
if (embeddedNode is not null)
35+
{
36+
var dynamicJson = new DynamicJson(embeddedNode.InnerText);
37+
var html = dynamicJson.GetValue("payload.tree.readme.richText")?.ToString();
38+
39+
htmlDoc.LoadHtml(html);
40+
}
41+
3242
var articleNode = htmlDoc.DocumentNode.SelectNodes("//article[@class='markdown-body entry-content container-lg']").First();
3343
var articleTableRows = articleNode.SelectNodes("//*//table[1]//tr").ToList();
3444

src/Atc.CodingRules.AnalyzerProviders/Providers/MicrosoftCompilerErrorsProvider.cs

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public MicrosoftCompilerErrorsProvider(
1313

1414
public static string Name => "Microsoft.CompilerErrors";
1515

16-
public override Uri? DocumentationLink { get; set; } = new("https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-messages", UriKind.Absolute);
16+
public override Uri? DocumentationLink { get; set; } = new("https://learn.microsoft.com/en-us/dotnet/csharp/language-reference", UriKind.Absolute);
1717

1818
protected override AnalyzerProviderBaseRuleData CreateData()
1919
=> new(Name);
@@ -37,26 +37,42 @@ protected override async Task ReCollect(
3737
while (jsonDocItems.MoveNext())
3838
{
3939
var jsonElement = jsonDocItems.Current;
40+
4041
if (!jsonElement.GetRawText().Contains("children", StringComparison.Ordinal))
4142
{
4243
continue;
4344
}
4445

46+
var tocTitle = jsonElement.GetProperty("toc_title").ToString();
47+
if (!tocTitle.Equals("C# compiler messages", StringComparison.Ordinal))
48+
{
49+
continue;
50+
}
51+
4552
var jsonChildItems = jsonElement.GetProperty("children").EnumerateArray();
4653
while (jsonChildItems.MoveNext())
4754
{
4855
var jsonChildElement = jsonChildItems.Current;
49-
var code = jsonChildElement.GetProperty("toc_title").ToString()!;
50-
var hrefPart = jsonChildElement.GetProperty("href").ToString()!;
51-
52-
var link = hrefPart.StartsWith("../../misc/", StringComparison.Ordinal)
53-
? "https://docs.microsoft.com/en-us/dotnet/csharp/" + hrefPart.Replace("../../", string.Empty, StringComparison.Ordinal)
54-
: DocumentationLink.AbsoluteUri + "/" + hrefPart;
56+
if (jsonChildElement.ValueKind != JsonValueKind.Object ||
57+
!jsonChildElement.TryGetProperty("children", out var jsonChildElement2))
58+
{
59+
continue;
60+
}
5561

56-
var rule = await GetRuleByCode(code, link);
57-
if (rule is not null)
62+
foreach (var element in jsonChildElement2.EnumerateArray())
5863
{
59-
data.Rules.Add(rule);
64+
var hrefPart = element.GetProperty("href").ToString();
65+
var code = element.GetProperty("toc_title").ToString();
66+
67+
var link = hrefPart.StartsWith("../misc/", StringComparison.Ordinal)
68+
? "https://docs.microsoft.com/en-us/dotnet/csharp/" + hrefPart.Replace("../", string.Empty, StringComparison.Ordinal)
69+
: DocumentationLink.AbsoluteUri + "/" + hrefPart;
70+
71+
var rule = await GetRuleByCode(code, link);
72+
if (rule is not null)
73+
{
74+
data.Rules.Add(rule);
75+
}
6076
}
6177
}
6278
}
@@ -79,18 +95,16 @@ protected override async Task ReCollect(
7995
return null;
8096
}
8197

98+
var header = mainNode.SelectSingleNode(".//h1");
99+
82100
var paragraphs = mainNode.SelectNodes(".//p").ToList();
83-
if (paragraphs.Count < 3)
101+
if (paragraphs.Count < 2)
84102
{
85103
return null;
86104
}
87105

88-
var title = paragraphs[2].InnerText;
89-
var description = string.Empty;
90-
if (paragraphs.Count > 3)
91-
{
92-
description = paragraphs[3].InnerText;
93-
}
106+
var title = header.InnerText;
107+
var description = paragraphs[1].InnerText;
94108

95109
return new Rule(
96110
code,

src/Atc.CodingRules.AnalyzerProviders/Providers/MicrosoftVisualStudioThreadingAnalyzersProvider.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,16 @@ protected override async Task ReCollect(
2929

3030
var web = new HtmlWeb();
3131
var htmlDoc = await web.LoadFromWebAsync(DocumentationLink!.AbsoluteUri).ConfigureAwait(false);
32+
33+
var embeddedNode = htmlDoc.DocumentNode.SelectSingleNode("//script[@data-target='react-app.embeddedData']");
34+
if (embeddedNode is not null)
35+
{
36+
var dynamicJson = new DynamicJson(embeddedNode.InnerText);
37+
var html = dynamicJson.GetValue("payload.blob.richText")?.ToString();
38+
39+
htmlDoc.LoadHtml(html);
40+
}
41+
3242
var articleNode = htmlDoc.DocumentNode.SelectNodes("//article[@class='markdown-body entry-content container-lg']").First();
3343
var articleTableRows = articleNode.SelectNodes("//*//table[1]//tr").ToList();
3444

src/Atc.CodingRules.AnalyzerProviders/Providers/NSubstituteAnalyzersProvider.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,16 @@ protected override async Task ReCollect(
2929

3030
var web = new HtmlWeb();
3131
var htmlDoc = await web.LoadFromWebAsync(DocumentationLink!.AbsoluteUri).ConfigureAwait(false);
32+
33+
var embeddedNode = htmlDoc.DocumentNode.SelectSingleNode("//script[@data-target='react-app.embeddedData']");
34+
if (embeddedNode is not null)
35+
{
36+
var dynamicJson = new DynamicJson(embeddedNode.InnerText);
37+
var html = dynamicJson.GetValue("payload.tree.readme.richText")?.ToString();
38+
39+
htmlDoc.LoadHtml(html);
40+
}
41+
3242
var articleNode = htmlDoc.DocumentNode.SelectNodes("//article[@class='markdown-body entry-content container-lg']").First();
3343
var articleTableRows = articleNode.SelectNodes("//*//table[1]//tr").ToList();
3444

src/Atc.CodingRules.AnalyzerProviders/Providers/SonarAnalyzerCSharpProvider.cs

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,39 @@ protected override async Task ReCollect(
2727

2828
var web = new HtmlWeb();
2929
var htmlDoc = await web.LoadFromWebAsync(DocumentationLink!.AbsoluteUri).ConfigureAwait(false);
30-
var jsonDoc = JsonDocument.Parse(htmlDoc.DocumentNode.InnerText);
31-
var jsonDocItems = jsonDoc.RootElement.GetProperty("result").GetProperty("pageContext").GetProperty("rules").EnumerateArray();
3230

33-
while (jsonDocItems.MoveNext())
31+
var dynamicJson = new DynamicJson(htmlDoc.DocumentNode.InnerText);
32+
if (dynamicJson.GetValue("result.data.allFile.nodes") is not List<object> nodes)
3433
{
35-
var jsonElement = jsonDocItems.Current;
36-
var ruleKey = jsonElement.GetProperty("ruleKey").GetString();
37-
var summary = jsonElement.GetProperty("summary").GetString() ?? string.Empty;
34+
return;
35+
}
36+
37+
if (nodes[0] is not Dictionary<string, object> node)
38+
{
39+
return;
40+
}
41+
42+
if (node["childLanguageJson"] is not Dictionary<string, object> childLanguageJson)
43+
{
44+
return;
45+
}
46+
47+
if (childLanguageJson["rules"] is not List<object> rules)
48+
{
49+
return;
50+
}
51+
52+
foreach (var ruleObj in rules)
53+
{
54+
if (ruleObj is not Dictionary<string, object> ruleDict)
55+
{
56+
continue;
57+
}
58+
59+
var ruleKey = ruleDict["ruleKey"].ToString();
60+
var summary = ruleDict["summary"].ToString() ?? string.Empty;
3861
var link = RuleLinkBase + ruleKey;
39-
var description = jsonElement.GetProperty("description").GetString();
62+
var description = ruleDict["description"].ToString();
4063

4164
if (ruleKey is null)
4265
{

src/Atc.CodingRules.AnalyzerProviders/Providers/StyleCopAnalyzersProvider.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,16 @@ protected override async Task ReCollect(
3030

3131
var web = new HtmlWeb();
3232
var htmlDoc = await web.LoadFromWebAsync(DocumentationLink!.AbsoluteUri).ConfigureAwait(false);
33+
34+
var embeddedNode = htmlDoc.DocumentNode.SelectSingleNode("//script[@data-target='react-app.embeddedData']");
35+
if (embeddedNode is not null)
36+
{
37+
var dynamicJson = new DynamicJson(embeddedNode.InnerText);
38+
var html = dynamicJson.GetValue("payload.blob.richText")?.ToString();
39+
40+
htmlDoc.LoadHtml(html);
41+
}
42+
3343
var articleNode = htmlDoc.DocumentNode.SelectNodes("//article[@class='markdown-body entry-content container-lg']").First();
3444
var articleRuleLinks = articleNode.SelectNodes("//*//strong//a").ToList();
3545

@@ -49,6 +59,16 @@ private static async Task<List<Rule>> GetRules(
4959
var link = $"https://github.com{item.Attributes["href"].Value}";
5060
var web = new HtmlWeb();
5161
var htmlDoc = await web.LoadFromWebAsync(link).ConfigureAwait(false);
62+
63+
var embeddedNode = htmlDoc.DocumentNode.SelectSingleNode("//script[@data-target='react-app.embeddedData']");
64+
if (embeddedNode is not null)
65+
{
66+
var dynamicJson = new DynamicJson(embeddedNode.InnerText);
67+
var html = dynamicJson.GetValue("payload.blob.richText")?.ToString();
68+
69+
htmlDoc.LoadHtml(html);
70+
}
71+
5272
var articleNode = htmlDoc.DocumentNode.SelectNodes("//article[@class='markdown-body entry-content container-lg']").First();
5373
var articleTableRows = articleNode.SelectNodes("//*//table[1]//tr").ToList();
5474
var category = articleNode.Descendants("h3").First().InnerText;

src/Atc.CodingRules.AnalyzerProviders/Providers/XunitProvider.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ namespace Atc.CodingRules.AnalyzerProviders.Providers;
33
public class XunitProvider : AnalyzerProviderBase
44
{
55
private const int TableThColumnId = 0;
6-
private const int TableTdColumnCategory = 2;
76
private const int TableTdColumnTitle = 0;
87

98
public XunitProvider(
@@ -56,14 +55,13 @@ protected override async Task ReCollect(
5655
var code = aHrefNode.InnerText.RemoveNewLines().Trim();
5756
var title = HtmlEntity.DeEntitize(cellsTd[TableTdColumnTitle].InnerText);
5857
var link = $"{DocumentationLink}/{code}";
59-
var category = cellsTd[TableTdColumnCategory].InnerText;
6058

6159
data.Rules.Add(
6260
new Rule(
6361
code,
6462
title,
6563
link,
66-
category: category));
64+
category: null));
6765
}
6866
}
6967
}

src/Atc.CodingRules.Updater.CLI/ProjectHelper.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ private static async Task CreateSuppressionsFileInTempPath(
391391
ILogger logger,
392392
DirectoryInfo temporarySuppressionsPath,
393393
bool temporarySuppressionAsExcel,
394-
IEnumerable<Tuple<string, List<string>>> suppressionLinesPrAnalyzer)
394+
IList<Tuple<string, List<string>>> suppressionLinesPrAnalyzer)
395395
{
396396
var temporarySuppressionsFile = Path.Join(
397397
temporarySuppressionsPath.FullName,
@@ -610,5 +610,5 @@ private static string Pluralize(
610610
string word,
611611
string pluralSuffix = "s",
612612
string singularSuffix = "")
613-
=> $@"{number} {word}{(number != 1 ? pluralSuffix : singularSuffix)}";
613+
=> $"{number} {word}{(number != 1 ? pluralSuffix : singularSuffix)}";
614614
}

0 commit comments

Comments
 (0)