diff --git a/TUnit.Analyzers.CodeFixers/NUnitExpectedResultRewriter.cs b/TUnit.Analyzers.CodeFixers/NUnitExpectedResultRewriter.cs index aa0797a9e1..48057ad6cd 100644 --- a/TUnit.Analyzers.CodeFixers/NUnitExpectedResultRewriter.cs +++ b/TUnit.Analyzers.CodeFixers/NUnitExpectedResultRewriter.cs @@ -441,19 +441,40 @@ private AttributeSyntax TransformTestCaseAttribute(AttributeSyntax attribute) var newArgs = new List(); ExpressionSyntax? expectedValue = null; + var unsupportedProperties = new List(); foreach (var arg in attribute.ArgumentList.Arguments) { - if (arg.NameEquals?.Name.Identifier.Text == "ExpectedResult") + var namedProperty = arg.NameEquals?.Name.Identifier.Text; + + if (namedProperty == "ExpectedResult") { expectedValue = arg.Expression; } - else if (arg.NameColon == null && arg.NameEquals == null) + else if (namedProperty == null) { // Positional argument - keep it newArgs.Add(arg); } - // Skip other named arguments for now + else if (namedProperty == "Ignore" || namedProperty == "IgnoreReason") + { + // Map NUnit's Ignore/IgnoreReason to TUnit's Skip + var skipArg = SyntaxFactory.AttributeArgument( + SyntaxFactory.NameEquals(SyntaxFactory.IdentifierName("Skip")), + null, + arg.Expression); + newArgs.Add(skipArg); + } + else if (namedProperty is "TestName" or "Category" or "Description" or "Author" or "Explicit" or "ExplicitReason") + { + // These properties don't have direct TUnit equivalents + unsupportedProperties.Add($"{namedProperty} = {arg.Expression}"); + } + // Other named arguments are preserved as-is (they might be TUnit-compatible) + else + { + newArgs.Add(arg); + } } // Add expected value as last positional argument @@ -462,9 +483,19 @@ private AttributeSyntax TransformTestCaseAttribute(AttributeSyntax attribute) newArgs.Add(SyntaxFactory.AttributeArgument(expectedValue)); } - // The attribute will be renamed to "Arguments" by the existing attribute rewriter - return attribute.WithArgumentList( + var newAttribute = attribute.WithArgumentList( SyntaxFactory.AttributeArgumentList(SyntaxFactory.SeparatedList(newArgs))); + + // Add TODO comment for unsupported properties + if (unsupportedProperties.Count > 0) + { + var todoComment = SyntaxFactory.Comment($"/* TODO: TUnit migration - unsupported TestCase properties: {string.Join(", ", unsupportedProperties)} */"); + newAttribute = newAttribute.WithLeadingTrivia( + newAttribute.GetLeadingTrivia().Add(todoComment).Add(SyntaxFactory.Space)); + } + + // The attribute will be renamed to "Arguments" by the existing attribute rewriter + return newAttribute; } private class ReturnToAssignmentRewriter : CSharpSyntaxRewriter diff --git a/TUnit.Analyzers.CodeFixers/NUnitMigrationCodeFixProvider.cs b/TUnit.Analyzers.CodeFixers/NUnitMigrationCodeFixProvider.cs index f96f49210f..4fd2145f1f 100644 --- a/TUnit.Analyzers.CodeFixers/NUnitMigrationCodeFixProvider.cs +++ b/TUnit.Analyzers.CodeFixers/NUnitMigrationCodeFixProvider.cs @@ -69,12 +69,52 @@ protected override bool IsFrameworkAttribute(string attributeName) { return attributeName switch { - "TestCase" => argumentList, // Arguments attribute uses the same format + "TestCase" => ConvertTestCaseArguments(argumentList), "TestCaseSource" => ConvertTestCaseSourceArguments(argumentList), "Category" => ConvertCategoryArguments(argumentList), _ => argumentList }; } + + private AttributeArgumentListSyntax ConvertTestCaseArguments(AttributeArgumentListSyntax argumentList) + { + var newArgs = new List(); + + foreach (var arg in argumentList.Arguments) + { + var namedProperty = arg.NameEquals?.Name.Identifier.Text; + + if (namedProperty == null) + { + // Positional argument - keep it + newArgs.Add(arg); + } + else if (namedProperty == "Ignore" || namedProperty == "IgnoreReason") + { + // Map NUnit's Ignore/IgnoreReason to TUnit's Skip + var skipArg = SyntaxFactory.AttributeArgument( + SyntaxFactory.NameEquals(SyntaxFactory.IdentifierName("Skip")), + null, + arg.Expression); + newArgs.Add(skipArg); + } + else if (namedProperty is "TestName" or "Category" or "Description" or "Author" or "Explicit" or "ExplicitReason" or "ExpectedResult") + { + // These properties don't have direct TUnit equivalents - preserve as comment + // ExpectedResult is handled by NUnitExpectedResultRewriter, so if we get here it's a case without special handling + var commentArg = SyntaxFactory.AttributeArgument(arg.Expression) + .WithLeadingTrivia(SyntaxFactory.Comment($"/* TODO: {namedProperty} not supported */ ")); + newArgs.Add(commentArg); + } + else + { + // Other named arguments are preserved as-is + newArgs.Add(arg); + } + } + + return SyntaxFactory.AttributeArgumentList(SyntaxFactory.SeparatedList(newArgs)); + } private AttributeArgumentListSyntax ConvertTestCaseSourceArguments(AttributeArgumentListSyntax argumentList) { diff --git a/TUnit.Analyzers.Tests/MSTestMigrationAnalyzerTests.cs b/TUnit.Analyzers.Tests/MSTestMigrationAnalyzerTests.cs index acdae641ae..14593d1c10 100644 --- a/TUnit.Analyzers.Tests/MSTestMigrationAnalyzerTests.cs +++ b/TUnit.Analyzers.Tests/MSTestMigrationAnalyzerTests.cs @@ -114,6 +114,7 @@ public void MyMethod() """, Verifier.Diagnostic(Rules.MSTestMigration).WithLocation(0), """ + using System.Threading.Tasks; using TUnit.Core; using TUnit.Assertions; using static TUnit.Assertions.Assert; @@ -285,6 +286,7 @@ public void MyMethod() """, Verifier.Diagnostic(Rules.MSTestMigration).WithLocation(0), """ + using System.Threading.Tasks; using TUnit.Core; using TUnit.Assertions; using static TUnit.Assertions.Assert; @@ -326,6 +328,7 @@ public void StringTests() """, Verifier.Diagnostic(Rules.MSTestMigration).WithLocation(0), """ + using System.Threading.Tasks; using TUnit.Core; using TUnit.Assertions; using static TUnit.Assertions.Assert; @@ -373,6 +376,7 @@ public void OuterTest() """, Verifier.Diagnostic(Rules.MSTestMigration).WithLocation(0), """ + using System.Threading.Tasks; using TUnit.Core; using TUnit.Assertions; using static TUnit.Assertions.Assert; @@ -419,6 +423,7 @@ public void GenericTest() """, Verifier.Diagnostic(Rules.MSTestMigration).WithLocation(0), """ + using System.Threading.Tasks; using TUnit.Core; using TUnit.Assertions; using static TUnit.Assertions.Assert; @@ -508,6 +513,7 @@ public static void ClassTeardown() Verifier.Diagnostic(Rules.MSTestMigration).WithLocation(0), """ using System; + using System.Threading.Tasks; using TUnit.Core; using TUnit.Assertions; using static TUnit.Assertions.Assert; @@ -608,6 +614,7 @@ public void TestMultipleAssertionTypes() """, Verifier.Diagnostic(Rules.MSTestMigration).WithLocation(0), """ + using System.Threading.Tasks; using TUnit.Core; using TUnit.Assertions; using static TUnit.Assertions.Assert; @@ -664,6 +671,7 @@ public void TestReferences() """, Verifier.Diagnostic(Rules.MSTestMigration).WithLocation(0), """ + using System.Threading.Tasks; using TUnit.Core; using TUnit.Assertions; using static TUnit.Assertions.Assert; @@ -708,6 +716,7 @@ public void TestWithMessages() """, Verifier.Diagnostic(Rules.MSTestMigration).WithLocation(0), """ + using System.Threading.Tasks; using TUnit.Core; using TUnit.Assertions; using static TUnit.Assertions.Assert; diff --git a/TUnit.Analyzers.Tests/NUnitMigrationAnalyzerTests.cs b/TUnit.Analyzers.Tests/NUnitMigrationAnalyzerTests.cs index ce0a920c8c..552286949c 100644 --- a/TUnit.Analyzers.Tests/NUnitMigrationAnalyzerTests.cs +++ b/TUnit.Analyzers.Tests/NUnitMigrationAnalyzerTests.cs @@ -113,6 +113,7 @@ public void MyMethod() """, Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0), """ + using System.Threading.Tasks; using TUnit.Core; using TUnit.Assertions; using static TUnit.Assertions.Assert; @@ -290,6 +291,7 @@ public void OuterTest() """, Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0), """ + using System.Threading.Tasks; using TUnit.Core; using TUnit.Assertions; using static TUnit.Assertions.Assert; @@ -336,6 +338,7 @@ public void GenericTest() """, Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0), """ + using System.Threading.Tasks; using TUnit.Core; using TUnit.Assertions; using static TUnit.Assertions.Assert; @@ -378,6 +381,7 @@ public void ComplexConstraints() """, Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0), """ + using System.Threading.Tasks; using TUnit.Core; using TUnit.Assertions; using static TUnit.Assertions.Assert; @@ -468,6 +472,7 @@ public void ClassTeardown() Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0), """ using System; + using System.Threading.Tasks; using TUnit.Core; using TUnit.Assertions; using static TUnit.Assertions.Assert; @@ -558,6 +563,7 @@ public void TestMultipleAssertions() """, Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0), """ + using System.Threading.Tasks; using TUnit.Core; using TUnit.Assertions; using static TUnit.Assertions.Assert; @@ -596,6 +602,7 @@ public class MyClass """, Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0), """ + using System.Threading.Tasks; using TUnit.Core; using TUnit.Assertions; using static TUnit.Assertions.Assert; @@ -632,6 +639,7 @@ public class MyClass """, Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0), """ + using System.Threading.Tasks; using TUnit.Core; using TUnit.Assertions; using static TUnit.Assertions.Assert; @@ -672,6 +680,7 @@ public int Add(int a, int b) """, Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0), """ + using System.Threading.Tasks; using TUnit.Core; using TUnit.Assertions; using static TUnit.Assertions.Assert; @@ -708,6 +717,7 @@ public class MyClass """, Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0), """ + using System.Threading.Tasks; using TUnit.Core; using TUnit.Assertions; using static TUnit.Assertions.Assert; @@ -746,6 +756,7 @@ public void TestMethod() """, Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0), """ + using System.Threading.Tasks; using TUnit.Core; using TUnit.Assertions; using static TUnit.Assertions.Assert; @@ -782,6 +793,7 @@ public void TestMethod() """, Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0), """ + using System.Threading.Tasks; using TUnit.Core; using TUnit.Assertions; using static TUnit.Assertions.Assert; @@ -818,6 +830,7 @@ public void TestMethod() """, Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0), """ + using System.Threading.Tasks; using TUnit.Core; using TUnit.Assertions; using static TUnit.Assertions.Assert; @@ -854,6 +867,7 @@ public void TestMethod() """, Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0), """ + using System.Threading.Tasks; using TUnit.Core; using TUnit.Assertions; using static TUnit.Assertions.Assert; @@ -890,6 +904,7 @@ public void TestMethod() """, Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0), """ + using System.Threading.Tasks; using TUnit.Core; using TUnit.Assertions; using static TUnit.Assertions.Assert; @@ -926,6 +941,7 @@ public void TestMethod() """, Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0), """ + using System.Threading.Tasks; using TUnit.Core; using TUnit.Assertions; using static TUnit.Assertions.Assert; @@ -962,6 +978,7 @@ public void TestMethod() """, Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0), """ + using System.Threading.Tasks; using TUnit.Core; using TUnit.Assertions; using static TUnit.Assertions.Assert; @@ -999,6 +1016,7 @@ public void AdditionTest(int a, int b, int expected) """, Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0), """ + using System.Threading.Tasks; using TUnit.Core; using TUnit.Assertions; using static TUnit.Assertions.Assert; @@ -1038,6 +1056,7 @@ public void AdditionTest(int a, int b, int expected) """, Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0), """ + using System.Threading.Tasks; using TUnit.Core; using TUnit.Assertions; using static TUnit.Assertions.Assert; diff --git a/TUnit.Analyzers/Migrators/Base/MigrationHelpers.cs b/TUnit.Analyzers/Migrators/Base/MigrationHelpers.cs index d5950980cd..266f09d4fc 100644 --- a/TUnit.Analyzers/Migrators/Base/MigrationHelpers.cs +++ b/TUnit.Analyzers/Migrators/Base/MigrationHelpers.cs @@ -184,9 +184,21 @@ public static CompilationUnitSyntax AddTUnitUsings(CompilationUnitSyntax compila var assertionsStaticUsing = SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("TUnit.Assertions.Assert")) .WithStaticKeyword(SyntaxFactory.Token(SyntaxKind.StaticKeyword)); var extensionsUsing = SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("TUnit.Assertions.Extensions")); + // Add System.Threading.Tasks for async Task methods + var tasksUsing = SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System.Threading.Tasks")); var existingUsings = compilationUnit.Usings.ToList(); + // Add System.Threading.Tasks only if the code has async methods or await expressions + bool hasAsyncCode = compilationUnit.DescendantNodes() + .Any(n => n is AwaitExpressionSyntax || + (n is MethodDeclarationSyntax m && m.Modifiers.Any(mod => mod.IsKind(SyntaxKind.AsyncKeyword)))); + + if (hasAsyncCode && !existingUsings.Any(u => u.Name?.ToString() == "System.Threading.Tasks")) + { + existingUsings.Add(tasksUsing); + } + if (!existingUsings.Any(u => u.Name?.ToString() == "TUnit.Core")) { existingUsings.Add(tunitUsing);