-
Notifications
You must be signed in to change notification settings - Fork 345
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add analyzers and code fixes for Dapr workflows
Introduce `WorkflowRegistrationAnalyzer` and `WorkflowActivityAnalyzer` to validate workflow and activity registrations in the DI container. Implement corresponding code fix providers to suggest automatic fixes. Add tests to ensure functionality and robustness of the Dapr workflow framework. Signed-off-by: Nils Gruson <[email protected]>
- Loading branch information
Showing
12 changed files
with
868 additions
and
212 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
104 changes: 0 additions & 104 deletions
104
src/Dapr.Workflow.Analyzers/WorkflowActivityAnalyzer.cs
This file was deleted.
Oops, something went wrong.
124 changes: 124 additions & 0 deletions
124
src/Dapr.Workflow.Analyzers/WorkflowActivityRegistrationCodeFixProvider.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
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; | ||
|
||
/// <summary> | ||
/// Provides code fixes for DAPR1002 diagnostic. | ||
/// </summary> | ||
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(WorkflowActivityRegistrationCodeFixProvider))] | ||
[Shared] | ||
public class WorkflowActivityRegistrationCodeFixProvider : CodeFixProvider | ||
{ | ||
/// <summary> | ||
/// Gets the diagnostic IDs that this provider can fix. | ||
/// </summary> | ||
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create("DAPR1002"); | ||
|
||
/// <summary> | ||
/// Registers the code fix for the diagnostic. | ||
/// </summary> | ||
public override Task RegisterCodeFixesAsync(CodeFixContext context) | ||
{ | ||
var title = "Register workflow activity"; | ||
context.RegisterCodeFix( | ||
CodeAction.Create( | ||
title, | ||
createChangedDocument: c => RegisterWorkflowActivityAsync(context.Document, context.Diagnostics.First(), c), | ||
equivalenceKey: title), | ||
context.Diagnostics); | ||
return Task.CompletedTask; | ||
} | ||
|
||
private async Task<Document> 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<InvocationExpressionSyntax>().First(); | ||
|
||
if (oldInvocation is null) | ||
return document; | ||
|
||
if (root == null || 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<InvocationExpressionSyntax>() | ||
.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<SimpleLambdaExpressionSyntax>() | ||
.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 == null || optionsLambda.Body is not 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); | ||
} | ||
|
||
/// <summary> | ||
/// Gets the FixAllProvider for this code fix provider. | ||
/// </summary> | ||
/// <returns>The FixAllProvider instance.</returns> | ||
public override FixAllProvider? GetFixAllProvider() | ||
{ | ||
return WellKnownFixAllProviders.BatchFixer; | ||
} | ||
} |
Oops, something went wrong.