diff --git a/src/Analyzers/AnalyzerReleases.Unshipped.md b/src/Analyzers/AnalyzerReleases.Unshipped.md
index cac23ffb..a0f1d8c6 100644
--- a/src/Analyzers/AnalyzerReleases.Unshipped.md
+++ b/src/Analyzers/AnalyzerReleases.Unshipped.md
@@ -1,4 +1,4 @@
-; Unshipped analyzer release
+; Unshipped analyzer release
; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md
### New Rules
@@ -8,3 +8,6 @@ Rule ID | Category | Severity | Notes
DURABLE0001 | Orchestration | Warning | DateTimeOrchestrationAnalyzer
DURABLE0002 | Orchestration | Warning | GuidOrchestrationAnalyzer
DURABLE0003 | Orchestration | Warning | DelayOrchestrationAnalyzer
+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/AnalyzersCategories.cs b/src/Analyzers/AnalyzersCategories.cs
index 2f2b1a6b..8bdae885 100644
--- a/src/Analyzers/AnalyzersCategories.cs
+++ b/src/Analyzers/AnalyzersCategories.cs
@@ -12,4 +12,9 @@ static class AnalyzersCategories
/// The category for the orchestration related analyzers.
///
public const string Orchestration = "Orchestration";
+
+ ///
+ /// The category for the attribute binding related analyzers.
+ ///
+ public const string AttributeBinding = "Attribute Binding";
}
diff --git a/src/Analyzers/Functions/AttributeBinding/DurableClientBindingAnalyzer.cs b/src/Analyzers/Functions/AttributeBinding/DurableClientBindingAnalyzer.cs
new file mode 100644
index 00000000..8089043e
--- /dev/null
+++ b/src/Analyzers/Functions/AttributeBinding/DurableClientBindingAnalyzer.cs
@@ -0,0 +1,51 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Collections.Immutable;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace Microsoft.DurableTask.Analyzers.Functions.AttributeBinding;
+
+///
+/// Analyzer that matches 'DurableClientAttribute' with 'DurableTaskClient' parameters.
+///
+[DiagnosticAnalyzer(LanguageNames.CSharp)]
+public sealed class DurableClientBindingAnalyzer : MatchingAttributeBindingAnalyzer
+{
+ ///
+ /// Diagnostic ID supported for the analyzer.
+ ///
+ public const string DiagnosticId = "DURABLE1002";
+
+ static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.DurableClientBindingAnalyzerTitle), Resources.ResourceManager, typeof(Resources));
+ static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.DurableClientBindingAnalyzerMessageFormat), Resources.ResourceManager, typeof(Resources));
+
+ static readonly DiagnosticDescriptor Rule = new(
+ DiagnosticId,
+ Title,
+ MessageFormat,
+ AnalyzersCategories.AttributeBinding,
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true);
+
+ ///
+ public override ImmutableArray SupportedDiagnostics => [Rule];
+
+ ///
+ protected override ExpectedBinding GetExpectedBinding(KnownTypeSymbols knownTypeSymbols)
+ {
+ return new ExpectedBinding()
+ {
+ Attribute = knownTypeSymbols.DurableClientAttribute,
+ Type = knownTypeSymbols.DurableTaskClient,
+ };
+ }
+
+ ///
+ protected override void ReportDiagnostic(SymbolAnalysisContext ctx, ExpectedBinding expected, IParameterSymbol parameter)
+ {
+ string wrongType = parameter.Type.ToDisplayString(SymbolDisplayFormat.CSharpShortErrorMessageFormat);
+ ctx.ReportDiagnostic(RoslynExtensions.BuildDiagnostic(Rule, parameter, wrongType));
+ }
+}
diff --git a/src/Analyzers/Functions/AttributeBinding/EntityTriggerBindingAnalyzer.cs b/src/Analyzers/Functions/AttributeBinding/EntityTriggerBindingAnalyzer.cs
new file mode 100644
index 00000000..8667cb32
--- /dev/null
+++ b/src/Analyzers/Functions/AttributeBinding/EntityTriggerBindingAnalyzer.cs
@@ -0,0 +1,51 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Collections.Immutable;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace Microsoft.DurableTask.Analyzers.Functions.AttributeBinding;
+
+///
+/// Analyzer that matches 'EntityTriggerAttribute' with 'TaskEntityDispatcher' parameters.
+///
+[DiagnosticAnalyzer(LanguageNames.CSharp)]
+public sealed class EntityTriggerBindingAnalyzer : MatchingAttributeBindingAnalyzer
+{
+ ///
+ /// Diagnostic ID supported for the analyzer.
+ ///
+ public const string DiagnosticId = "DURABLE1003";
+
+ static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.EntityTriggerBindingAnalyzerTitle), Resources.ResourceManager, typeof(Resources));
+ static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.EntityTriggerBindingAnalyzerMessageFormat), Resources.ResourceManager, typeof(Resources));
+
+ static readonly DiagnosticDescriptor Rule = new(
+ DiagnosticId,
+ Title,
+ MessageFormat,
+ AnalyzersCategories.AttributeBinding,
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true);
+
+ ///
+ public override ImmutableArray SupportedDiagnostics => [Rule];
+
+ ///
+ protected override ExpectedBinding GetExpectedBinding(KnownTypeSymbols knownTypeSymbols)
+ {
+ return new ExpectedBinding()
+ {
+ Attribute = knownTypeSymbols.EntityTriggerAttribute,
+ Type = knownTypeSymbols.TaskEntityDispatcher,
+ };
+ }
+
+ ///
+ protected override void ReportDiagnostic(SymbolAnalysisContext ctx, ExpectedBinding expected, IParameterSymbol parameter)
+ {
+ string wrongType = parameter.Type.ToDisplayString(SymbolDisplayFormat.CSharpShortErrorMessageFormat);
+ ctx.ReportDiagnostic(RoslynExtensions.BuildDiagnostic(Rule, parameter, wrongType));
+ }
+}
diff --git a/src/Analyzers/Functions/AttributeBinding/MatchingAttributeBindingAnalyzer.cs b/src/Analyzers/Functions/AttributeBinding/MatchingAttributeBindingAnalyzer.cs
new file mode 100644
index 00000000..28c2df24
--- /dev/null
+++ b/src/Analyzers/Functions/AttributeBinding/MatchingAttributeBindingAnalyzer.cs
@@ -0,0 +1,79 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace Microsoft.DurableTask.Analyzers.Functions.AttributeBinding;
+
+///
+/// Expected attribute binding for a given parameter type.
+///
+public struct ExpectedBinding
+{
+ ///
+ /// Gets or sets the expected attribute.
+ ///
+ public INamedTypeSymbol? Attribute { get; set; }
+
+ ///
+ /// Gets or sets the expected type.
+ ///
+ public INamedTypeSymbol? Type { get; set; }
+}
+
+///
+/// Analyzer that inspects the parameter type of a method to ensure it matches the expected attribute binding.
+/// It expects one parameter in the DiagnosticRule message template, so the analyzer can report the wrong type.
+///
+public abstract class MatchingAttributeBindingAnalyzer : DiagnosticAnalyzer
+{
+ ///
+ public override void Initialize(AnalysisContext context)
+ {
+ context.EnableConcurrentExecution();
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
+
+ context.RegisterCompilationStartAction(
+ ctx =>
+ {
+ KnownTypeSymbols knownTypeSymbols = new(ctx.Compilation);
+
+ ExpectedBinding expectedBinding = this.GetExpectedBinding(knownTypeSymbols);
+ if (expectedBinding.Attribute is null || expectedBinding.Type is null)
+ {
+ return;
+ }
+
+ ctx.RegisterSymbolAction(c => this.Analyze(c, expectedBinding), SymbolKind.Parameter);
+ });
+ }
+
+ ///
+ /// Gets the expected attribute binding and the related type to be used during parameters analysis.
+ ///
+ /// The set of well-known types.
+ /// The expected binding for this analyzer.
+ protected abstract ExpectedBinding GetExpectedBinding(KnownTypeSymbols knownTypeSymbols);
+
+ ///
+ /// After an incorrect attribute/type matching is found, this method is called so the concrete implementation can report a diagnostic.
+ ///
+ /// Context for a symbol action. Allows reporting a diagnostic.
+ /// Expected binding for an attribute/type.
+ /// Analyzed parameter symbol.
+ protected abstract void ReportDiagnostic(SymbolAnalysisContext ctx, ExpectedBinding expected, IParameterSymbol parameter);
+
+ void Analyze(SymbolAnalysisContext ctx, ExpectedBinding expected)
+ {
+ IParameterSymbol parameter = (IParameterSymbol)ctx.Symbol;
+
+ if (parameter.GetAttributes().Any(a => expected.Attribute!.Equals(a.AttributeClass, SymbolEqualityComparer.Default)))
+ {
+ if (!parameter.Type.Equals(expected.Type, SymbolEqualityComparer.Default))
+ {
+ this.ReportDiagnostic(ctx, expected, parameter);
+ }
+ }
+ }
+}
diff --git a/src/Analyzers/Functions/AttributeBinding/OrchestrationTriggerBindingAnalyzer.cs b/src/Analyzers/Functions/AttributeBinding/OrchestrationTriggerBindingAnalyzer.cs
new file mode 100644
index 00000000..d6642043
--- /dev/null
+++ b/src/Analyzers/Functions/AttributeBinding/OrchestrationTriggerBindingAnalyzer.cs
@@ -0,0 +1,51 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Collections.Immutable;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace Microsoft.DurableTask.Analyzers.Functions.AttributeBinding;
+
+///
+/// Analyzer that matches 'OrchestrationTriggerAttribute' with 'TaskOrchestrationContext' parameters.
+///
+[DiagnosticAnalyzer(LanguageNames.CSharp)]
+public sealed class OrchestrationTriggerBindingAnalyzer : MatchingAttributeBindingAnalyzer
+{
+ ///
+ /// Diagnostic ID supported for the analyzer.
+ ///
+ public const string DiagnosticId = "DURABLE1001";
+
+ static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.OrchestrationTriggerBindingAnalyzerTitle), Resources.ResourceManager, typeof(Resources));
+ static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.OrchestrationTriggerBindingAnalyzerMessageFormat), Resources.ResourceManager, typeof(Resources));
+
+ static readonly DiagnosticDescriptor Rule = new(
+ DiagnosticId,
+ Title,
+ MessageFormat,
+ AnalyzersCategories.AttributeBinding,
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true);
+
+ ///
+ public override ImmutableArray SupportedDiagnostics => [Rule];
+
+ ///
+ protected override ExpectedBinding GetExpectedBinding(KnownTypeSymbols knownTypeSymbols)
+ {
+ return new ExpectedBinding()
+ {
+ Attribute = knownTypeSymbols.FunctionOrchestrationAttribute,
+ Type = knownTypeSymbols.TaskOrchestrationContext,
+ };
+ }
+
+ ///
+ protected override void ReportDiagnostic(SymbolAnalysisContext ctx, ExpectedBinding expected, IParameterSymbol parameter)
+ {
+ string wrongType = parameter.Type.ToDisplayString(SymbolDisplayFormat.CSharpShortErrorMessageFormat);
+ ctx.ReportDiagnostic(RoslynExtensions.BuildDiagnostic(Rule, parameter, wrongType));
+ }
+}
diff --git a/src/Analyzers/KnownTypeSymbols.Durable.cs b/src/Analyzers/KnownTypeSymbols.Durable.cs
new file mode 100644
index 00000000..d314f564
--- /dev/null
+++ b/src/Analyzers/KnownTypeSymbols.Durable.cs
@@ -0,0 +1,47 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.CodeAnalysis;
+
+namespace Microsoft.DurableTask.Analyzers;
+
+///
+/// Provides a set of well-known types that are used by the analyzers.
+/// Inspired by KnownTypeSymbols class in
+/// System.Text.Json.SourceGeneration source code.
+/// Lazy initialization is used to avoid the the initialization of all types during class construction, since not all symbols are used by all analyzers.
+///
+public sealed partial class KnownTypeSymbols
+{
+ INamedTypeSymbol? taskOrchestratorInterface;
+ INamedTypeSymbol? taskOrchestratorBaseClass;
+ INamedTypeSymbol? durableTaskRegistry;
+ INamedTypeSymbol? taskOrchestrationContext;
+ INamedTypeSymbol? durableTaskClient;
+
+ ///
+ /// Gets an ITaskOrchestrator type symbol.
+ ///
+ public INamedTypeSymbol? TaskOrchestratorInterface => this.GetOrResolveFullyQualifiedType("Microsoft.DurableTask.ITaskOrchestrator", ref this.taskOrchestratorInterface);
+
+ ///
+ /// Gets a TaskOrchestrator type symbol.
+ ///
+ public INamedTypeSymbol? TaskOrchestratorBaseClass => this.GetOrResolveFullyQualifiedType("Microsoft.DurableTask.TaskOrchestrator`2", ref this.taskOrchestratorBaseClass);
+
+ ///
+ /// Gets a DurableTaskRegistry type symbol.
+ ///
+ public INamedTypeSymbol? DurableTaskRegistry => this.GetOrResolveFullyQualifiedType("Microsoft.DurableTask.DurableTaskRegistry", ref this.durableTaskRegistry);
+
+ ///
+ /// Gets a TaskOrchestrationContext type symbol.
+ ///
+ public INamedTypeSymbol? TaskOrchestrationContext => this.GetOrResolveFullyQualifiedType("Microsoft.DurableTask.TaskOrchestrationContext", ref this.taskOrchestrationContext);
+
+
+ ///
+ /// Gets a DurableTaskClient type symbol.
+ ///
+ public INamedTypeSymbol? DurableTaskClient => this.GetOrResolveFullyQualifiedType("Microsoft.DurableTask.Client.DurableTaskClient", ref this.durableTaskClient);
+}
diff --git a/src/Analyzers/KnownTypeSymbols.Functions.cs b/src/Analyzers/KnownTypeSymbols.Functions.cs
new file mode 100644
index 00000000..a0c106ba
--- /dev/null
+++ b/src/Analyzers/KnownTypeSymbols.Functions.cs
@@ -0,0 +1,46 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.CodeAnalysis;
+
+namespace Microsoft.DurableTask.Analyzers;
+
+///
+/// Provides a set of well-known types that are used by the analyzers.
+/// Inspired by KnownTypeSymbols class in
+/// System.Text.Json.SourceGeneration source code.
+/// Lazy initialization is used to avoid the the initialization of all types during class construction, since not all symbols are used by all analyzers.
+///
+public sealed partial class KnownTypeSymbols
+{
+ INamedTypeSymbol? functionOrchestrationAttribute;
+ INamedTypeSymbol? functionNameAttribute;
+ INamedTypeSymbol? durableClientAttribute;
+ INamedTypeSymbol? entityTriggerAttribute;
+ INamedTypeSymbol? taskEntityDispatcher;
+
+ ///
+ /// Gets an OrchestrationTriggerAttribute type symbol.
+ ///
+ public INamedTypeSymbol? FunctionOrchestrationAttribute => this.GetOrResolveFullyQualifiedType("Microsoft.Azure.Functions.Worker.OrchestrationTriggerAttribute", ref this.functionOrchestrationAttribute);
+
+ ///
+ /// Gets a FunctionNameAttribute type symbol.
+ ///
+ public INamedTypeSymbol? FunctionNameAttribute => this.GetOrResolveFullyQualifiedType("Microsoft.Azure.Functions.Worker.FunctionAttribute", ref this.functionNameAttribute);
+
+ ///
+ /// Gets a DurableClientAttribute type symbol.
+ ///
+ public INamedTypeSymbol? DurableClientAttribute => this.GetOrResolveFullyQualifiedType("Microsoft.Azure.Functions.Worker.DurableClientAttribute", ref this.durableClientAttribute);
+
+ ///
+ /// Gets an EntityTriggerAttribute type symbol.
+ ///
+ public INamedTypeSymbol? EntityTriggerAttribute => this.GetOrResolveFullyQualifiedType("Microsoft.Azure.Functions.Worker.EntityTriggerAttribute", ref this.entityTriggerAttribute);
+
+ ///
+ /// Gets a TaskEntityDispatcher type symbol.
+ ///
+ public INamedTypeSymbol? TaskEntityDispatcher => this.GetOrResolveFullyQualifiedType("Microsoft.Azure.Functions.Worker.TaskEntityDispatcher", ref this.taskEntityDispatcher);
+}
diff --git a/src/Analyzers/KnownTypeSymbols.Net.cs b/src/Analyzers/KnownTypeSymbols.Net.cs
new file mode 100644
index 00000000..5e7467a7
--- /dev/null
+++ b/src/Analyzers/KnownTypeSymbols.Net.cs
@@ -0,0 +1,40 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.CodeAnalysis;
+
+namespace Microsoft.DurableTask.Analyzers;
+
+///
+/// Provides a set of well-known types that are used by the analyzers.
+/// Inspired by KnownTypeSymbols class in
+/// System.Text.Json.SourceGeneration source code.
+/// Lazy initialization is used to avoid the the initialization of all types during class construction, since not all symbols are used by all analyzers.
+///
+public sealed partial class KnownTypeSymbols
+{
+ INamedTypeSymbol? guid;
+ INamedTypeSymbol? thread;
+ INamedTypeSymbol? task;
+ INamedTypeSymbol? taskT;
+
+ ///
+ /// Gets a Guid type symbol.
+ ///
+ public INamedTypeSymbol? GuidType => this.GetOrResolveFullyQualifiedType(typeof(Guid).FullName, ref this.guid);
+
+ ///
+ /// Gets a Thread type symbol.
+ ///
+ public INamedTypeSymbol? Thread => this.GetOrResolveFullyQualifiedType(typeof(Thread).FullName, ref this.thread);
+
+ ///
+ /// Gets a Task type symbol.
+ ///
+ public INamedTypeSymbol? Task => this.GetOrResolveFullyQualifiedType(typeof(Task).FullName, ref this.task);
+
+ ///
+ /// Gets a Task<T> type symbol.
+ ///
+ public INamedTypeSymbol? TaskT => this.GetOrResolveFullyQualifiedType(typeof(Task<>).FullName, ref this.taskT);
+}
diff --git a/src/Analyzers/KnownTypeSymbols.cs b/src/Analyzers/KnownTypeSymbols.cs
index 109bd440..a4144fd2 100644
--- a/src/Analyzers/KnownTypeSymbols.cs
+++ b/src/Analyzers/KnownTypeSymbols.cs
@@ -11,65 +11,10 @@ namespace Microsoft.DurableTask.Analyzers;
/// System.Text.Json.SourceGeneration source code.
/// Lazy initialization is used to avoid the the initialization of all types during class construction, since not all symbols are used by all analyzers.
///
-public sealed class KnownTypeSymbols(Compilation compilation)
+public sealed partial class KnownTypeSymbols(Compilation compilation)
{
readonly Compilation compilation = compilation;
- INamedTypeSymbol? functionOrchestrationAttribute;
- INamedTypeSymbol? functionNameAttribute;
- INamedTypeSymbol? taskOrchestratorInterface;
- INamedTypeSymbol? taskOrchestratorBaseClass;
- INamedTypeSymbol? durableTaskRegistry;
- INamedTypeSymbol? guid;
- INamedTypeSymbol? thread;
- INamedTypeSymbol? task;
- INamedTypeSymbol? taskT;
-
- ///
- /// Gets an OrchestrationTriggerAttribute type symbol.
- ///
- public INamedTypeSymbol? FunctionOrchestrationAttribute => this.GetOrResolveFullyQualifiedType("Microsoft.Azure.Functions.Worker.OrchestrationTriggerAttribute", ref this.functionOrchestrationAttribute);
-
- ///
- /// Gets a FunctionNameAttribute type symbol.
- ///
- public INamedTypeSymbol? FunctionNameAttribute => this.GetOrResolveFullyQualifiedType("Microsoft.Azure.Functions.Worker.FunctionAttribute", ref this.functionNameAttribute);
-
- ///
- /// Gets an ITaskOrchestrator type symbol.
- ///
- public INamedTypeSymbol? TaskOrchestratorInterface => this.GetOrResolveFullyQualifiedType("Microsoft.DurableTask.ITaskOrchestrator", ref this.taskOrchestratorInterface);
-
- ///
- /// Gets a TaskOrchestrator type symbol.
- ///
- public INamedTypeSymbol? TaskOrchestratorBaseClass => this.GetOrResolveFullyQualifiedType("Microsoft.DurableTask.TaskOrchestrator`2", ref this.taskOrchestratorBaseClass);
-
- ///
- /// Gets a DurableTaskRegistry type symbol.
- ///
- public INamedTypeSymbol? DurableTaskRegistry => this.GetOrResolveFullyQualifiedType("Microsoft.DurableTask.DurableTaskRegistry", ref this.durableTaskRegistry);
-
- ///
- /// Gets a Guid type symbol.
- ///
- public INamedTypeSymbol? GuidType => this.GetOrResolveFullyQualifiedType(typeof(Guid).FullName, ref this.guid);
-
- ///
- /// Gets a Thread type symbol.
- ///
- public INamedTypeSymbol? Thread => this.GetOrResolveFullyQualifiedType(typeof(Thread).FullName, ref this.thread);
-
- ///
- /// Gets a Task type symbol.
- ///
- public INamedTypeSymbol? Task => this.GetOrResolveFullyQualifiedType(typeof(Task).FullName, ref this.task);
-
- ///
- /// Gets a Task<T> type symbol.
- ///
- public INamedTypeSymbol? TaskT => this.GetOrResolveFullyQualifiedType(typeof(Task<>).FullName, ref this.taskT);
-
INamedTypeSymbol? GetOrResolveFullyQualifiedType(string fullyQualifiedName, ref INamedTypeSymbol? field)
{
if (field != null)
diff --git a/src/Analyzers/Resources.resx b/src/Analyzers/Resources.resx
index 96e7ca8b..d15e4b9b 100644
--- a/src/Analyzers/Resources.resx
+++ b/src/Analyzers/Resources.resx
@@ -135,4 +135,22 @@
Thread.Sleep and Task.Delay calls are not allowed inside an orchestrator
+
+ [OrchestrationTrigger] is associated with the wrong type '{0}', it must be used with TaskOrchestrationContext
+
+
+ [OrchestrationTrigger] must be used with TaskOrchestrationContext
+
+
+ [EntityTrigger] is associated with the wrong type '{0}', it must be used with TaskEntityDispatcher
+
+
+ [EntityTrigger] must be used with TaskOrchestrationContext
+
+
+ [DurableClient] is associated with the wrong type '{0}', it must be used with DurableTaskClient
+
+
+ [DurableClient] must be used with DurableTaskClient
+
\ No newline at end of file
diff --git a/test/Analyzers.Tests/Functions/AttributeBinding/DurableClientBindingAnalyzerTests.cs b/test/Analyzers.Tests/Functions/AttributeBinding/DurableClientBindingAnalyzerTests.cs
new file mode 100644
index 00000000..a44842fb
--- /dev/null
+++ b/test/Analyzers.Tests/Functions/AttributeBinding/DurableClientBindingAnalyzerTests.cs
@@ -0,0 +1,15 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.DurableTask.Analyzers.Functions.AttributeBinding;
+
+namespace Microsoft.DurableTask.Analyzers.Tests.Functions.AttributeBinding;
+
+public class DurableClientBindingAnalyzerTests : MatchingAttributeBindingSpecificationTests
+{
+ protected override string ExpectedDiagnosticId => DurableClientBindingAnalyzer.DiagnosticId;
+
+ protected override string ExpectedAttribute => "[DurableClient]";
+
+ protected override string ExpectedType => "DurableTaskClient";
+}
diff --git a/test/Analyzers.Tests/Functions/AttributeBinding/EntityTriggerBindingAnalyzerTests.cs b/test/Analyzers.Tests/Functions/AttributeBinding/EntityTriggerBindingAnalyzerTests.cs
new file mode 100644
index 00000000..bde399f4
--- /dev/null
+++ b/test/Analyzers.Tests/Functions/AttributeBinding/EntityTriggerBindingAnalyzerTests.cs
@@ -0,0 +1,15 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.DurableTask.Analyzers.Functions.AttributeBinding;
+
+namespace Microsoft.DurableTask.Analyzers.Tests.Functions.AttributeBinding;
+
+public class EntityTriggerBindingAnalyzerTests : MatchingAttributeBindingSpecificationTests
+{
+ protected override string ExpectedDiagnosticId => EntityTriggerBindingAnalyzer.DiagnosticId;
+
+ protected override string ExpectedAttribute => "[EntityTrigger]";
+
+ protected override string ExpectedType => "TaskEntityDispatcher";
+}
diff --git a/test/Analyzers.Tests/Functions/AttributeBinding/MatchingAttributeBindingSpecificationTests.cs b/test/Analyzers.Tests/Functions/AttributeBinding/MatchingAttributeBindingSpecificationTests.cs
new file mode 100644
index 00000000..35018340
--- /dev/null
+++ b/test/Analyzers.Tests/Functions/AttributeBinding/MatchingAttributeBindingSpecificationTests.cs
@@ -0,0 +1,72 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.CodeAnalysis.Testing;
+using Microsoft.DurableTask.Analyzers.Functions.AttributeBinding;
+using Microsoft.DurableTask.Analyzers.Tests.Verifiers;
+
+namespace Microsoft.DurableTask.Analyzers.Tests.Functions.AttributeBinding;
+
+public abstract class MatchingAttributeBindingSpecificationTests where TAnalyzer : MatchingAttributeBindingAnalyzer, new()
+{
+ protected abstract string ExpectedDiagnosticId { get; }
+ protected abstract string ExpectedAttribute { get; }
+ protected abstract string ExpectedType { get; }
+ protected virtual string WrongType { get; } = "int";
+
+ [Fact]
+ public async Task EmptyCodeHasNoDiag()
+ {
+ string code = @"";
+
+ await VerifyAsync(code);
+ }
+
+ [Fact]
+ public async Task TypeWithoutExpectedAttributeHasNoDiag()
+ {
+ string code = Wrapper.WrapDurableFunctionOrchestration($@"
+void Method({this.ExpectedType} paramName)
+{{
+}}
+");
+
+ await VerifyAsync(code);
+ }
+
+ [Fact]
+ public async Task ExpectedAttributeWithExpectedTypeHasNoDiag()
+ {
+ string code = Wrapper.WrapDurableFunctionOrchestration($@"
+void Method({this.ExpectedAttribute} {this.ExpectedType} paramName)
+{{
+}}
+");
+
+ await VerifyAsync(code);
+ }
+
+ [Fact]
+ public async Task ExpectedAttributeWithWrongTypeHasDiag()
+ {
+ string code = Wrapper.WrapDurableFunctionOrchestration($@"
+void Method({{|#0:{this.ExpectedAttribute} {this.WrongType} paramName|}})
+{{
+}}
+");
+
+ DiagnosticResult expected = this.BuildDiagnostic().WithLocation(0).WithArguments(this.WrongType);
+
+ await VerifyAsync(code, expected);
+ }
+
+ static async Task VerifyAsync(string source, params DiagnosticResult[] expected)
+ {
+ await CSharpAnalyzerVerifier.VerifyDurableTaskAnalyzerAsync(source, expected);
+ }
+
+ DiagnosticResult BuildDiagnostic()
+ {
+ return CSharpAnalyzerVerifier.Diagnostic(this.ExpectedDiagnosticId);
+ }
+}
diff --git a/test/Analyzers.Tests/Functions/AttributeBinding/OrchestrationTriggerBindingAnalyzerTests.cs b/test/Analyzers.Tests/Functions/AttributeBinding/OrchestrationTriggerBindingAnalyzerTests.cs
new file mode 100644
index 00000000..f1d020f9
--- /dev/null
+++ b/test/Analyzers.Tests/Functions/AttributeBinding/OrchestrationTriggerBindingAnalyzerTests.cs
@@ -0,0 +1,15 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.DurableTask.Analyzers.Functions.AttributeBinding;
+
+namespace Microsoft.DurableTask.Analyzers.Tests.Functions.AttributeBinding;
+
+public class OrchestrationTriggerBindingAnalyzerTests : MatchingAttributeBindingSpecificationTests
+{
+ protected override string ExpectedDiagnosticId => OrchestrationTriggerBindingAnalyzer.DiagnosticId;
+
+ protected override string ExpectedAttribute => "[OrchestrationTrigger]";
+
+ protected override string ExpectedType => "TaskOrchestrationContext";
+}