From 14a8abf8455b1b054646152989edda18fc925e62 Mon Sep 17 00:00:00 2001 From: Allan Targino <13934447+allantargino@users.noreply.github.com> Date: Mon, 6 May 2024 10:22:04 -0300 Subject: [PATCH 1/3] azure function orchestration bindings analyzer --- src/Analyzers/AnalyzerReleases.Unshipped.md | 1 + .../OtherBindingsOrchestrationAnalyzer.cs | 73 ++++++++++++++++ src/Analyzers/Resources.resx | 6 ++ .../OtherBindingsOrchestrationAnalyzer.cs | 86 +++++++++++++++++++ 4 files changed, 166 insertions(+) create mode 100644 src/Analyzers/Functions/Orchestration/OtherBindingsOrchestrationAnalyzer.cs create mode 100644 test/Analyzers.Tests/Functions/Orchestration/OtherBindingsOrchestrationAnalyzer.cs diff --git a/src/Analyzers/AnalyzerReleases.Unshipped.md b/src/Analyzers/AnalyzerReleases.Unshipped.md index bf14e3b2..690cd6a4 100644 --- a/src/Analyzers/AnalyzerReleases.Unshipped.md +++ b/src/Analyzers/AnalyzerReleases.Unshipped.md @@ -9,6 +9,7 @@ DURABLE0001 | Orchestration | Warning | DateTimeOrchestrationAnalyzer DURABLE0002 | Orchestration | Warning | GuidOrchestrationAnalyzer DURABLE0003 | Orchestration | Warning | DelayOrchestrationAnalyzer DURABLE0007 | Orchestration | Warning | CancellationTokenOrchestrationAnalyzer +DURABLE0008 | Orchestration | Warning | OtherBindingsOrchestrationAnalyzer DURABLE1001 | Attribute Binding | Error | OrchestrationTriggerBindingAnalyzer DURABLE1002 | Attribute Binding | Error | DurableClientBindingAnalyzer DURABLE1003 | Attribute Binding | Error | EntityTriggerBindingAnalyzer \ No newline at end of file diff --git a/src/Analyzers/Functions/Orchestration/OtherBindingsOrchestrationAnalyzer.cs b/src/Analyzers/Functions/Orchestration/OtherBindingsOrchestrationAnalyzer.cs new file mode 100644 index 00000000..9092e25a --- /dev/null +++ b/src/Analyzers/Functions/Orchestration/OtherBindingsOrchestrationAnalyzer.cs @@ -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; + +/// +/// Analyzer that reports a warning when a Durable Function Orchestration has parameters bindings other than OrchestrationTrigger. +/// +[DiagnosticAnalyzer(LanguageNames.CSharp)] +class OtherBindingsOrchestrationAnalyzer : OrchestrationAnalyzer +{ + /// + /// Diagnostic ID supported for the analyzer. + /// + 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); + + /// + public override ImmutableArray SupportedDiagnostics => [Rule]; + + /// + /// Visitor that inspects Durable Functions's method signatures for parameters binding other than OrchestrationTrigger. + /// + public sealed class OtherBindingsOrchestrationOrchestrationVisitor : OrchestrationVisitor + { + ImmutableArray bannedBindings; + + /// + public override bool Initialize() + { + List 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; + } + + /// + public override void VisitDurableFunction(SemanticModel sm, MethodDeclarationSyntax methodSyntax, IMethodSymbol methodSymbol, string orchestrationName, Action reportDiagnostic) + { + foreach (IParameterSymbol parameter in methodSymbol.Parameters) + { + IEnumerable 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)); + } + } + } + } +} diff --git a/src/Analyzers/Resources.resx b/src/Analyzers/Resources.resx index 5e5da2cf..7f3481a0 100644 --- a/src/Analyzers/Resources.resx +++ b/src/Analyzers/Resources.resx @@ -159,4 +159,10 @@ CancellationToken should not be used as an orchestrator function parameter + + Orchestration '{0}' is using multiple bindings + + + OrchestrationTrigger methods must not use any other bindings + \ No newline at end of file diff --git a/test/Analyzers.Tests/Functions/Orchestration/OtherBindingsOrchestrationAnalyzer.cs b/test/Analyzers.Tests/Functions/Orchestration/OtherBindingsOrchestrationAnalyzer.cs new file mode 100644 index 00000000..9c048eab --- /dev/null +++ b/test/Analyzers.Tests/Functions/Orchestration/OtherBindingsOrchestrationAnalyzer.cs @@ -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; + +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); + } +} From fa4f30a560e83772d4c8dcd233c0627b6d240f2c Mon Sep 17 00:00:00 2001 From: Allan Targino <13934447+allantargino@users.noreply.github.com> Date: Mon, 6 May 2024 16:35:45 -0300 Subject: [PATCH 2/3] fixing nullability warning --- .../Orchestration/OtherBindingsOrchestrationAnalyzer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Analyzers/Functions/Orchestration/OtherBindingsOrchestrationAnalyzer.cs b/src/Analyzers/Functions/Orchestration/OtherBindingsOrchestrationAnalyzer.cs index 9092e25a..495d8469 100644 --- a/src/Analyzers/Functions/Orchestration/OtherBindingsOrchestrationAnalyzer.cs +++ b/src/Analyzers/Functions/Orchestration/OtherBindingsOrchestrationAnalyzer.cs @@ -45,13 +45,13 @@ public sealed class OtherBindingsOrchestrationOrchestrationVisitor : Orchestrati /// public override bool Initialize() { - List candidateSymbols = [ + List 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(); + this.bannedBindings = candidateSymbols.Where(s => s != null).ToImmutableArray()!; return this.bannedBindings.Length > 0; } From a571908cbcc4f82cbd2072ceb6e66329eec1874a Mon Sep 17 00:00:00 2001 From: Allan Targino <13934447+allantargino@users.noreply.github.com> Date: Mon, 6 May 2024 17:11:11 -0300 Subject: [PATCH 3/3] Update src/Analyzers/Functions/Orchestration/OtherBindingsOrchestrationAnalyzer.cs Co-authored-by: Varshitha Bachu --- .../Orchestration/OtherBindingsOrchestrationAnalyzer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Analyzers/Functions/Orchestration/OtherBindingsOrchestrationAnalyzer.cs b/src/Analyzers/Functions/Orchestration/OtherBindingsOrchestrationAnalyzer.cs index 495d8469..6cea911d 100644 --- a/src/Analyzers/Functions/Orchestration/OtherBindingsOrchestrationAnalyzer.cs +++ b/src/Analyzers/Functions/Orchestration/OtherBindingsOrchestrationAnalyzer.cs @@ -36,7 +36,7 @@ class OtherBindingsOrchestrationAnalyzer : OrchestrationAnalyzer SupportedDiagnostics => [Rule]; /// - /// Visitor that inspects Durable Functions's method signatures for parameters binding other than OrchestrationTrigger. + /// Visitor that inspects Durable Functions' method signatures for parameters binding other than OrchestrationTrigger. /// public sealed class OtherBindingsOrchestrationOrchestrationVisitor : OrchestrationVisitor {