-
-
Notifications
You must be signed in to change notification settings - Fork 108
feat: comprehensive migration code fixer improvements #4202
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| using Microsoft.CodeAnalysis; | ||
| using Microsoft.CodeAnalysis.CSharp; | ||
| using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
|
|
||
| namespace TUnit.Analyzers.CodeFixers.Base; | ||
|
|
||
| /// <summary> | ||
| /// Transforms method signatures that contain await expressions but are not marked as async. | ||
| /// Converts void methods to async Task and T-returning methods to async Task<T>. | ||
| /// </summary> | ||
| public class AsyncMethodSignatureRewriter : CSharpSyntaxRewriter | ||
| { | ||
| public override SyntaxNode? VisitMethodDeclaration(MethodDeclarationSyntax node) | ||
| { | ||
| // First, visit children to ensure nested content is processed | ||
| node = (MethodDeclarationSyntax)base.VisitMethodDeclaration(node)!; | ||
|
|
||
| // Skip if already async or abstract | ||
| if (node.Modifiers.Any(SyntaxKind.AsyncKeyword) || | ||
| node.Modifiers.Any(SyntaxKind.AbstractKeyword)) | ||
| { | ||
| return node; | ||
| } | ||
|
|
||
| // Check if method contains await expressions | ||
| bool hasAwait = node.DescendantNodes().OfType<AwaitExpressionSyntax>().Any(); | ||
| if (!hasAwait) | ||
| { | ||
| return node; | ||
| } | ||
|
|
||
| // Convert the return type | ||
| var newReturnType = ConvertReturnType(node.ReturnType); | ||
|
|
||
| // Add async modifier after access modifiers but before other modifiers (like static) | ||
| var newModifiers = InsertAsyncModifier(node.Modifiers); | ||
|
|
||
| return node | ||
| .WithReturnType(newReturnType) | ||
| .WithModifiers(newModifiers); | ||
| } | ||
|
|
||
| private static TypeSyntax ConvertReturnType(TypeSyntax returnType) | ||
| { | ||
| // void -> Task | ||
| if (returnType is PredefinedTypeSyntax predefined && predefined.Keyword.IsKind(SyntaxKind.VoidKeyword)) | ||
| { | ||
| return SyntaxFactory.ParseTypeName("Task") | ||
| .WithLeadingTrivia(returnType.GetLeadingTrivia()) | ||
| .WithTrailingTrivia(returnType.GetTrailingTrivia()); | ||
| } | ||
|
|
||
| // T -> Task<T> | ||
| var innerType = returnType.WithoutTrivia(); | ||
| return SyntaxFactory.GenericName("Task") | ||
| .WithTypeArgumentList( | ||
| SyntaxFactory.TypeArgumentList( | ||
| SyntaxFactory.SingletonSeparatedList(innerType))) | ||
| .WithLeadingTrivia(returnType.GetLeadingTrivia()) | ||
| .WithTrailingTrivia(returnType.GetTrailingTrivia()); | ||
|
Comment on lines
+53
to
+60
|
||
| } | ||
|
|
||
| private static SyntaxTokenList InsertAsyncModifier(SyntaxTokenList modifiers) | ||
| { | ||
| // Find the right position for async (after public/private/etc, before static/virtual/etc) | ||
| int insertIndex = 0; | ||
|
|
||
| for (int i = 0; i < modifiers.Count; i++) | ||
| { | ||
| var modifier = modifiers[i]; | ||
| if (modifier.IsKind(SyntaxKind.PublicKeyword) || | ||
| modifier.IsKind(SyntaxKind.PrivateKeyword) || | ||
| modifier.IsKind(SyntaxKind.ProtectedKeyword) || | ||
| modifier.IsKind(SyntaxKind.InternalKeyword)) | ||
| { | ||
| insertIndex = i + 1; | ||
| } | ||
| } | ||
|
|
||
| var asyncModifier = SyntaxFactory.Token(SyntaxKind.AsyncKeyword) | ||
| .WithTrailingTrivia(SyntaxFactory.Space); | ||
|
|
||
| return modifiers.Insert(insertIndex, asyncModifier); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ExtractMessageWithFormatArgs method doesn't validate that format arguments are actually format placeholders before attempting to wrap them in string.Format. If a testing framework allows passing extra parameters that aren't format args (like tolerance values or comparers), this could incorrectly wrap them in string.Format. Consider checking if the message expression is actually a string literal with format placeholders before treating additional arguments as format args.