Skip to content

Commit e94bcd5

Browse files
CopilotYunchuWang
andcommitted
Use semantic lookup instead of assembly references
Replace fragile assembly reference checking with semantic lookup using ContainsDurableSymbols. This checks if an assembly contains any Durable Task types (TaskActivity, ITaskOrchestrator, ActivityTriggerAttribute, OrchestrationTriggerAttribute) by examining the symbol tree, which is more robust and maintainable than checking package references. Co-authored-by: YunchuWang <12449837+YunchuWang@users.noreply.github.com>
1 parent 7d44132 commit e94bcd5

File tree

1 file changed

+67
-12
lines changed

1 file changed

+67
-12
lines changed

src/Analyzers/Activities/FunctionNotFoundAnalyzer.cs

Lines changed: 67 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,7 @@ static void ScanReferencedAssemblies(
360360
CancellationToken cancellationToken)
361361
{
362362
// Scan all referenced assemblies for activities and orchestrators
363-
// Skip system assemblies and assemblies without Durable Task references for performance
363+
// Skip system assemblies and assemblies without Durable symbols for performance
364364
foreach (MetadataReference reference in compilation.References)
365365
{
366366
cancellationToken.ThrowIfCancellationRequested();
@@ -375,7 +375,7 @@ static void ScanReferencedAssemblies(
375375
continue;
376376
}
377377

378-
if (!ShouldScanAssembly(assembly))
378+
if (!ShouldScanAssembly(assembly, knownSymbols))
379379
{
380380
continue;
381381
}
@@ -409,18 +409,59 @@ static bool IsSystemAssembly(IAssemblySymbol assembly)
409409
return false;
410410
}
411411

412-
static bool ShouldScanAssembly(IAssemblySymbol assembly)
412+
static bool ShouldScanAssembly(IAssemblySymbol assembly, KnownTypeSymbols knownSymbols)
413413
{
414-
// Only scan assemblies that reference Durable Task types
415-
// This filters out transitive dependencies that don't contain activities/orchestrators
416-
foreach (AssemblyIdentity referencedAssembly in assembly.Modules.SelectMany(m => m.ReferencedAssemblies))
414+
// Check if assembly contains any Durable Task symbols using semantic lookup
415+
// This is more robust than checking assembly references
416+
return ContainsDurableSymbols(assembly.GlobalNamespace, knownSymbols);
417+
}
418+
419+
static bool ContainsDurableSymbols(INamespaceSymbol namespaceSymbol, KnownTypeSymbols knownSymbols)
420+
{
421+
// Check types in this namespace for Durable Task symbols
422+
foreach (INamedTypeSymbol typeSymbol in namespaceSymbol.GetTypeMembers())
417423
{
418-
string refName = referencedAssembly.Name;
419-
// Check for packages that users directly reference when defining activities/orchestrators
420-
// Microsoft.DurableTask.Worker: for non-function scenarios (console apps, etc.)
421-
// Microsoft.Azure.Functions.Worker.Extensions.DurableTask: for Azure Functions scenarios
422-
if (refName == "Microsoft.DurableTask.Worker" ||
423-
refName == "Microsoft.Azure.Functions.Worker.Extensions.DurableTask")
424+
// Check if type derives from TaskActivity
425+
if (knownSymbols.TaskActivityBase != null && DerivesFrom(typeSymbol, knownSymbols.TaskActivityBase))
426+
{
427+
return true;
428+
}
429+
430+
// Check if type implements ITaskOrchestrator
431+
if (knownSymbols.TaskOrchestratorInterface != null &&
432+
ImplementsInterface(typeSymbol, knownSymbols.TaskOrchestratorInterface))
433+
{
434+
return true;
435+
}
436+
437+
// Check methods for Durable Task attributes
438+
foreach (ISymbol member in typeSymbol.GetMembers())
439+
{
440+
if (member is not IMethodSymbol methodSymbol)
441+
{
442+
continue;
443+
}
444+
445+
// Check for ActivityTrigger attribute
446+
if (knownSymbols.ActivityTriggerAttribute != null &&
447+
methodSymbol.ContainsAttributeInAnyMethodArguments(knownSymbols.ActivityTriggerAttribute))
448+
{
449+
return true;
450+
}
451+
452+
// Check for OrchestrationTrigger attribute
453+
if (knownSymbols.FunctionOrchestrationAttribute != null &&
454+
methodSymbol.ContainsAttributeInAnyMethodArguments(knownSymbols.FunctionOrchestrationAttribute))
455+
{
456+
return true;
457+
}
458+
}
459+
}
460+
461+
// Recursively check nested namespaces
462+
foreach (INamespaceSymbol nestedNamespace in namespaceSymbol.GetNamespaceMembers())
463+
{
464+
if (ContainsDurableSymbols(nestedNamespace, knownSymbols))
424465
{
425466
return true;
426467
}
@@ -429,6 +470,20 @@ static bool ShouldScanAssembly(IAssemblySymbol assembly)
429470
return false;
430471
}
431472

473+
static bool DerivesFrom(INamedTypeSymbol typeSymbol, INamedTypeSymbol baseType)
474+
{
475+
INamedTypeSymbol? current = typeSymbol.BaseType;
476+
while (current != null)
477+
{
478+
if (SymbolEqualityComparer.Default.Equals(current.OriginalDefinition, baseType))
479+
{
480+
return true;
481+
}
482+
current = current.BaseType;
483+
}
484+
return false;
485+
}
486+
432487
static void ScanNamespaceForFunctions(
433488
INamespaceSymbol namespaceSymbol,
434489
KnownTypeSymbols knownSymbols,

0 commit comments

Comments
 (0)