diff --git a/src/Analyzers/MSTest.Analyzers.CodeFixes/CodeFixResources.resx b/src/Analyzers/MSTest.Analyzers.CodeFixes/CodeFixResources.resx
index 6258c66e21..1d11cc494a 100644
--- a/src/Analyzers/MSTest.Analyzers.CodeFixes/CodeFixResources.resx
+++ b/src/Analyzers/MSTest.Analyzers.CodeFixes/CodeFixResources.resx
@@ -216,4 +216,7 @@
Remove duplicate 'DataRow'
+
+ Use MSTest 'Description' attribute instead
+
\ No newline at end of file
diff --git a/src/Analyzers/MSTest.Analyzers.CodeFixes/DoNotUseSystemDescriptionAttributeFixer.cs b/src/Analyzers/MSTest.Analyzers.CodeFixes/DoNotUseSystemDescriptionAttributeFixer.cs
new file mode 100644
index 0000000000..a0f223ffb3
--- /dev/null
+++ b/src/Analyzers/MSTest.Analyzers.CodeFixes/DoNotUseSystemDescriptionAttributeFixer.cs
@@ -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;
+
+///
+/// Code fixer for .
+///
+[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(DoNotUseSystemDescriptionAttributeFixer))]
+[Shared]
+public sealed class DoNotUseSystemDescriptionAttributeFixer : CodeFixProvider
+{
+ ///
+ public override ImmutableArray FixableDiagnosticIds { get; }
+ = ImmutableArray.Create(DiagnosticIds.DoNotUseSystemDescriptionAttributeRuleId);
+
+ ///
+ 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;
+
+ ///
+ 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().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 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);
+ }
+}
diff --git a/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.cs.xlf b/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.cs.xlf
index 80dd2445d2..722afbbd15 100644
--- a/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.cs.xlf
+++ b/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.cs.xlf
@@ -157,6 +157,11 @@
Místo řetězcového argumentu použijte vlastnost DisplayName
+
+ Use MSTest 'Description' attribute instead
+ Use MSTest 'Description' attribute instead
+
+
Use '{0}'
Použít {0}
diff --git a/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.de.xlf b/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.de.xlf
index 9535d2a0dd..0a5a4b1fea 100644
--- a/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.de.xlf
+++ b/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.de.xlf
@@ -157,6 +157,11 @@
Verwenden Sie die Eigenschaft „DisplayName“ anstelle eines Zeichenfolgenarguments.
+
+ Use MSTest 'Description' attribute instead
+ Use MSTest 'Description' attribute instead
+
+
Use '{0}'
"{0}" verwenden
diff --git a/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.es.xlf b/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.es.xlf
index 798ff759f0..bd6aaeae95 100644
--- a/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.es.xlf
+++ b/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.es.xlf
@@ -157,6 +157,11 @@
Usar la propiedad "DisplayName" en lugar del argumento de cadena
+
+ Use MSTest 'Description' attribute instead
+ Use MSTest 'Description' attribute instead
+
+
Use '{0}'
Usar "{0}"
diff --git a/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.fr.xlf b/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.fr.xlf
index 0e6304383a..3925d6c798 100644
--- a/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.fr.xlf
+++ b/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.fr.xlf
@@ -157,6 +157,11 @@
Utilisez la propriété « DisplayName » au lieu d’un argument de type chaîne
+
+ Use MSTest 'Description' attribute instead
+ Use MSTest 'Description' attribute instead
+
+
Use '{0}'
Utiliser « {0} »
diff --git a/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.it.xlf b/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.it.xlf
index 16c7156765..30882dc38e 100644
--- a/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.it.xlf
+++ b/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.it.xlf
@@ -157,6 +157,11 @@
Usare la proprietà 'DisplayName' invece di un argomento stringa
+
+ Use MSTest 'Description' attribute instead
+ Use MSTest 'Description' attribute instead
+
+
Use '{0}'
Usa '{0}'
diff --git a/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.ja.xlf b/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.ja.xlf
index d954cfb4b6..a334f49ac1 100644
--- a/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.ja.xlf
+++ b/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.ja.xlf
@@ -157,6 +157,11 @@
文字列引数の代わりに 'DisplayName' プロパティを使用する
+
+ Use MSTest 'Description' attribute instead
+ Use MSTest 'Description' attribute instead
+
+
Use '{0}'
'{0}' を使用します
diff --git a/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.ko.xlf b/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.ko.xlf
index 8124039f0f..4da44421c0 100644
--- a/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.ko.xlf
+++ b/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.ko.xlf
@@ -157,6 +157,11 @@
문자열 인수 대신 'DisplayName' 속성 사용
+
+ Use MSTest 'Description' attribute instead
+ Use MSTest 'Description' attribute instead
+
+
Use '{0}'
'{0}' 사용
diff --git a/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.pl.xlf b/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.pl.xlf
index 4989c1549d..cf68f15258 100644
--- a/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.pl.xlf
+++ b/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.pl.xlf
@@ -157,6 +157,11 @@
Użyj właściwości „DisplayName” zamiast argumentu ciągu
+
+ Use MSTest 'Description' attribute instead
+ Use MSTest 'Description' attribute instead
+
+
Use '{0}'
Użyj „{0}”
diff --git a/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.pt-BR.xlf b/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.pt-BR.xlf
index adcaf792ad..a293b3c41e 100644
--- a/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.pt-BR.xlf
+++ b/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.pt-BR.xlf
@@ -157,6 +157,11 @@
Usar a propriedade "DisplayName" em vez do argumento de cadeia de caracteres
+
+ Use MSTest 'Description' attribute instead
+ Use MSTest 'Description' attribute instead
+
+
Use '{0}'
Usar '{0}'
diff --git a/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.ru.xlf b/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.ru.xlf
index 1d407ee96e..47374accfd 100644
--- a/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.ru.xlf
+++ b/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.ru.xlf
@@ -157,6 +157,11 @@
Использовать свойство "DisplayName" вместо строкового аргумента
+
+ Use MSTest 'Description' attribute instead
+ Use MSTest 'Description' attribute instead
+
+
Use '{0}'
Использовать "{0}"
diff --git a/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.tr.xlf b/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.tr.xlf
index 5dde45c0a7..4774553d44 100644
--- a/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.tr.xlf
+++ b/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.tr.xlf
@@ -157,6 +157,11 @@
Dize bağımsız değişkeni yerine 'DisplayName' özelliğini kullanın
+
+ Use MSTest 'Description' attribute instead
+ Use MSTest 'Description' attribute instead
+
+
Use '{0}'
'{0}' kullan
diff --git a/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.zh-Hans.xlf b/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.zh-Hans.xlf
index 8b7e0ad15d..1464045a21 100644
--- a/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.zh-Hans.xlf
+++ b/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.zh-Hans.xlf
@@ -157,6 +157,11 @@
使用属性‘DisplayName’替代字符串参数
+
+ Use MSTest 'Description' attribute instead
+ Use MSTest 'Description' attribute instead
+
+
Use '{0}'
使用“{0}”
diff --git a/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.zh-Hant.xlf b/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.zh-Hant.xlf
index 4968d57da8..6c2e650c45 100644
--- a/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.zh-Hant.xlf
+++ b/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.zh-Hant.xlf
@@ -157,6 +157,11 @@
使用 'DisplayName' 屬性取代字串引數
+
+ Use MSTest 'Description' attribute instead
+ Use MSTest 'Description' attribute instead
+
+
Use '{0}'
使用 '{0}'
diff --git a/test/UnitTests/MSTest.Analyzers.UnitTests/DoNotUseSystemDescriptionAttributeAnalyzerTests.cs b/test/UnitTests/MSTest.Analyzers.UnitTests/DoNotUseSystemDescriptionAttributeAnalyzerTests.cs
index f927d76c1c..b8e88b2e19 100644
--- a/test/UnitTests/MSTest.Analyzers.UnitTests/DoNotUseSystemDescriptionAttributeAnalyzerTests.cs
+++ b/test/UnitTests/MSTest.Analyzers.UnitTests/DoNotUseSystemDescriptionAttributeAnalyzerTests.cs
@@ -1,9 +1,9 @@
-// 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;
@@ -11,7 +11,7 @@ namespace MSTest.Analyzers.Test;
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]
+ 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);
+ }
+
+ [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]