1010
1111namespace Microsoft . DurableTask . Analyzers . Activities ;
1212
13+ /// <summary>
14+ /// Analyzer that detects calls to non-existent activities and sub-orchestrations.
1315/// <summary>
1416/// Analyzer that detects calls to non-existent activities and sub-orchestrations.
1517/// </summary>
@@ -26,6 +28,22 @@ public sealed class FunctionNotFoundAnalyzer : DiagnosticAnalyzer
2628 /// </summary>
2729 public const string SubOrchestrationNotFoundDiagnosticId = "DURABLE2004" ;
2830
31+ // System assemblies to skip when scanning referenced assemblies for performance
32+ static readonly HashSet < string > SystemAssemblyNames =
33+ [
34+ "mscorlib" ,
35+ "System" ,
36+ "netstandard"
37+ ] ;
38+
39+ static readonly string [ ] SystemAssemblyPrefixes =
40+ [
41+ "System." ,
42+ "Microsoft.CodeAnalysis" ,
43+ "Microsoft.CSharp" ,
44+ "Microsoft.VisualBasic"
45+ ] ;
46+
2947 static readonly LocalizableString ActivityNotFoundTitle = new LocalizableResourceString ( nameof ( Resources . ActivityNotFoundAnalyzerTitle ) , Resources . ResourceManager , typeof ( Resources ) ) ;
3048 static readonly LocalizableString ActivityNotFoundMessageFormat = new LocalizableResourceString ( nameof ( Resources . ActivityNotFoundAnalyzerMessageFormat ) , Resources . ResourceManager , typeof ( Resources ) ) ;
3149
@@ -129,19 +147,13 @@ public override void Initialize(AnalysisContext context)
129147 }
130148
131149 // Check for Activity defined via [ActivityTrigger]
132- if ( knownSymbols . ActivityTriggerAttribute != null &&
133- methodSymbol . ContainsAttributeInAnyMethodArguments ( knownSymbols . ActivityTriggerAttribute ) &&
134- knownSymbols . FunctionNameAttribute != null &&
135- methodSymbol . TryGetSingleValueFromAttribute ( knownSymbols . FunctionNameAttribute , out string functionName ) )
150+ if ( IsActivityMethod ( methodSymbol , knownSymbols , out string functionName ) )
136151 {
137152 activityNames . Add ( functionName ) ;
138153 }
139154
140155 // Check for Orchestrator defined via [OrchestrationTrigger]
141- if ( knownSymbols . FunctionOrchestrationAttribute != null &&
142- methodSymbol . ContainsAttributeInAnyMethodArguments ( knownSymbols . FunctionOrchestrationAttribute ) &&
143- knownSymbols . FunctionNameAttribute != null &&
144- methodSymbol . TryGetSingleValueFromAttribute ( knownSymbols . FunctionNameAttribute , out string orchestratorFunctionName ) )
156+ if ( IsOrchestratorMethod ( methodSymbol , knownSymbols , out string orchestratorFunctionName ) )
145157 {
146158 orchestratorNames . Add ( orchestratorFunctionName ) ;
147159 }
@@ -173,7 +185,7 @@ public override void Initialize(AnalysisContext context)
173185
174186 // Check for ITaskOrchestrator implementations (class-based orchestrators)
175187 if ( knownSymbols . TaskOrchestratorInterface != null &&
176- classSymbol . AllInterfaces . Any ( i => SymbolEqualityComparer . Default . Equals ( i , knownSymbols . TaskOrchestratorInterface ) ) )
188+ ImplementsInterface ( classSymbol , knownSymbols . TaskOrchestratorInterface ) )
177189 {
178190 orchestratorNames . Add ( classSymbol . Name ) ;
179191 }
@@ -281,6 +293,49 @@ public override void Initialize(AnalysisContext context)
281293 return constant . Value ? . ToString ( ) ;
282294 }
283295
296+ static bool IsActivityMethod ( IMethodSymbol methodSymbol , KnownTypeSymbols knownSymbols , out string functionName )
297+ {
298+ functionName = string . Empty ;
299+
300+ if ( knownSymbols . ActivityTriggerAttribute == null ||
301+ ! methodSymbol . ContainsAttributeInAnyMethodArguments ( knownSymbols . ActivityTriggerAttribute ) )
302+ {
303+ return false ;
304+ }
305+
306+ if ( knownSymbols . FunctionNameAttribute == null ||
307+ ! methodSymbol . TryGetSingleValueFromAttribute ( knownSymbols . FunctionNameAttribute , out functionName ) )
308+ {
309+ return false ;
310+ }
311+
312+ return true ;
313+ }
314+
315+ static bool IsOrchestratorMethod ( IMethodSymbol methodSymbol , KnownTypeSymbols knownSymbols , out string functionName )
316+ {
317+ functionName = string . Empty ;
318+
319+ if ( knownSymbols . FunctionOrchestrationAttribute == null ||
320+ ! methodSymbol . ContainsAttributeInAnyMethodArguments ( knownSymbols . FunctionOrchestrationAttribute ) )
321+ {
322+ return false ;
323+ }
324+
325+ if ( knownSymbols . FunctionNameAttribute == null ||
326+ ! methodSymbol . TryGetSingleValueFromAttribute ( knownSymbols . FunctionNameAttribute , out functionName ) )
327+ {
328+ return false ;
329+ }
330+
331+ return true ;
332+ }
333+
334+ static bool ImplementsInterface ( INamedTypeSymbol typeSymbol , INamedTypeSymbol interfaceSymbol )
335+ {
336+ return typeSymbol . AllInterfaces . Any ( i => SymbolEqualityComparer . Default . Equals ( i , interfaceSymbol ) ) ;
337+ }
338+
284339 static bool ClassOverridesMethod ( INamedTypeSymbol classSymbol , IMethodSymbol methodToFind )
285340 {
286341 INamedTypeSymbol ? baseType = classSymbol ;
@@ -308,14 +363,21 @@ static void ScanReferencedAssemblies(
308363 {
309364 // Scan all referenced assemblies for activities and orchestrators
310365 // Skip system assemblies for performance
311- foreach ( IAssemblySymbol assembly in compilation . References
312- . Select ( r => compilation . GetAssemblyOrModuleSymbol ( r ) )
313- . OfType < IAssemblySymbol > ( )
314- . Where ( a => ! IsSystemAssembly ( a ) ) )
366+ foreach ( MetadataReference reference in compilation . References )
315367 {
316368 cancellationToken . ThrowIfCancellationRequested ( ) ;
317369
318- // Get all types in the assembly
370+ if ( compilation . GetAssemblyOrModuleSymbol ( reference ) is not IAssemblySymbol assembly )
371+ {
372+ continue ;
373+ }
374+
375+ if ( IsSystemAssembly ( assembly ) )
376+ {
377+ continue ;
378+ }
379+
380+ // Scan this assembly
319381 ScanNamespaceForFunctions (
320382 assembly . GlobalNamespace ,
321383 knownSymbols ,
@@ -330,13 +392,21 @@ static bool IsSystemAssembly(IAssemblySymbol assembly)
330392 {
331393 // Skip well-known system assemblies to improve performance
332394 string assemblyName = assembly . Name ;
333- return assemblyName == "mscorlib" ||
334- assemblyName == "System" ||
335- assemblyName == "netstandard" ||
336- assemblyName . StartsWith ( "System." , StringComparison . Ordinal ) ||
337- assemblyName . StartsWith ( "Microsoft.CodeAnalysis" , StringComparison . Ordinal ) ||
338- assemblyName . StartsWith ( "Microsoft.CSharp" , StringComparison . Ordinal ) ||
339- assemblyName . StartsWith ( "Microsoft.VisualBasic" , StringComparison . Ordinal ) ;
395+
396+ if ( SystemAssemblyNames . Contains ( assemblyName ) )
397+ {
398+ return true ;
399+ }
400+
401+ foreach ( string prefix in SystemAssemblyPrefixes )
402+ {
403+ if ( assemblyName . StartsWith ( prefix , StringComparison . Ordinal ) )
404+ {
405+ return true ;
406+ }
407+ }
408+
409+ return false ;
340410 }
341411
342412 static void ScanNamespaceForFunctions (
@@ -365,7 +435,7 @@ static void ScanNamespaceForFunctions(
365435
366436 // Check for ITaskOrchestrator implementations (class-based orchestrators)
367437 if ( knownSymbols . TaskOrchestratorInterface != null &&
368- typeSymbol . AllInterfaces . Any ( i => SymbolEqualityComparer . Default . Equals ( i , knownSymbols . TaskOrchestratorInterface ) ) )
438+ ImplementsInterface ( typeSymbol , knownSymbols . TaskOrchestratorInterface ) )
369439 {
370440 orchestratorNames . Add ( typeSymbol . Name ) ;
371441 }
@@ -381,19 +451,13 @@ static void ScanNamespaceForFunctions(
381451 }
382452
383453 // Check for Activity defined via [ActivityTrigger]
384- if ( knownSymbols . ActivityTriggerAttribute != null &&
385- methodSymbol . ContainsAttributeInAnyMethodArguments ( knownSymbols . ActivityTriggerAttribute ) &&
386- knownSymbols . FunctionNameAttribute != null &&
387- methodSymbol . TryGetSingleValueFromAttribute ( knownSymbols . FunctionNameAttribute , out string functionName ) )
454+ if ( IsActivityMethod ( methodSymbol , knownSymbols , out string functionName ) )
388455 {
389456 activityNames . Add ( functionName ) ;
390457 }
391458
392459 // Check for Orchestrator defined via [OrchestrationTrigger]
393- if ( knownSymbols . FunctionOrchestrationAttribute != null &&
394- methodSymbol . ContainsAttributeInAnyMethodArguments ( knownSymbols . FunctionOrchestrationAttribute ) &&
395- knownSymbols . FunctionNameAttribute != null &&
396- methodSymbol . TryGetSingleValueFromAttribute ( knownSymbols . FunctionNameAttribute , out string orchestratorFunctionName ) )
460+ if ( IsOrchestratorMethod ( methodSymbol , knownSymbols , out string orchestratorFunctionName ) )
397461 {
398462 orchestratorNames . Add ( orchestratorFunctionName ) ;
399463 }
0 commit comments