diff --git a/src/Analyzers/Orchestration/DateTimeOrchestrationFixer.cs b/src/Analyzers/Orchestration/DateTimeOrchestrationFixer.cs
new file mode 100644
index 00000000..5b32857c
--- /dev/null
+++ b/src/Analyzers/Orchestration/DateTimeOrchestrationFixer.cs
@@ -0,0 +1,82 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Collections.Immutable;
+using System.Composition;
+using System.Globalization;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CodeActions;
+using Microsoft.CodeAnalysis.CodeFixes;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
+
+namespace Microsoft.DurableTask.Analyzers.Orchestration;
+
+///
+/// Code fix provider for the .
+///
+[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(DateTimeOrchestrationFixer))]
+[Shared]
+public sealed class DateTimeOrchestrationFixer : OrchestrationContextFixer
+{
+ ///
+ public override ImmutableArray FixableDiagnosticIds => [DateTimeOrchestrationAnalyzer.DiagnosticId];
+
+ ///
+ protected override void RegisterCodeFixes(CodeFixContext context, OrchestrationCodeFixContext orchestrationContext)
+ {
+ // Parses the syntax node to see if it is a member access expression (e.g. DateTime.Now)
+ if (orchestrationContext.SyntaxNodeWithDiagnostic is not MemberAccessExpressionSyntax dateTimeExpression)
+ {
+ return;
+ }
+
+ // Gets the name of the TaskOrchestrationContext parameter (e.g. "context" or "ctx")
+ string contextParameterName = orchestrationContext.TaskOrchestrationContextSymbol.Name;
+
+ bool isDateTimeToday = dateTimeExpression.Name.ToString() == "Today";
+ string dateTimeTodaySuffix = isDateTimeToday ? ".Date" : string.Empty;
+ string recommendation = $"{contextParameterName}.CurrentUtcDateTime{dateTimeTodaySuffix}";
+
+ // e.g: "Use 'context.CurrentUtcDateTime' instead of 'DateTime.Now'"
+ // e.g: "Use 'context.CurrentUtcDateTime.Date' instead of 'DateTime.Today'"
+ string title = string.Format(
+ CultureInfo.InvariantCulture,
+ Resources.UseInsteadFixerTitle,
+ recommendation,
+ dateTimeExpression.ToString());
+
+ context.RegisterCodeFix(
+ CodeAction.Create(
+ title: title,
+ createChangedDocument: c => ReplaceDateTime(context.Document, orchestrationContext.Root, dateTimeExpression, contextParameterName, isDateTimeToday),
+ equivalenceKey: title), // This key is used to prevent duplicate code fixes.
+ context.Diagnostics);
+ }
+
+ static Task ReplaceDateTime(Document document, SyntaxNode oldRoot, MemberAccessExpressionSyntax incorrectDateTimeSyntax, string contextParameterName, bool isDateTimeToday)
+ {
+ // Builds a 'context.CurrentUtcDateTime' syntax node
+ MemberAccessExpressionSyntax correctDateTimeSyntax =
+ MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ IdentifierName(contextParameterName),
+ IdentifierName("CurrentUtcDateTime"));
+
+ // If the original expression was DateTime.Today, we add ".Date" to the context expression.
+ if (isDateTimeToday)
+ {
+ correctDateTimeSyntax = MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ correctDateTimeSyntax,
+ IdentifierName("Date"));
+ }
+
+ // Replaces the old local declaration with the new local declaration.
+ SyntaxNode newRoot = oldRoot.ReplaceNode(incorrectDateTimeSyntax, correctDateTimeSyntax);
+ Document newDocument = document.WithSyntaxRoot(newRoot);
+
+ return Task.FromResult(newDocument);
+ }
+}
diff --git a/src/Analyzers/Orchestration/DelayOrchestrationFixer.cs b/src/Analyzers/Orchestration/DelayOrchestrationFixer.cs
new file mode 100644
index 00000000..18d85cc4
--- /dev/null
+++ b/src/Analyzers/Orchestration/DelayOrchestrationFixer.cs
@@ -0,0 +1,139 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Collections.Immutable;
+using System.Composition;
+using System.Diagnostics;
+using System.Globalization;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CodeActions;
+using Microsoft.CodeAnalysis.CodeFixes;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Operations;
+using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
+
+namespace Microsoft.DurableTask.Analyzers.Orchestration;
+
+///
+/// Code fix provider for the .
+///
+[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(DelayOrchestrationFixer))]
+[Shared]
+public sealed class DelayOrchestrationFixer : OrchestrationContextFixer
+{
+ ///
+ public override ImmutableArray FixableDiagnosticIds => [DelayOrchestrationAnalyzer.DiagnosticId];
+
+ ///
+ protected override void RegisterCodeFixes(CodeFixContext context, OrchestrationCodeFixContext orchestrationContext)
+ {
+ if (orchestrationContext.SyntaxNodeWithDiagnostic is not InvocationExpressionSyntax invocationExpressionsSyntax)
+ {
+ return;
+ }
+
+ if (orchestrationContext.SemanticModel.GetOperation(invocationExpressionsSyntax) is not IInvocationOperation invocationOperation)
+ {
+ return;
+ }
+
+ // Only fix Task.Delay(int[,CancellationToken]) or Task.Delay(TimeSpan[,CancellationToken]) invocations.
+ // For now, fixing Thread.Sleep(int) is not supported
+ if (!SymbolEqualityComparer.Default.Equals(invocationOperation.Type, orchestrationContext.KnownTypeSymbols.Task))
+ {
+ return;
+ }
+
+ Compilation compilation = orchestrationContext.SemanticModel.Compilation;
+ INamedTypeSymbol int32 = compilation.GetSpecialType(SpecialType.System_Int32);
+
+ // Extracts the arguments from the Task.Delay invocation
+ IMethodSymbol taskDelaySymbol = invocationOperation.TargetMethod;
+ Debug.Assert(taskDelaySymbol.Parameters.Length >= 1, "Task.Delay should have at least one parameter");
+ bool isInt = SymbolEqualityComparer.Default.Equals(taskDelaySymbol.Parameters[0].Type, int32);
+ IArgumentOperation delayArgumentOperation = invocationOperation.Arguments[0];
+ IArgumentOperation? cancellationTokenArgumentOperation = invocationOperation.Arguments.Length == 2 ? invocationOperation.Arguments[1] : null;
+
+ // Gets the name of the TaskOrchestrationContext parameter (e.g. "context" or "ctx")
+ string contextParameterName = orchestrationContext.TaskOrchestrationContextSymbol.Name;
+ string recommendation = $"{contextParameterName}.CreateTimer";
+
+ // e.g: "Use 'context.CreateTimer' instead of 'Task.Delay'"
+ string title = string.Format(
+ CultureInfo.InvariantCulture,
+ Resources.UseInsteadFixerTitle,
+ recommendation,
+ "Task.Delay");
+
+ context.RegisterCodeFix(
+ CodeAction.Create(
+ title: title,
+ createChangedDocument: c => ReplaceTaskDelay(
+ context.Document, orchestrationContext.Root, invocationExpressionsSyntax, contextParameterName, delayArgumentOperation, cancellationTokenArgumentOperation, isInt),
+ equivalenceKey: title), // This key is used to prevent duplicate code fixes.
+ context.Diagnostics);
+ }
+
+ static Task ReplaceTaskDelay(
+ Document document,
+ SyntaxNode oldRoot,
+ InvocationExpressionSyntax incorrectTaskDelaySyntax,
+ string contextParameterName,
+ IArgumentOperation delayArgumentOperation,
+ IArgumentOperation? cancellationTokenArgumentOperation,
+ bool isInt)
+ {
+ if (delayArgumentOperation.Syntax is not ArgumentSyntax timeSpanOrIntArgumentSyntax)
+ {
+ return Task.FromResult(document);
+ }
+
+ // Either use the original TimeSpan argument, or in case it is an int, transform it into TimeSpan
+ ArgumentSyntax timeSpanArgumentSyntax;
+ if (isInt)
+ {
+ timeSpanArgumentSyntax =
+ Argument(
+ InvocationExpression(
+ MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ IdentifierName("TimeSpan"),
+ IdentifierName("FromMilliseconds")),
+ ArgumentList(
+ SeparatedList(new[] { timeSpanOrIntArgumentSyntax }))));
+ }
+ else
+ {
+ timeSpanArgumentSyntax = timeSpanOrIntArgumentSyntax;
+ }
+
+ // Either gets the original cancellation token argument or create a 'CancellationToken.None'
+ ArgumentSyntax cancellationTokenArgumentSyntax = cancellationTokenArgumentOperation?.Syntax as ArgumentSyntax ??
+ Argument(
+ MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ IdentifierName("CancellationToken"),
+ IdentifierName("None")));
+
+ // Builds a 'context.CreateTimer(TimeSpan.FromMilliseconds(1000), CancellationToken.None)' syntax node
+ InvocationExpressionSyntax correctTimerSyntax =
+ InvocationExpression(
+ MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ IdentifierName(contextParameterName),
+ IdentifierName("CreateTimer")),
+ ArgumentList(
+ SeparatedList(new[]
+ {
+ timeSpanArgumentSyntax,
+ cancellationTokenArgumentSyntax,
+ })));
+
+ // Replaces the old local declaration with the new local declaration.
+ SyntaxNode newRoot = oldRoot.ReplaceNode(incorrectTaskDelaySyntax, correctTimerSyntax);
+ Document newDocument = document.WithSyntaxRoot(newRoot);
+
+ return Task.FromResult(newDocument);
+ }
+}
diff --git a/src/Analyzers/Orchestration/GuidOrchestrationFixer.cs b/src/Analyzers/Orchestration/GuidOrchestrationFixer.cs
new file mode 100644
index 00000000..139bd8ff
--- /dev/null
+++ b/src/Analyzers/Orchestration/GuidOrchestrationFixer.cs
@@ -0,0 +1,72 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Collections.Immutable;
+using System.Composition;
+using System.Globalization;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CodeActions;
+using Microsoft.CodeAnalysis.CodeFixes;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
+
+namespace Microsoft.DurableTask.Analyzers.Orchestration;
+
+///
+/// Code fix provider for the .
+///
+[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(GuidOrchestrationFixer))]
+[Shared]
+public sealed class GuidOrchestrationFixer : OrchestrationContextFixer
+{
+ ///
+ public override ImmutableArray FixableDiagnosticIds => [GuidOrchestrationAnalyzer.DiagnosticId];
+
+ ///
+ protected override void RegisterCodeFixes(CodeFixContext context, OrchestrationCodeFixContext orchestrationContext)
+ {
+ // Parses the syntax node to see if it is a invocation expression (Guid.NewGuid())
+ if (orchestrationContext.SyntaxNodeWithDiagnostic is not InvocationExpressionSyntax guidExpression)
+ {
+ return;
+ }
+
+ // Gets the name of the TaskOrchestrationContext parameter (e.g. "context" or "ctx")
+ string contextParameterName = orchestrationContext.TaskOrchestrationContextSymbol.Name;
+
+ string recommendation = $"{contextParameterName}.NewGuid()";
+
+ // e.g: "Use 'context.NewGuid()' instead of 'Guid.NewGuid()'"
+ string title = string.Format(
+ CultureInfo.InvariantCulture,
+ Resources.UseInsteadFixerTitle,
+ recommendation,
+ guidExpression.ToString());
+
+ context.RegisterCodeFix(
+ CodeAction.Create(
+ title: title,
+ createChangedDocument: c => ReplaceGuid(context.Document, orchestrationContext.Root, guidExpression, contextParameterName),
+ equivalenceKey: title), // This key is used to prevent duplicate code fixes.
+ context.Diagnostics);
+ }
+
+ static Task ReplaceGuid(Document document, SyntaxNode oldRoot, InvocationExpressionSyntax incorrectGuidSyntax, string contextParameterName)
+ {
+ // Builds a 'context.NewGuid()' syntax node
+ InvocationExpressionSyntax correctGuidSyntax =
+ InvocationExpression(
+ MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ IdentifierName(contextParameterName),
+ IdentifierName("NewGuid")),
+ ArgumentList());
+
+ // Replaces the old local declaration with the new local declaration.
+ SyntaxNode newRoot = oldRoot.ReplaceNode(incorrectGuidSyntax, correctGuidSyntax);
+ Document newDocument = document.WithSyntaxRoot(newRoot);
+
+ return Task.FromResult(newDocument);
+ }
+}
diff --git a/src/Analyzers/Orchestration/OrchestrationContextFixer.cs b/src/Analyzers/Orchestration/OrchestrationContextFixer.cs
new file mode 100644
index 00000000..50097d21
--- /dev/null
+++ b/src/Analyzers/Orchestration/OrchestrationContextFixer.cs
@@ -0,0 +1,116 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CodeFixes;
+using Microsoft.CodeAnalysis.CSharp;
+
+namespace Microsoft.DurableTask.Analyzers.Orchestration;
+
+///
+/// Context information for the .
+///
+/// The Semantic Model retrieved from the Document.
+/// Well-known types that are used by the Durable analyzers.
+/// The root Syntax Node retrieved from the Document.
+/// Syntax Node that contains the diagnostic.
+/// The 'TaskOrchestrationContext' symbol.
+public readonly struct OrchestrationCodeFixContext(
+ SemanticModel semanticModel,
+ KnownTypeSymbols knownTypeSymbols,
+ SyntaxNode root,
+ SyntaxNode syntaxNodeWithDiagnostic,
+ IParameterSymbol taskOrchestrationContextSymbol)
+{
+ ///
+ /// Gets the Semantic Model retrieved from the Document.
+ ///
+ public SemanticModel SemanticModel { get; } = semanticModel;
+
+ ///
+ /// Gets the well-known types that are used by the Durable analyzers.
+ ///
+ public KnownTypeSymbols KnownTypeSymbols { get; } = knownTypeSymbols;
+
+ ///
+ /// Gets the root Syntax Node retrieved from the Document.
+ ///
+ public SyntaxNode Root { get; } = root;
+
+ ///
+ /// Gets the Syntax Node that contains the diagnostic.
+ ///
+ public SyntaxNode SyntaxNodeWithDiagnostic { get; } = syntaxNodeWithDiagnostic;
+
+ ///
+ /// Gets the 'TaskOrchestrationContext' symbol.
+ ///
+ public IParameterSymbol TaskOrchestrationContextSymbol { get; } = taskOrchestrationContextSymbol;
+}
+
+///
+/// Base class for code fix providers that fix issues in orchestrator methods by replacing a SyntaxNode with a TaskOrchestrationContext member or invocation.
+///
+public abstract class OrchestrationContextFixer : CodeFixProvider
+{
+ ///
+ public override FixAllProvider? GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;
+
+ ///
+ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
+ {
+ SyntaxNode? root = await context.Document.GetSyntaxRootAsync(context.CancellationToken);
+ if (root == null)
+ {
+ return;
+ }
+
+ // Find the Syntax Node that is causing the diagnostic.
+ if (root.FindNode(context.Span, getInnermostNodeForTie: true) is not SyntaxNode syntaxNodeWithDiagnostic)
+ {
+ return;
+ }
+
+ SemanticModel? semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken);
+ if (semanticModel == null)
+ {
+ return;
+ }
+
+ // Analyze the data flow to determine which variables are assigned (reachable) within the scope.
+ DataFlowAnalysis? dataFlowAnalysis = semanticModel.AnalyzeDataFlow(syntaxNodeWithDiagnostic);
+ if (dataFlowAnalysis == null)
+ {
+ return;
+ }
+
+ KnownTypeSymbols knownTypeSymbols = new(semanticModel.Compilation);
+ if (knownTypeSymbols.TaskOrchestrationContext == null)
+ {
+ return;
+ }
+
+ // Find the TaskOrchestrationContext parameter available in the scope.
+ IParameterSymbol? taskOrchestrationContextSymbol = dataFlowAnalysis.DefinitelyAssignedOnEntry
+ .OfType()
+ .FirstOrDefault(
+ p => p.Type.Equals(knownTypeSymbols.TaskOrchestrationContext, SymbolEqualityComparer.Default));
+ if (taskOrchestrationContextSymbol == null)
+ {
+ // This method does not have a TaskOrchestrationContext parameter, so we should not offer this code fix.
+ return;
+ }
+
+ var orchestrationContext = new OrchestrationCodeFixContext(
+ semanticModel, knownTypeSymbols, root, syntaxNodeWithDiagnostic, taskOrchestrationContextSymbol);
+
+ this.RegisterCodeFixes(context, orchestrationContext);
+ }
+
+ ///
+ /// Registers a code fix for an orchestration diagnostic that can be fixed by replacing a SyntaxNode with a TaskOrchestrationContext member or invocation.
+ ///
+ /// A containing context information about the diagnostics to fix.
+ /// A containing context information about the orchestration code fixer.
+ protected abstract void RegisterCodeFixes(CodeFixContext context, OrchestrationCodeFixContext orchestrationContext);
+}
diff --git a/test/Analyzers.Tests/Orchestration/DateTimeOrchestrationAnalyzerTests.cs b/test/Analyzers.Tests/Orchestration/DateTimeOrchestrationAnalyzerTests.cs
index 18a90349..b9ae81e5 100644
--- a/test/Analyzers.Tests/Orchestration/DateTimeOrchestrationAnalyzerTests.cs
+++ b/test/Analyzers.Tests/Orchestration/DateTimeOrchestrationAnalyzerTests.cs
@@ -4,7 +4,7 @@
using Microsoft.CodeAnalysis.Testing;
using Microsoft.DurableTask.Analyzers.Orchestration;
-using VerifyCS = Microsoft.DurableTask.Analyzers.Tests.Verifiers.CSharpAnalyzerVerifier;
+using VerifyCS = Microsoft.DurableTask.Analyzers.Tests.Verifiers.CSharpCodeFixVerifier;
namespace Microsoft.DurableTask.Analyzers.Tests.Orchestration;
@@ -45,7 +45,6 @@ void Method(){
[Theory]
[InlineData("DateTime.Now")]
[InlineData("DateTime.UtcNow")]
- [InlineData("DateTime.Today")]
public async Task DurableFunctionOrchestrationUsingDateTimeNonDeterministicPropertiesHasDiag(string expression)
{
string code = Wrapper.WrapDurableFunctionOrchestration($@"
@@ -54,11 +53,43 @@ DateTime Run([OrchestrationTrigger] TaskOrchestrationContext context)
{{
return {{|#0:{expression}|}};
}}
+");
+
+ string fix = Wrapper.WrapDurableFunctionOrchestration($@"
+[Function(""Run"")]
+DateTime Run([OrchestrationTrigger] TaskOrchestrationContext context)
+{{
+ return context.CurrentUtcDateTime;
+}}
");
DiagnosticResult expected = BuildDiagnostic().WithLocation(0).WithArguments("Run", $"System.{expression}", "Run");
- await VerifyCS.VerifyDurableTaskAnalyzerAsync(code, expected);
+ await VerifyCS.VerifyDurableTaskCodeFixAsync(code, expected, fix);
+ }
+
+ [Fact]
+ public async Task DurableFunctionOrchestrationUsingDateTimeTodayHasDiag()
+ {
+ string code = Wrapper.WrapDurableFunctionOrchestration(@"
+[Function(""Run"")]
+DateTime Run([OrchestrationTrigger] TaskOrchestrationContext context)
+{
+ return {|#0:DateTime.Today|};
+}
+");
+
+ string fix = Wrapper.WrapDurableFunctionOrchestration($@"
+[Function(""Run"")]
+DateTime Run([OrchestrationTrigger] TaskOrchestrationContext context)
+{{
+ return context.CurrentUtcDateTime.Date;
+}}
+");
+
+ DiagnosticResult expected = BuildDiagnostic().WithLocation(0).WithArguments("Run", $"System.DateTime.Today", "Run");
+
+ await VerifyCS.VerifyDurableTaskCodeFixAsync(code, expected, fix);
}
[Fact]
@@ -187,16 +218,24 @@ public class MyOrchestrator : TaskOrchestrator
{
public override Task RunAsync(TaskOrchestrationContext context, string input)
{
- return Task.FromResult(Method());
+ return Task.FromResult({|#0:DateTime.Now|});
}
+}
+");
- private DateTime Method() => {|#0:DateTime.Now|};
+ string fix = Wrapper.WrapTaskOrchestrator(@"
+public class MyOrchestrator : TaskOrchestrator
+{
+ public override Task RunAsync(TaskOrchestrationContext context, string input)
+ {
+ return Task.FromResult(context.CurrentUtcDateTime);
+ }
}
");
- DiagnosticResult expected = BuildDiagnostic().WithLocation(0).WithArguments("Method", "System.DateTime.Now", "MyOrchestrator");
+ DiagnosticResult expected = BuildDiagnostic().WithLocation(0).WithArguments("RunAsync", "System.DateTime.Now", "MyOrchestrator");
- await VerifyCS.VerifyDurableTaskAnalyzerAsync(code, expected);
+ await VerifyCS.VerifyDurableTaskCodeFixAsync(code, expected, fix);
}
[Fact]
@@ -257,11 +296,18 @@ public async Task FuncOrchestratorWithLambdaHasDiag()
{
return {|#0:DateTime.Now|};
});
+");
+
+ string fix = Wrapper.WrapFuncOrchestrator(@"
+tasks.AddOrchestratorFunc(""HelloSequence"", context =>
+{
+ return context.CurrentUtcDateTime;
+});
");
DiagnosticResult expected = BuildDiagnostic().WithLocation(0).WithArguments("Main", "System.DateTime.Now", "HelloSequence");
- await VerifyCS.VerifyDurableTaskAnalyzerAsync(code, expected);
+ await VerifyCS.VerifyDurableTaskCodeFixAsync(code, expected, fix);
}
[Fact]
@@ -291,11 +337,37 @@ static DateTime MyRunAsync(TaskOrchestrationContext context)
return {|#0:DateTime.Now|};
}
}
+";
+
+ string fix = @"
+using System;
+using Microsoft.DurableTask;
+using Microsoft.DurableTask.Worker;
+using Microsoft.Extensions.DependencyInjection;
+
+public class Program
+{
+ public static void Main()
+ {
+ new ServiceCollection().AddDurableTaskWorker(builder =>
+ {
+ builder.AddTasks(tasks =>
+ {
+ tasks.AddOrchestratorFunc(""MyRun"", MyRunAsync);
+ });
+ });
+ }
+
+ static DateTime MyRunAsync(TaskOrchestrationContext context)
+ {
+ return context.CurrentUtcDateTime;
+ }
+}
";
DiagnosticResult expected = BuildDiagnostic().WithLocation(0).WithArguments("MyRunAsync", "System.DateTime.Now", "MyRun");
- await VerifyCS.VerifyDurableTaskAnalyzerAsync(code, expected);
+ await VerifyCS.VerifyDurableTaskCodeFixAsync(code, expected, fix);
}
diff --git a/test/Analyzers.Tests/Orchestration/DelayOrchestrationAnalyzerTests.cs b/test/Analyzers.Tests/Orchestration/DelayOrchestrationAnalyzerTests.cs
index 39053224..6f1b9151 100644
--- a/test/Analyzers.Tests/Orchestration/DelayOrchestrationAnalyzerTests.cs
+++ b/test/Analyzers.Tests/Orchestration/DelayOrchestrationAnalyzerTests.cs
@@ -3,8 +3,7 @@
using Microsoft.CodeAnalysis.Testing;
using Microsoft.DurableTask.Analyzers.Orchestration;
-
-using VerifyCS = Microsoft.DurableTask.Analyzers.Tests.Verifiers.CSharpAnalyzerVerifier;
+using VerifyCS = Microsoft.DurableTask.Analyzers.Tests.Verifiers.CSharpCodeFixVerifier;
namespace Microsoft.DurableTask.Analyzers.Tests.Orchestration;
@@ -40,12 +39,23 @@ public async Task DurableFunctionOrchestrationUsingTaskDelayHasDiag()
[Function(""Run"")]
async Task Method([OrchestrationTrigger] TaskOrchestrationContext context)
{
- await {|#0:Task.Delay(1000)|};
+ CancellationToken t = CancellationToken.None;
+ await {|#0:Task.Delay(1000, t)|};
}
");
- DiagnosticResult expected = BuildDiagnostic().WithLocation(0).WithArguments("Method", "Task.Delay(int)", "Run");
- await VerifyCS.VerifyDurableTaskAnalyzerAsync(code, expected);
+ string fix = Wrapper.WrapDurableFunctionOrchestration(@"
+[Function(""Run"")]
+async Task Method([OrchestrationTrigger] TaskOrchestrationContext context)
+{
+ CancellationToken t = CancellationToken.None;
+ await context.CreateTimer(TimeSpan.FromMilliseconds(1000), t);
+}
+");
+
+ DiagnosticResult expected = BuildDiagnostic().WithLocation(0).WithArguments("Method", "Task.Delay(int, CancellationToken)", "Run");
+
+ await VerifyCS.VerifyDurableTaskCodeFixAsync(code, expected, fix);
}
[Fact]
diff --git a/test/Analyzers.Tests/Orchestration/GuidOrchestrationAnalyzerTests.cs b/test/Analyzers.Tests/Orchestration/GuidOrchestrationAnalyzerTests.cs
index 838a81d1..335f02f9 100644
--- a/test/Analyzers.Tests/Orchestration/GuidOrchestrationAnalyzerTests.cs
+++ b/test/Analyzers.Tests/Orchestration/GuidOrchestrationAnalyzerTests.cs
@@ -4,7 +4,7 @@
using Microsoft.CodeAnalysis.Testing;
using Microsoft.DurableTask.Analyzers.Orchestration;
-using VerifyCS = Microsoft.DurableTask.Analyzers.Tests.Verifiers.CSharpAnalyzerVerifier;
+using VerifyCS = Microsoft.DurableTask.Analyzers.Tests.Verifiers.CSharpCodeFixVerifier;
namespace Microsoft.DurableTask.Analyzers.Tests.Orchestration;
@@ -27,11 +27,19 @@ Guid Method([OrchestrationTrigger] TaskOrchestrationContext context)
{
return {|#0:Guid.NewGuid()|};
}
+");
+
+ string fix = Wrapper.WrapDurableFunctionOrchestration(@"
+[Function(""Run"")]
+Guid Method([OrchestrationTrigger] TaskOrchestrationContext context)
+{
+ return context.NewGuid();
+}
");
DiagnosticResult expected = BuildDiagnostic().WithLocation(0).WithArguments("Method", "Guid.NewGuid()", "Run");
- await VerifyCS.VerifyDurableTaskAnalyzerAsync(code, expected);
+ await VerifyCS.VerifyDurableTaskCodeFixAsync(code, expected, fix);
}
[Fact]
@@ -45,11 +53,21 @@ public override Task RunAsync(TaskOrchestrationContext context, string inp
return Task.FromResult({|#0:Guid.NewGuid()|});
}
}
+");
+
+ string fix = Wrapper.WrapTaskOrchestrator(@"
+public class MyOrchestrator : TaskOrchestrator
+{
+ public override Task RunAsync(TaskOrchestrationContext context, string input)
+ {
+ return Task.FromResult(context.NewGuid());
+ }
+}
");
DiagnosticResult expected = BuildDiagnostic().WithLocation(0).WithArguments("RunAsync", "Guid.NewGuid()", "MyOrchestrator");
- await VerifyCS.VerifyDurableTaskAnalyzerAsync(code, expected);
+ await VerifyCS.VerifyDurableTaskCodeFixAsync(code, expected, fix);
}
[Fact]
@@ -60,11 +78,18 @@ public async Task FuncOrchestratorUsingNewGuidHasDiag()
{
return {|#0:Guid.NewGuid()|};
});
+");
+
+ string fix = Wrapper.WrapFuncOrchestrator(@"
+tasks.AddOrchestratorFunc(""HelloSequence"", context =>
+{
+ return context.NewGuid();
+});
");
DiagnosticResult expected = BuildDiagnostic().WithLocation(0).WithArguments("Main", "Guid.NewGuid()", "HelloSequence");
- await VerifyCS.VerifyDurableTaskAnalyzerAsync(code, expected);
+ await VerifyCS.VerifyDurableTaskCodeFixAsync(code, expected, fix);
}
static DiagnosticResult BuildDiagnostic()