diff --git a/TUnit.Analyzers.CodeFixers/MatrixDataSourceCodeFixProvider.cs b/TUnit.Analyzers.CodeFixers/MatrixDataSourceCodeFixProvider.cs new file mode 100644 index 0000000000..e0caf920c4 --- /dev/null +++ b/TUnit.Analyzers.CodeFixers/MatrixDataSourceCodeFixProvider.cs @@ -0,0 +1,66 @@ +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.Editing; + +namespace TUnit.Analyzers.CodeFixers; + +[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(MatrixDataSourceCodeFixProvider)), Shared] +public class MatrixDataSourceCodeFixProvider : CodeFixProvider +{ + private const string Title = "Add [MatrixDataSource]"; + + public sealed override ImmutableArray FixableDiagnosticIds { get; } = + ImmutableArray.Create(Rules.MatrixDataSourceAttributeRequired.Id); + + public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + + public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + if (root is null) + { + return; + } + + foreach (var diagnostic in context.Diagnostics) + { + var node = root.FindNode(diagnostic.Location.SourceSpan); + var target = node.FirstAncestorOrSelf(n => n is MethodDeclarationSyntax or TypeDeclarationSyntax); + + if (target is null) + { + continue; + } + + context.RegisterCodeFix( + CodeAction.Create( + title: Title, + createChangedDocument: c => AddMatrixDataSourceAsync(context.Document, target, c), + equivalenceKey: Title), + diagnostic); + } + } + + private static async Task AddMatrixDataSourceAsync(Document document, SyntaxNode target, CancellationToken cancellationToken) + { + var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); + var attributeList = SyntaxFactory.AttributeList( + SyntaxFactory.SingletonSeparatedList( + SyntaxFactory.Attribute(SyntaxFactory.IdentifierName("MatrixDataSource")))); + + SyntaxNode updated = target switch + { + MethodDeclarationSyntax method => method.AddAttributeLists(attributeList), + TypeDeclarationSyntax type => type.AddAttributeLists(attributeList), + _ => throw new InvalidOperationException($"Unexpected node kind: {target.Kind()}"), + }; + + editor.ReplaceNode(target, updated); + return editor.GetChangedDocument(); + } +} diff --git a/TUnit.Analyzers.Tests/MatrixDataSourceCodeFixProviderTests.cs b/TUnit.Analyzers.Tests/MatrixDataSourceCodeFixProviderTests.cs new file mode 100644 index 0000000000..abee8815b3 --- /dev/null +++ b/TUnit.Analyzers.Tests/MatrixDataSourceCodeFixProviderTests.cs @@ -0,0 +1,86 @@ +using Verifier = TUnit.Analyzers.Tests.Verifiers.CSharpCodeFixVerifier< + TUnit.Analyzers.MatrixAnalyzer, + TUnit.Analyzers.CodeFixers.MatrixDataSourceCodeFixProvider>; + +namespace TUnit.Analyzers.Tests; + +public class MatrixDataSourceCodeFixProviderTests +{ + [Test] + public async Task Adds_MatrixDataSource_On_Method() + { + await Verifier.VerifyCodeFixAsync( + """ + using TUnit.Core; + + public class MyClass + { + [Test] + public void {|#0:MyTest|}( + [Matrix(1, 2, 3)] int value, + [Matrix(true, false)] bool flag) + { + } + } + """, + Verifier.Diagnostic(Rules.MatrixDataSourceAttributeRequired).WithLocation(0), + """ + using TUnit.Core; + + public class MyClass + { + [Test] + [MatrixDataSource] + public void MyTest( + [Matrix(1, 2, 3)] int value, + [Matrix(true, false)] bool flag) + { + } + } + """ + ); + } + + [Test] + public async Task Adds_MatrixDataSource_On_Class() + { + await Verifier.VerifyCodeFixAsync( + """ + using TUnit.Core; + + public class {|#0:MyClass|} + { + public MyClass( + [Matrix(1, 2)] int value, + [Matrix(true, false)] bool flag) + { + } + + [Test] + public void MyTest() + { + } + } + """, + Verifier.Diagnostic(Rules.MatrixDataSourceAttributeRequired).WithLocation(0), + """ + using TUnit.Core; + + [MatrixDataSource] + public class MyClass + { + public MyClass( + [Matrix(1, 2)] int value, + [Matrix(true, false)] bool flag) + { + } + + [Test] + public void MyTest() + { + } + } + """ + ); + } +}