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 15154ba439..455a074006 100644 --- a/TUnit.Analyzers.Tests/XUnitMigrationAnalyzerTests.cs +++ b/TUnit.Analyzers.Tests/XUnitMigrationAnalyzerTests.cs @@ -501,4 +501,63 @@ 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() + { + 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 = new TimeSpan[] + { + TimeSpan.FromSeconds(1), + TimeSpan.FromHours(1), + TimeSpan.FromMilliseconds(10) + }; + } + """ + ); + } } \ No newline at end of file