-
Notifications
You must be signed in to change notification settings - Fork 34
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Function orchestration bindings analyzer (#304)
Adds an analyzer that reports a warning when a Durable Function orchestration has parameters bindings other than `OrchestrationTrigger` (such as `DurableClient` or `EntityTrigger`).
- Loading branch information
1 parent
c4fac92
commit c65608e
Showing
4 changed files
with
166 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
73 changes: 73 additions & 0 deletions
73
src/Analyzers/Functions/Orchestration/OtherBindingsOrchestrationAnalyzer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT License. | ||
|
||
using System.Collections.Immutable; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
using Microsoft.DurableTask.Analyzers.Orchestration; | ||
using static Microsoft.DurableTask.Analyzers.Functions.Orchestration.OtherBindingsOrchestrationAnalyzer; | ||
|
||
namespace Microsoft.DurableTask.Analyzers.Functions.Orchestration; | ||
|
||
/// <summary> | ||
/// Analyzer that reports a warning when a Durable Function Orchestration has parameters bindings other than OrchestrationTrigger. | ||
/// </summary> | ||
[DiagnosticAnalyzer(LanguageNames.CSharp)] | ||
class OtherBindingsOrchestrationAnalyzer : OrchestrationAnalyzer<OtherBindingsOrchestrationOrchestrationVisitor> | ||
{ | ||
/// <summary> | ||
/// Diagnostic ID supported for the analyzer. | ||
/// </summary> | ||
public const string DiagnosticId = "DURABLE0008"; | ||
|
||
static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.OtherBindingsOrchestrationAnalyzerTitle), Resources.ResourceManager, typeof(Resources)); | ||
static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.OtherBindingsOrchestrationAnalyzerMessageFormat), Resources.ResourceManager, typeof(Resources)); | ||
|
||
static readonly DiagnosticDescriptor Rule = new( | ||
DiagnosticId, | ||
Title, | ||
MessageFormat, | ||
AnalyzersCategories.Orchestration, | ||
DiagnosticSeverity.Warning, | ||
isEnabledByDefault: true); | ||
|
||
/// <inheritdoc/> | ||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [Rule]; | ||
|
||
/// <summary> | ||
/// Visitor that inspects Durable Functions' method signatures for parameters binding other than OrchestrationTrigger. | ||
/// </summary> | ||
public sealed class OtherBindingsOrchestrationOrchestrationVisitor : OrchestrationVisitor | ||
{ | ||
ImmutableArray<INamedTypeSymbol> bannedBindings; | ||
|
||
/// <inheritdoc/> | ||
public override bool Initialize() | ||
{ | ||
List<INamedTypeSymbol?> candidateSymbols = [ | ||
this.KnownTypeSymbols.DurableClientAttribute, | ||
this.KnownTypeSymbols.EntityTriggerAttribute, | ||
]; | ||
|
||
// filter out null values, since some of them may not be available during compilation | ||
this.bannedBindings = candidateSymbols.Where(s => s != null).ToImmutableArray()!; | ||
|
||
return this.bannedBindings.Length > 0; | ||
} | ||
|
||
/// <inheritdoc/> | ||
public override void VisitDurableFunction(SemanticModel sm, MethodDeclarationSyntax methodSyntax, IMethodSymbol methodSymbol, string orchestrationName, Action<Diagnostic> reportDiagnostic) | ||
{ | ||
foreach (IParameterSymbol parameter in methodSymbol.Parameters) | ||
{ | ||
IEnumerable<INamedTypeSymbol?> attributesSymbols = parameter.GetAttributes().Select(att => att.AttributeClass); | ||
|
||
if (attributesSymbols.Any(att => att != null && this.bannedBindings.Contains(att, SymbolEqualityComparer.Default))) | ||
{ | ||
reportDiagnostic(RoslynExtensions.BuildDiagnostic(Rule, parameter, orchestrationName)); | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
86 changes: 86 additions & 0 deletions
86
test/Analyzers.Tests/Functions/Orchestration/OtherBindingsOrchestrationAnalyzer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT License. | ||
|
||
using Microsoft.CodeAnalysis.Testing; | ||
using Microsoft.DurableTask.Analyzers.Functions.Orchestration; | ||
using VerifyCS = Microsoft.DurableTask.Analyzers.Tests.Verifiers.CSharpAnalyzerVerifier<Microsoft.DurableTask.Analyzers.Functions.Orchestration.OtherBindingsOrchestrationAnalyzer>; | ||
|
||
namespace Microsoft.DurableTask.Analyzers.Tests.Functions.Orchestration; | ||
|
||
public class OtherBindingsOrchestrationAnalyzerTests | ||
{ | ||
[Fact] | ||
public async Task EmptyCodeHasNoDiag() | ||
{ | ||
string code = @""; | ||
|
||
await VerifyCS.VerifyDurableTaskAnalyzerAsync(code); | ||
} | ||
|
||
[Fact] | ||
public async Task DurableFunctionOrchestrationWithNoBannedBindingHasNoDiag() | ||
{ | ||
string code = Wrapper.WrapDurableFunctionOrchestration(@" | ||
[Function(""Run"")] | ||
void Method([OrchestrationTrigger] TaskOrchestrationContext context) | ||
{ | ||
} | ||
"); | ||
|
||
await VerifyCS.VerifyDurableTaskAnalyzerAsync(code); | ||
} | ||
|
||
[Fact] | ||
public async Task DurableFunctionOrchestrationUsingDurableClientHasDiag() | ||
{ | ||
string code = Wrapper.WrapDurableFunctionOrchestration(@" | ||
[Function(""Run"")] | ||
void Method([OrchestrationTrigger] TaskOrchestrationContext context, {|#0:[DurableClient] DurableTaskClient client|}) | ||
{ | ||
} | ||
"); | ||
|
||
DiagnosticResult expected = BuildDiagnostic().WithLocation(0).WithArguments("Run"); | ||
|
||
await VerifyCS.VerifyDurableTaskAnalyzerAsync(code, expected); | ||
} | ||
|
||
[Fact] | ||
public async Task DurableFunctionOrchestrationUsingEntityTriggerHasDiag() | ||
{ | ||
string code = Wrapper.WrapDurableFunctionOrchestration(@" | ||
[Function(""Run"")] | ||
void Method([OrchestrationTrigger] TaskOrchestrationContext context, {|#0:[EntityTrigger] TaskEntityDispatcher dispatcher|}) | ||
{ | ||
} | ||
"); | ||
|
||
DiagnosticResult expected = BuildDiagnostic().WithLocation(0).WithArguments("Run"); | ||
|
||
await VerifyCS.VerifyDurableTaskAnalyzerAsync(code, expected); | ||
} | ||
|
||
|
||
[Fact] | ||
public async Task DurableFunctionOrchestrationUsingMultipleBannedBindingsHasDiag() | ||
{ | ||
string code = Wrapper.WrapDurableFunctionOrchestration(@" | ||
[Function(""Run"")] | ||
void Method([OrchestrationTrigger] TaskOrchestrationContext context, | ||
{|#0:[EntityTrigger] TaskEntityDispatcher dispatcher|}, | ||
{|#1:[DurableClient] DurableTaskClient client|}) | ||
{ | ||
} | ||
"); | ||
|
||
DiagnosticResult[] expected = Enumerable.Range(0, 2).Select( | ||
i => BuildDiagnostic().WithLocation(i).WithArguments("Run")).ToArray(); | ||
|
||
await VerifyCS.VerifyDurableTaskAnalyzerAsync(code, expected); | ||
} | ||
|
||
static DiagnosticResult BuildDiagnostic() | ||
{ | ||
return VerifyCS.Diagnostic(OtherBindingsOrchestrationAnalyzer.DiagnosticId); | ||
} | ||
} |