-
Notifications
You must be signed in to change notification settings - Fork 293
Add code fix for MSTEST0031 — DoNotUseSystemDescriptionAttribute #7898
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
0b9e5ab
333fb36
aace8a7
2b6086a
0cc145c
18b3fdb
61a2df4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,112 @@ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
|
|
||
| using System.Collections.Immutable; | ||
| using System.Composition; | ||
|
|
||
| using Analyzer.Utilities; | ||
|
|
||
| using Microsoft.CodeAnalysis; | ||
| using Microsoft.CodeAnalysis.CodeActions; | ||
| using Microsoft.CodeAnalysis.CodeFixes; | ||
| using Microsoft.CodeAnalysis.CSharp; | ||
| using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
| using Microsoft.CodeAnalysis.Simplification; | ||
|
|
||
| using MSTest.Analyzers.Helpers; | ||
|
|
||
| namespace MSTest.Analyzers; | ||
|
|
||
| /// <summary> | ||
| /// Code fixer for <see cref="DoNotUseSystemDescriptionAttributeAnalyzer"/>. | ||
| /// </summary> | ||
| [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(DoNotUseSystemDescriptionAttributeFixer))] | ||
| [Shared] | ||
| public sealed class DoNotUseSystemDescriptionAttributeFixer : CodeFixProvider | ||
| { | ||
| /// <inheritdoc /> | ||
| public override ImmutableArray<string> FixableDiagnosticIds { get; } | ||
| = ImmutableArray.Create(DiagnosticIds.DoNotUseSystemDescriptionAttributeRuleId); | ||
|
|
||
| /// <inheritdoc /> | ||
| public override FixAllProvider GetFixAllProvider() | ||
| // See https://github.com/dotnet/roslyn/blob/main/docs/analyzers/FixAllProvider.md for more information on Fix All Providers | ||
| => WellKnownFixAllProviders.BatchFixer; | ||
|
|
||
| /// <inheritdoc /> | ||
| public override async Task RegisterCodeFixesAsync(CodeFixContext context) | ||
| { | ||
| SyntaxNode root = await context.Document.GetRequiredSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); | ||
|
|
||
| Diagnostic diagnostic = context.Diagnostics[0]; | ||
| SyntaxToken syntaxToken = root.FindToken(diagnostic.Location.SourceSpan.Start); | ||
| if (syntaxToken.Parent is null) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| MethodDeclarationSyntax? methodDeclaration = syntaxToken.Parent.AncestorsAndSelf().OfType<MethodDeclarationSyntax>().FirstOrDefault(); | ||
| if (methodDeclaration is null) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| context.RegisterCodeFix( | ||
| CodeAction.Create( | ||
| title: CodeFixResources.UseMSTestDescriptionAttributeInsteadFix, | ||
| createChangedDocument: c => ReplaceWithMSTestDescriptionAttributeAsync(context.Document, methodDeclaration, c), | ||
| equivalenceKey: nameof(DoNotUseSystemDescriptionAttributeFixer)), | ||
| diagnostic); | ||
| } | ||
|
|
||
| private static async Task<Document> ReplaceWithMSTestDescriptionAttributeAsync(Document document, MethodDeclarationSyntax methodDeclaration, CancellationToken cancellationToken) | ||
| { | ||
| SemanticModel semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); | ||
|
|
||
| INamedTypeSymbol? systemDescriptionAttributeSymbol = semanticModel.Compilation.GetTypeByMetadataName(WellKnownTypeNames.SystemDescriptionAttribute); | ||
|
|
||
| if (systemDescriptionAttributeSymbol is null) | ||
| { | ||
| return document; | ||
| } | ||
|
|
||
| AttributeSyntax? systemDescriptionAttribute = null; | ||
|
|
||
| foreach (AttributeListSyntax attributeList in methodDeclaration.AttributeLists) | ||
| { | ||
| foreach (AttributeSyntax attribute in attributeList.Attributes) | ||
| { | ||
| if (semanticModel.GetSymbolInfo(attribute, cancellationToken).Symbol is IMethodSymbol { ContainingType: { } containingType } | ||
| && SymbolEqualityComparer.Default.Equals(containingType, systemDescriptionAttributeSymbol)) | ||
| { | ||
| systemDescriptionAttribute = attribute; | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| if (systemDescriptionAttribute is not null) | ||
| { | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| if (systemDescriptionAttribute is null) | ||
| { | ||
| return document; | ||
| } | ||
|
|
||
| // Replace the System.ComponentModel.Description attribute name with the fully-qualified MSTest Description | ||
| // attribute name, annotated for simplification. The Simplifier will reduce it to the simple name if the | ||
| // MSTest namespace is already in scope and there is no ambiguity; otherwise it keeps the fully-qualified form. | ||
| NameSyntax msTestDescriptionName = SyntaxFactory.ParseName(WellKnownTypeNames.MicrosoftVisualStudioTestToolsUnitTestingDescriptionAttribute) | ||
| .WithTriviaFrom(systemDescriptionAttribute.Name) | ||
| .WithAdditionalAnnotations(Simplifier.Annotation); | ||
|
|
||
| AttributeSyntax newAttribute = systemDescriptionAttribute.WithName(msTestDescriptionName); | ||
|
|
||
| SyntaxNode root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); | ||
| Document updatedDocument = document.WithSyntaxRoot(root.ReplaceNode(systemDescriptionAttribute, newAttribute)); | ||
|
|
||
| return await Simplifier.ReduceAsync(updatedDocument, cancellationToken: cancellationToken).ConfigureAwait(false); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,17 +1,17 @@ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
|
|
||
| using VerifyCS = MSTest.Analyzers.Test.CSharpCodeFixVerifier< | ||
| MSTest.Analyzers.DoNotUseSystemDescriptionAttributeAnalyzer, | ||
| Microsoft.CodeAnalysis.Testing.EmptyCodeFixProvider>; | ||
| MSTest.Analyzers.DoNotUseSystemDescriptionAttributeFixer>; | ||
|
|
||
| namespace MSTest.Analyzers.Test; | ||
|
|
||
| [TestClass] | ||
| public sealed class DoNotUseSystemDescriptionAttributeAnalyzerTests | ||
| { | ||
| [TestMethod] | ||
| public async Task WhenTestMethodHasSystemDescriptionAttribute_Diagnostic() | ||
| public async Task WhenTestMethodHasFullyQualifiedSystemDescriptionAttribute_Diagnostic() | ||
| { | ||
| string code = """ | ||
| using Microsoft.VisualStudio.TestTools.UnitTesting; | ||
|
|
@@ -27,7 +27,87 @@ public class MyTestClass | |
| } | ||
| """; | ||
|
|
||
| await VerifyCS.VerifyAnalyzerAsync(code); | ||
| string fixedCode = """ | ||
| using Microsoft.VisualStudio.TestTools.UnitTesting; | ||
|
|
||
| [TestClass] | ||
| public class MyTestClass | ||
| { | ||
| [TestMethod] | ||
| [Description("Description")] | ||
| public void MyTestMethod() | ||
| { | ||
| } | ||
| } | ||
| """; | ||
|
|
||
| await VerifyCS.VerifyCodeFixAsync(code, fixedCode); | ||
| } | ||
|
|
||
| [TestMethod] | ||
|
Evangelink marked this conversation as resolved.
|
||
| public async Task WhenTestMethodHasSystemDescriptionAttributeWithSystemComponentModelUsing_UsesFullyQualifiedMSTestDescription() | ||
| { | ||
| string code = """ | ||
| using System.ComponentModel; | ||
| using Microsoft.VisualStudio.TestTools.UnitTesting; | ||
|
|
||
| [TestClass] | ||
| public class MyTestClass | ||
| { | ||
| [TestMethod] | ||
| [System.ComponentModel.Description("Description")] | ||
| public void [|MyTestMethod|]() | ||
| { | ||
| } | ||
| } | ||
| """; | ||
|
|
||
| string fixedCode = """ | ||
| using System.ComponentModel; | ||
| using Microsoft.VisualStudio.TestTools.UnitTesting; | ||
|
|
||
| [TestClass] | ||
| public class MyTestClass | ||
| { | ||
| [TestMethod] | ||
| [Microsoft.VisualStudio.TestTools.UnitTesting.Description("Description")] | ||
| public void MyTestMethod() | ||
| { | ||
| } | ||
| } | ||
| """; | ||
|
|
||
| await VerifyCS.VerifyCodeFixAsync(code, fixedCode); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Coverage] The PR description explicitly calls out a key scenario that is not tested: the user writes the short form Impact: If the fixer silently fails (returns the original document) or produces incorrect output for the short-form input, no test would catch it. Suggestion: Add a complementary test case: [TestMethod]
public async Task WhenTestMethodHasShortFormDescriptionAttributeWithSystemComponentModelUsing_UsesFullyQualifiedMSTestDescription()
{
string code = """
using System.ComponentModel;
using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class MyTestClass
{
[TestMethod]
[Description("Description")]
public void [|MyTestMethod|]()
{
}
}
""";
string fixedCode = """
using System.ComponentModel;
using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class MyTestClass
{
[TestMethod]
[Microsoft.VisualStudio.TestTools.UnitTesting.Description("Description")]
public void MyTestMethod()
{
}
}
""";
await VerifyCS.VerifyCodeFixAsync(code, fixedCode);
}This is the scenario described in the PR body under "Before / After (with |
||
| } | ||
|
|
||
| [TestMethod] | ||
| public async Task WhenTestMethodHasSystemDescriptionAttributeWithoutUnitTestingUsing_UsesFullyQualifiedMSTestDescription() | ||
| { | ||
| string code = """ | ||
| [Microsoft.VisualStudio.TestTools.UnitTesting.TestClass] | ||
| public class MyTestClass | ||
| { | ||
| [Microsoft.VisualStudio.TestTools.UnitTesting.TestMethod] | ||
| [System.ComponentModel.Description("Description")] | ||
| public void [|MyTestMethod|]() | ||
| { | ||
| } | ||
| } | ||
| """; | ||
|
|
||
| string fixedCode = """ | ||
| [Microsoft.VisualStudio.TestTools.UnitTesting.TestClass] | ||
| public class MyTestClass | ||
| { | ||
| [Microsoft.VisualStudio.TestTools.UnitTesting.TestMethod] | ||
| [Microsoft.VisualStudio.TestTools.UnitTesting.Description("Description")] | ||
| public void MyTestMethod() | ||
| { | ||
| } | ||
| } | ||
| """; | ||
|
|
||
| await VerifyCS.VerifyCodeFixAsync(code, fixedCode); | ||
| } | ||
|
|
||
| [TestMethod] | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.