@@ -13,126 +13,138 @@ public MicrosoftCompilerErrorsProvider(
1313
1414 public static string Name => "Microsoft.CompilerErrors" ;
1515
16- public override Uri ? DocumentationLink { get ; set ; } = new ( "https://learn.microsoft.com/en-us/dotnet/csharp/language-reference" , 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 ) ;
2020
21- [ SuppressMessage ( "Design" , "MA0051:Method is too long" , Justification = "OK." ) ]
2221 protected override async Task ReCollect ( AnalyzerProviderBaseRuleData data )
2322 {
2423 ArgumentNullException . ThrowIfNull ( data ) ;
2524
26- var web = new HtmlWeb ( ) ;
27- var htmlDoc = await web
28- . LoadFromWebAsync ( DocumentationLink ! . AbsoluteUri + "/toc.json" )
29- . ConfigureAwait ( false ) ;
30-
31- if ( htmlDoc . DocumentNode . HasTitleWithAccessDenied ( ) )
25+ var categoryPages = new [ ]
26+ {
27+ "compiler-messages/preprocessor-errors" ,
28+ "compiler-messages/attribute-usage-errors" ,
29+ "compiler-messages/feature-version-errors" ,
30+ "compiler-messages/assembly-references" ,
31+ "compiler-messages/constructor-errors" ,
32+ "compiler-messages/overloaded-operator-errors" ,
33+ "compiler-messages/parameter-argument-mismatch" ,
34+ "compiler-messages/generic-type-parameters-errors" ,
35+ "compiler-messages/async-await-errors" ,
36+ "compiler-messages/interface-implementation-errors" ,
37+ "compiler-messages/ref-modifiers-errors" ,
38+ "compiler-messages/ref-safety-errors" ,
39+ "compiler-messages/ref-struct-errors" ,
40+ "compiler-messages/iterator-yield" ,
41+ "compiler-messages/extension-declarations" ,
42+ "compiler-messages/partial-declarations" ,
43+ "compiler-messages/params-arrays" ,
44+ "compiler-messages/nullable-warnings" ,
45+ "compiler-messages/pattern-matching-warnings" ,
46+ "compiler-messages/string-literal" ,
47+ "compiler-messages/array-declaration-errors" ,
48+ "compiler-messages/inline-array-errors" ,
49+ "compiler-messages/lambda-expression-errors" ,
50+ "compiler-messages/overload-resolution" ,
51+ "compiler-messages/expression-tree-restrictions" ,
52+ "compiler-messages/using-directive-errors" ,
53+ "compiler-messages/using-statement-declaration-errors" ,
54+ "compiler-messages/source-generator-errors" ,
55+ "compiler-messages/static-abstract-interfaces" ,
56+ "compiler-messages/lock-semantics" ,
57+ "compiler-messages/dynamic-type-and-binding-errors" ,
58+ "compiler-messages/unsafe-code-errors" ,
59+ "compiler-messages/warning-waves" ,
60+ } ;
61+
62+ foreach ( var categoryPath in categoryPages )
3263 {
33- data . ExceptionMessage = "Access Denied" ;
34- return ;
64+ var categoryUri = new Uri ( DocumentationLink ! , categoryPath ) ;
65+ var rules = await GetRulesFromCategoryPage ( categoryUri . AbsoluteUri ) ;
66+ foreach ( var rule in rules )
67+ {
68+ data . Rules . Add ( rule ) ;
69+ }
3570 }
71+ }
3672
37- var jsonDoc = JsonDocument . Parse ( htmlDoc . DocumentNode . InnerText ) ;
38- var jsonDocItems = jsonDoc . RootElement
39- . GetProperty ( "items" )
40- . EnumerateArray ( ) ;
73+ [ SuppressMessage ( "Design" , "MA0051:Method is too long" , Justification = "OK." ) ]
74+ [ SuppressMessage ( "Design" , "CA1031:Do not catch general exception types" , Justification = "OK - graceful degradation for individual category pages." ) ]
75+ private static async Task < List < Rule > > GetRulesFromCategoryPage (
76+ string categoryUrl )
77+ {
78+ var rules = new List < Rule > ( ) ;
79+ var web = new HtmlWeb ( ) ;
4180
42- while ( jsonDocItems . MoveNext ( ) )
81+ try
4382 {
44- var jsonElement = jsonDocItems . Current ;
83+ var htmlDoc = await web
84+ . LoadFromWebAsync ( categoryUrl )
85+ . ConfigureAwait ( false ) ;
4586
46- if ( ! jsonElement . GetRawText ( ) . Contains ( "children" , StringComparison . Ordinal ) )
87+ if ( htmlDoc . DocumentNode . HasTitleWithAccessDenied ( ) )
4788 {
48- continue ;
89+ return rules ;
4990 }
5091
51- var tocTitle = jsonElement
52- . GetProperty ( "toc_title" )
53- . ToString ( ) ;
54-
55- if ( ! tocTitle . Equals ( "C# compiler messages" , StringComparison . Ordinal ) )
92+ var mainNode = htmlDoc . DocumentNode . SelectSingleNode ( "//main[@id='main']" ) ;
93+ if ( mainNode is null )
5694 {
57- continue ;
95+ return rules ;
5896 }
5997
60- var jsonChildItems = jsonElement
61- . GetProperty ( "children" )
62- . EnumerateArray ( ) ;
98+ // Find all strong elements that contain CS error codes
99+ var strongNodes = mainNode . SelectNodes ( ".//strong" ) ;
100+ if ( strongNodes is null )
101+ {
102+ return rules ;
103+ }
63104
64- while ( jsonChildItems . MoveNext ( ) )
105+ foreach ( var strongNode in strongNodes )
65106 {
66- var jsonChildElement = jsonChildItems . Current ;
67- if ( jsonChildElement . ValueKind != JsonValueKind . Object ||
68- ! jsonChildElement . TryGetProperty ( "children" , out var jsonChildElement2 ) )
107+ var text = strongNode . InnerText . Trim ( ) ;
108+ if ( ! text . StartsWith ( "CS" , StringComparison . Ordinal ) )
69109 {
70110 continue ;
71111 }
72112
73- foreach ( var element in jsonChildElement2 . EnumerateArray ( ) )
113+ // Extract just the CS code (e.g., "CS1024" from "CS1024:")
114+ var codeTrimmed = text . TrimEnd ( ':' ) ;
115+ var code = codeTrimmed . Trim ( ) ;
116+ if ( code . Length is < 5 or > 7 )
74117 {
75- var hrefPart = element
76- . GetProperty ( "href" )
77- . ToString ( ) ;
78-
79- var code = element
80- . GetProperty ( "toc_title" )
81- . ToString ( ) ;
82-
83- var link = hrefPart . StartsWith ( "../misc/" , StringComparison . Ordinal )
84- ? "https://docs.microsoft.com/en-us/dotnet/csharp/" + hrefPart . Replace ( "../" , string . Empty , StringComparison . Ordinal )
85- : DocumentationLink . AbsoluteUri + "/" + hrefPart ;
86-
87- var rule = await GetRuleByCode ( code , link ) ;
88- if ( rule is not null )
89- {
90- data . Rules . Add ( rule ) ;
91- }
118+ continue ;
92119 }
93- }
94- }
95- }
96120
97- private static async Task < Rule ? > GetRuleByCode (
98- string code ,
99- string link )
100- {
101- var web = new HtmlWeb ( ) ;
102- var htmlDoc = await web
103- . LoadFromWebAsync ( link )
104- . ConfigureAwait ( false ) ;
121+ // Get the description from the following text
122+ var description = string . Empty ;
123+ var nextSibling = strongNode . NextSibling ;
124+ if ( nextSibling is not null )
125+ {
126+ var deEntitized = HtmlEntity . DeEntitize ( nextSibling . InnerText ) ;
127+ var trimmed = deEntitized . Trim ( ) ;
128+ var withoutColon = trimmed . TrimStart ( ':' ) ;
129+ description = withoutColon . Trim ( ) ;
130+ }
105131
106- if ( htmlDoc . DocumentNode . HasTitleWithAccessDenied ( ) )
107- {
108- return null ;
109- }
132+ var link = categoryUrl + "#" + code . ToLowerInvariant ( ) ;
110133
111- var mainNode = htmlDoc . DocumentNode . SelectSingleNode ( "//main[@id='main']" ) ;
112- if ( mainNode is null )
113- {
114- return null ;
134+ rules . Add (
135+ new Rule (
136+ code ,
137+ code ,
138+ link ,
139+ category : null ,
140+ description ) ) ;
141+ }
115142 }
116-
117- var header = mainNode . SelectSingleNode ( ".//h1" ) ;
118-
119- var paragraphs = mainNode
120- . SelectNodes ( ".//p" )
121- . ToList ( ) ;
122-
123- if ( paragraphs . Count < 2 )
143+ catch
124144 {
125- return null ;
145+ // Ignore errors for individual category pages
126146 }
127147
128- var title = header . InnerText ;
129- var description = paragraphs [ 1 ] . InnerText ;
130-
131- return new Rule (
132- code ,
133- title ,
134- link ,
135- category : null ,
136- description ) ;
148+ return rules ;
137149 }
138150}
0 commit comments