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 RunAsync(WorkflowActivityContext context, Notification notification) - { - return Task.FromResult(null); - } - } - """; - - const string startupCode = """ - using Dapr.Workflow; - using Microsoft.Extensions.DependencyInjection; - - internal static class Extensions - { - public static void AddApplicationServices(this IServiceCollection services) - { - services.AddDaprWorkflow(options => - { - options.RegisterActivity(); - }); - } - } - """; - - var analyzer = new VerifyAnalyzer(Utilities.GetReferences()); - await analyzer.VerifyAnalyzerAsync(testCode, startupCode); - } -} diff --git a/test/Dapr.Workflow.Analyzers.Test/WorkflowActivityRegistrationCodeFixProviderTests.cs b/test/Dapr.Workflow.Analyzers.Test/WorkflowActivityRegistrationCodeFixProviderTests.cs deleted file mode 100644 index 109353883..000000000 --- a/test/Dapr.Workflow.Analyzers.Test/WorkflowActivityRegistrationCodeFixProviderTests.cs +++ /dev/null @@ -1,93 +0,0 @@ -using Dapr.Analyzers.Common; - -namespace Dapr.Workflow.Analyzers.Test; - -public sealed class WorkflowActivityRegistrationCodeFixProviderTests -{ - [Fact] - public async Task VerifyWorkflowActivityRegistrationCodeFix() - { - const string code = """ - using Dapr.Workflow; - using System; - using Microsoft.Extensions.DependencyInjection; - using System.Threading.Tasks; - - public static class Program - { - public static void Main() - { - var services = new ServiceCollection(); - - services.AddDaprWorkflow(options => - { - }); - } - } - - 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) { } }; - internal sealed class NotifyActivity : WorkflowActivity - { - public override async Task RunAsync(WorkflowActivityContext context, Notification input) - { - await Task.Delay(TimeSpan.FromSeconds(15)); - return true; - } - } - """; - - const string expectedChangedCode = """ - using Dapr.Workflow; - using System; - using Microsoft.Extensions.DependencyInjection; - using System.Threading.Tasks; - - public static class Program - { - public static void Main() - { - var services = new ServiceCollection(); - - services.AddDaprWorkflow(options => - { - options.RegisterActivity(); - }); - } - } - - 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) { } }; - internal sealed class NotifyActivity : WorkflowActivity - { - public override async Task RunAsync(WorkflowActivityContext context, Notification input) - { - await Task.Delay(TimeSpan.FromSeconds(15)); - return true; - } - } - """; - - await VerifyCodeFix.RunTest(code, expectedChangedCode, typeof(object).Assembly.Location, Utilities.GetReferences(), Utilities.GetAnalyzers()); - } -} diff --git a/test/Dapr.Workflow.Analyzers.Test/WorkflowRegistrationAnalyzerTests.cs b/test/Dapr.Workflow.Analyzers.Test/WorkflowRegistrationAnalyzerTests.cs deleted file mode 100644 index a3ecbc4c8..000000000 --- a/test/Dapr.Workflow.Analyzers.Test/WorkflowRegistrationAnalyzerTests.cs +++ /dev/null @@ -1,172 +0,0 @@ -using Dapr.Analyzers.Common; - -namespace Dapr.Workflow.Analyzers.Test; - -public sealed class WorkflowRegistrationAnalyzerTests -{ - [Fact] - public async Task VerifyWorkflowNotRegistered() - { - const string testCode = """ - using Dapr.Workflow; - using System.Threading.Tasks; - - class OrderProcessingWorkflow : Workflow - { - public override async Task RunAsync(WorkflowContext context, OrderPayload order) - { - return new OrderResult("Order processed"); - } - } - - class UseWorkflow() - { - public async Task RunWorkflow(DaprWorkflowClient client, OrderPayload order) - { - await client.ScheduleNewWorkflowAsync(nameof(OrderProcessingWorkflow), null, order); - } - } - - record OrderPayload { } - record OrderResult(string message) { } - """; - - var expected = VerifyAnalyzer.Diagnostic( WorkflowRegistrationAnalyzer.WorkflowDiagnosticDescriptor) - .WithSpan(16, 63, 16, 94).WithMessage("The workflow type 'OrderProcessingWorkflow' is not registered with the dependency injection provider"); - - var analyzer = new VerifyAnalyzer(Utilities.GetReferences()); - await analyzer.VerifyAnalyzerAsync(testCode, expected); - } - - [Fact] - public async Task VerifyWorkflowNotRegisteredButVersioningPresent() - { - const string testCode = """ - using Dapr.Workflow; - using System.Threading.Tasks; - - class OrderProcessingWorkflow : Workflow - { - public override async Task RunAsync(WorkflowContext context, OrderPayload order) - { - return new OrderResult("Order processed"); - } - } - - class UseWorkflow() - { - public async Task RunWorkflow(DaprWorkflowClient client, OrderPayload order) - { - await client.ScheduleNewWorkflowAsync(nameof(OrderProcessingWorkflow), null, order); - } - } - - record OrderPayload { } - record OrderResult(string message) { } - """; - - const string startupCode = """ - using Dapr.Workflow.Versioning; - using Microsoft.Extensions.DependencyInjection; - - internal static class Extensions - { - public static void AddApplicationServices(this IServiceCollection services) - { - services.AddDaprWorkflowVersioning(); - } - } - """; - - var analyzer = new VerifyAnalyzer(Utilities.GetReferences()); - await analyzer.VerifyAnalyzerAsync(testCode, startupCode); - } - - [Fact] - public async Task VerifyWorkflowRegisteredWithVersioningPresent() - { - const string testCode = """ - using Dapr.Workflow; - using System.Threading.Tasks; - - class OrderProcessingWorkflow : Workflow - { - public override async Task RunAsync(WorkflowContext context, OrderPayload order) - { - return new OrderResult("Order processed"); - } - } - - class UseWorkflow() - { - public async Task RunWorkflow(DaprWorkflowClient client, OrderPayload order) - { - await client.ScheduleNewWorkflowAsync(nameof(OrderProcessingWorkflow), null, order); - } - } - - record OrderPayload { } - record OrderResult(string message) { } - """; - - const string startupCode = """ - using Dapr.Workflow; - using Dapr.Workflow.Versioning; - using Microsoft.Extensions.DependencyInjection; - - internal static class Extensions - { - public static void AddApplicationServices(this IServiceCollection services) - { - services.AddDaprWorkflowVersioning(); - services.AddDaprWorkflow(options => - { - options.RegisterWorkflow(); - }); - } - } - """; - - var analyzer = new VerifyAnalyzer(Utilities.GetReferences()); - await analyzer.VerifyAnalyzerAsync(testCode, startupCode); - } - - [Fact] - public async Task VerifyWorkflowRegistered() - { - const string testCode = """ - using Dapr.Workflow; - using System.Threading.Tasks; - - class OrderProcessingWorkflow : Workflow - { - public override async Task RunAsync(WorkflowContext context, OrderPayload order) - { - return new OrderResult("Order processed"); - } - } - - record OrderPayload { } - record OrderResult(string message) { } - """; - - const string startupCode = """ - using Dapr.Workflow; - using Microsoft.Extensions.DependencyInjection; - - internal static class Extensions - { - public static void AddApplicationServices(this IServiceCollection services) - { - services.AddDaprWorkflow(options => - { - options.RegisterWorkflow(); - }); - } - } - """; - - var analyzer = new VerifyAnalyzer(Utilities.GetReferences()); - await analyzer.VerifyAnalyzerAsync(testCode, startupCode); - } -} diff --git a/test/Dapr.Workflow.Analyzers.Test/WorkflowRegistrationCodeFixProviderTests.cs b/test/Dapr.Workflow.Analyzers.Test/WorkflowRegistrationCodeFixProviderTests.cs deleted file mode 100644 index b2d9ce98c..000000000 --- a/test/Dapr.Workflow.Analyzers.Test/WorkflowRegistrationCodeFixProviderTests.cs +++ /dev/null @@ -1,439 +0,0 @@ -using Dapr.Analyzers.Common; - -namespace Dapr.Workflow.Analyzers.Test; - -public sealed class WorkflowRegistrationCodeFixProviderTests -{ - [Fact] - public async Task RegisterWorkflow() - { - const string code = """ - using Dapr.Workflow; - using Microsoft.Extensions.DependencyInjection; - using System.Threading.Tasks; - - public static class Program - { - public static void Main() - { - var services = new ServiceCollection(); - - services.AddDaprWorkflow(options => - { - }); - } - - private static async Task ScheduleWorkflow(DaprWorkflowClient client) - { - await client.ScheduleNewWorkflowAsync(nameof(OrderProcessingWorkflow), null, new OrderPayload()); - } - } - - class OrderProcessingWorkflow : Workflow - { - public override Task RunAsync(WorkflowContext context, OrderPayload order) - { - return Task.FromResult(new OrderResult("Order processed")); - } - } - - record OrderPayload { } - record OrderResult(string message) { } - - """; - - const string expectedChangedCode = """ - - using Dapr.Workflow; - using Microsoft.Extensions.DependencyInjection; - using System.Threading.Tasks; - - public static class Program - { - public static void Main() - { - var services = new ServiceCollection(); - - services.AddDaprWorkflow(options => - { - options.RegisterWorkflow(); - }); - } - - private static async Task ScheduleWorkflow(DaprWorkflowClient client) - { - await client.ScheduleNewWorkflowAsync(nameof(OrderProcessingWorkflow), null, new OrderPayload()); - } - } - - class OrderProcessingWorkflow : Workflow - { - public override Task RunAsync(WorkflowContext context, OrderPayload order) - { - return Task.FromResult(new OrderResult("Order processed")); - } - } - - record OrderPayload { } - record OrderResult(string message) { } - - """; - - await VerifyCodeFix.RunTest(code, expectedChangedCode, typeof(object).Assembly.Location, Utilities.GetReferences(), Utilities.GetAnalyzers()); - } - - [Fact] - public async Task RegisterWorkflow_WhenAddDaprWorkflowIsNotFound() - { - const string code = """ - using Dapr.Workflow; - using Microsoft.Extensions.Hosting; - using System.Threading.Tasks; - - public static class Program - { - public static void Main() - { - var builder = Host.CreateApplicationBuilder(); - - var app = builder.Build(); - } - - private static async Task ScheduleWorkflow(DaprWorkflowClient client) - { - await client.ScheduleNewWorkflowAsync(nameof(OrderProcessingWorkflow), null, new OrderPayload()); - } - } - - class OrderProcessingWorkflow : Workflow - { - public override Task RunAsync(WorkflowContext context, OrderPayload order) - { - return Task.FromResult(new OrderResult("Order processed")); - } - } - - record OrderPayload { } - record OrderResult(string message) { } - """; - - const string expectedChangedCode = """ - using Dapr.Workflow; - using Microsoft.Extensions.Hosting; - using System.Threading.Tasks; - - public static class Program - { - public static void Main() - { - var builder = Host.CreateApplicationBuilder(); - - builder.Services.AddDaprWorkflow(options => - { - options.RegisterWorkflow(); - }); - - var app = builder.Build(); - } - - private static async Task ScheduleWorkflow(DaprWorkflowClient client) - { - await client.ScheduleNewWorkflowAsync(nameof(OrderProcessingWorkflow), null, new OrderPayload()); - } - } - - class OrderProcessingWorkflow : Workflow - { - public override Task RunAsync(WorkflowContext context, OrderPayload order) - { - return Task.FromResult(new OrderResult("Order processed")); - } - } - - record OrderPayload { } - record OrderResult(string message) { } - """; - - await VerifyCodeFix.RunTest(code, expectedChangedCode, typeof(object).Assembly.Location, Utilities.GetReferences(), Utilities.GetAnalyzers()); - } - - [Fact] - public async Task RegisterWorkflow_WhenAddDaprWorkflowIsNotFound_TopLevelStatements() - { - const string code = """ - using Dapr.Workflow; - using Microsoft.Extensions.DependencyInjection; - using Microsoft.Extensions.Hosting; - using System.Threading.Tasks; - - var builder = Host.CreateApplicationBuilder(); - - var app = builder.Build(); - - var workflowClient = app.Services.GetRequiredService(); - await workflowClient.ScheduleNewWorkflowAsync(nameof(TestNamespace.OrderProcessingWorkflow), null, new OrderPayload()); - - namespace TestNamespace - { - class OrderProcessingWorkflow : Workflow - { - public override Task RunAsync(WorkflowContext context, OrderPayload order) - { - return Task.FromResult(new OrderResult("Order processed")); - } - } - } - - record OrderPayload { } - record OrderResult(string message) { } - """; - - const string expectedChangedCode = """ - using Dapr.Workflow; - using Microsoft.Extensions.DependencyInjection; - using Microsoft.Extensions.Hosting; - using System.Threading.Tasks; - - var builder = Host.CreateApplicationBuilder(); - - builder.Services.AddDaprWorkflow(options => - { - options.RegisterWorkflow(); - }); - - var app = builder.Build(); - - var workflowClient = app.Services.GetRequiredService(); - await workflowClient.ScheduleNewWorkflowAsync(nameof(TestNamespace.OrderProcessingWorkflow), null, new OrderPayload()); - - namespace TestNamespace - { - class OrderProcessingWorkflow : Workflow - { - public override Task RunAsync(WorkflowContext context, OrderPayload order) - { - return Task.FromResult(new OrderResult("Order processed")); - } - } - } - - record OrderPayload { } - record OrderResult(string message) { } - """; - - await VerifyCodeFix.RunTest(code, expectedChangedCode, typeof(object).Assembly.Location, Utilities.GetReferences(), Utilities.GetAnalyzers()); - } - - [Fact] - public async Task RegisterWorkflow_WhenAddDaprWorkflowIsNotFound_ConfigureServices_BlockBody() - { - const string code = """ - using Dapr.Workflow; - using Microsoft.Extensions.Hosting; - using System.Threading.Tasks; - - public static class Program - { - public static void Main(string[] args) - { - Host.CreateDefaultBuilder(args) - .ConfigureServices((context, services) => - { - }); - } - - private static async Task ScheduleWorkflow(DaprWorkflowClient client) - { - await client.ScheduleNewWorkflowAsync(nameof(OrderProcessingWorkflow), null, new OrderPayload()); - } - } - - class OrderProcessingWorkflow : Workflow - { - public override Task RunAsync(WorkflowContext context, OrderPayload order) - { - return Task.FromResult(new OrderResult("Order processed")); - } - } - - record OrderPayload { } - record OrderResult(string message) { } - """; - - const string expectedChangedCode = """ - using Dapr.Workflow; - using Microsoft.Extensions.Hosting; - using System.Threading.Tasks; - - public static class Program - { - public static void Main(string[] args) - { - Host.CreateDefaultBuilder(args) - .ConfigureServices((context, services) => - { - services.AddDaprWorkflow(options => - { - options.RegisterWorkflow(); - }); - }); - } - - private static async Task ScheduleWorkflow(DaprWorkflowClient client) - { - await client.ScheduleNewWorkflowAsync(nameof(OrderProcessingWorkflow), null, new OrderPayload()); - } - } - - class OrderProcessingWorkflow : Workflow - { - public override Task RunAsync(WorkflowContext context, OrderPayload order) - { - return Task.FromResult(new OrderResult("Order processed")); - } - } - - record OrderPayload { } - record OrderResult(string message) { } - """; - - await VerifyCodeFix.RunTest( - code, - expectedChangedCode, - typeof(object).Assembly.Location, - Utilities.GetReferences(), - Utilities.GetAnalyzers()); - } - - [Fact] - public async Task RegisterWorkflow_WhenAddDaprWorkflowIsNotFound_ConfigureServices_ExpressionBody() - { - const string code = """ - using Dapr.Workflow; - using Microsoft.Extensions.DependencyInjection; - using Microsoft.Extensions.Hosting; - using System.Threading.Tasks; - - public static class Program - { - public static void Main(string[] args) - { - var _ = new ServiceCollection(); - - Host.CreateDefaultBuilder(args) - .ConfigureServices((context, services) => services.Count.ToString()); - } - - private static async Task ScheduleWorkflow(DaprWorkflowClient client) - { - await client.ScheduleNewWorkflowAsync(nameof(OrderProcessingWorkflow), null, new OrderPayload()); - } - } - - class OrderProcessingWorkflow : Workflow - { - public override Task RunAsync(WorkflowContext context, OrderPayload order) - { - return Task.FromResult(new OrderResult("Order processed")); - } - } - - record OrderPayload { } - record OrderResult(string message) { } - """; - - const string expectedChangedCode = """ - using Dapr.Workflow; - using Microsoft.Extensions.DependencyInjection; - using Microsoft.Extensions.Hosting; - using System.Threading.Tasks; - - public static class Program - { - public static void Main(string[] args) - { - var _ = new ServiceCollection(); - - Host.CreateDefaultBuilder(args) - .ConfigureServices((context, services) => - { - services.AddDaprWorkflow(options => - { - options.RegisterWorkflow(); - }); - services.Count.ToString(); - }); - } - - private static async Task ScheduleWorkflow(DaprWorkflowClient client) - { - await client.ScheduleNewWorkflowAsync(nameof(OrderProcessingWorkflow), null, new OrderPayload()); - } - } - - class OrderProcessingWorkflow : Workflow - { - public override Task RunAsync(WorkflowContext context, OrderPayload order) - { - return Task.FromResult(new OrderResult("Order processed")); - } - } - - record OrderPayload { } - record OrderResult(string message) { } - """; - - await VerifyCodeFix.RunTest( - code, - expectedChangedCode, - typeof(object).Assembly.Location, - Utilities.GetReferences(), - Utilities.GetAnalyzers()); - } - - [Fact] - public async Task RegisterWorkflow_NoChange_WhenAddDaprWorkflowOptionsLambdaIsParenthesized() - { - const string code = """ - using Dapr.Workflow; - using Microsoft.Extensions.DependencyInjection; - using System.Threading.Tasks; - - public static class Program - { - public static void Main() - { - var services = new ServiceCollection(); - - services.AddDaprWorkflow((options) => - { - }); - } - - private static async Task ScheduleWorkflow(DaprWorkflowClient client) - { - await client.ScheduleNewWorkflowAsync(nameof(OrderProcessingWorkflow), null, new OrderPayload()); - } - } - - class OrderProcessingWorkflow : Workflow - { - public override Task RunAsync(WorkflowContext context, OrderPayload order) - { - return Task.FromResult(new OrderResult("Order processed")); - } - } - - record OrderPayload { } - record OrderResult(string message) { } - """; - - // The code fix currently only supports SimpleLambdaExpressionSyntax for AddDaprWorkflow options. - await VerifyCodeFix.RunTest( - code, - code, - typeof(object).Assembly.Location, - Utilities.GetReferences(), - Utilities.GetAnalyzers()); - } -}