@@ -18,23 +18,19 @@ public override void Initialize(AnalysisContext context)
1818 {
1919 context . RegisterSyntaxNodeAction (
2020 new Action < SyntaxNodeAnalysisContext > ( this . AnalyzeSyntaxNodes ) ,
21- new SyntaxKind [ ] {
22- SyntaxKind . OptionDataType
23- }
24- ) ;
21+ SyntaxKind . OptionDataType ) ;
2522
2623 context . RegisterSymbolAction (
2724 new Action < SymbolAnalysisContext > ( this . AnalyzeVariables ) ,
2825 SymbolKind . GlobalVariable ,
2926 SymbolKind . LocalVariable ) ;
3027 }
3128
29+ #region SyntaxNodeAnalysis
3230 private void AnalyzeSyntaxNodes ( SyntaxNodeAnalysisContext ctx )
3331 {
3432 if ( ctx . IsObsoletePendingOrRemoved ( ) || ctx . Node is not OptionDataTypeSyntax optionDataType )
35- {
3633 return ;
37- }
3834
3935 bool skipDueToLocalOrGlobalVariable = optionDataType . Parent is SimpleTypeReferenceSyntax && ! IsParameterOrReturnValue ( optionDataType ) ;
4036 if ( skipDueToLocalOrGlobalVariable )
@@ -48,23 +44,10 @@ private void AnalyzeSyntaxNodes(SyntaxNodeAnalysisContext ctx)
4844 if ( skipDueToTableIsOfTypeCDS )
4945 return ;
5046
51- // Handle FlowField with CalcFormula to a Field of Type option
52- if ( optionDataType . Parent is FieldSyntax fieldSyntax )
47+ if ( optionDataType . Parent is FieldSyntax fieldSyntax &&
48+ IsFlowFieldWithOptionCalculation ( fieldSyntax , ctx ) )
5349 {
54- var calcFormulaPropertySyntax = fieldSyntax . PropertyList ? . Properties
55- . OfType < PropertySyntax > ( )
56- . Select ( p => p . Value )
57- . OfType < CalculationFormulaPropertyValueSyntax > ( )
58- . FirstOrDefault ( ) ;
59-
60- if ( calcFormulaPropertySyntax is not null &&
61- calcFormulaPropertySyntax is FieldCalculationFormulaSyntax fieldCalculation &&
62- fieldCalculation . Field is QualifiedNameSyntax qualifiedNameSyntax &&
63- ctx . SemanticModel . GetSymbolInfo ( qualifiedNameSyntax , ctx . CancellationToken ) . Symbol is ISymbol fieldSymbol &&
64- fieldSymbol . GetTypeSymbol ( ) . GetNavTypeKindSafe ( ) == NavTypeKind . Option )
65- {
66- return ;
67- }
50+ return ;
6851 }
6952
7053 ctx . ReportDiagnostic ( Diagnostic . Create (
@@ -79,6 +62,32 @@ private static bool IsParameterOrReturnValue(OptionDataTypeSyntax optionDataType
7962 return parent is not null && ( parent . Kind == SyntaxKind . Parameter || parent . Kind == SyntaxKind . ReturnValue ) ;
8063 }
8164
65+ private static bool IsFlowFieldWithOptionCalculation ( FieldSyntax fieldSyntax , SyntaxNodeAnalysisContext ctx )
66+ {
67+ var propertyList = fieldSyntax . PropertyList ? . Properties ;
68+ if ( propertyList is null )
69+ return false ;
70+
71+ foreach ( var property in propertyList )
72+ {
73+ ctx . CancellationToken . ThrowIfCancellationRequested ( ) ;
74+
75+ if ( property is PropertySyntax propertySyntax &&
76+ propertySyntax . Value is FieldCalculationFormulaSyntax fieldCalculation &&
77+ fieldCalculation . Field is QualifiedNameSyntax qualifiedNameSyntax )
78+ {
79+ var fieldSymbol = ctx . SemanticModel . GetSymbolInfo ( qualifiedNameSyntax , ctx . CancellationToken ) . Symbol ;
80+ if ( fieldSymbol ? . GetTypeSymbol ( ) . GetNavTypeKindSafe ( ) == NavTypeKind . Option )
81+ {
82+ return true ;
83+ }
84+ }
85+ }
86+ return false ;
87+ }
88+ #endregion
89+
90+ #region VariableAnalysis
8291 private void AnalyzeVariables ( SymbolAnalysisContext ctx )
8392 {
8493 if ( ctx . IsObsoletePendingOrRemoved ( ) || ctx . Symbol is not IVariableSymbol variable )
@@ -95,31 +104,23 @@ private void AnalyzeVariables(SymbolAnalysisContext ctx)
95104 if ( containingSymbol is null )
96105 return ;
97106
98- var LocalVariablesName = GetReferencedVariableNames ( containingSymbol , variable ) ;
99- HasVariablesNotInSource = ( ( IMethodSymbol ) containingSymbol . OriginalDefinition ) . LocalVariables
100- . Where ( var => ! var . Type . GetLocation ( ) . IsInSource )
101- . Where ( var => LocalVariablesName ? . Contains ( var . OriginalDefinition . Name ) == true )
102- . Any ( ) ;
107+ var localVariablesName = GetReferencedVariableNames ( containingSymbol , variable ) ;
108+ var localVariables = ( ( IMethodSymbol ) containingSymbol . OriginalDefinition ) . LocalVariables ;
109+ HasVariablesNotInSource = HasReferencedVariablesNotInSource ( localVariables , localVariablesName ) ;
103110 break ;
104111
105112 case SymbolKind . GlobalVariable :
106113 IApplicationObjectTypeSymbol ? applicationObjectTypeSymbol = variable . GetContainingApplicationObjectTypeSymbol ( ) ;
107114 if ( applicationObjectTypeSymbol is null )
108115 return ;
109116
110- var GlobalVariablesName = GetReferencedVariableNames ( applicationObjectTypeSymbol , variable ) ;
111- HasVariablesNotInSource = applicationObjectTypeSymbol . GetMembers ( )
112- . Where ( member => member . Kind == SymbolKind . GlobalVariable || member . Kind == SymbolKind . Method )
113- . SelectMany ( member => member is IMethodSymbol methodSymbol
114- ? methodSymbol . LocalVariables
115- : member is IVariableSymbol variableSymbol ? Enumerable . Repeat ( variableSymbol , 1 ) : Enumerable . Empty < IVariableSymbol > ( ) )
116- . Where ( var => ! var . Type . GetLocation ( ) . IsInSource )
117- . Where ( var => GlobalVariablesName ? . Contains ( var . OriginalDefinition . Name ) == true )
118- . Any ( ) ;
117+ var globalVariablesName = GetReferencedVariableNames ( applicationObjectTypeSymbol , variable ) ;
118+ var globalVariables = GetGlobalVariablesFromApplicationObject ( applicationObjectTypeSymbol ) ;
119+ HasVariablesNotInSource = HasReferencedVariablesNotInSource ( globalVariables , globalVariablesName ) ;
119120 break ;
120121 }
121122
122- if ( HasVariablesNotInSource == false )
123+ if ( HasVariablesNotInSource is false )
123124 {
124125 ctx . ReportDiagnostic ( Diagnostic . Create (
125126 DiagnosticDescriptors . Rule0088AvoidOptionTypes ,
@@ -128,25 +129,84 @@ private void AnalyzeVariables(SymbolAnalysisContext ctx)
128129 }
129130 }
130131
131- private static IEnumerable < string > ? GetReferencedVariableNames ( ISymbol ? containingSymbol , IVariableSymbol variable )
132+ private static bool HasReferencedVariablesNotInSource (
133+ IEnumerable < IVariableSymbol > variables ,
134+ HashSet < string > ? referencedNames )
135+ {
136+ if ( referencedNames is null )
137+ return false ;
138+
139+ // Filter by name first (cheaper operation), then check location
140+ return variables
141+ . Where ( var => referencedNames . Contains ( var . OriginalDefinition . Name ) )
142+ . Any ( var => ! var . Type . GetLocation ( ) . IsInSource ) ;
143+ }
144+
145+ private static List < IVariableSymbol > GetGlobalVariablesFromApplicationObject ( IApplicationObjectTypeSymbol applicationObjectTypeSymbol )
146+ {
147+ var variables = new List < IVariableSymbol > ( ) ;
148+
149+ foreach ( var member in applicationObjectTypeSymbol . GetMembers ( ) )
150+ {
151+ if ( member . Kind == SymbolKind . GlobalVariable && member is IVariableSymbol globalVar )
152+ {
153+ variables . Add ( globalVar ) ;
154+ }
155+ else if ( member . Kind == SymbolKind . Method && member is IMethodSymbol method )
156+ {
157+ variables . AddRange ( method . LocalVariables ) ;
158+ }
159+ }
160+
161+ return variables ;
162+ }
163+
164+ private static HashSet < string > ? GetReferencedVariableNames ( ISymbol ? containingSymbol , IVariableSymbol variable )
132165 {
133166 SyntaxNode ? syntaxNode = containingSymbol ? . DeclaringSyntaxReference ? . GetSyntax ( ) ;
134167 if ( syntaxNode is null )
135168 return null ;
136169
137- var nodes = syntaxNode . DescendantNodes ( )
138- . OfType < ArgumentListSyntax > ( )
139- . Where ( argList => argList . Arguments . Any ( argument =>
140- ( argument is OptionAccessExpressionSyntax optionAccess &&
141- optionAccess . Expression . GetIdentifierOrLiteralValue ( ) == variable . Name ) ||
142- argument . GetIdentifierOrLiteralValue ( ) == variable . Name ) ) ;
143-
144- return nodes
145- . SelectMany ( node => node . AncestorsAndSelf ( )
146- . OfType < ExpressionStatementSyntax > ( )
147- . SelectMany ( exprStmt => exprStmt . DescendantNodes ( )
148- . OfType < MemberAccessExpressionSyntax > ( )
149- . Select ( memberAccess => memberAccess . Expression . ToString ( ) . UnquoteIdentifier ( ) ) ) )
150- . Distinct ( ) ;
170+ var variableName = variable . Name ;
171+ var referencedNames = new HashSet < string > ( ) ;
172+
173+ // Find all argument lists that reference the variable
174+ var argumentLists = syntaxNode . DescendantNodes ( )
175+ . OfType < ArgumentListSyntax > ( ) ;
176+
177+ foreach ( var argList in argumentLists )
178+ {
179+ // Check if any argument in this list references our variable
180+ bool hasVariableReference = false ;
181+ foreach ( var argument in argList . Arguments )
182+ {
183+ if ( ( argument is OptionAccessExpressionSyntax optionAccess &&
184+ optionAccess . Expression . GetIdentifierOrLiteralValue ( ) == variableName ) ||
185+ argument . GetIdentifierOrLiteralValue ( ) == variableName )
186+ {
187+ hasVariableReference = true ;
188+ break ;
189+ }
190+ }
191+
192+ if ( hasVariableReference )
193+ {
194+ // Get the member access expressions from the containing expression statement
195+ foreach ( var exprStmt in argList . AncestorsAndSelf ( ) . OfType < ExpressionStatementSyntax > ( ) )
196+ {
197+ foreach ( var memberAccess in exprStmt . DescendantNodes ( ) . OfType < MemberAccessExpressionSyntax > ( ) )
198+ {
199+ var expressionName = memberAccess . Expression . ToString ( ) . UnquoteIdentifier ( ) ;
200+ if ( ! string . IsNullOrEmpty ( expressionName ) )
201+ {
202+ referencedNames . Add ( expressionName ) ;
203+ }
204+ }
205+ }
206+ }
207+ }
208+
209+ return referencedNames ;
151210 }
211+ #endregion
152212}
0 commit comments