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 PluginStepConfigurationAnalyzer : DiagnosticAnalyzer
11+ {
12+ private static readonly Lazy < DiagnosticDescriptor > LazyRuleFilteredAttributesOnCreate = new ( ( ) => new DiagnosticDescriptor (
13+ "CT0008" ,
14+ Resources . CT0008_Title ,
15+ Resources . CT0008_MessageFormat ,
16+ "Usage" ,
17+ DiagnosticSeverity . Error ,
18+ isEnabledByDefault : true ,
19+ description : Resources . CT0008_Description ) ) ;
20+
21+ private static readonly Lazy < DiagnosticDescriptor > LazyRulePreImageOnCreate = new ( ( ) => new DiagnosticDescriptor (
22+ "CT0009" ,
23+ Resources . CT0009_Title ,
24+ Resources . CT0009_MessageFormat ,
25+ "Usage" ,
26+ DiagnosticSeverity . Error ,
27+ isEnabledByDefault : true ,
28+ description : Resources . CT0009_Description ) ) ;
29+
30+ private static readonly Lazy < DiagnosticDescriptor > LazyRulePostImageOnDelete = new ( ( ) => new DiagnosticDescriptor (
31+ "CT0010" ,
32+ Resources . CT0010_Title ,
33+ Resources . CT0010_MessageFormat ,
34+ "Usage" ,
35+ DiagnosticSeverity . Error ,
36+ isEnabledByDefault : true ,
37+ description : Resources . CT0010_Description ) ) ;
38+
39+ public static DiagnosticDescriptor RuleFilteredAttributesOnCreate => LazyRuleFilteredAttributesOnCreate . Value ;
40+
41+ public static DiagnosticDescriptor RulePreImageOnCreate => LazyRulePreImageOnCreate . Value ;
42+
43+ public static DiagnosticDescriptor RulePostImageOnDelete => LazyRulePostImageOnDelete . Value ;
44+
45+ public override ImmutableArray < DiagnosticDescriptor > SupportedDiagnostics => ImmutableArray . Create (
46+ RuleFilteredAttributesOnCreate ,
47+ RulePreImageOnCreate ,
48+ RulePostImageOnDelete ) ;
49+
50+ public override void Initialize ( AnalysisContext context )
51+ {
52+ if ( context is null )
53+ {
54+ throw new ArgumentNullException ( nameof ( context ) ) ;
55+ }
56+
57+ context . ConfigureGeneratedCodeAnalysis ( GeneratedCodeAnalysisFlags . None ) ;
58+ context . EnableConcurrentExecution ( ) ;
59+
60+ context . RegisterSyntaxNodeAction ( AnalyzeInvocation , SyntaxKind . InvocationExpression ) ;
61+ }
62+
63+ private static void AnalyzeInvocation ( SyntaxNodeAnalysisContext context )
64+ {
65+ var invocation = ( InvocationExpressionSyntax ) context . Node ;
66+
67+ if ( invocation . Expression is not MemberAccessExpressionSyntax memberAccess )
68+ return ;
69+
70+ var methodName = memberAccess . Name . Identifier . ValueText ;
71+
72+ if ( methodName == "AddFilteredAttributes" )
73+ AnalyzeAddFilteredAttributes ( context , invocation , memberAccess ) ;
74+ else if ( methodName == "AddImage" )
75+ AnalyzeAddImage ( context , invocation , memberAccess ) ;
76+ }
77+
78+ private static void AnalyzeAddFilteredAttributes (
79+ SyntaxNodeAnalysisContext context ,
80+ InvocationExpressionSyntax invocation ,
81+ MemberAccessExpressionSyntax memberAccess )
82+ {
83+ var operation = FindEventOperation ( memberAccess . Expression ) ;
84+
85+ if ( operation == "Create" )
86+ {
87+ var diagnostic = Diagnostic . Create (
88+ RuleFilteredAttributesOnCreate ,
89+ invocation . GetLocation ( ) ) ;
90+ context . ReportDiagnostic ( diagnostic ) ;
91+ }
92+ }
93+
94+ private static void AnalyzeAddImage (
95+ SyntaxNodeAnalysisContext context ,
96+ InvocationExpressionSyntax invocation ,
97+ MemberAccessExpressionSyntax memberAccess )
98+ {
99+ var operation = FindEventOperation ( memberAccess . Expression ) ;
100+ var imageType = GetImageTypeFromArguments ( invocation ) ;
101+
102+ if ( operation == "Create" && imageType is "PreImage" or "Both" )
103+ {
104+ var diagnostic = Diagnostic . Create (
105+ RulePreImageOnCreate ,
106+ invocation . GetLocation ( ) ,
107+ imageType ) ;
108+ context . ReportDiagnostic ( diagnostic ) ;
109+ }
110+ else if ( operation == "Delete" && imageType is "PostImage" or "Both" )
111+ {
112+ var diagnostic = Diagnostic . Create (
113+ RulePostImageOnDelete ,
114+ invocation . GetLocation ( ) ,
115+ imageType ) ;
116+ context . ReportDiagnostic ( diagnostic ) ;
117+ }
118+ }
119+
120+ private static string ? FindEventOperation ( ExpressionSyntax expression )
121+ {
122+ var current = expression ;
123+
124+ while ( current is not null )
125+ {
126+ if ( current is InvocationExpressionSyntax inv )
127+ {
128+ var methodName = GetMethodName ( inv ) ;
129+ if ( methodName == "RegisterPluginStep" )
130+ return GetOperationFromRegisterPluginStep ( inv ) ;
131+
132+ current = GetReceiverExpression ( inv ) ;
133+ }
134+ else if ( current is MemberAccessExpressionSyntax ma )
135+ {
136+ current = ma . Expression ;
137+ }
138+ else
139+ {
140+ break ;
141+ }
142+ }
143+
144+ return null ;
145+ }
146+
147+ private static string ? GetMethodName ( InvocationExpressionSyntax invocation )
148+ {
149+ return invocation . Expression switch
150+ {
151+ MemberAccessExpressionSyntax ma => ma . Name switch
152+ {
153+ GenericNameSyntax gns => gns . Identifier . ValueText ,
154+ IdentifierNameSyntax ins => ins . Identifier . ValueText ,
155+ _ => null ,
156+ } ,
157+ IdentifierNameSyntax id => id . Identifier . ValueText ,
158+ GenericNameSyntax gn => gn . Identifier . ValueText ,
159+ _ => null ,
160+ } ;
161+ }
162+
163+ private static ExpressionSyntax ? GetReceiverExpression ( InvocationExpressionSyntax invocation )
164+ {
165+ return invocation . Expression switch
166+ {
167+ MemberAccessExpressionSyntax ma => ma . Expression ,
168+ _ => null ,
169+ } ;
170+ }
171+
172+ private static string ? GetOperationFromRegisterPluginStep ( InvocationExpressionSyntax invocation )
173+ {
174+ foreach ( var arg in invocation . ArgumentList . Arguments )
175+ {
176+ var argText = arg . Expression . ToString ( ) ;
177+
178+ if ( argText . IndexOf ( "Create" , StringComparison . Ordinal ) >= 0 )
179+ return "Create" ;
180+ if ( argText . IndexOf ( "Delete" , StringComparison . Ordinal ) >= 0 )
181+ return "Delete" ;
182+ if ( argText . IndexOf ( "Update" , StringComparison . Ordinal ) >= 0 )
183+ return "Update" ;
184+ }
185+
186+ return null ;
187+ }
188+
189+ private static string ? GetImageTypeFromArguments ( InvocationExpressionSyntax invocation )
190+ {
191+ if ( invocation . ArgumentList . Arguments . Count == 0 )
192+ return null ;
193+
194+ var firstArg = invocation . ArgumentList . Arguments [ 0 ] . Expression ;
195+ var argText = firstArg . ToString ( ) ;
196+
197+ if ( argText . IndexOf ( "PreImage" , StringComparison . Ordinal ) >= 0 )
198+ return "PreImage" ;
199+ if ( argText . IndexOf ( "PostImage" , StringComparison . Ordinal ) >= 0 )
200+ return "PostImage" ;
201+ if ( argText . IndexOf ( "Both" , StringComparison . Ordinal ) >= 0 )
202+ return "Both" ;
203+
204+ return null ;
205+ }
206+ }
0 commit comments