Skip to content

Commit

Permalink
CancellationToken roslyn analyzer (#303)
Browse files Browse the repository at this point in the history
Currently, CancellationToken binding is not supported (ignored) during [input conversion](https://github.com/Azure/azure-functions-durable-extension/blob/dev/src/Worker.Extensions.DurableTask/OrchestrationInputConverter.cs#L79). This analyzer will prevent users from using it inside Azure Functions Orchestrators.
  • Loading branch information
allantargino authored May 6, 2024
1 parent aa810cc commit c4fac92
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/Analyzers/AnalyzerReleases.Unshipped.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Rule ID | Category | Severity | Notes
DURABLE0001 | Orchestration | Warning | DateTimeOrchestrationAnalyzer
DURABLE0002 | Orchestration | Warning | GuidOrchestrationAnalyzer
DURABLE0003 | Orchestration | Warning | DelayOrchestrationAnalyzer
DURABLE0007 | Orchestration | Warning | CancellationTokenOrchestrationAnalyzer
DURABLE1001 | Attribute Binding | Error | OrchestrationTriggerBindingAnalyzer
DURABLE1002 | Attribute Binding | Error | DurableClientBindingAnalyzer
DURABLE1003 | Attribute Binding | Error | EntityTriggerBindingAnalyzer
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// 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.CancellationTokenOrchestrationAnalyzer;

namespace Microsoft.DurableTask.Analyzers.Functions.Orchestration;

/// <summary>
/// Analyzer that reports a warning when CancellationToken is used in a Durable Functions Orchestration.
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class CancellationTokenOrchestrationAnalyzer : OrchestrationAnalyzer<CancellationTokenOrchestrationVisitor>
{
/// <summary>
/// Diagnostic ID supported for the analyzer.
/// </summary>
public const string DiagnosticId = "DURABLE0007";

static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.CancellationTokenOrchestrationAnalyzerTitle), Resources.ResourceManager, typeof(Resources));
static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.CancellationTokenOrchestrationAnalyzerMessageFormat), 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's method signatures for CancellationToken parameters.
/// </summary>
public sealed class CancellationTokenOrchestrationVisitor : OrchestrationVisitor
{
/// <inheritdoc/>
public override bool Initialize()
{
return this.KnownTypeSymbols.CancellationToken is not null;
}

/// <inheritdoc/>
public override void VisitDurableFunction(SemanticModel semanticModel, MethodDeclarationSyntax methodSyntax, IMethodSymbol methodSymbol, string orchestrationName, Action<Diagnostic> reportDiagnostic)
{
foreach (IParameterSymbol parameter in methodSymbol.Parameters)
{
if (parameter.Type.Equals(this.KnownTypeSymbols.CancellationToken, SymbolEqualityComparer.Default))
{
reportDiagnostic(RoslynExtensions.BuildDiagnostic(Rule, parameter, orchestrationName));
}
}
}
}
}
6 changes: 6 additions & 0 deletions src/Analyzers/KnownTypeSymbols.Net.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public sealed partial class KnownTypeSymbols
INamedTypeSymbol? thread;
INamedTypeSymbol? task;
INamedTypeSymbol? taskT;
INamedTypeSymbol? cancellationToken;

/// <summary>
/// Gets a Guid type symbol.
Expand All @@ -37,4 +38,9 @@ public sealed partial class KnownTypeSymbols
/// Gets a Task&lt;T&gt; type symbol.
/// </summary>
public INamedTypeSymbol? TaskT => this.GetOrResolveFullyQualifiedType(typeof(Task<>).FullName, ref this.taskT);

/// <summary>
/// Gets a CancellationToken type symbol.
/// </summary>
public INamedTypeSymbol? CancellationToken => this.GetOrResolveFullyQualifiedType(typeof(CancellationToken).FullName, ref this.cancellationToken);
}
6 changes: 6 additions & 0 deletions src/Analyzers/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -153,4 +153,10 @@
<data name="DurableClientBindingAnalyzerTitle" xml:space="preserve">
<value>[DurableClient] must be used with DurableTaskClient</value>
</data>
<data name="CancellationTokenOrchestrationAnalyzerMessageFormat" xml:space="preserve">
<value>Orchestration '{0}' is using a CancellationToken binding, which is not supported</value>
</data>
<data name="CancellationTokenOrchestrationAnalyzerTitle" xml:space="preserve">
<value>CancellationToken should not be used as an orchestrator function parameter</value>
</data>
</root>
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// 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.CancellationTokenOrchestrationAnalyzer>;

namespace Microsoft.DurableTask.Analyzers.Tests.Functions.Orchestration;

public class CancellationTokenOrchestrationAnalyzerTests
{
[Fact]
public async Task EmptyCodeHasNoDiag()
{
string code = @"";

await VerifyCS.VerifyDurableTaskAnalyzerAsync(code);
}

[Fact]
public async Task DurableFunctionOrchestrationUsingCancellationTokenAsParameterHasDiag()
{
string code = Wrapper.WrapDurableFunctionOrchestration(@"
[Function(""Run"")]
void Method([OrchestrationTrigger] TaskOrchestrationContext context, {|#0:CancellationToken token|})
{
}
");

DiagnosticResult expected = BuildDiagnostic().WithLocation(0).WithArguments("Run");

await VerifyCS.VerifyDurableTaskAnalyzerAsync(code, expected);
}

static DiagnosticResult BuildDiagnostic()
{
return VerifyCS.Diagnostic(CancellationTokenOrchestrationAnalyzer.DiagnosticId);
}
}

0 comments on commit c4fac92

Please sign in to comment.