1+ using System . Collections . Immutable ;
2+ using Microsoft . CodeAnalysis ;
3+ using Microsoft . CodeAnalysis . CSharp ;
4+ using Microsoft . CodeAnalysis . CSharp . Syntax ;
5+ using Microsoft . CodeAnalysis . Diagnostics ;
6+
7+ namespace DataverseAnalyzer ;
8+
9+ [ DiagnosticAnalyzer ( LanguageNames . CSharp ) ]
10+ public sealed class PluginDocumentationAnalyzer : DiagnosticAnalyzer
11+ {
12+ private static readonly Lazy < DiagnosticDescriptor > LazyRule = new ( ( ) => new DiagnosticDescriptor (
13+ "CT0006" ,
14+ Resources . CT0006_Title ,
15+ Resources . CT0006_MessageFormat ,
16+ "Documentation" ,
17+ DiagnosticSeverity . Warning ,
18+ isEnabledByDefault : true ,
19+ description : Resources . CT0006_Description ) ) ;
20+
21+ public static DiagnosticDescriptor Rule => LazyRule . Value ;
22+
23+ public override ImmutableArray < DiagnosticDescriptor > SupportedDiagnostics => ImmutableArray . Create ( Rule ) ;
24+
25+ public override void Initialize ( AnalysisContext context )
26+ {
27+ if ( context is null )
28+ {
29+ throw new ArgumentNullException ( nameof ( context ) ) ;
30+ }
31+
32+ context . ConfigureGeneratedCodeAnalysis ( GeneratedCodeAnalysisFlags . None ) ;
33+ context . EnableConcurrentExecution ( ) ;
34+
35+ context . RegisterSyntaxNodeAction ( AnalyzeClassDeclaration , SyntaxKind . ClassDeclaration ) ;
36+ }
37+
38+ private static void AnalyzeClassDeclaration ( SyntaxNodeAnalysisContext context )
39+ {
40+ var classDeclaration = ( ClassDeclarationSyntax ) context . Node ;
41+
42+ if ( ! InheritsFromPlugin ( classDeclaration ) )
43+ return ;
44+
45+ if ( HasValidDocumentation ( classDeclaration ) )
46+ return ;
47+
48+ var diagnostic = Diagnostic . Create (
49+ Rule ,
50+ classDeclaration . Identifier . GetLocation ( ) ,
51+ classDeclaration . Identifier . ValueText ) ;
52+
53+ context . ReportDiagnostic ( diagnostic ) ;
54+ }
55+
56+ private static bool InheritsFromPlugin ( ClassDeclarationSyntax classDeclaration )
57+ {
58+ if ( classDeclaration . BaseList is null )
59+ return false ;
60+
61+ foreach ( var baseType in classDeclaration . BaseList . Types )
62+ {
63+ var typeName = GetBaseTypeName ( baseType . Type ) ;
64+ if ( typeName == "Plugin" )
65+ return true ;
66+ }
67+
68+ return false ;
69+ }
70+
71+ private static string ? GetBaseTypeName ( TypeSyntax type )
72+ {
73+ return type switch
74+ {
75+ IdentifierNameSyntax identifier => identifier . Identifier . ValueText ,
76+ QualifiedNameSyntax qualified => qualified . Right . Identifier . ValueText ,
77+ _ => null ,
78+ } ;
79+ }
80+
81+ private static bool HasValidDocumentation ( ClassDeclarationSyntax classDeclaration )
82+ {
83+ var leadingTrivia = classDeclaration . GetLeadingTrivia ( ) ;
84+
85+ foreach ( var trivia in leadingTrivia )
86+ {
87+ if ( ! trivia . IsKind ( SyntaxKind . SingleLineDocumentationCommentTrivia ) )
88+ continue ;
89+
90+ var structure = trivia . GetStructure ( ) ;
91+ if ( structure is null )
92+ continue ;
93+
94+ if ( HasInheritdoc ( structure ) )
95+ return true ;
96+
97+ if ( HasNonEmptySummary ( structure ) )
98+ return true ;
99+ }
100+
101+ return false ;
102+ }
103+
104+ private static bool HasInheritdoc ( SyntaxNode structure )
105+ {
106+ foreach ( var node in structure . DescendantNodes ( ) )
107+ {
108+ if ( node is XmlEmptyElementSyntax emptyElement &&
109+ emptyElement . Name . LocalName . ValueText == "inheritdoc" )
110+ {
111+ return true ;
112+ }
113+ }
114+
115+ return false ;
116+ }
117+
118+ private static bool HasNonEmptySummary ( SyntaxNode structure )
119+ {
120+ foreach ( var node in structure . DescendantNodes ( ) )
121+ {
122+ if ( node is not XmlElementSyntax element )
123+ continue ;
124+
125+ if ( element . StartTag . Name . LocalName . ValueText != "summary" )
126+ continue ;
127+
128+ var content = element . Content ;
129+ foreach ( var contentNode in content )
130+ {
131+ if ( contentNode is XmlTextSyntax textSyntax )
132+ {
133+ var text = string . Concat ( textSyntax . TextTokens . Select ( t => t . ValueText ) ) ;
134+ if ( ! string . IsNullOrWhiteSpace ( text ) )
135+ return true ;
136+ }
137+ }
138+ }
139+
140+ return false ;
141+ }
142+ }
0 commit comments