diff --git a/src/Dapr.Workflow.Analyzers/AnalyzerReleases.Shipped.md b/src/Dapr.Workflow.Analyzers/AnalyzerReleases.Shipped.md
index ad7437da4..24f3b4f3c 100644
--- a/src/Dapr.Workflow.Analyzers/AnalyzerReleases.Shipped.md
+++ b/src/Dapr.Workflow.Analyzers/AnalyzerReleases.Shipped.md
@@ -5,4 +5,8 @@
Rule ID | Category | Severity | Notes
--------|----------|----------|--------------------
DAPR1301 | Usage | Warning | The workflow class '{0}' is not registered
-DAPR1302 | Usage | Warning | The workflow activity class '{0}' is not registered
\ No newline at end of file
+DAPR1302 | Usage | Warning | The workflow activity class '{0}' is not registered
+DAPR1303 | Usage | Warning | The provided input type does not match the target workflow or activity input type
+DAPR1304 | Usage | Warning | The requested output type does not match the target workflow or activity output type
+DAPR1305 | Usage | Warning | Workflow implementations do not support dependency injection via constructors
+
diff --git a/src/Dapr.Workflow.Analyzers/AnalyzerReleases.Unshipped.md b/src/Dapr.Workflow.Analyzers/AnalyzerReleases.Unshipped.md
index 6d0050326..5f1591ac4 100644
--- a/src/Dapr.Workflow.Analyzers/AnalyzerReleases.Unshipped.md
+++ b/src/Dapr.Workflow.Analyzers/AnalyzerReleases.Unshipped.md
@@ -5,6 +5,10 @@
Rule ID | Category | Severity | Notes
--------|----------|----------|--------------------
-DAPR1303 | Usage | Warning | The provided input type does not match the target workflow or activity input type
-DAPR1304 | Usage | Warning | The requested output type does not match the target workflow or activity output type
-DAPR1305 | Usage | Warning | Workflow implementations do not support dependency injection via constructors
+
+### Removed Rules
+
+Rule ID | Category | Severity | Notes
+--------|----------|----------|--------------------
+DAPR1301 | Usage | Warning | The workflow class '{0}' is not registered
+DAPR1302 | Usage | Warning | The workflow activity class '{0}' is not registered
\ No newline at end of file
diff --git a/src/Dapr.Workflow.Analyzers/WorkflowActivityRegistrationAnalyzer.cs b/src/Dapr.Workflow.Analyzers/WorkflowActivityRegistrationAnalyzer.cs
deleted file mode 100644
index 9141327cb..000000000
--- a/src/Dapr.Workflow.Analyzers/WorkflowActivityRegistrationAnalyzer.cs
+++ /dev/null
@@ -1,143 +0,0 @@
-// ------------------------------------------------------------------------
-// Copyright 2025 The Dapr Authors
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-// ------------------------------------------------------------------------
-
-using System.Collections.Immutable;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
-using Microsoft.CodeAnalysis.Diagnostics;
-
-namespace Dapr.Workflow.Analyzers;
-
-///
-/// An analyzer for Dapr workflows that validates that each workflow activity is registered with the
-/// dependency injection provider.
-///
-[DiagnosticAnalyzer(LanguageNames.CSharp)]
-public sealed class WorkflowActivityRegistrationAnalyzer : DiagnosticAnalyzer
-{
- internal static readonly DiagnosticDescriptor WorkflowActivityRegistrationDescriptor = new(
- id: "DAPR1302",
- title: new LocalizableResourceString(nameof(Resources.DAPR1302Title), Resources.ResourceManager,
- typeof(Resources)),
- messageFormat: new LocalizableResourceString(nameof(Resources.DAPR1302MessageFormat), Resources.ResourceManager,
- typeof(Resources)),
- category: "Usage",
- DiagnosticSeverity.Warning,
- isEnabledByDefault: true
- );
-
- ///
- /// Returns a set of descriptors for the diagnostics that this analyzer is capable of producing.
- ///
- public override ImmutableArray SupportedDiagnostics =>
- [
- WorkflowActivityRegistrationDescriptor
- ];
-
-
- ///
- /// Called once at session start to register actions in the analysis context.
- ///
- ///
- public override void Initialize(AnalysisContext context)
- {
- context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
- context.EnableConcurrentExecution();
- context.RegisterSyntaxNodeAction(AnalyzeWorkflowActivityRegistration, SyntaxKind.InvocationExpression);
- }
-
- private static void AnalyzeWorkflowActivityRegistration(SyntaxNodeAnalysisContext context)
- {
- var invocationExpr = (InvocationExpressionSyntax)context.Node;
-
- if (invocationExpr.Expression is not MemberAccessExpressionSyntax memberAccessExpr)
- {
- return;
- }
-
- if (memberAccessExpr.Name.Identifier.Text != "CallActivityAsync")
- {
- return;
- }
-
- var argumentList = invocationExpr.ArgumentList.Arguments;
- if (argumentList.Count == 0)
- {
- return;
- }
-
- var firstArgument = argumentList[0].Expression;
- if (firstArgument is not InvocationExpressionSyntax nameofInvocation)
- {
- return;
- }
-
- var activityName = nameofInvocation.ArgumentList.Arguments.FirstOrDefault()?.Expression.ToString().Trim('"');
- if (activityName == null)
- {
- return;
- }
-
- bool isRegistered = CheckIfActivityIsRegistered(activityName, context.SemanticModel);
- if (isRegistered)
- {
- return;
- }
-
- var diagnostic = Diagnostic.Create(WorkflowActivityRegistrationDescriptor, firstArgument.GetLocation(), activityName);
- context.ReportDiagnostic(diagnostic);
- }
-
- private static bool CheckIfActivityIsRegistered(string activityName, SemanticModel semanticModel)
- {
- var methodInvocations = new List();
- foreach (var syntaxTree in semanticModel.Compilation.SyntaxTrees)
- {
- var root = syntaxTree.GetRoot();
- methodInvocations.AddRange(root.DescendantNodes().OfType());
- }
-
- foreach (var invocation in methodInvocations)
- {
- if (invocation.Expression is not MemberAccessExpressionSyntax memberAccess)
- {
- continue;
- }
-
- var methodName = memberAccess.Name.Identifier.Text;
- if (methodName != "RegisterActivity")
- {
- continue;
- }
-
- if (memberAccess.Name is not GenericNameSyntax typeArgumentList ||
- typeArgumentList.TypeArgumentList.Arguments.Count <= 0)
- {
- continue;
- }
-
- if (typeArgumentList.TypeArgumentList.Arguments[0] is not IdentifierNameSyntax typeArgument)
- {
- continue;
- }
-
- if (typeArgument.Identifier.Text == activityName)
- {
- return true;
- }
- }
-
- return false;
- }
-}
diff --git a/src/Dapr.Workflow.Analyzers/WorkflowActivityRegistrationCodeFixProvider.cs b/src/Dapr.Workflow.Analyzers/WorkflowActivityRegistrationCodeFixProvider.cs
deleted file mode 100644
index 77ed6c9cf..000000000
--- a/src/Dapr.Workflow.Analyzers/WorkflowActivityRegistrationCodeFixProvider.cs
+++ /dev/null
@@ -1,128 +0,0 @@
-using System.Collections.Immutable;
-using System.Composition;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CodeActions;
-using Microsoft.CodeAnalysis.CodeFixes;
-using Microsoft.CodeAnalysis.CSharp;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
-using Microsoft.CodeAnalysis.Formatting;
-
-namespace Dapr.Workflow.Analyzers;
-
-///
-/// Provides code fixes for DAPR1002 diagnostic.
-///
-[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(WorkflowActivityRegistrationCodeFixProvider))]
-public sealed class WorkflowActivityRegistrationCodeFixProvider : CodeFixProvider
-{
- ///
- /// Gets the diagnostic IDs that this provider can fix.
- ///
- public override ImmutableArray FixableDiagnosticIds => ["DAPR1002"];
-
- ///
- /// Registers the code fix for the diagnostic.
- ///
- public override Task RegisterCodeFixesAsync(CodeFixContext context)
- {
- const string title = "Register Dapr workflow activity";
- context.RegisterCodeFix(
- CodeAction.Create(
- title,
- createChangedDocument: c => RegisterWorkflowActivityAsync(context.Document, context.Diagnostics.First(), c),
- equivalenceKey: title),
- context.Diagnostics);
- return Task.CompletedTask;
- }
-
- private static async Task RegisterWorkflowActivityAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken)
- {
- var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
- var diagnosticSpan = diagnostic.Location.SourceSpan;
-
- var oldInvocation = root?.FindToken(diagnosticSpan.Start).Parent?.AncestorsAndSelf().OfType().First();
-
- if (oldInvocation == null)
- {
- return document;
- }
-
- // Extract the workflow activity type name
- var workflowActivityType = oldInvocation.ArgumentList.Arguments.FirstOrDefault()?.Expression.ToString();
-
- if (string.IsNullOrEmpty(workflowActivityType))
- {
- return document;
- }
-
- // Get the compilation
- var compilation = await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
-
- if (compilation == null)
- {
- return document;
- }
-
- InvocationExpressionSyntax? addDaprWorkflowInvocation = null;
- SyntaxNode? targetRoot = null;
- Document? targetDocument = null;
-
- // Iterate through all syntax trees in the compilation
- foreach (var syntaxTree in compilation.SyntaxTrees)
- {
- var syntaxRoot = await syntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false);
-
- addDaprWorkflowInvocation = syntaxRoot.DescendantNodes()
- .OfType()
- .FirstOrDefault(invocation => invocation.Expression is MemberAccessExpressionSyntax memberAccess &&
- memberAccess.Name.Identifier.Text == "AddDaprWorkflow");
-
- if (addDaprWorkflowInvocation != null)
- {
- targetRoot = syntaxRoot;
- targetDocument = document.Project.GetDocument(syntaxTree);
- break;
- }
- }
-
- if (addDaprWorkflowInvocation == null || targetRoot == null || targetDocument == null)
- return document;
-
- // Find the options lambda block
- var optionsLambda = addDaprWorkflowInvocation.ArgumentList.Arguments
- .Select(arg => arg.Expression)
- .OfType()
- .FirstOrDefault();
-
- // Extract the parameter name from the lambda expression
- var parameterName = optionsLambda?.Parameter.Identifier.Text;
-
- // Create the new workflow registration statement
- var registerWorkflowStatement = SyntaxFactory.ParseStatement($"{parameterName}.RegisterActivity<{workflowActivityType}>();");
-
- if (optionsLambda is not { Body: BlockSyntax optionsBlock })
- {
- return document;
- }
-
- // Add the new registration statement to the options block
- var newOptionsBlock = optionsBlock.AddStatements(registerWorkflowStatement);
-
- // Replace the old options block with the new one
- var newRoot = targetRoot.ReplaceNode(optionsBlock, newOptionsBlock);
-
- // Format the new root.
- newRoot = Formatter.Format(newRoot, document.Project.Solution.Workspace);
-
- return targetDocument.WithSyntaxRoot(newRoot);
- }
-
- ///
- /// Gets the FixAllProvider for this code fix provider.
- ///
- /// The FixAllProvider instance.
- public override FixAllProvider? GetFixAllProvider()
- {
- return WellKnownFixAllProviders.BatchFixer;
- }
-}
diff --git a/src/Dapr.Workflow.Analyzers/WorkflowRegistrationAnalyzer.cs b/src/Dapr.Workflow.Analyzers/WorkflowRegistrationAnalyzer.cs
deleted file mode 100644
index bef1c5c3e..000000000
--- a/src/Dapr.Workflow.Analyzers/WorkflowRegistrationAnalyzer.cs
+++ /dev/null
@@ -1,179 +0,0 @@
-using Microsoft.CodeAnalysis.Diagnostics;
-using Microsoft.CodeAnalysis;
-using System.Collections.Concurrent;
-using System.Collections.Immutable;
-using System.Threading;
-using Microsoft.CodeAnalysis.CSharp;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
-
-namespace Dapr.Workflow.Analyzers;
-
-///
-/// Analyzes whether or not workflow activities are registered.
-///
-[DiagnosticAnalyzer(LanguageNames.CSharp)]
-public class WorkflowRegistrationAnalyzer : DiagnosticAnalyzer
-{
- private const string WorkflowVersioningExtensionsMetadataName =
- "Dapr.Workflow.Versioning.WorkflowVersioningServiceCollectionExtensions";
-
- internal static readonly DiagnosticDescriptor WorkflowDiagnosticDescriptor = new(
- id: "DAPR1301",
- title: new LocalizableResourceString(nameof(Resources.DAPR1301Title), Resources.ResourceManager, typeof(Resources)),
- messageFormat: new LocalizableResourceString(nameof(Resources.DAPR1301MessageFormat), Resources.ResourceManager, typeof(Resources)),
- category: "Usage",
- DiagnosticSeverity.Warning,
- isEnabledByDefault: true);
-
- ///
- /// Gets the supported diagnostics for this analyzer.
- ///
- public override ImmutableArray SupportedDiagnostics => [WorkflowDiagnosticDescriptor];
-
- ///
- /// Initializes the analyzer.
- ///
- /// The analysis context.
- public override void Initialize(AnalysisContext context)
- {
- context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
- context.EnableConcurrentExecution();
- context.RegisterCompilationStartAction(compilationContext =>
- {
- var versioningExtensionsType = compilationContext.Compilation
- .GetTypeByMetadataName(WorkflowVersioningExtensionsMetadataName);
-
- if (versioningExtensionsType is null)
- {
- // Versioning package is not referenced; use the direct reporting path.
- compilationContext.RegisterSyntaxNodeAction(AnalyzeWorkflowRegistration, SyntaxKind.InvocationExpression);
- return;
- }
-
- // Versioning package is referenced. Use the deferred-diagnostics pattern so that
- // AddDaprWorkflowVersioning can be verified semantically (avoiding RS1030) while
- // still checking explicit RegisterWorkflow registrations per workflow call.
- // Node actions can execute concurrently, so both collections are thread-safe and
- // results are only acted on in the compilation-end action (which runs after all
- // node actions have finished).
- int versioningCalled = 0;
- var pendingDiagnostics = new ConcurrentBag();
-
- compilationContext.RegisterSyntaxNodeAction(nodeContext =>
- {
- var invocation = (InvocationExpressionSyntax)nodeContext.Node;
-
- // Semantic check: verify the call resolves to the Dapr versioning extension method.
- if (invocation.Expression is MemberAccessExpressionSyntax memberAccess &&
- memberAccess.Name.Identifier.Text == "AddDaprWorkflowVersioning" &&
- nodeContext.SemanticModel.GetSymbolInfo(invocation, nodeContext.CancellationToken).Symbol is IMethodSymbol method &&
- SymbolEqualityComparer.Default.Equals(method.ContainingType, versioningExtensionsType))
- {
- Interlocked.Exchange(ref versioningCalled, 1);
- }
- }, SyntaxKind.InvocationExpression);
-
- compilationContext.RegisterSyntaxNodeAction(nodeContext =>
- {
- // Collect potential DAPR1301 diagnostics; explicit RegisterWorkflow
- // registrations are still respected here.
- var diagnostic = TryBuildWorkflowDiagnostic(nodeContext);
- if (diagnostic is not null)
- pendingDiagnostics.Add(diagnostic);
- }, SyntaxKind.InvocationExpression);
-
- compilationContext.RegisterCompilationEndAction(endContext =>
- {
- // If AddDaprWorkflowVersioning was confirmed, all workflows are auto-registered
- // by the source generator — suppress any pending DAPR1301 diagnostics.
- if (Volatile.Read(ref versioningCalled) == 1)
- return;
-
- foreach (var d in pendingDiagnostics)
- endContext.ReportDiagnostic(d);
- });
- });
- }
-
- private static void AnalyzeWorkflowRegistration(SyntaxNodeAnalysisContext context)
- {
- var diagnostic = TryBuildWorkflowDiagnostic(context);
- if (diagnostic is not null)
- context.ReportDiagnostic(diagnostic);
- }
-
- private static Diagnostic? TryBuildWorkflowDiagnostic(SyntaxNodeAnalysisContext context)
- {
- var invocationExpr = (InvocationExpressionSyntax)context.Node;
-
- if (invocationExpr.Expression is not MemberAccessExpressionSyntax memberAccessExpr)
- return null;
-
- if (memberAccessExpr.Name.Identifier.Text != "ScheduleNewWorkflowAsync")
- return null;
-
- var argumentList = invocationExpr.ArgumentList.Arguments;
- if (argumentList.Count == 0)
- return null;
-
- var firstArgument = argumentList[0].Expression;
- if (firstArgument is not InvocationExpressionSyntax nameofInvocation ||
- nameofInvocation.Expression is not IdentifierNameSyntax { Identifier.Text: "nameof" } ||
- nameofInvocation.ArgumentList.Arguments.FirstOrDefault()?.Expression is not { } nameofArgExpr)
- return null;
-
- if (context.SemanticModel.GetSymbolInfo(nameofArgExpr, context.CancellationToken).Symbol is not INamedTypeSymbol workflowTypeSymbol)
- return null;
-
- if (CheckIfWorkflowIsRegistered(workflowTypeSymbol, context.SemanticModel, context.CancellationToken))
- return null;
-
- return Diagnostic.Create(WorkflowDiagnosticDescriptor, firstArgument.GetLocation(), workflowTypeSymbol.Name);
- }
-
- private static bool CheckIfWorkflowIsRegistered(INamedTypeSymbol workflowType, SemanticModel semanticModel, CancellationToken cancellationToken)
- {
- foreach (var syntaxTree in semanticModel.Compilation.SyntaxTrees)
- {
- var root = syntaxTree.GetRoot(cancellationToken);
- var isSameTree = syntaxTree == semanticModel.SyntaxTree;
-
- foreach (var invocation in root.DescendantNodes().OfType())
- {
- if (invocation.Expression is not MemberAccessExpressionSyntax memberAccess)
- {
- continue;
- }
-
- if (memberAccess.Name is not GenericNameSyntax genericName ||
- genericName.Identifier.Text != "RegisterWorkflow" ||
- genericName.TypeArgumentList.Arguments.Count == 0)
- {
- continue;
- }
-
- var typeArgSyntax = genericName.TypeArgumentList.Arguments[0];
-
- if (isSameTree)
- {
- // Use full semantic comparison for nodes in the same tree.
- var typeArgSymbol = semanticModel.GetSymbolInfo(typeArgSyntax, cancellationToken).Symbol as INamedTypeSymbol;
- if (typeArgSymbol is not null && SymbolEqualityComparer.Default.Equals(typeArgSymbol, workflowType))
- return true;
- }
- else
- {
- // For nodes in other trees we cannot use this semantic model (RS1030 prevents
- // calling Compilation.GetSemanticModel). Fall back to a syntactic name
- // comparison, which is sufficient for the common case of non-generic workflow
- // types with distinct names.
- if (typeArgSyntax is IdentifierNameSyntax identifierName &&
- identifierName.Identifier.Text == workflowType.Name)
- return true;
- }
- }
- }
-
- return false;
- }
-}
diff --git a/src/Dapr.Workflow.Analyzers/WorkflowRegistrationCodeFixProvider.cs b/src/Dapr.Workflow.Analyzers/WorkflowRegistrationCodeFixProvider.cs
deleted file mode 100644
index e73c2f634..000000000
--- a/src/Dapr.Workflow.Analyzers/WorkflowRegistrationCodeFixProvider.cs
+++ /dev/null
@@ -1,362 +0,0 @@
-using System.Collections.Immutable;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CodeActions;
-using Microsoft.CodeAnalysis.CodeFixes;
-using Microsoft.CodeAnalysis.CSharp;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
-using Microsoft.CodeAnalysis.Formatting;
-
-namespace Dapr.Workflow.Analyzers;
-
-///
-/// Provides code fixes for DAPR1301 diagnostic.
-///
-[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(WorkflowRegistrationCodeFixProvider))]
-public sealed class WorkflowRegistrationCodeFixProvider : CodeFixProvider
-{
- ///
- /// Gets the diagnostic IDs that this provider can fix.
- ///
- public override ImmutableArray FixableDiagnosticIds => ["DAPR1301"];
-
- ///
- /// Registers the code fix for the diagnostic.
- ///
- public override Task RegisterCodeFixesAsync(CodeFixContext context)
- {
- const string title = "Register Dapr workflow";
- context.RegisterCodeFix(
- CodeAction.Create(
- title,
- createChangedDocument: c => RegisterWorkflowAsync(context.Document, context.Diagnostics.First(), c),
- equivalenceKey: title),
- context.Diagnostics);
- return Task.CompletedTask;
- }
-
- private static async Task RegisterWorkflowAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken)
- {
- var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
- var diagnosticSpan = diagnostic.Location.SourceSpan;
-
- // Prefer the ScheduleNewWorkflowAsync(...) invocation even if the diagnostic is reported on nameof(...)
- var invocationsAtLocation = root?
- .FindToken(diagnosticSpan.Start)
- .Parent?
- .AncestorsAndSelf()
- .OfType()
- .ToList();
-
- var oldInvocation = invocationsAtLocation?
- .FirstOrDefault(invocation =>
- invocation.Expression is MemberAccessExpressionSyntax memberAccessExpr &&
- memberAccessExpr.Name.Identifier.Text == "ScheduleNewWorkflowAsync")
- ?? invocationsAtLocation?.FirstOrDefault();
-
- if (oldInvocation is null || root is null)
- return document;
-
- // Get the semantic model
- var semanticModel = await document.GetSemanticModelAsync(cancellationToken);
- if (semanticModel is null)
- return document;
-
- // Extract the workflow type name from nameof(SomeWorkflow)
- var firstArgExpr = oldInvocation.ArgumentList.Arguments.FirstOrDefault()?.Expression;
- if (firstArgExpr is not InvocationExpressionSyntax nameofInvocation ||
- nameofInvocation.Expression is not IdentifierNameSyntax { Identifier.Text: "nameof" } ||
- nameofInvocation.ArgumentList.Arguments.FirstOrDefault()?.Expression is not { } nameofArgExpr)
- return document;
-
- // Get the symbol for the workflow type
- if (semanticModel.GetSymbolInfo(nameofArgExpr, cancellationToken).Symbol is not INamedTypeSymbol workflowTypeSymbol)
- return document;
-
- // Get the fully qualified name
- var workflowType = workflowTypeSymbol.ToDisplayString(new SymbolDisplayFormat(
- typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces));
-
- if (string.IsNullOrEmpty(workflowType))
- return document;
-
- var (targetDocument, addDaprWorkflowInvocation) =
- await FindAddDaprWorkflowInvocationAsync(document.Project, cancellationToken);
-
- if (addDaprWorkflowInvocation == null)
- {
- (targetDocument, addDaprWorkflowInvocation) =
- await CreateAddDaprWorkflowInvocation(document.Project, cancellationToken);
- }
-
- if (addDaprWorkflowInvocation == null || targetDocument == null)
- return document;
-
- var targetRoot = await addDaprWorkflowInvocation.SyntaxTree.GetRootAsync(cancellationToken);
- if (targetRoot == null)
- return document;
-
- // Find the options lambda block
- var optionsLambda = addDaprWorkflowInvocation.ArgumentList.Arguments
- .Select(arg => arg.Expression)
- .OfType()
- .FirstOrDefault();
-
- if (optionsLambda is not { Body: BlockSyntax optionsBlock })
- return document;
-
- // Extract the parameter name from the lambda expression
- var parameterName = optionsLambda.Parameter.Identifier.Text;
-
- // Create the new workflow registration statement
- var registerWorkflowStatement = SyntaxFactory.ParseStatement($"{parameterName}.RegisterWorkflow<{workflowType}>();");
-
- // Add the new registration statement to the options block
- var newOptionsBlock = optionsBlock.AddStatements(registerWorkflowStatement);
-
- // Replace the old options block with the new one
- var newRoot = targetRoot.ReplaceNode(optionsBlock, newOptionsBlock);
-
- // Format the new root.
- newRoot = Formatter.Format(newRoot, targetDocument.Project.Solution.Workspace);
-
- return targetDocument.WithSyntaxRoot(newRoot);
- }
-
- ///
- /// Gets the FixAllProvider for this code fix provider.
- ///
- /// The FixAllProvider instance.
- public override FixAllProvider? GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;
-
- private static async Task<(Document?, InvocationExpressionSyntax?)> FindAddDaprWorkflowInvocationAsync(Project project, CancellationToken cancellationToken)
- {
- var compilation = await project.GetCompilationAsync(cancellationToken);
-
- foreach (var syntaxTree in compilation!.SyntaxTrees)
- {
- var syntaxRoot = await syntaxTree.GetRootAsync(cancellationToken);
-
- var addDaprWorkflowInvocation = syntaxRoot.DescendantNodes()
- .OfType()
- .FirstOrDefault(invocation => invocation.Expression is MemberAccessExpressionSyntax memberAccess &&
- memberAccess.Name.Identifier.Text == "AddDaprWorkflow");
-
- if (addDaprWorkflowInvocation == null)
- {
- continue;
- }
-
- var document = project.GetDocument(addDaprWorkflowInvocation.SyntaxTree);
- return (document, addDaprWorkflowInvocation);
- }
-
- return (null, null);
- }
-
- private static async Task<(Document?, InvocationExpressionSyntax?)> CreateAddDaprWorkflowInvocation(Project project, CancellationToken cancellationToken)
- {
- // Case 1/2 : var builder = WebApplication.CreateBuilder(...);
- // var builder = Host.CreateApplicationBuilder(...);
- var createBuilderInvocation = await FindCreateBuilderInvocationAsync(project, cancellationToken);
- if (createBuilderInvocation != null)
- {
- var variableDeclarator = createBuilderInvocation.Ancestors()
- .OfType()
- .FirstOrDefault();
-
- var builderVariable = variableDeclarator?.Identifier.Text;
- if (string.IsNullOrWhiteSpace(builderVariable))
- return (null, null);
-
- var targetRoot = await createBuilderInvocation.SyntaxTree.GetRootAsync(cancellationToken);
- var document = project.GetDocument(createBuilderInvocation.SyntaxTree);
- if (targetRoot == null || document == null)
- return (null, null);
-
- // Force a block-bodied lambda so formatting is stable and matches tests.
- var addDaprWorkflowStatement = SyntaxFactory.ParseStatement(
- $"{builderVariable}.Services.AddDaprWorkflow(options =>\n{{\n}});\n");
-
- // Insert immediately after the statement containing the builder creation.
- // Handles:
- // - inside a method/body (BlockSyntax)
- // - top-level statements (GlobalStatementSyntax -> CompilationUnitSyntax)
- var containingStatement = createBuilderInvocation.Ancestors().OfType().FirstOrDefault();
- if (containingStatement is null)
- return (null, null);
-
- if (containingStatement.Parent is BlockSyntax block)
- {
- var newBlock = block.InsertNodesAfter(containingStatement, [addDaprWorkflowStatement]);
- targetRoot = targetRoot.ReplaceNode(block, newBlock);
- }
- else if (containingStatement.Parent is GlobalStatementSyntax globalStatement &&
- globalStatement.Parent is CompilationUnitSyntax compilationUnitFromGlobal)
- {
- var newCompilationUnit = compilationUnitFromGlobal.InsertNodesAfter(
- globalStatement,
- [SyntaxFactory.GlobalStatement(addDaprWorkflowStatement)]);
-
- targetRoot = targetRoot.ReplaceNode(compilationUnitFromGlobal, newCompilationUnit);
- }
- else if (containingStatement.Parent is CompilationUnitSyntax compilationUnit)
- {
- var newCompilationUnit = compilationUnit.InsertNodesAfter(
- containingStatement,
- [SyntaxFactory.GlobalStatement(addDaprWorkflowStatement)]);
-
- targetRoot = targetRoot.ReplaceNode(compilationUnit, newCompilationUnit);
- }
- else
- {
- return (null, null);
- }
-
- var addDaprWorkflowInvocation = targetRoot.DescendantNodes()
- .OfType()
- .FirstOrDefault(invocation => invocation.Expression is MemberAccessExpressionSyntax memberAccess &&
- memberAccess.Name.Identifier.Text == "AddDaprWorkflow");
-
- return (document, addDaprWorkflowInvocation);
- }
-
- // Case 3 : Host.CreateDefaultBuilder(args).ConfigureServices(services => {...});
- var configureServicesInvocation = await FindConfigureServicesInvocationAsync(project, cancellationToken);
- if (configureServicesInvocation != null)
- {
- var document = project.GetDocument(configureServicesInvocation.SyntaxTree);
- var targetRoot = await configureServicesInvocation.SyntaxTree.GetRootAsync(cancellationToken);
- if (targetRoot == null || document == null)
- return (null, null);
-
- var lambda = configureServicesInvocation.ArgumentList.Arguments
- .Select(a => a.Expression)
- .OfType()
- .FirstOrDefault();
-
- if (lambda is null)
- return (null, null);
-
- var servicesParamName =
- lambda switch
- {
- SimpleLambdaExpressionSyntax s => s.Parameter.Identifier.Text,
- ParenthesizedLambdaExpressionSyntax p => p.ParameterList.Parameters.LastOrDefault()?.Identifier.Text,
- _ => null
- };
-
- if (string.IsNullOrWhiteSpace(servicesParamName))
- return (null, null);
-
- var addDaprWorkflowStatement = SyntaxFactory.ParseStatement(
- $"{servicesParamName}.AddDaprWorkflow(options =>\n{{\n}});\n");
-
- SyntaxNode newRoot = targetRoot;
-
- if (lambda.Body is BlockSyntax bodyBlock)
- {
- var newBodyBlock = bodyBlock.WithStatements(bodyBlock.Statements.Insert(0, addDaprWorkflowStatement));
-
- LambdaExpressionSyntax? newLambda =
- lambda switch
- {
- SimpleLambdaExpressionSyntax s => s.WithBody(newBodyBlock),
- ParenthesizedLambdaExpressionSyntax p => p.WithBody(newBodyBlock),
- _ => null
- };
-
- if (newLambda is null)
- return (null, null);
-
- newRoot = newRoot.ReplaceNode((SyntaxNode)lambda, (SyntaxNode)newLambda);
- }
- else if (lambda.Body is ExpressionSyntax exprBody)
- {
- var newBodyBlock = SyntaxFactory.Block(
- addDaprWorkflowStatement,
- SyntaxFactory.ExpressionStatement(exprBody));
-
- LambdaExpressionSyntax? newLambda =
- lambda switch
- {
- SimpleLambdaExpressionSyntax s => s.WithBody(newBodyBlock),
- ParenthesizedLambdaExpressionSyntax p => p.WithBody(newBodyBlock),
- _ => null
- };
-
- if (newLambda is null)
- return (null, null);
-
- newRoot = newRoot.ReplaceNode((SyntaxNode)lambda, (SyntaxNode)newLambda);
- }
- else
- {
- return (null, null);
- }
-
- newRoot = Formatter.Format(newRoot, document.Project.Solution.Workspace);
-
- var updatedDoc = document.WithSyntaxRoot(newRoot);
- var updatedRoot = await updatedDoc.GetSyntaxRootAsync(cancellationToken);
- var addDaprWorkflowInvocation = updatedRoot?.DescendantNodes()
- .OfType()
- .FirstOrDefault(invocation => invocation.Expression is MemberAccessExpressionSyntax memberAccess &&
- memberAccess.Name.Identifier.Text == "AddDaprWorkflow");
-
- return (updatedDoc, addDaprWorkflowInvocation);
- }
-
- return (null, null);
- }
-
- private static async Task FindCreateBuilderInvocationAsync(Project project, CancellationToken cancellationToken)
- {
- var compilation = await project.GetCompilationAsync(cancellationToken);
-
- foreach (var syntaxTree in compilation!.SyntaxTrees)
- {
- var syntaxRoot = await syntaxTree.GetRootAsync(cancellationToken);
-
- var createBuilderInvocation = syntaxRoot.DescendantNodes()
- .OfType()
- .FirstOrDefault(invocation => invocation.Expression is MemberAccessExpressionSyntax
- {
- Expression: IdentifierNameSyntax { Identifier.Text: "WebApplication" },
- Name.Identifier.Text: "CreateBuilder"
- } or MemberAccessExpressionSyntax
- {
- Expression: IdentifierNameSyntax { Identifier.Text: "Host" },
- Name.Identifier.Text: "CreateApplicationBuilder"
- });
-
- if (createBuilderInvocation != null)
- {
- return createBuilderInvocation;
- }
- }
-
- return null;
- }
-
- private static async Task FindConfigureServicesInvocationAsync(Project project, CancellationToken cancellationToken)
- {
- var compilation = await project.GetCompilationAsync(cancellationToken);
-
- foreach (var syntaxTree in compilation!.SyntaxTrees)
- {
- var root = await syntaxTree.GetRootAsync(cancellationToken);
-
- var configureServicesInvocation = root.DescendantNodes()
- .OfType()
- .FirstOrDefault(invocation =>
- invocation.Expression is MemberAccessExpressionSyntax { Name.Identifier.Text: "ConfigureServices" });
-
- if (configureServicesInvocation != null)
- {
- return configureServicesInvocation;
- }
- }
-
- return null;
- }
-}
diff --git a/test/Dapr.Workflow.Analyzers.Test/Utilities.cs b/test/Dapr.Workflow.Analyzers.Test/Utilities.cs
index d860082bf..9fd924428 100644
--- a/test/Dapr.Workflow.Analyzers.Test/Utilities.cs
+++ b/test/Dapr.Workflow.Analyzers.Test/Utilities.cs
@@ -12,16 +12,12 @@ internal static class Utilities
{
internal static ImmutableArray GetAnalyzers() =>
[
- new WorkflowRegistrationAnalyzer(),
- new WorkflowActivityRegistrationAnalyzer(),
new WorkflowTypeSafetyAnalyzer()
];
internal static IReadOnlyList GetReferences()
{
- var metadataReferences = TestUtilities.GetAllReferencesNeededForType(typeof(WorkflowActivityRegistrationAnalyzer)).ToList();
- metadataReferences.AddRange(TestUtilities.GetAllReferencesNeededForType(typeof(WorkflowRegistrationAnalyzer)));
- metadataReferences.AddRange(TestUtilities.GetAllReferencesNeededForType(typeof(WorkflowTypeSafetyAnalyzer)));
+ var metadataReferences = TestUtilities.GetAllReferencesNeededForType(typeof(WorkflowTypeSafetyAnalyzer)).ToList();
metadataReferences.AddRange(TestUtilities.GetAllReferencesNeededForType(typeof(TimeSpan)));
metadataReferences.AddRange(TestUtilities.GetAllReferencesNeededForType(typeof(Workflow<,>)));
metadataReferences.AddRange(TestUtilities.GetAllReferencesNeededForType(typeof(WorkflowActivity<,>)));
diff --git a/test/Dapr.Workflow.Analyzers.Test/WorkflowActivityRegistrationAnalyzerTests.cs b/test/Dapr.Workflow.Analyzers.Test/WorkflowActivityRegistrationAnalyzerTests.cs
deleted file mode 100644
index 9e96ccb5a..000000000
--- a/test/Dapr.Workflow.Analyzers.Test/WorkflowActivityRegistrationAnalyzerTests.cs
+++ /dev/null
@@ -1,99 +0,0 @@
-// ------------------------------------------------------------------------
-// Copyright 2025 The Dapr Authors
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-// ------------------------------------------------------------------------
-
-using Dapr.Analyzers.Common;
-
-namespace Dapr.Workflow.Analyzers.Test;
-
-public sealed class WorkflowActivityRegistrationAnalyzerTests
-{
- [Fact]
- public async Task VerifyActivityNotRegistered()
- {
- const string testCode = """
- using Dapr.Workflow;
- using System.Threading.Tasks;
-
- class OrderProcessingWorkflow : Workflow
- {
- public override async Task RunAsync(WorkflowContext context, OrderPayload order)
- {
- await context.CallActivityAsync(nameof(NotifyActivity), new Notification("Order received"));
- return new OrderResult("Order processed");
- }
- }
-
- record OrderPayload { }
- record OrderResult(string message) { }
- record Notification { public Notification(string message) { } }
- class NotifyActivity { }
- """;
-
- var expected = VerifyAnalyzer.Diagnostic(WorkflowActivityRegistrationAnalyzer.WorkflowActivityRegistrationDescriptor)
- .WithSpan(8, 57, 8, 79).WithMessage("The workflow activity type 'NotifyActivity' is not registered with the dependency injection provider");
-
- var analyzer = new VerifyAnalyzer(Utilities.GetReferences());
- await analyzer.VerifyAnalyzerAsync(testCode, expected);
- }
-
- [Fact]
- public async Task VerifyActivityRegistered()
- {
- const string testCode = """
- using Dapr.Workflow;
- using Microsoft.Extensions.DependencyInjection;
- using System.Threading.Tasks;
-
- class OrderProcessingWorkflow : Workflow
- {
- public override async Task RunAsync(WorkflowContext context, OrderPayload order)
- {
- await context.CallActivityAsync(nameof(NotifyActivity), new Notification("Order received"));
- return new OrderResult("Order processed");
- }
- }
-
- record OrderPayload { }
- record OrderResult(string message) { }
- record Notification(string Message);
-
- class NotifyActivity : WorkflowActivity
- {
-
- public override Task