@@ -45,7 +45,11 @@ public sealed class EnvironmentOrchestrationVisitor : MethodProbeOrchestrationVi
4545 /// <inheritdoc/>
4646 public override bool Initialize ( )
4747 {
48- return this . KnownTypeSymbols . Environment != null ;
48+ return this . KnownTypeSymbols . Environment != null
49+ || this . KnownTypeSymbols . IConfiguration != null
50+ || this . KnownTypeSymbols . IOptions != null
51+ || this . KnownTypeSymbols . IOptionsSnapshot != null
52+ || this . KnownTypeSymbols . IOptionsMonitor != null ;
4953 }
5054
5155 /// <inheritdoc/>
@@ -57,19 +61,106 @@ protected override void VisitMethod(SemanticModel semanticModel, SyntaxNode meth
5761 return ;
5862 }
5963
60- foreach ( IInvocationOperation invocation in methodOperation . Descendants ( ) . OfType < IInvocationOperation > ( ) )
64+ foreach ( IOperation operation in methodOperation . Descendants ( ) )
6165 {
62- IMethodSymbol targetMethod = invocation . TargetMethod ;
66+ this . CheckOperationForEnvironmentVariableAccess ( operation , methodSymbol , orchestrationName , reportDiagnostic ) ;
67+ }
68+ }
6369
64- if ( targetMethod . ContainingType . Equals ( this . KnownTypeSymbols . Environment , SymbolEqualityComparer . Default ) &&
65- targetMethod . Name is nameof ( Environment . GetEnvironmentVariable ) or nameof ( Environment . GetEnvironmentVariables ) or nameof ( Environment . ExpandEnvironmentVariables ) )
66- {
67- string invocationName = targetMethod . ToDisplayString ( SymbolDisplayFormat . CSharpShortErrorMessageFormat ) ;
70+ void CheckOperationForEnvironmentVariableAccess ( IOperation operation , IMethodSymbol methodSymbol , string orchestrationName , Action < Diagnostic > reportDiagnostic )
71+ {
72+ switch ( operation )
73+ {
74+ case IInvocationOperation invocation when this . IsEnvironmentInvocation ( invocation . TargetMethod ) :
75+ this . Report ( operation , invocation . TargetMethod . ToDisplayString ( SymbolDisplayFormat . CSharpShortErrorMessageFormat ) , methodSymbol , orchestrationName , reportDiagnostic ) ;
76+ break ;
77+ case IInvocationOperation invocation when this . IsConfigurationOrOptionsInvocation ( invocation ) :
78+ this . Report ( operation , invocation . TargetMethod . ToDisplayString ( SymbolDisplayFormat . CSharpShortErrorMessageFormat ) , methodSymbol , orchestrationName , reportDiagnostic ) ;
79+ break ;
80+ case IPropertyReferenceOperation propertyReference when this . IsConfigurationOrOptionsPropertyReference ( propertyReference ) :
81+ this . Report ( operation , propertyReference . Property . ToDisplayString ( SymbolDisplayFormat . CSharpShortErrorMessageFormat ) , methodSymbol , orchestrationName , reportDiagnostic ) ;
82+ break ;
83+ }
84+ }
85+
86+ void Report ( IOperation operation , string memberName , IMethodSymbol methodSymbol , string orchestrationName , Action < Diagnostic > reportDiagnostic )
87+ {
88+ reportDiagnostic ( RoslynExtensions . BuildDiagnostic ( Rule , operation , methodSymbol . Name , memberName , orchestrationName ) ) ;
89+ }
90+
91+ bool IsEnvironmentInvocation ( IMethodSymbol targetMethod )
92+ {
93+ return this . KnownTypeSymbols . Environment != null &&
94+ targetMethod . ContainingType . Equals ( this . KnownTypeSymbols . Environment , SymbolEqualityComparer . Default ) &&
95+ targetMethod . Name is nameof ( Environment . GetEnvironmentVariable ) or nameof ( Environment . GetEnvironmentVariables ) or nameof ( Environment . ExpandEnvironmentVariables ) ;
96+ }
97+
98+ bool IsConfigurationOrOptionsInvocation ( IInvocationOperation invocation )
99+ {
100+ if ( this . IsConfigurationOrOptionsType ( invocation . Instance ? . Type ) )
101+ {
102+ return true ;
103+ }
68104
69- // e.g.: "The method 'Method1' uses environment variables through 'Environment.GetEnvironmentVariable()' that may cause non-deterministic behavior when invoked from orchestration 'MyOrchestrator'"
70- reportDiagnostic ( RoslynExtensions . BuildDiagnostic ( Rule , invocation , methodSymbol . Name , invocationName , orchestrationName ) ) ;
105+ if ( invocation . TargetMethod . IsExtensionMethod )
106+ {
107+ ITypeSymbol ? receiverType = invocation . TargetMethod . ReducedFrom ? . Parameters . FirstOrDefault ( ) ? . Type ?? invocation . TargetMethod . Parameters . FirstOrDefault ( ) ? . Type ;
108+ if ( this . IsConfigurationOrOptionsType ( receiverType ) )
109+ {
110+ return true ;
71111 }
72112 }
113+
114+ return false ;
115+ }
116+
117+ bool IsConfigurationOrOptionsPropertyReference ( IPropertyReferenceOperation propertyReference )
118+ {
119+ if ( this . IsConfigurationOrOptionsType ( propertyReference . Instance ? . Type ) )
120+ {
121+ return true ;
122+ }
123+
124+ return this . IsConfigurationOrOptionsType ( propertyReference . Property . ContainingType ) ;
125+ }
126+
127+ bool IsConfigurationOrOptionsType ( ITypeSymbol ? type )
128+ {
129+ if ( type is null )
130+ {
131+ return false ;
132+ }
133+
134+ if ( this . IsConfigurationType ( type ) )
135+ {
136+ return true ;
137+ }
138+
139+ if ( type is INamedTypeSymbol namedType && this . IsOptionsType ( namedType ) )
140+ {
141+ return true ;
142+ }
143+
144+ return type . AllInterfaces . Any ( this . IsConfigurationType ) ||
145+ ( type is INamedTypeSymbol typeSymbol && typeSymbol . AllInterfaces . Any ( this . IsOptionsType ) ) ;
146+ }
147+
148+ bool IsConfigurationType ( ITypeSymbol type )
149+ {
150+ return this . KnownTypeSymbols . IConfiguration != null &&
151+ SymbolEqualityComparer . Default . Equals ( type , this . KnownTypeSymbols . IConfiguration ) ;
152+ }
153+
154+ bool IsOptionsType ( INamedTypeSymbol type )
155+ {
156+ return this . IsOptionsType ( type , this . KnownTypeSymbols . IOptions )
157+ || this . IsOptionsType ( type , this . KnownTypeSymbols . IOptionsSnapshot )
158+ || this . IsOptionsType ( type , this . KnownTypeSymbols . IOptionsMonitor ) ;
159+ }
160+
161+ bool IsOptionsType ( INamedTypeSymbol type , INamedTypeSymbol ? optionsSymbol )
162+ {
163+ return optionsSymbol != null && SymbolEqualityComparer . Default . Equals ( type . OriginalDefinition , optionsSymbol ) ;
73164 }
74165 }
75166}
0 commit comments