Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 36 additions & 5 deletions TUnit.Analyzers.CodeFixers/NUnitExpectedResultRewriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -441,19 +441,40 @@ private AttributeSyntax TransformTestCaseAttribute(AttributeSyntax attribute)

var newArgs = new List<AttributeArgumentSyntax>();
ExpressionSyntax? expectedValue = null;
var unsupportedProperties = new List<string>();

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
Expand All @@ -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
Expand Down
42 changes: 41 additions & 1 deletion TUnit.Analyzers.CodeFixers/NUnitMigrationCodeFixProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<AttributeArgumentSyntax>();

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)
{
Expand Down
9 changes: 9 additions & 0 deletions TUnit.Analyzers.Tests/MSTestMigrationAnalyzerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
19 changes: 19 additions & 0 deletions TUnit.Analyzers.Tests/NUnitMigrationAnalyzerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
12 changes: 12 additions & 0 deletions TUnit.Analyzers/Migrators/Base/MigrationHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The check for existing System.Threading.Tasks using should use string comparison with qualified name. The current comparison u.Name?.ToString() == "System.Threading.Tasks" might not work correctly if the using directive uses an alias or has different formatting. Consider checking against the full qualified name more robustly or ensuring the name is normalized before comparison.

Copilot uses AI. Check for mistakes.
{
existingUsings.Add(tasksUsing);
}

if (!existingUsings.Any(u => u.Name?.ToString() == "TUnit.Core"))
{
existingUsings.Add(tunitUsing);
Expand Down
Loading