From af8f83fd109d7725bddd85940b6d897fc95454c1 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Tue, 27 May 2025 19:04:27 +0100 Subject: [PATCH 1/2] Theory Data test --- .../XUnitMigrationAnalyzerTests.cs | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/TUnit.Analyzers.Tests/XUnitMigrationAnalyzerTests.cs b/TUnit.Analyzers.Tests/XUnitMigrationAnalyzerTests.cs index 15154ba439..6a8a75dd92 100644 --- a/TUnit.Analyzers.Tests/XUnitMigrationAnalyzerTests.cs +++ b/TUnit.Analyzers.Tests/XUnitMigrationAnalyzerTests.cs @@ -501,4 +501,40 @@ public ValueTask DisposeAsync() """ ); } + + [Test] + public async Task TheoryData_Can_Be_Converted() + { + await CodeFixer + .VerifyCodeFixAsync( + """ + {|#0:using TUnit.Core; + using Xunit; + + public class MyClass + { + public static readonly TheoryData Times = new() + { + TimeSpan.FromSeconds(1), + TimeSpan.FromHours(1), + TimeSpan.FromMilliseconds(10) + }; + }|} + """, + Verifier.Diagnostic(Rules.XunitMigration).WithLocation(0), + """ + using TUnit.Core; + + public class MyClass + { + public static readonly IEnumerable Times = + [ + TimeSpan.FromSeconds(1), + TimeSpan.FromHours(1), + TimeSpan.FromMilliseconds(10) + ]; + } + """ + ); + } } \ No newline at end of file From 0834870f352ff6b6ded83c516b5bfce43417bc3c Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Tue, 27 May 2025 20:33:46 +0100 Subject: [PATCH 2/2] xUnit TheoryData Migration Code Fix --- .../XUnitMigrationCodeFixProvider.cs | 56 +++++++++++++++++++ .../XUnitMigrationAnalyzerTests.cs | 29 +++++++++- 2 files changed, 82 insertions(+), 3 deletions(-) diff --git a/TUnit.Analyzers.CodeFixers/XUnitMigrationCodeFixProvider.cs b/TUnit.Analyzers.CodeFixers/XUnitMigrationCodeFixProvider.cs index ff1940510c..30fa9f1c03 100644 --- a/TUnit.Analyzers.CodeFixers/XUnitMigrationCodeFixProvider.cs +++ b/TUnit.Analyzers.CodeFixers/XUnitMigrationCodeFixProvider.cs @@ -70,10 +70,66 @@ private static async Task ConvertCodeAsync(Document document, Cancella updatedRoot = RemoveUsingDirectives(updatedRoot); UpdateSyntaxTrees(ref compilation, ref syntaxTree, updatedRoot); + updatedRoot = ConvertTheoryData(compilation, updatedRoot); + UpdateSyntaxTrees(ref compilation, ref syntaxTree, updatedRoot); + // Apply all changes in one step return document.WithSyntaxRoot(updatedRoot); } + private static SyntaxNode ConvertTheoryData(Compilation compilation, SyntaxNode root) + { + var currentRoot = root; + foreach (var objectCreationExpressionSyntax in currentRoot.DescendantNodes().OfType()) + { + var type = objectCreationExpressionSyntax switch + { + ObjectCreationExpressionSyntax explicitObjectCreationExpressionSyntax => explicitObjectCreationExpressionSyntax.Type, + ImplicitObjectCreationExpressionSyntax implicitObjectCreationExpressionSyntax => SyntaxFactory.ParseTypeName(compilation.GetSemanticModel(implicitObjectCreationExpressionSyntax.SyntaxTree).GetTypeInfo(implicitObjectCreationExpressionSyntax).Type!.ToDisplayString()), + _ => null + }; + + if (type is not GenericNameSyntax genericNameSyntax || + genericNameSyntax.Identifier.Text != "TheoryData") + { + continue; + } + + var collectionItems = objectCreationExpressionSyntax.Initializer! + .ChildNodes() + .Select(x => x.DescendantNodesAndSelf().OfType().First()); + + var arrayCreationExpressionSyntax = SyntaxFactory.ArrayCreationExpression( + SyntaxFactory.ArrayType(genericNameSyntax.TypeArgumentList.Arguments[0], + SyntaxFactory.SingletonList( + SyntaxFactory.ArrayRankSpecifier( + SyntaxFactory.SingletonSeparatedList( + SyntaxFactory.OmittedArraySizeExpression() + ) + ) + ) + ), + SyntaxFactory.InitializerExpression( + SyntaxKind.ArrayInitializerExpression, + SyntaxFactory.SeparatedList(collectionItems) + ) + ).NormalizeWhitespace(); + + currentRoot = currentRoot.ReplaceNode(objectCreationExpressionSyntax, arrayCreationExpressionSyntax); + } + + foreach (var genericTheoryDataTypeSyntax in currentRoot.DescendantNodes().OfType().Where(x => x.Identifier.Text == "TheoryData")) + { + var enumerableTypeSyntax = SyntaxFactory.GenericName( + SyntaxFactory.Identifier("IEnumerable"), + SyntaxFactory.TypeArgumentList(SyntaxFactory.SeparatedList(genericTheoryDataTypeSyntax.TypeArgumentList.Arguments))); + + currentRoot = currentRoot.ReplaceNode(genericTheoryDataTypeSyntax, enumerableTypeSyntax); + } + + return currentRoot.NormalizeWhitespace(); + } + private static SyntaxNode UpdateInitializeDispose(Compilation compilation, SyntaxNode root) { // Always operate on the latest root diff --git a/TUnit.Analyzers.Tests/XUnitMigrationAnalyzerTests.cs b/TUnit.Analyzers.Tests/XUnitMigrationAnalyzerTests.cs index 6a8a75dd92..455a074006 100644 --- a/TUnit.Analyzers.Tests/XUnitMigrationAnalyzerTests.cs +++ b/TUnit.Analyzers.Tests/XUnitMigrationAnalyzerTests.cs @@ -502,6 +502,29 @@ public ValueTask DisposeAsync() ); } + [Test] + public async Task TheoryData_Is_Flagged() + { + await CodeFixer + .VerifyAnalyzerAsync( + """ + {|#0:using System; + using Xunit; + + public class MyClass + { + public static readonly TheoryData Times = new() + { + TimeSpan.FromSeconds(1), + TimeSpan.FromHours(1), + TimeSpan.FromMilliseconds(10) + }; + }|} + """, + Verifier.Diagnostic(Rules.XunitMigration).WithLocation(0) + ); + } + [Test] public async Task TheoryData_Can_Be_Converted() { @@ -527,12 +550,12 @@ public class MyClass public class MyClass { - public static readonly IEnumerable Times = - [ + public static readonly IEnumerable Times = new TimeSpan[] + { TimeSpan.FromSeconds(1), TimeSpan.FromHours(1), TimeSpan.FromMilliseconds(10) - ]; + }; } """ );