diff --git a/TUnit.Analyzers.CodeFixers/Extensions/DocumentExtensions.cs b/TUnit.Analyzers.CodeFixers/Extensions/DocumentExtensions.cs index 52c9c768ab..bce49dbee5 100644 --- a/TUnit.Analyzers.CodeFixers/Extensions/DocumentExtensions.cs +++ b/TUnit.Analyzers.CodeFixers/Extensions/DocumentExtensions.cs @@ -16,7 +16,7 @@ public static async Task AddUsingDirectiveIfNotExistsAsyn } var usingDirective = SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(namespaceName).WithLeadingTrivia(SyntaxFactory.Space)) - .WithTrailingTrivia(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? SyntaxFactory.ElasticCarriageReturnLineFeed : SyntaxFactory.ElasticLineFeed); + .WithTrailingTrivia(SyntaxFactory.ElasticCarriageReturnLineFeed); return root.AddUsings(usingDirective); } diff --git a/TUnit.Analyzers.CodeFixers/XUnitAttributesCodeFixProvider.cs b/TUnit.Analyzers.CodeFixers/XUnitAttributesCodeFixProvider.cs deleted file mode 100644 index 6a09812dd1..0000000000 --- a/TUnit.Analyzers.CodeFixers/XUnitAttributesCodeFixProvider.cs +++ /dev/null @@ -1,282 +0,0 @@ -using System.Collections.Immutable; -using System.Composition; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Operations; - -namespace TUnit.Analyzers.CodeFixers; - -[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(XUnitAttributesCodeFixProvider)), Shared] -public class XUnitAttributesCodeFixProvider : CodeFixProvider -{ - public override sealed ImmutableArray FixableDiagnosticIds { get; } = - ImmutableArray.Create(Rules.XunitAttributes.Id); - - public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; - - public override sealed async Task RegisterCodeFixesAsync(CodeFixContext context) - { - foreach (var diagnostic in context.Diagnostics) - { - var diagnosticSpan = diagnostic.Location.SourceSpan; - - var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); - - context.RegisterCodeFix( - CodeAction.Create( - title: Rules.XunitAttributes.Title.ToString(), - createChangedDocument: c => ConvertAttributesAsync(context.Document, root?.FindNode(diagnosticSpan), c), - equivalenceKey: Rules.XunitAttributes.Title.ToString()), - diagnostic); - } - } - - private static async Task ConvertAttributesAsync(Document document, SyntaxNode? node, CancellationToken cancellationToken) - { - if (node is not AttributeSyntax attributeSyntax) - { - return document; - } - - var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - - if (root is null) - { - return document; - } - - var newExpression = await GetNewExpression(document, attributeSyntax); - - root = root.ReplaceNode(attributeSyntax, newExpression); - - return document.WithSyntaxRoot(root); - } - - private static async Task> GetNewExpression(Document document, AttributeSyntax attributeSyntax) - { - var name = GetSimpleName(attributeSyntax); - - return name switch - { - "Fact" or "FactAttribute" - or "Theory" or "TheoryAttribute" => ConvertTestAttribute(attributeSyntax), - - "Trait" or "TraitAttribute" => [SyntaxFactory.Attribute(SyntaxFactory.IdentifierName("Property"), - attributeSyntax.ArgumentList)], - - "InlineData" or "InlineDataAttribute" => [SyntaxFactory.Attribute(SyntaxFactory.IdentifierName("Arguments"), - attributeSyntax.ArgumentList)], - - "MemberData" or "MemberDataAttribute" => [SyntaxFactory.Attribute( - SyntaxFactory.IdentifierName("MethodDataSource"), attributeSyntax.ArgumentList)], - - "ClassData" or "ClassDataAttribute" => [SyntaxFactory.Attribute( - SyntaxFactory.IdentifierName("MethodDataSource"), - (attributeSyntax.ArgumentList ?? SyntaxFactory.AttributeArgumentList()).AddArguments( - SyntaxFactory.AttributeArgument(SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, - SyntaxFactory.Literal("GetEnumerator")))))], - - "Collection" or "CollectionAttribute" => await ConvertCollection(document, attributeSyntax), - - "CollectionDefinition" or "CollectionDefinitionAttribute" => [SyntaxFactory.Attribute( - SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("System"), - SyntaxFactory.IdentifierName("Obsolete")))], - - _ => [] - }; - } - - private static string GetSimpleName(AttributeSyntax attributeSyntax) - { - var name = attributeSyntax.Name; - - while (name is not SimpleNameSyntax) - { - name = (name as QualifiedNameSyntax)?.Right; - } - - return name.ToString(); - } - - private static IEnumerable ConvertTestAttribute(AttributeSyntax attributeSyntax) - { - yield return SyntaxFactory.Attribute(SyntaxFactory.IdentifierName("Test")); - - if (attributeSyntax.ArgumentList?.Arguments.FirstOrDefault(x => x.NameEquals?.Name.Identifier.ValueText == "Skip") is {} skip) - { - yield return SyntaxFactory.Attribute(SyntaxFactory.IdentifierName("Skip")) - .AddArgumentListArguments(SyntaxFactory.AttributeArgument(skip.Expression)); - } - } - - private static async Task> ConvertCollection(Document document, AttributeSyntax attributeSyntax) - { - var compilation = await document.Project.GetCompilationAsync(); - - if (compilation is null) - { - return []; - } - - var collectionDefinition = GetCollectionAttribute(compilation, attributeSyntax); - - if (collectionDefinition is null) - { - return []; - } - - var disableParallelism = - collectionDefinition.ArgumentList?.Arguments.Any( - x => x.NameEquals?.Name.Identifier.Text == "DisableParallelization" - && x.Expression is LiteralExpressionSyntax { Token.ValueText: "true" }) ?? false; - - var attributes = new List(); - - if (disableParallelism) - { - attributes.Add(SyntaxFactory.Attribute(SyntaxFactory.ParseName("NotInParallel"))); - } - - var baseListSyntax = collectionDefinition.Parent?.Parent?.ChildNodes().OfType().FirstOrDefault(); - - if (baseListSyntax is null) - { - return attributes; - } - - var collectionFixture = baseListSyntax.Types.Select(x => x.Type).OfType().FirstOrDefault(x => x.Identifier.Text == "ICollectionFixture"); - - if (collectionFixture is null) - { - return attributes; - } - - var type = collectionFixture.TypeArgumentList.Arguments.FirstOrDefault(); - - if (type is null) - { - return attributes; - } - - attributes.Add(SyntaxFactory.Attribute( - SyntaxFactory.GenericName(SyntaxFactory.Identifier("ClassDataSource"), - SyntaxFactory.TypeArgumentList(SyntaxFactory.SingletonSeparatedList(type))), - SyntaxFactory.AttributeArgumentList() - .AddArguments( - SyntaxFactory.AttributeArgument( - nameEquals: SyntaxFactory.NameEquals("Shared"), - nameColon: null, - expression: SyntaxFactory.ParseExpression("SharedType.Keyed") - ), - SyntaxFactory.AttributeArgument( - nameEquals: SyntaxFactory.NameEquals("Key"), - nameColon: null, - expression: GetMethodArgumentName(attributeSyntax) - ) - ) - )); - - return attributes; - } - - private static ExpressionSyntax GetMethodArgumentName(AttributeSyntax attributeSyntax) - { - var firstToken = attributeSyntax.ArgumentList?.Arguments.FirstOrDefault()?.GetFirstToken(); - - if (!firstToken.HasValue) - { - return SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal("")); - } - - return SyntaxFactory.ParseExpression(firstToken.Value.Text); - } - - private static TypeArgumentListSyntax GetGenericArguments(Compilation compilation, Document document, - AttributeSyntax attributeSyntax) - { - var collectionAttribute = GetCollectionAttribute(compilation, attributeSyntax); - - if (collectionAttribute is null) - { - return SyntaxFactory.TypeArgumentList(); - } - - var classDeclaration = collectionAttribute.Parent?.Parent; - - if (classDeclaration is null) - { - return SyntaxFactory.TypeArgumentList(); - } - - var classSymbol = compilation.GetSemanticModel(classDeclaration.SyntaxTree).GetDeclaredSymbol(classDeclaration); - - if (classSymbol is not ITypeSymbol typeSymbol) - { - return SyntaxFactory.TypeArgumentList(); - } - - var interfaceType = typeSymbol.AllInterfaces.FirstOrDefault(x => x.Name == "ICollectionFixture"); - - if (interfaceType is null) - { - return SyntaxFactory.TypeArgumentList(); - } - - return SyntaxFactory.TypeArgumentList(SyntaxFactory.SingletonSeparatedList( - SyntaxFactory.IdentifierName(interfaceType.TypeArguments.First().Name) - )); - } - - private static AttributeSyntax? GetCollectionAttribute(Compilation compilation, AttributeSyntax attributeSyntax) - { - var firstToken = attributeSyntax.ArgumentList?.Arguments.FirstOrDefault()?.GetFirstToken(); - - if (!firstToken.HasValue) - { - return null; - } - - var collectionName = firstToken.Value.IsKind(SyntaxKind.NameOfKeyword) - ? GetNameFromNameOfToken(firstToken.Value) : firstToken.Value.ValueText; - - if (collectionName is null) - { - return null; - } - - return compilation.SyntaxTrees - .Select(x => x.GetRoot()) - .SelectMany(x => x.DescendantNodes().OfType()) - .Where(attr => attr.Name.ToString() == "CollectionDefinition") - .FirstOrDefault(x => - { - var syntaxToken = x.ArgumentList?.Arguments.FirstOrDefault()?.GetFirstToken(); - - if (!syntaxToken.HasValue) - { - return false; - } - - var name = syntaxToken.Value.IsKind(SyntaxKind.NameOfKeyword) - ? GetNameFromNameOfToken(syntaxToken.Value) : syntaxToken.Value.ValueText; - - return name == collectionName; - }); - } - - private static string? GetNameFromNameOfToken(SyntaxToken token) - { - var expression = SyntaxFactory.ParseExpression(token.Text) as InvocationExpressionSyntax; - - if (expression?.Expression is IdentifierNameSyntax { Identifier.Text: "nameof" } && - expression.ArgumentList.Arguments.FirstOrDefault()?.Expression is IdentifierNameSyntax nameOfArgument) - { - return nameOfArgument.Identifier.Text; - } - - return null; - } -} \ No newline at end of file diff --git a/TUnit.Analyzers.CodeFixers/XUnitClassFixtureCodeFixProvider.cs b/TUnit.Analyzers.CodeFixers/XUnitClassFixtureCodeFixProvider.cs deleted file mode 100644 index ec44f1444f..0000000000 --- a/TUnit.Analyzers.CodeFixers/XUnitClassFixtureCodeFixProvider.cs +++ /dev/null @@ -1,112 +0,0 @@ -using System.Collections.Immutable; -using System.Composition; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using TUnit.Analyzers.CodeFixers.Extensions; - -namespace TUnit.Analyzers.CodeFixers; - -[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(XUnitClassFixtureCodeFixProvider)), Shared] -public class XUnitClassFixtureCodeFixProvider : CodeFixProvider -{ - public override sealed ImmutableArray FixableDiagnosticIds { get; } = - ImmutableArray.Create(Rules.XunitClassFixtures.Id); - - public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; - - public override sealed async Task RegisterCodeFixesAsync(CodeFixContext context) - { - foreach (var diagnostic in context.Diagnostics) - { - var diagnosticSpan = diagnostic.Location.SourceSpan; - - var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); - - context.RegisterCodeFix( - CodeAction.Create( - title: Rules.XunitClassFixtures.Title.ToString(), - createChangedDocument: c => ConvertAttributesAsync(context.Document, root?.FindNode(diagnosticSpan), c), - equivalenceKey: Rules.XunitClassFixtures.Title.ToString()), - diagnostic); - } - } - - private static async Task ConvertAttributesAsync(Document document, SyntaxNode? node, CancellationToken cancellationToken) - { - if (node is not SimpleBaseTypeSyntax simpleBaseTypeSyntax) - { - return document; - } - - var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - - if (root is null) - { - return document; - } - - var newExpression = GetNewExpression(simpleBaseTypeSyntax); - - if (newExpression != null) - { - var classDeclaration = GetClassDeclaration(simpleBaseTypeSyntax)!; - - SyntaxNode toRemove = classDeclaration.BaseList?.ChildNodes().Count() > 1 - ? simpleBaseTypeSyntax - : classDeclaration.BaseList!; - - root = root.ReplaceNode(classDeclaration, - classDeclaration - .RemoveNode(toRemove, SyntaxRemoveOptions.AddElasticMarker)! - .AddAttributeLists( - SyntaxFactory.AttributeList(SyntaxFactory.SingletonSeparatedList(newExpression)) - )! - ); - - var compilationUnit = root as CompilationUnitSyntax; - - if (compilationUnit is null) - { - return document.WithSyntaxRoot(root); - } - } - - return document.WithSyntaxRoot(root); - } - - private static ClassDeclarationSyntax? GetClassDeclaration(SimpleBaseTypeSyntax simpleBaseTypeSyntax) - { - var parent = simpleBaseTypeSyntax.Parent; - - while (parent != null && !parent.IsKind(SyntaxKind.ClassDeclaration)) - { - parent = parent.Parent; - } - - return parent as ClassDeclarationSyntax; - } - - private static AttributeSyntax? GetNewExpression(SimpleBaseTypeSyntax simpleBaseTypeSyntax) - { - if (simpleBaseTypeSyntax.Type is not GenericNameSyntax genericNameSyntax - || !genericNameSyntax.TypeArgumentList.Arguments.Any()) - { - return null; - } - - return SyntaxFactory.Attribute( - SyntaxFactory.GenericName(SyntaxFactory.ParseToken("ClassDataSource"), genericNameSyntax.TypeArgumentList).WithoutTrailingTrivia(), - SyntaxFactory.AttributeArgumentList() - .AddArguments( - SyntaxFactory.AttributeArgument( - nameEquals: SyntaxFactory.NameEquals("Shared"), - nameColon: null, - expression: SyntaxFactory.ParseExpression("SharedType.PerClass") - ) - ) - ).WithLeadingTrivia(SyntaxFactory.ElasticMarker); - } -} \ No newline at end of file diff --git a/TUnit.Analyzers.CodeFixers/XUnitMigrationCodeFixProvider.cs b/TUnit.Analyzers.CodeFixers/XUnitMigrationCodeFixProvider.cs new file mode 100644 index 0000000000..ff1940510c --- /dev/null +++ b/TUnit.Analyzers.CodeFixers/XUnitMigrationCodeFixProvider.cs @@ -0,0 +1,587 @@ +using System.Collections.Immutable; +using System.Composition; +using System.Runtime.InteropServices; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace TUnit.Analyzers.CodeFixers; + +[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(XUnitMigrationCodeFixProvider)), Shared] +public class XUnitMigrationCodeFixProvider : CodeFixProvider +{ + public override sealed ImmutableArray FixableDiagnosticIds { get; } = + ImmutableArray.Create(Rules.XunitMigration.Id); + + public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + + public override sealed Task RegisterCodeFixesAsync(CodeFixContext context) + { + foreach (var diagnostic in context.Diagnostics) + { + context.RegisterCodeFix( + CodeAction.Create( + title: Rules.XunitMigration.Title.ToString(), + createChangedDocument: c => ConvertCodeAsync(context.Document, c), + equivalenceKey: Rules.XunitMigration.Title.ToString()), + diagnostic); + } + + return Task.CompletedTask; + } + + private static async Task ConvertCodeAsync(Document document, CancellationToken cancellationToken) + { + var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + + if (root is null) + { + return document; + } + + var compilation = await document.Project.GetCompilationAsync(cancellationToken); + + if (compilation is null) + { + return document; + } + + var semanticModel = await document.GetSemanticModelAsync(cancellationToken); + + if (semanticModel is null) + { + return document; + } + + var syntaxTree = root.SyntaxTree; + + // Always use the latest updatedRoot as input for the next transformation + var updatedRoot = UpdateInitializeDispose(compilation, root); + UpdateSyntaxTrees(ref compilation, ref syntaxTree, updatedRoot); + + updatedRoot = UpdateClassAttributes(compilation, updatedRoot); + UpdateSyntaxTrees(ref compilation, ref syntaxTree, updatedRoot); + + updatedRoot = RemoveInterfacesAndBaseClasses(compilation, updatedRoot); + UpdateSyntaxTrees(ref compilation, ref syntaxTree, updatedRoot); + + updatedRoot = RemoveUsingDirectives(updatedRoot); + UpdateSyntaxTrees(ref compilation, ref syntaxTree, updatedRoot); + + // Apply all changes in one step + return document.WithSyntaxRoot(updatedRoot); + } + + private static SyntaxNode UpdateInitializeDispose(Compilation compilation, SyntaxNode root) + { + // Always operate on the latest root + var currentRoot = root; + foreach (var classDeclaration in root.DescendantNodes().OfType().ToList()) + { + if (classDeclaration.BaseList is null) + { + continue; + } + + var semanticModel = compilation.GetSemanticModel(classDeclaration.SyntaxTree); + + if (semanticModel.GetDeclaredSymbol(classDeclaration) is not { } symbol) + { + continue; + } + + // Always get the latest node from the current root + var currentClass = currentRoot.DescendantNodes().OfType() + .FirstOrDefault(n => n.SpanStart == classDeclaration.SpanStart && n.Identifier.Text == classDeclaration.Identifier.Text); + + if (currentClass == null) + { + continue; + } + + var newNode = new InitializeDisposeRewriter(symbol).VisitClassDeclaration(currentClass); + + if (!ReferenceEquals(currentClass, newNode)) + { + currentRoot = currentRoot.ReplaceNode(currentClass, newNode); + } + } + + return currentRoot.NormalizeWhitespace(); + } + + private static SyntaxNode RemoveInterfacesAndBaseClasses(Compilation compilation, SyntaxNode root) + { + // Always operate on the latest root + var currentRoot = root; + foreach (var classDeclaration in root.DescendantNodes().OfType().ToList()) + { + if (classDeclaration.BaseList is null) + { + continue; + } + + var semanticModel = compilation.GetSemanticModel(classDeclaration.SyntaxTree); + + if (semanticModel.GetDeclaredSymbol(classDeclaration) is not { } symbol) + { + continue; + } + + // Always get the latest node from the current root + var currentClass = currentRoot.DescendantNodes().OfType() + .FirstOrDefault(n => n.SpanStart == classDeclaration.SpanStart && n.Identifier.Text == classDeclaration.Identifier.Text); + + if (currentClass == null) + { + continue; + } + + var newNode = new BaseTypeRewriter(symbol).VisitClassDeclaration(currentClass); + + if (!ReferenceEquals(currentClass, newNode)) + { + currentRoot = currentRoot.ReplaceNode(currentClass, newNode); + } + } + + return currentRoot; + } + + private static SyntaxNode RemoveUsingDirectives(SyntaxNode updatedRoot) + { + var compilationUnit = updatedRoot.DescendantNodesAndSelf() + .OfType() + .FirstOrDefault(); + + if (compilationUnit is null) + { + return updatedRoot; + } + + return compilationUnit.WithUsings( + SyntaxFactory.List( + compilationUnit.Usings + .Where(x => x.Name?.ToString().StartsWith("Xunit") is false) + ) + ).NormalizeWhitespace(); + } + + private static SyntaxNode UpdateClassAttributes(Compilation compilation, SyntaxNode root) + { + // Always operate on the latest root + var rewriter = new AttributeRewriter(compilation); + return rewriter.Visit(root); + } + + private static string GetSimpleName(AttributeSyntax attributeSyntax) + { + var name = attributeSyntax.Name; + + while (name is not SimpleNameSyntax) + { + name = (name as QualifiedNameSyntax)?.Right; + } + + return name.ToString(); + } + + private static IEnumerable ConvertTestAttribute(AttributeSyntax attributeSyntax) + { + yield return SyntaxFactory.Attribute(SyntaxFactory.IdentifierName("Test")); + + if (attributeSyntax.ArgumentList?.Arguments.FirstOrDefault(x => x.NameEquals?.Name.Identifier.ValueText == "Skip") is { } skip) + { + yield return SyntaxFactory.Attribute(SyntaxFactory.IdentifierName("Skip")) + .AddArgumentListArguments(SyntaxFactory.AttributeArgument(skip.Expression)); + } + } + + private static IEnumerable ConvertCollection(Compilation compilation, AttributeSyntax attributeSyntax) + { + var collectionDefinition = GetCollectionAttribute(compilation, attributeSyntax); + + if (collectionDefinition is null) + { + return [attributeSyntax]; + } + + var disableParallelism = + collectionDefinition.ArgumentList?.Arguments.Any(x => x.NameEquals?.Name.Identifier.Text == "DisableParallelization" + && x.Expression is LiteralExpressionSyntax { Token.ValueText: "true" }) ?? false; + + var attributes = new List(); + + if (disableParallelism) + { + attributes.Add(SyntaxFactory.Attribute(SyntaxFactory.ParseName("NotInParallel"))); + } + + var baseListSyntax = collectionDefinition.Parent?.Parent?.ChildNodes().OfType().FirstOrDefault(); + + if (baseListSyntax is null) + { + return attributes; + } + + var collectionFixture = baseListSyntax.Types.Select(x => x.Type).OfType().FirstOrDefault(x => x.Identifier.Text == "ICollectionFixture"); + + if (collectionFixture is null) + { + return attributes; + } + + var type = collectionFixture.TypeArgumentList.Arguments.FirstOrDefault(); + + if (type is null) + { + return attributes; + } + + attributes.Add(SyntaxFactory.Attribute( + SyntaxFactory.GenericName(SyntaxFactory.Identifier("ClassDataSource"), + SyntaxFactory.TypeArgumentList(SyntaxFactory.SingletonSeparatedList(type))), + SyntaxFactory.AttributeArgumentList() + .AddArguments( + SyntaxFactory.AttributeArgument( + nameEquals: SyntaxFactory.NameEquals("Shared"), + nameColon: null, + expression: SyntaxFactory.ParseExpression("SharedType.Keyed") + ), + SyntaxFactory.AttributeArgument( + nameEquals: SyntaxFactory.NameEquals("Key"), + nameColon: null, + expression: GetMethodArgumentName(attributeSyntax) + ) + ).NormalizeWhitespace() + )); + + return attributes; + } + + private static ExpressionSyntax GetMethodArgumentName(AttributeSyntax attributeSyntax) + { + var firstToken = attributeSyntax.ArgumentList?.Arguments.FirstOrDefault()?.GetFirstToken(); + + if (!firstToken.HasValue) + { + return SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal("")); + } + + return SyntaxFactory.ParseExpression(firstToken.Value.Text); + } + + private static AttributeSyntax? GetCollectionAttribute(Compilation compilation, AttributeSyntax attributeSyntax) + { + var firstToken = attributeSyntax.ArgumentList?.Arguments.FirstOrDefault()?.GetFirstToken(); + + if (!firstToken.HasValue) + { + return null; + } + + var collectionName = firstToken.Value.IsKind(SyntaxKind.NameOfKeyword) + ? GetNameFromNameOfToken(firstToken.Value) + : firstToken.Value.ValueText; + + if (collectionName is null) + { + return null; + } + + return compilation.SyntaxTrees + .Select(x => x.GetRoot()) + .SelectMany(x => x.DescendantNodes().OfType()) + .Where(attr => attr.Name.ToString() == "CollectionDefinition") + .FirstOrDefault(x => + { + var syntaxToken = x.ArgumentList?.Arguments.FirstOrDefault()?.GetFirstToken(); + + if (!syntaxToken.HasValue) + { + return false; + } + + var name = syntaxToken.Value.IsKind(SyntaxKind.NameOfKeyword) + ? GetNameFromNameOfToken(syntaxToken.Value) + : syntaxToken.Value.ValueText; + + return name == collectionName; + }); + } + + private static string? GetNameFromNameOfToken(SyntaxToken token) + { + var expression = SyntaxFactory.ParseExpression(token.Text) as InvocationExpressionSyntax; + + if (expression?.Expression is IdentifierNameSyntax { Identifier.Text: "nameof" } && + expression.ArgumentList.Arguments.FirstOrDefault()?.Expression is IdentifierNameSyntax nameOfArgument) + { + return nameOfArgument.Identifier.Text; + } + + return null; + } + + private class AttributeRewriter(Compilation compilation) : CSharpSyntaxRewriter + { + public override SyntaxNode VisitAttributeList(AttributeListSyntax node) + { + var newAttributes = new List(); + + foreach (var attr in node.Attributes) + { + var name = GetSimpleName(attr); + + var converted = name switch + { + "Fact" or "FactAttribute" or "Theory" or "TheoryAttribute" => ConvertTestAttribute(attr), + "Trait" or "TraitAttribute" => [SyntaxFactory.Attribute(SyntaxFactory.IdentifierName("Property"), attr.ArgumentList)], + "InlineData" or "InlineDataAttribute" => [SyntaxFactory.Attribute(SyntaxFactory.IdentifierName("Arguments"), attr.ArgumentList)], + "MemberData" or "MemberDataAttribute" => [SyntaxFactory.Attribute(SyntaxFactory.IdentifierName("MethodDataSource"), attr.ArgumentList)], + "ClassData" or "ClassDataAttribute" => + [ + SyntaxFactory.Attribute( + SyntaxFactory.IdentifierName("MethodDataSource"), + (attr.ArgumentList ?? SyntaxFactory.AttributeArgumentList()).AddArguments( + SyntaxFactory.AttributeArgument(SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, + SyntaxFactory.Literal("GetEnumerator"))))) + ], + "Collection" or "CollectionAttribute" => ConvertCollection(compilation, attr), + "CollectionDefinition" or "CollectionDefinitionAttribute" => [SyntaxFactory.Attribute( + SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("System"), + SyntaxFactory.IdentifierName("Obsolete")))], + _ => [attr] + }; + + newAttributes.AddRange(converted); + } + + // Preserve original trivia instead of forcing elastic trivia + return SyntaxFactory.AttributeList(SyntaxFactory.SeparatedList(newAttributes)) + .WithLeadingTrivia(node.GetLeadingTrivia()) + .WithTrailingTrivia(node.GetTrailingTrivia()); + } + } + + private class BaseTypeRewriter(INamedTypeSymbol namedTypeSymbol) : CSharpSyntaxRewriter + { + public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node) + { + if (node.BaseList is null) + { + return node; + } + + INamedTypeSymbol[] types = namedTypeSymbol.BaseType != null && namedTypeSymbol.BaseType.SpecialType != SpecialType.System_Object + ? [namedTypeSymbol.BaseType, ..namedTypeSymbol.AllInterfaces] + : [..namedTypeSymbol.AllInterfaces]; + + var classFixturesToConvert = types + .Where(x => x.Name == "IClassFixture" && x.ContainingNamespace.Name.StartsWith("Xunit")) + .Select(x => SyntaxFactory.Attribute( + SyntaxFactory.GenericName(SyntaxFactory.ParseToken("ClassDataSource"), SyntaxFactory.TypeArgumentList(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.ParseTypeName(x.TypeArguments.First().ToDisplayString())))).WithoutTrailingTrivia(), + SyntaxFactory.AttributeArgumentList() + .AddArguments( + SyntaxFactory.AttributeArgument( + nameEquals: SyntaxFactory.NameEquals("Shared"), + nameColon: null, + expression: SyntaxFactory.ParseExpression("SharedType.PerClass") + ) + ) + ).WithLeadingTrivia(SyntaxFactory.ElasticMarker)) + .ToList(); + + if(classFixturesToConvert.Count > 0) + { + node = node.AddAttributeLists(SyntaxFactory.AttributeList(SyntaxFactory.SeparatedList(classFixturesToConvert))); + } + + var newBaseList = types.Where(x => !x.ContainingNamespace.Name.StartsWith("Xunit")) + .Select(x => SyntaxFactory.SimpleBaseType(SyntaxFactory.ParseTypeName(x.ToDisplayString()))) + .ToList(); + + if (newBaseList.Count == 0) + { + // Preserve original trivia instead of forcing elastic trivia + return node.WithBaseList(null) + .WithOpenBraceToken(node.OpenBraceToken.WithLeadingTrivia(node.BaseList!.GetTrailingTrivia())); + } + + var baseListSyntax = node.BaseList!.WithTypes(SyntaxFactory.SeparatedList(newBaseList)); + + return node.WithBaseList(baseListSyntax); + } + } + + private class InitializeDisposeRewriter(INamedTypeSymbol namedTypeSymbol) : CSharpSyntaxRewriter + { + public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node) + { + if (node.BaseList is null) + { + return node; + } + + var interfaces = namedTypeSymbol.Interfaces + .Where(x => x.ToDisplayString(DisplayFormats.FullyQualifiedGenericWithGlobalPrefix) is "global::Xunit.IAsyncLifetime" or "global::System.IAsyncDisposable" or "global::System.IDisposable") + .ToArray(); + + if (interfaces.Length == 0) + { + return node; + } + + var hasAsyncLifetime = interfaces.Any(x => x.ToDisplayString(DisplayFormats.FullyQualifiedGenericWithGlobalPrefix) == "global::Xunit.IAsyncLifetime"); + var hasAsyncDisposable = interfaces.Any(x => x.ToDisplayString(DisplayFormats.FullyQualifiedGenericWithGlobalPrefix) == "global::System.IAsyncDisposable"); + var hasDisposable = interfaces.Any(x => x.ToDisplayString(DisplayFormats.FullyQualifiedGenericWithGlobalPrefix) == "global::System.IDisposable"); + + var isTestClass = namedTypeSymbol + .GetMembers() + .OfType() + .Any(m => m.GetAttributes() + .Any(x => x.AttributeClass?.ToDisplayString(DisplayFormats.FullyQualifiedGenericWithGlobalPrefix) is "global::Xunit.FactAttribute" + or "global::Xunit.TheoryAttribute") + ); + + if (isTestClass) + { + if(hasAsyncLifetime && GetInitializeMethod(node) is {} initializeMethod) + { + node = node + .AddMembers( + SyntaxFactory.MethodDeclaration(SyntaxFactory.ParseTypeName("Task"), "InitializeAsync") + .WithModifiers(initializeMethod.Modifiers) + .WithBody(initializeMethod.Body) + .WithAttributeLists( + SyntaxFactory.SingletonList( + SyntaxFactory.AttributeList( + SyntaxFactory.SingletonSeparatedList( + SyntaxFactory.Attribute(SyntaxFactory.ParseName("Before"), SyntaxFactory.ParseAttributeArgumentList("(Test)"))) + ) + ) + ) + .WithTrailingTrivia(SyntaxFactory.ElasticCarriageReturnLineFeed) + ); + + node = node.RemoveNode(GetInitializeMethod(node)!, SyntaxRemoveOptions.AddElasticMarker)!.NormalizeWhitespace(); + + node = node.WithBaseList(SyntaxFactory.BaseList(SyntaxFactory.SeparatedList( + node.BaseList!.Types.Where(x => x.Type.TryGetInferredMemberName()?.EndsWith("IAsyncLifetime") is null or false)))) + .WithTrailingTrivia(node.BaseList.GetTrailingTrivia()); + + } + + if((hasAsyncLifetime || hasAsyncDisposable) && GetDisposeAsyncMethod(node) is { } disposeAsyncMethod) + { + node = node + .AddMembers( + SyntaxFactory.MethodDeclaration(SyntaxFactory.ParseTypeName("Task"), "DisposeAsync") + .WithModifiers(disposeAsyncMethod.Modifiers) + .WithBody(disposeAsyncMethod.Body) + .WithAttributeLists( + SyntaxFactory.SingletonList( + SyntaxFactory.AttributeList( + SyntaxFactory.SingletonSeparatedList( + SyntaxFactory.Attribute(SyntaxFactory.ParseName("After"), SyntaxFactory.ParseAttributeArgumentList("(Test)"))) + ) + ) + ) + .WithTrailingTrivia(SyntaxFactory.ElasticCarriageReturnLineFeed) + ); + + node = node.RemoveNode(GetDisposeAsyncMethod(node)!, SyntaxRemoveOptions.AddElasticMarker)!.NormalizeWhitespace(); + + node = node.WithBaseList(SyntaxFactory.BaseList(SyntaxFactory.SeparatedList( + node.BaseList!.Types.Where(x => x.Type.TryGetInferredMemberName()?.EndsWith("IAsyncDisposable") is null or false)))) + .WithTrailingTrivia(node.BaseList.GetTrailingTrivia()); + } + + if(hasDisposable && GetDisposeMethod(node) is {} disposeMethod) + { + node = node + .AddMembers( + SyntaxFactory.MethodDeclaration(SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.VoidKeyword)), "Dispose") + .WithModifiers(disposeMethod.Modifiers) + .WithBody(disposeMethod.Body) + .WithAttributeLists( + SyntaxFactory.SingletonList( + SyntaxFactory.AttributeList( + SyntaxFactory.SingletonSeparatedList( + SyntaxFactory.Attribute(SyntaxFactory.ParseName("After"), SyntaxFactory.ParseAttributeArgumentList("(Test)"))) + ) + ) + ) + .WithTrailingTrivia(SyntaxFactory.ElasticCarriageReturnLineFeed) + ); + + node = node.RemoveNode(GetDisposeMethod(node)!, SyntaxRemoveOptions.AddElasticMarker)!.NormalizeWhitespace(); + + node = node.WithBaseList(SyntaxFactory.BaseList(SyntaxFactory.SeparatedList( + node.BaseList!.Types.Where(x => x.Type.TryGetInferredMemberName()?.EndsWith("IDisposable") is null or false)))) + .WithTrailingTrivia(node.BaseList.GetTrailingTrivia()); + } + } + else + { + if (hasAsyncLifetime && GetInitializeMethod(node) is {} initializeMethod) + { + node = node + .ReplaceNode(initializeMethod, initializeMethod.WithReturnType(SyntaxFactory.ParseTypeName("Task"))) + .NormalizeWhitespace(); + + node = node.WithBaseList(SyntaxFactory.BaseList(SyntaxFactory.SeparatedList( + [ + ..node.BaseList!.Types.Where(x => x.Type.TryGetInferredMemberName()?.EndsWith("IAsyncLifetime") is null or false), + SyntaxFactory.SimpleBaseType(SyntaxFactory.ParseTypeName("IAsyncInitializer")) + ]))) + .WithTrailingTrivia(node.BaseList.GetTrailingTrivia()); + } + + if (hasAsyncLifetime && !hasAsyncDisposable) + { + node = node + .WithBaseList(node.BaseList!.AddTypes(SyntaxFactory.SimpleBaseType(SyntaxFactory.ParseTypeName("IAsyncDisposable")))); + } + } + + if (node.BaseList is not null && node.BaseList.Types.Count == 0) + { + node = node.WithBaseList(null) + .WithOpenBraceToken(node.OpenBraceToken.WithLeadingTrivia(node.BaseList.GetTrailingTrivia())) + .NormalizeWhitespace(); + } + + return node.NormalizeWhitespace(); + + MethodDeclarationSyntax? GetInitializeMethod(ClassDeclarationSyntax classDeclaration) + { + return classDeclaration.Members + .OfType() + .FirstOrDefault(m => m.Identifier.Text == "InitializeAsync"); + } + + MethodDeclarationSyntax? GetDisposeAsyncMethod(ClassDeclarationSyntax classDeclaration) + { + return classDeclaration.Members + .OfType() + .FirstOrDefault(m => m.Identifier.Text == "DisposeAsync"); + } + + MethodDeclarationSyntax? GetDisposeMethod(ClassDeclarationSyntax classDeclaration) + { + return classDeclaration.Members + .OfType() + .FirstOrDefault(m => m.Identifier.Text == "Dispose"); + } + } + } + + private static void UpdateSyntaxTrees(ref Compilation compilation, ref SyntaxTree syntaxTree, SyntaxNode updatedRoot) + { + compilation = compilation.ReplaceSyntaxTree(syntaxTree, updatedRoot.SyntaxTree); + syntaxTree = updatedRoot.SyntaxTree; + } +} diff --git a/TUnit.Analyzers.CodeFixers/XUnitUsingDirectiveCodeFixProvider.cs b/TUnit.Analyzers.CodeFixers/XUnitUsingDirectiveCodeFixProvider.cs index dd33414ea2..e8693e4374 100644 --- a/TUnit.Analyzers.CodeFixers/XUnitUsingDirectiveCodeFixProvider.cs +++ b/TUnit.Analyzers.CodeFixers/XUnitUsingDirectiveCodeFixProvider.cs @@ -1,53 +1,53 @@ -using System.Collections.Immutable; -using System.Composition; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.CSharp.Syntax; - -namespace TUnit.Analyzers.CodeFixers; - -[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(XUnitUsingDirectiveCodeFixProvider)), Shared] -public class XUnitUsingDirectiveCodeFixProvider : CodeFixProvider -{ - public override sealed ImmutableArray FixableDiagnosticIds { get; } = - ImmutableArray.Create(Rules.XunitUsingDirectives.Id); - - public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; - - public override sealed async Task RegisterCodeFixesAsync(CodeFixContext context) - { - foreach (var diagnostic in context.Diagnostics) - { - var diagnosticSpan = diagnostic.Location.SourceSpan; - - var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); - - context.RegisterCodeFix( - CodeAction.Create( - title: Rules.XunitUsingDirectives.Title.ToString(), - createChangedDocument: c => RemoveDirectivesAsync(context.Document, root?.FindNode(diagnosticSpan), c), - equivalenceKey: Rules.XunitUsingDirectives.Title.ToString()), - diagnostic); - } - } - - private static async Task RemoveDirectivesAsync(Document document, SyntaxNode? node, CancellationToken cancellationToken) - { - if (node is not UsingDirectiveSyntax usingDirectiveSyntax) - { - return document; - } - - var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - - if (root is null) - { - return document; - } - - var newNode = root.RemoveNode(usingDirectiveSyntax, SyntaxRemoveOptions.KeepNoTrivia); - - return newNode is not null ? document.WithSyntaxRoot(newNode!) : document; - } -} \ No newline at end of file +// using System.Collections.Immutable; +// using System.Composition; +// using Microsoft.CodeAnalysis; +// using Microsoft.CodeAnalysis.CodeActions; +// using Microsoft.CodeAnalysis.CodeFixes; +// using Microsoft.CodeAnalysis.CSharp.Syntax; +// +// namespace TUnit.Analyzers.CodeFixers; +// +// [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(XUnitUsingDirectiveCodeFixProvider)), Shared] +// public class XUnitUsingDirectiveCodeFixProvider : CodeFixProvider +// { +// public override sealed ImmutableArray FixableDiagnosticIds { get; } = +// ImmutableArray.Create(Rules.XunitMigration.Id); +// +// public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; +// +// public override sealed async Task RegisterCodeFixesAsync(CodeFixContext context) +// { +// foreach (var diagnostic in context.Diagnostics) +// { +// var diagnosticSpan = diagnostic.Location.SourceSpan; +// +// var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); +// +// context.RegisterCodeFix( +// CodeAction.Create( +// title: Rules.XunitMigration.Title.ToString(), +// createChangedDocument: c => RemoveDirectivesAsync(context.Document, root?.FindNode(diagnosticSpan), c), +// equivalenceKey: Rules.XunitMigration.Title.ToString()), +// diagnostic); +// } +// } +// +// private static async Task RemoveDirectivesAsync(Document document, SyntaxNode? node, CancellationToken cancellationToken) +// { +// if (node is not UsingDirectiveSyntax usingDirectiveSyntax) +// { +// return document; +// } +// +// var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); +// +// if (root is null) +// { +// return document; +// } +// +// var newNode = root.RemoveNode(usingDirectiveSyntax, SyntaxRemoveOptions.KeepNoTrivia); +// +// return newNode is not null ? document.WithSyntaxRoot(newNode!) : document; +// } +// } \ No newline at end of file diff --git a/TUnit.Analyzers.Tests/Extensions/StringExtensions.cs b/TUnit.Analyzers.Tests/Extensions/StringExtensions.cs index d9e612fc14..883d9eaf49 100644 --- a/TUnit.Analyzers.Tests/Extensions/StringExtensions.cs +++ b/TUnit.Analyzers.Tests/Extensions/StringExtensions.cs @@ -4,6 +4,6 @@ public static class StringExtensions { public static string NormalizeLineEndings(this string value) { - return value.Replace("\r\n", "\n").Replace("\n", Environment.NewLine); + return value.Replace("\r\n", "\n").Replace("\n", "\r\n"); } } \ No newline at end of file diff --git a/TUnit.Analyzers.Tests/Verifiers/CSharpCodeFixVerifier`2.cs b/TUnit.Analyzers.Tests/Verifiers/CSharpCodeFixVerifier`2.cs index 12086e5be6..5f0706cd0d 100644 --- a/TUnit.Analyzers.Tests/Verifiers/CSharpCodeFixVerifier`2.cs +++ b/TUnit.Analyzers.Tests/Verifiers/CSharpCodeFixVerifier`2.cs @@ -27,7 +27,7 @@ public static DiagnosticResult Diagnostic(DiagnosticDescriptor descriptor) /// public static async Task VerifyAnalyzerAsync( - [StringSyntax("c#-test")] string source, + [StringSyntax("c#")] string source, params DiagnosticResult[] expected ) { @@ -51,18 +51,18 @@ params DiagnosticResult[] expected } /// - public static async Task VerifyCodeFixAsync([StringSyntax("c#-test")] string source, [StringSyntax("c#-test")] string fixedSource) + public static async Task VerifyCodeFixAsync([StringSyntax("c#")] string source, [StringSyntax("c#")] string fixedSource) => await VerifyCodeFixAsync(source, DiagnosticResult.EmptyDiagnosticResults, fixedSource); /// - public static async Task VerifyCodeFixAsync([StringSyntax("c#-test")] string source, DiagnosticResult expected, [StringSyntax("c#-test")] string fixedSource) + public static async Task VerifyCodeFixAsync([StringSyntax("c#")] string source, DiagnosticResult expected, [StringSyntax("c#")] string fixedSource) => await VerifyCodeFixAsync(source, [expected], fixedSource); /// public static async Task VerifyCodeFixAsync( - [StringSyntax("c#-test")] string source, + [StringSyntax("c#")] string source, IEnumerable expected, - [StringSyntax("c#-test")] string fixedSource + [StringSyntax("c#")] string fixedSource ) { var test = new Test diff --git a/TUnit.Analyzers.Tests/XUnitClassFixtureAnalyzerTests.cs b/TUnit.Analyzers.Tests/XUnitClassFixtureAnalyzerTests.cs deleted file mode 100644 index 65cc38cfe0..0000000000 --- a/TUnit.Analyzers.Tests/XUnitClassFixtureAnalyzerTests.cs +++ /dev/null @@ -1,65 +0,0 @@ -using Verifier = TUnit.Analyzers.Tests.Verifiers.CSharpAnalyzerVerifier; -using CodeFixer = TUnit.Analyzers.Tests.Verifiers.CSharpCodeFixVerifier; - -namespace TUnit.Analyzers.Tests; - -public class XUnitClassFixtureAnalyzerTests -{ - [Test] - public async Task ClassFixture_Flagged() - { - await Verifier - .VerifyAnalyzerAsync( - """ - using Xunit; - - public class MyType; - - public class MyClass : {|#0:IClassFixture|} - { - [Fact] - public void MyTest() - { - } - } - """, - Verifier.Diagnostic(Rules.XunitClassFixtures).WithLocation(0) - ); - } - - [Test] - public async Task ClassFixture_Can_Be_Fixed() - { - await CodeFixer - .VerifyCodeFixAsync( - """ - using Xunit; - - public class MyType; - - public class MyClass(MyType myType) : {|#0:IClassFixture|} - { - [Fact] - public void MyTest() - { - } - } - """, - Verifier.Diagnostic(Rules.XunitClassFixtures).WithLocation(0), - """ - using Xunit; - - public class MyType; - - [ClassDataSource(Shared = SharedType.PerClass)] - public class MyClass(MyType myType) - { - [Fact] - public void MyTest() - { - } - } - """ - ); - } -} \ No newline at end of file diff --git a/TUnit.Analyzers.Tests/XUnitAttributesAnalyzerTests.cs b/TUnit.Analyzers.Tests/XUnitMigrationAnalyzerTests.cs similarity index 52% rename from TUnit.Analyzers.Tests/XUnitAttributesAnalyzerTests.cs rename to TUnit.Analyzers.Tests/XUnitMigrationAnalyzerTests.cs index 7f0e8a31f1..15154ba439 100644 --- a/TUnit.Analyzers.Tests/XUnitAttributesAnalyzerTests.cs +++ b/TUnit.Analyzers.Tests/XUnitMigrationAnalyzerTests.cs @@ -1,9 +1,9 @@ -using Verifier = TUnit.Analyzers.Tests.Verifiers.CSharpAnalyzerVerifier; -using CodeFixer = TUnit.Analyzers.Tests.Verifiers.CSharpCodeFixVerifier; +using Verifier = TUnit.Analyzers.Tests.Verifiers.CSharpAnalyzerVerifier; +using CodeFixer = TUnit.Analyzers.Tests.Verifiers.CSharpCodeFixVerifier; namespace TUnit.Analyzers.Tests; -public class XUnitAttributesAnalyzerTests +public class XUnitMigrationAnalyzerTests { [Arguments("Fact")] [Arguments("Theory")] @@ -15,17 +15,17 @@ public async Task Test_Attribute_Flagged(string attributeName) await Verifier .VerifyAnalyzerAsync( $$""" - using Xunit; + {|#0:using Xunit; public class MyClass { - [{|#0:{{attributeName}}|}] + [{{attributeName}}] public void MyTest() { } - } + }|} """, - Verifier.Diagnostic(Rules.XunitAttributes).WithLocation(0) + Verifier.Diagnostic(Rules.XunitMigration).WithLocation(0) ); } @@ -47,21 +47,20 @@ public async Task Test_Attributes_Can_Be_Fixed(string attribute, string expected await CodeFixer .VerifyCodeFixAsync( $$""" - using TUnit.Core; + {|#0:using TUnit.Core; using Xunit; public class MyClass { - [{|#0:{{attribute}}|}] + [{{attribute}}] public void MyTest() { } - } + }|} """, - Verifier.Diagnostic(Rules.XunitAttributes).WithLocation(0), + Verifier.Diagnostic(Rules.XunitMigration).WithLocation(0), $$""" using TUnit.Core; - using Xunit; public class MyClass { @@ -84,21 +83,20 @@ public async Task Skipped_Test_Attributes_Can_Be_Fixed(string attribute) await CodeFixer .VerifyCodeFixAsync( $$""" - using TUnit.Core; + {|#0:using TUnit.Core; using Xunit; public class MyClass { - [{|#0:{{attribute}}(Skip = "Reason")|}] + [{{attribute}}(Skip = "Reason")] public void MyTest() { } - } + }|} """, - Verifier.Diagnostic(Rules.XunitAttributes).WithLocation(0), + Verifier.Diagnostic(Rules.XunitMigration).WithLocation(0), $$""" using TUnit.Core; - using Xunit; public class MyClass { @@ -117,12 +115,12 @@ public async Task Collection_Attributes_Can_Be_Fixed() await CodeFixer .VerifyCodeFixAsync( """ - using TUnit.Core; + {|#0:using TUnit.Core; using Xunit; public class MyType; - [{|#1:Collection("MyCollection")|}] + [Collection("MyCollection")] public class MyClass { [Test] @@ -131,21 +129,18 @@ public void MyTest() } } - [{|#0:CollectionDefinition("MyCollection")|}] + [CollectionDefinition("MyCollection")] public class MyCollection : ICollectionFixture { - } + }|} """, [ - Verifier.Diagnostic(Rules.XunitAttributes).WithLocation(0), - Verifier.Diagnostic(Rules.XunitAttributes).WithLocation(1) + Verifier.Diagnostic(Rules.XunitMigration).WithLocation(0), ], """ using TUnit.Core; - using Xunit; public class MyType; - [ClassDataSource(Shared = SharedType.Keyed, Key = "MyCollection")] public class MyClass { @@ -156,7 +151,7 @@ public void MyTest() } [System.Obsolete] - public class MyCollection : ICollectionFixture + public class MyCollection { } """ @@ -169,12 +164,12 @@ public async Task Collection_Disable_Parallelism_Attributes_Can_Be_Fixed() await CodeFixer .VerifyCodeFixAsync( """ - using TUnit.Core; + {|#0:using TUnit.Core; using Xunit; public class MyType; - [{|#1:Collection("MyCollection")|}] + [Collection("MyCollection")] public class MyClass { [Test] @@ -183,21 +178,18 @@ public void MyTest() } } - [{|#0:CollectionDefinition("MyCollection", DisableParallelization = true)|}] + [CollectionDefinition("MyCollection", DisableParallelization = true)] public class MyCollection { - } + }|} """, [ - Verifier.Diagnostic(Rules.XunitAttributes).WithLocation(0), - Verifier.Diagnostic(Rules.XunitAttributes).WithLocation(1) + Verifier.Diagnostic(Rules.XunitMigration).WithLocation(0), ], """ using TUnit.Core; - using Xunit; public class MyType; - [NotInParallel] public class MyClass { @@ -221,12 +213,12 @@ public async Task Combined_Collection_Fixture_And_Disable_Parallelism_Attributes await CodeFixer .VerifyCodeFixAsync( """ - using TUnit.Core; + {|#0:using TUnit.Core; using Xunit; public class MyType; - [{|#1:Collection("MyCollection")|}] + [Collection("MyCollection")] public class MyClass { [Test] @@ -235,21 +227,18 @@ public void MyTest() } } - [{|#0:CollectionDefinition("MyCollection", DisableParallelization = true)|}] + [CollectionDefinition("MyCollection", DisableParallelization = true)] public class MyCollection : ICollectionFixture { - } + }|} """, [ - Verifier.Diagnostic(Rules.XunitAttributes).WithLocation(0), - Verifier.Diagnostic(Rules.XunitAttributes).WithLocation(1) + Verifier.Diagnostic(Rules.XunitMigration).WithLocation(0), ], """ using TUnit.Core; - using Xunit; public class MyType; - [NotInParallel, ClassDataSource(Shared = SharedType.Keyed, Key = "MyCollection")] public class MyClass { @@ -260,7 +249,7 @@ public void MyTest() } [System.Obsolete] - public class MyCollection : ICollectionFixture + public class MyCollection { } """ @@ -275,7 +264,7 @@ public async Task Assembly_Attributes_Can_Be_Fixed(string attribute, string expe await CodeFixer .VerifyCodeFixAsync( $$""" - using System; + {|#0:using System; using TUnit.Core; using Xunit; @@ -288,13 +277,12 @@ public class MyClass public void MyTest() { } - } + }|} """, - Verifier.Diagnostic(Rules.XunitAttributes).WithLocation(0), + Verifier.Diagnostic(Rules.XunitMigration).WithLocation(0), $$""" using System; using TUnit.Core; - using Xunit; [assembly: {{expected}}] namespace MyNamespace; @@ -309,4 +297,208 @@ public void MyTest() """ ); } + + [Test] + public async Task ClassFixture_Flagged() + { + await Verifier + .VerifyAnalyzerAsync( + """ + {|#0:using Xunit; + + public class MyType; + + public class MyClass : IClassFixture + { + [Fact] + public void MyTest() + { + } + }|} + """, + Verifier.Diagnostic(Rules.XunitMigration).WithLocation(0) + ); + } + + [Test] + public async Task ClassFixture_Can_Be_Fixed() + { + await CodeFixer + .VerifyCodeFixAsync( + """ + {|#0:using Xunit; + + public class MyType; + + public class MyClass(MyType myType) : IClassFixture + { + [Fact] + public void MyTest() + { + } + }|} + """, + Verifier.Diagnostic(Rules.XunitMigration).WithLocation(0), + """ + public class MyType; + [ClassDataSource(Shared = SharedType.PerClass)] + public class MyClass(MyType myType) + { + [Test] + public void MyTest() + { + } + } + """ + ); + } + + [Test] + public async Task Xunit_Directive_Flagged() + { + await Verifier + .VerifyAnalyzerAsync( + """ + {|#0:using TUnit.Core; + using Xunit; + + public class MyClass + { + [Test] + public void MyTest() + { + } + }|} + """, + Verifier.Diagnostic(Rules.XunitMigration).WithLocation(0) + ); + } + + [Test] + public async Task Xunit_Directive_Can_Be_Removed() + { + await CodeFixer + .VerifyCodeFixAsync( + """ + {|#0:using TUnit.Core; + using Xunit; + + public class MyClass + { + [Test] + public void MyTest() + { + } + }|} + """, + Verifier.Diagnostic(Rules.XunitMigration).WithLocation(0), + """ + using TUnit.Core; + + public class MyClass + { + [Test] + public void MyTest() + { + } + } + """ + ); + } + + [Test] + public async Task Test_Initialize_Can_Be_Converted() + { + await CodeFixer + .VerifyCodeFixAsync( + """ + {|#0:using TUnit.Core; + using Xunit; + + public class MyClass : IAsyncLifetime + { + public ValueTask InitializeAsync() + { + return default; + } + + public ValueTask DisposeAsync() + { + return default; + } + + [Fact] + public void MyTest() + { + } + }|} + """, + Verifier.Diagnostic(Rules.XunitMigration).WithLocation(0), + """ + using TUnit.Core; + + public class MyClass + { + [Test] + public void MyTest() + { + } + + [Before(Test)] + public Task InitializeAsync() + { + return default; + } + + [After(Test)] + public Task DisposeAsync() + { + return default; + } + } + """ + ); + } + + [Test] + public async Task NonTest_Initialize_Can_Be_Converted() + { + await CodeFixer + .VerifyCodeFixAsync( + """ + {|#0:using TUnit.Core; + using Xunit; + + public class MyClass : IAsyncLifetime + { + public ValueTask InitializeAsync() + { + return default; + } + + public ValueTask DisposeAsync() + { + return default; + } + }|} + """, + Verifier.Diagnostic(Rules.XunitMigration).WithLocation(0), + """ + using TUnit.Core; + + public class MyClass : IAsyncInitializer, IAsyncDisposable + { + public Task InitializeAsync() + { + return default; + } + + public ValueTask DisposeAsync() + { + return default; + } + } + """ + ); + } } \ No newline at end of file diff --git a/TUnit.Analyzers.Tests/XUnitUsingDirectiveAnalyzerTests.cs b/TUnit.Analyzers.Tests/XUnitUsingDirectiveAnalyzerTests.cs deleted file mode 100644 index 05304fd172..0000000000 --- a/TUnit.Analyzers.Tests/XUnitUsingDirectiveAnalyzerTests.cs +++ /dev/null @@ -1,60 +0,0 @@ -using Verifier = TUnit.Analyzers.Tests.Verifiers.CSharpAnalyzerVerifier; -using CodeFixer = TUnit.Analyzers.Tests.Verifiers.CSharpCodeFixVerifier; - -namespace TUnit.Analyzers.Tests; - -public class XUnitUsingDirectiveAnalyzerTests -{ - [Test] - public async Task Xunit_Directive_Flagged() - { - await Verifier - .VerifyAnalyzerAsync( - """ - using TUnit.Core; - {|#0:using Xunit;|} - - public class MyClass - { - [Test] - public void MyTest() - { - } - } - """, - Verifier.Diagnostic(Rules.XunitUsingDirectives).WithLocation(0) - ); - } - - [Test] - public async Task Xunit_Directive_Can_Be_Removed() - { - await CodeFixer - .VerifyCodeFixAsync( - """ - using TUnit.Core; - {|#0:using Xunit;|} - - public class MyClass - { - [Test] - public void MyTest() - { - } - } - """, - Verifier.Diagnostic(Rules.XunitUsingDirectives).WithLocation(0), - """ - using TUnit.Core; - - public class MyClass - { - [Test] - public void MyTest() - { - } - } - """ - ); - } -} \ No newline at end of file diff --git a/TUnit.Analyzers/Migrators/XUnitMigrationAnalyzer.cs b/TUnit.Analyzers/Migrators/XUnitMigrationAnalyzer.cs new file mode 100644 index 0000000000..2b49d1f4c1 --- /dev/null +++ b/TUnit.Analyzers/Migrators/XUnitMigrationAnalyzer.cs @@ -0,0 +1,97 @@ +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace TUnit.Analyzers; + +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public class XUnitMigrationAnalyzer : ConcurrentDiagnosticAnalyzer +{ + public override ImmutableArray SupportedDiagnostics { get; } = + ImmutableArray.Create(Rules.XunitMigration); + + protected override void InitializeInternal(AnalysisContext context) + { + context.RegisterSyntaxNodeAction(AnalyzeSyntax, SyntaxKind.CompilationUnit); + } + + private void AnalyzeSyntax(SyntaxNodeAnalysisContext context) + { + if (context.Node is not CompilationUnitSyntax compilationUnitSyntax) + { + return; + } + + var classDeclarationSyntaxes = compilationUnitSyntax + .DescendantNodes() + .OfType(); + + foreach (var classDeclarationSyntax in classDeclarationSyntaxes) + { + var symbol = context.SemanticModel.GetDeclaredSymbol(classDeclarationSyntax); + + if (symbol is null) + { + return; + } + + if (symbol.AllInterfaces.Any(i => i.ContainingNamespace.Name.StartsWith("Xunit"))) + { + context.ReportDiagnostic( + Diagnostic.Create(Rules.XunitMigration, context.Node.GetLocation()) + ); + + return; + } + + if (AnalyzeAttributes(context, symbol)) + { + return; + } + + foreach (var methodSymbol in symbol.GetMembers().OfType()) + { + if (AnalyzeAttributes(context, methodSymbol)) + { + return; + } + } + + var usingDirectiveSyntaxes = classDeclarationSyntax + .SyntaxTree + .GetCompilationUnitRoot() + .Usings; + + foreach (var usingDirectiveSyntax in usingDirectiveSyntaxes) + { + if (usingDirectiveSyntax.Name is QualifiedNameSyntax { Left: IdentifierNameSyntax { Identifier.Text: "Xunit" } } + or IdentifierNameSyntax { Identifier.Text: "Xunit" }) + { + context.ReportDiagnostic(Diagnostic.Create(Rules.XunitMigration, context.Node.GetLocation())); + return; + } + } + } + } + + private bool AnalyzeAttributes(SyntaxNodeAnalysisContext context, ISymbol symbol) + { + foreach (var attributeData in symbol.GetAttributes()) + { + var @namespace = attributeData.AttributeClass?.ContainingNamespace?.Name; + + if(@namespace == "Xunit") + { + context.ReportDiagnostic( + Diagnostic.Create(Rules.XunitMigration, context.Node.GetLocation()) + ); + + return true; + } + } + + return false; + } +} \ No newline at end of file diff --git a/TUnit.Analyzers/Resources.Designer.cs b/TUnit.Analyzers/Resources.Designer.cs index f0ff35565a..a46e536f81 100644 --- a/TUnit.Analyzers/Resources.Designer.cs +++ b/TUnit.Analyzers/Resources.Designer.cs @@ -1436,87 +1436,6 @@ internal static string TUnit0051Title { } } - /// - /// Looks up a localized string similar to xUnit attribute can be converted to TUnit attribute.. - /// - internal static string TUnit0052Description { - get { - return ResourceManager.GetString("TUnit0052Description", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to xUnit attribute can be converted to TUnit attribute. - /// - internal static string TUnit0052MessageFormat { - get { - return ResourceManager.GetString("TUnit0052MessageFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to xUnit attribute can be converted to TUnit attribute. - /// - internal static string TUnit0052Title { - get { - return ResourceManager.GetString("TUnit0052Title", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to xUnit class fixture can be converted to TUnit attribute.. - /// - internal static string TUnit0053Description { - get { - return ResourceManager.GetString("TUnit0053Description", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to xUnit class fixture can be converted to TUnit attribute. - /// - internal static string TUnit0053MessageFormat { - get { - return ResourceManager.GetString("TUnit0053MessageFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to xUnit class fixture can be converted to TUnit attribute. - /// - internal static string TUnit0053Title { - get { - return ResourceManager.GetString("TUnit0053Title", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to xUnit using directives can be removed once migrated to TUnit.. - /// - internal static string TUnit0054Description { - get { - return ResourceManager.GetString("TUnit0054Description", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to xUnit using directives can be removed once migrated to TUnit. - /// - internal static string TUnit0054MessageFormat { - get { - return ResourceManager.GetString("TUnit0054MessageFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to xUnit using directives can be removed once migrated to TUnit. - /// - internal static string TUnit0054Title { - get { - return ResourceManager.GetString("TUnit0054Title", resourceCulture); - } - } - /// /// Looks up a localized string similar to Overwriting the Console writer can break TUnit logging.. /// @@ -1570,5 +1489,32 @@ internal static string TUnit0056Title { return ResourceManager.GetString("TUnit0056Title", resourceCulture); } } + + /// + /// Looks up a localized string similar to xUnit code can be converted to TUnit code.. + /// + internal static string TUXU0001Description { + get { + return ResourceManager.GetString("TUXU0001Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to xUnit code can be converted to TUnit code. + /// + internal static string TUXU0001MessageFormat { + get { + return ResourceManager.GetString("TUXU0001MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to xUnit code can be converted to TUnit code. + /// + internal static string TUXU0001Title { + get { + return ResourceManager.GetString("TUXU0001Title", resourceCulture); + } + } } } diff --git a/TUnit.Analyzers/Resources.resx b/TUnit.Analyzers/Resources.resx index b3c1d255f4..96ff40f2c8 100644 --- a/TUnit.Analyzers/Resources.resx +++ b/TUnit.Analyzers/Resources.resx @@ -477,32 +477,14 @@ Type must be public - - xUnit attribute can be converted to TUnit attribute. + + xUnit code can be converted to TUnit code. - - xUnit attribute can be converted to TUnit attribute + + xUnit code can be converted to TUnit code - - xUnit attribute can be converted to TUnit attribute - - - xUnit class fixture can be converted to TUnit attribute. - - - xUnit class fixture can be converted to TUnit attribute - - - xUnit class fixture can be converted to TUnit attribute - - - xUnit using directives can be removed once migrated to TUnit. - - - xUnit using directives can be removed once migrated to TUnit - - - xUnit using directives can be removed once migrated to TUnit + + xUnit code can be converted to TUnit code Overwriting the Console writer can break TUnit logging. diff --git a/TUnit.Analyzers/Rules.cs b/TUnit.Analyzers/Rules.cs index e755888c74..e952fcc7b3 100644 --- a/TUnit.Analyzers/Rules.cs +++ b/TUnit.Analyzers/Rules.cs @@ -123,15 +123,9 @@ public static class Rules public static readonly DiagnosticDescriptor TypeMustBePublic = CreateDescriptor("TUnit0051", UsageCategory, DiagnosticSeverity.Error); - public static readonly DiagnosticDescriptor XunitAttributes = - CreateDescriptor("TUnit0052", UsageCategory, DiagnosticSeverity.Info); - - public static readonly DiagnosticDescriptor XunitClassFixtures = - CreateDescriptor("TUnit0053", UsageCategory, DiagnosticSeverity.Info); - - public static readonly DiagnosticDescriptor XunitUsingDirectives = - CreateDescriptor("TUnit0054", UsageCategory, DiagnosticSeverity.Info); - + public static readonly DiagnosticDescriptor XunitMigration = + CreateDescriptor("TUXU0001", UsageCategory, DiagnosticSeverity.Info); + public static readonly DiagnosticDescriptor OverwriteConsole = CreateDescriptor("TUnit0055", UsageCategory, DiagnosticSeverity.Warning); diff --git a/TUnit.Analyzers/XUnitAttributesAnalyzer.cs b/TUnit.Analyzers/XUnitAttributesAnalyzer.cs deleted file mode 100644 index e09da223ef..0000000000 --- a/TUnit.Analyzers/XUnitAttributesAnalyzer.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System.Collections.Immutable; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; - -namespace TUnit.Analyzers; - -[DiagnosticAnalyzer(LanguageNames.CSharp)] -public class XUnitAttributesAnalyzer : ConcurrentDiagnosticAnalyzer -{ - public override ImmutableArray SupportedDiagnostics { get; } = - ImmutableArray.Create(Rules.XunitAttributes); - - protected override void InitializeInternal(AnalysisContext context) - { - context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.Method, SymbolKind.NamedType); - // context.RegisterSyntaxNodeAction(AnalyzeAttribute, SyntaxKind.Attribute); - } - - private void AnalyzeSymbol(SymbolAnalysisContext context) - { - foreach (var attributeData in context.Symbol.GetAttributes()) - { - var @namespace = attributeData.AttributeClass?.ContainingNamespace?.Name; - - if(@namespace == "Xunit") - { - context.ReportDiagnostic( - Diagnostic.Create(Rules.XunitAttributes, attributeData.ApplicationSyntaxReference?.GetSyntax().GetLocation()) - ); - } - } - } - - private void AnalyzeAttribute(SyntaxNodeAnalysisContext context) - { - var attributeSyntax = (AttributeSyntax)context.Node; - - var attributeListSyntax = attributeSyntax.Parent as AttributeListSyntax; - - if (attributeListSyntax?.Target?.Identifier.ValueText is not "assembly") - { - return; - } - - var symbol = context.SemanticModel.GetSymbolInfo(attributeSyntax).Symbol; - - if (symbol is not IMethodSymbol) - { - return; - } - - if (symbol.ContainingNamespace.Name.StartsWith("Xunit")) - { - context.ReportDiagnostic( - Diagnostic.Create(Rules.XunitAttributes, attributeSyntax.GetLocation()) - ); - } - } -} \ No newline at end of file diff --git a/TUnit.Analyzers/XUnitClassFixtureAnalyzer.cs b/TUnit.Analyzers/XUnitClassFixtureAnalyzer.cs deleted file mode 100644 index 187a2db6e1..0000000000 --- a/TUnit.Analyzers/XUnitClassFixtureAnalyzer.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.Collections.Immutable; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; -using TUnit.Analyzers.Extensions; - -namespace TUnit.Analyzers; - -[DiagnosticAnalyzer(LanguageNames.CSharp)] -public class XUnitClassFixtureAnalyzer : ConcurrentDiagnosticAnalyzer -{ - public override ImmutableArray SupportedDiagnostics { get; } = - ImmutableArray.Create(Rules.XunitClassFixtures); - - protected override void InitializeInternal(AnalysisContext context) - { - context.RegisterSyntaxNodeAction(AnalyzeSyntax, SyntaxKind.SimpleBaseType); - } - - private void AnalyzeSyntax(SyntaxNodeAnalysisContext context) - { - if (context.Node is not SimpleBaseTypeSyntax simpleBaseTypeSyntax) - { - return; - } - - var interfaceSymbol = context.SemanticModel.GetSymbolInfo(simpleBaseTypeSyntax.Type).Symbol; - - if (interfaceSymbol is INamedTypeSymbol { TypeKind: TypeKind.Interface, IsGenericType: true } - && SymbolEqualityComparer.Default.Equals(interfaceSymbol.OriginalDefinition, - context.Compilation.GetTypeByMetadataName("Xunit.IClassFixture`1"))) - { - context.ReportDiagnostic( - Diagnostic.Create(Rules.XunitClassFixtures, simpleBaseTypeSyntax.GetLocation()) - ); - } - } -} \ No newline at end of file diff --git a/TUnit.Analyzers/XUnitUsingDirectiveAnalyzer.cs b/TUnit.Analyzers/XUnitUsingDirectiveAnalyzer.cs deleted file mode 100644 index c906cc45af..0000000000 --- a/TUnit.Analyzers/XUnitUsingDirectiveAnalyzer.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Collections.Immutable; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; - -namespace TUnit.Analyzers; - -[DiagnosticAnalyzer(LanguageNames.CSharp)] -public class XUnitUsingDirectiveAnalyzer : ConcurrentDiagnosticAnalyzer -{ - public override ImmutableArray SupportedDiagnostics { get; } = - ImmutableArray.Create(Rules.XunitUsingDirectives); - - protected override void InitializeInternal(AnalysisContext context) - { - context.RegisterSyntaxNodeAction(AnalyzeSyntax, SyntaxKind.UsingDirective); - } - - private void AnalyzeSyntax(SyntaxNodeAnalysisContext context) - { - if (context.Node is not UsingDirectiveSyntax usingDirectiveSyntax) - { - return; - } - - if (usingDirectiveSyntax.Name is QualifiedNameSyntax { Left: IdentifierNameSyntax { Identifier.Text: "Xunit" } } - or IdentifierNameSyntax { Identifier.Text: "Xunit" }) - { - context.ReportDiagnostic(Diagnostic.Create(Rules.XunitUsingDirectives, usingDirectiveSyntax.GetLocation())); - } - } -} \ No newline at end of file diff --git a/TUnit.Assertions.Analyzers.CodeFixers/Extensions/DocumentExtensions.cs b/TUnit.Assertions.Analyzers.CodeFixers/Extensions/DocumentExtensions.cs index 6201695af2..a14ccf7c47 100644 --- a/TUnit.Assertions.Analyzers.CodeFixers/Extensions/DocumentExtensions.cs +++ b/TUnit.Assertions.Analyzers.CodeFixers/Extensions/DocumentExtensions.cs @@ -16,7 +16,7 @@ public static async Task AddUsingDirectiveIfNotExistsAsyn } var usingDirective = SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(namespaceName).WithLeadingTrivia(SyntaxFactory.Space)) - .WithTrailingTrivia(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? SyntaxFactory.ElasticCarriageReturnLineFeed : SyntaxFactory.ElasticLineFeed); + .WithTrailingTrivia(SyntaxFactory.ElasticCarriageReturnLineFeed); return root.AddUsings(usingDirective); } diff --git a/TUnit.Assertions/TUnit.Assertions.props b/TUnit.Assertions/TUnit.Assertions.props index bdcc3c680e..e61a9c8714 100644 --- a/TUnit.Assertions/TUnit.Assertions.props +++ b/TUnit.Assertions/TUnit.Assertions.props @@ -1,11 +1,26 @@ - + + false + + + + + + <_XunitReference Include="@(PackageReference)" + Condition="'%(PackageReference.Identity)' == 'xunit' Or '%(PackageReference.Identity)' == 'xunit.v3'" /> + + + true + + + + true - + - + \ No newline at end of file diff --git a/TUnit.Core/TUnit.Core.props b/TUnit.Core/TUnit.Core.props index 4ebab0c7ae..18734affcd 100644 --- a/TUnit.Core/TUnit.Core.props +++ b/TUnit.Core/TUnit.Core.props @@ -2,11 +2,26 @@ + false + + + + + + <_XunitReference Include="@(PackageReference)" + Condition="'%(PackageReference.Identity)' == 'xunit' Or '%(PackageReference.Identity)' == 'xunit.v3'" /> + + + true + + + + true - - + + \ No newline at end of file diff --git a/TUnit.Pipeline/Modules/RunEngineTestsModule.cs b/TUnit.Pipeline/Modules/RunEngineTestsModule.cs index 1de6991573..d62335ec1a 100644 --- a/TUnit.Pipeline/Modules/RunEngineTestsModule.cs +++ b/TUnit.Pipeline/Modules/RunEngineTestsModule.cs @@ -12,6 +12,17 @@ namespace TUnit.Pipeline.Modules; [NotInParallel("DotNetTests")] [DependsOn] [DependsOn] +[DependsOn] +[DependsOn] +[DependsOn] +[DependsOn] +[DependsOn] +[DependsOn] +[DependsOn] +[DependsOn] +[DependsOn] +[DependsOn] +[DependsOn] public class RunEngineTestsModule : Module { protected override async Task ExecuteAsync(IPipelineContext context, CancellationToken cancellationToken) diff --git a/TUnit/TUnit.csproj b/TUnit/TUnit.csproj index 9a0fd47e34..3643eabc4e 100644 --- a/TUnit/TUnit.csproj +++ b/TUnit/TUnit.csproj @@ -7,16 +7,4 @@ - - \ No newline at end of file diff --git a/TUnit/TUnit.targets b/TUnit/TUnit.targets index 5a237a978c..db43a7c6df 100644 --- a/TUnit/TUnit.targets +++ b/TUnit/TUnit.targets @@ -5,14 +5,4 @@ latest - - - - - - - - - - \ No newline at end of file diff --git a/docs/docs/migration/xunit.md b/docs/docs/migration/xunit.md index f963ca8d89..5427c78ef1 100644 --- a/docs/docs/migration/xunit.md +++ b/docs/docs/migration/xunit.md @@ -25,25 +25,14 @@ In your csproj add: This is temporary - Just to make sure no types clash, and so the code fixers can distinguish between xUnit and TUnit types with similar names. -### Rebuild the project +#### Rebuild the project This ensures the TUnit packages have been restored and the analyzers should be loaded. -#### Run the code fixers via the dotnet CLI +#### Run the code fixer via the dotnet CLI -Running them in a specific order is recommended. -So try the following: +`dotnet format analyzers --severity info --diagnostics TUXU0001` -`dotnet format analyzers --severity info --diagnostics TUnit0052` - -`dotnet format analyzers --severity info --diagnostics TUnit0053` - -`dotnet format analyzers --severity info --diagnostics TUnitAssertions0009` - -Revert step `Remove the automatically added global usings` - -`dotnet format analyzers --severity info --diagnostics TUnit0054` - -`dotnet format analyzers --severity info --diagnostics TUnitAssertions0002` +#### Revert step `Remove the automatically added global usings` #### Perform any manual bits that are still necessary This bit's on you! You'll have to work out what still needs doing.