From 6897a2921cbfad6458fa209c9b2e176e58f23bd2 Mon Sep 17 00:00:00 2001 From: Jay Malhotra <5047192+SapiensAnatis@users.noreply.github.com> Date: Sun, 1 Dec 2024 02:28:46 +0000 Subject: [PATCH] Handle switching from params to non-params overload in xUnit1051 (#192) --- .../X1000/UseCancellationTokenFixer.cs | 48 +++++++ .../X1000/UseCancellationTokenFixerTests.cs | 123 ++++++++++++++++++ 2 files changed, 171 insertions(+) diff --git a/src/xunit.analyzers.fixes/X1000/UseCancellationTokenFixer.cs b/src/xunit.analyzers.fixes/X1000/UseCancellationTokenFixer.cs index c0561889..5a0aeee0 100644 --- a/src/xunit.analyzers.fixes/X1000/UseCancellationTokenFixer.cs +++ b/src/xunit.analyzers.fixes/X1000/UseCancellationTokenFixer.cs @@ -7,6 +7,7 @@ using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Operations; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace Xunit.Analyzers.Fixes; @@ -51,6 +52,9 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) if (root.FindNode(diagnostic.Location.SourceSpan) is not InvocationExpressionSyntax invocation) return; + if (semanticModel.GetOperation(invocation, context.CancellationToken) is not IInvocationOperation invocationOperation) + return; + var arguments = invocation.ArgumentList.Arguments; for (var argumentIndex = 0; argumentIndex < arguments.Count; argumentIndex++) @@ -78,6 +82,16 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) var args = new List(arguments); + if ( + invocationOperation.Arguments.FirstOrDefault(arg => + arg.ArgumentKind == ArgumentKind.ParamArray + ) is + { } paramsArgument + ) + { + TransformParamsArgument(args, paramsArgument, editor.Generator); + } + if (parameterIndex < args.Count) { args[parameterIndex] = args[parameterIndex].WithExpression(testContextCancellationTokenExpression); @@ -106,4 +120,38 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) context.Diagnostics ); } + + private static void TransformParamsArgument( + List arguments, + IArgumentOperation paramsOperation, + SyntaxGenerator generator + ) + { + + if (paramsOperation is not + { + Value: IArrayCreationOperation + { + Type: IArrayTypeSymbol arrayTypeSymbol, + Initializer: { } initializer + } + }) + { + return; + } + + // We know that the params arguments occupy the end of the list because the language + // does not allow regular arguments after params. + int paramsCount = initializer.ElementValues.Length; + int paramsStart = arguments.Count - paramsCount; + + arguments.RemoveRange(paramsStart, paramsCount); + + ExpressionSyntax arrayCreation = (ExpressionSyntax)generator.ArrayCreationExpression( + generator.TypeExpression(arrayTypeSymbol.ElementType), + initializer.ElementValues.Select(x => x.Syntax) + ); + + arguments.Add(Argument(arrayCreation)); + } } diff --git a/src/xunit.analyzers.tests/Fixes/X1000/UseCancellationTokenFixerTests.cs b/src/xunit.analyzers.tests/Fixes/X1000/UseCancellationTokenFixerTests.cs index 578690a7..c31a2913 100644 --- a/src/xunit.analyzers.tests/Fixes/X1000/UseCancellationTokenFixerTests.cs +++ b/src/xunit.analyzers.tests/Fixes/X1000/UseCancellationTokenFixerTests.cs @@ -96,4 +96,127 @@ public void TestMethod() await Verify.VerifyCodeFixV3(before, after, UseCancellationTokenFixer.Key_UseCancellationTokenArgument); } + + [Fact] + public async Task UseCancellationTokenArgument_ParamsArgument() + { + var before = /* lang=c#-test */ """ + using System.Threading; + using System.Threading.Tasks; + using MyContext = Xunit.TestContext; + + public class TestClass { + [Xunit.Fact] + public void TestMethod() + { + [|Function(1, 2, 3)|]; + } + + void Function(params int[] integers) { } + + void Function(int[] integers, CancellationToken token = default(CancellationToken)) { } + } + """; + var after = /* lang=c#-test */ """ + using System.Threading; + using System.Threading.Tasks; + using MyContext = Xunit.TestContext; + + public class TestClass { + [Xunit.Fact] + public void TestMethod() + { + Function(new int[] { 1, 2, 3 }, MyContext.Current.CancellationToken); + } + + void Function(params int[] integers) { } + + void Function(int[] integers, CancellationToken token = default(CancellationToken)) { } + } + """; + + await Verify.VerifyCodeFixV3(before, after, UseCancellationTokenFixer.Key_UseCancellationTokenArgument); + } + + [Fact] + public async Task UseCancellationTokenArgument_ParamsArgumentAfterRegularArguments() + { + var before = /* lang=c#-test */ """ + using System.Threading; + using System.Threading.Tasks; + using MyContext = Xunit.TestContext; + + public class TestClass { + [Xunit.Fact] + public void TestMethod() + { + [|Function("hello", System.Guid.NewGuid(), System.Guid.NewGuid())|]; + } + + void Function(string str, params System.Guid[] guids) { } + + void Function(string str, System.Guid[] guids, CancellationToken token = default(CancellationToken)) { } + } + """; + var after = /* lang=c#-test */ """ + using System.Threading; + using System.Threading.Tasks; + using MyContext = Xunit.TestContext; + + public class TestClass { + [Xunit.Fact] + public void TestMethod() + { + Function("hello", new System.Guid[] { System.Guid.NewGuid(), System.Guid.NewGuid() }, MyContext.Current.CancellationToken); + } + + void Function(string str, params System.Guid[] guids) { } + + void Function(string str, System.Guid[] guids, CancellationToken token = default(CancellationToken)) { } + } + """; + + await Verify.VerifyCodeFixV3(before, after, UseCancellationTokenFixer.Key_UseCancellationTokenArgument); + } + + [Fact] + public async Task UseCancellationTokenArgument_ParamsArgumentWithNoValues() + { + var before = /* lang=c#-test */ """ + using System.Threading; + using System.Threading.Tasks; + using MyContext = Xunit.TestContext; + + public class TestClass { + [Xunit.Fact] + public void TestMethod() + { + [|Function()|]; + } + + void Function(params int[] integers) { } + + void Function(int[] integers, CancellationToken token = default(CancellationToken)) { } + } + """; + var after = /* lang=c#-test */ """ + using System.Threading; + using System.Threading.Tasks; + using MyContext = Xunit.TestContext; + + public class TestClass { + [Xunit.Fact] + public void TestMethod() + { + Function(new int[] { }, MyContext.Current.CancellationToken); + } + + void Function(params int[] integers) { } + + void Function(int[] integers, CancellationToken token = default(CancellationToken)) { } + } + """; + + await Verify.VerifyCodeFixV3(before, after, UseCancellationTokenFixer.Key_UseCancellationTokenArgument); + } }