From 0b9e5ab8203663709241f95a819765715f54ed67 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 28 Apr 2026 07:04:24 +0000
Subject: [PATCH 1/7] Initial plan
From 333fb36af55d69e9dc1e8378acdf5d1194503a58 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 28 Apr 2026 07:24:57 +0000
Subject: [PATCH 2/7] Add code fix for MSTEST0031
DoNotUseSystemDescriptionAttribute
Agent-Logs-Url: https://github.com/microsoft/testfx/sessions/bfc27008-89bf-43d2-8334-80122ee20056
Co-authored-by: Evangelink <11340282+Evangelink@users.noreply.github.com>
---
.../CodeFixResources.resx | 3 +
...DoNotUseSystemDescriptionAttributeFixer.cs | 160 ++++++++++++++++++
.../xlf/CodeFixResources.cs.xlf | 5 +
.../xlf/CodeFixResources.de.xlf | 5 +
.../xlf/CodeFixResources.es.xlf | 5 +
.../xlf/CodeFixResources.fr.xlf | 5 +
.../xlf/CodeFixResources.it.xlf | 5 +
.../xlf/CodeFixResources.ja.xlf | 5 +
.../xlf/CodeFixResources.ko.xlf | 5 +
.../xlf/CodeFixResources.pl.xlf | 5 +
.../xlf/CodeFixResources.pt-BR.xlf | 5 +
.../xlf/CodeFixResources.ru.xlf | 5 +
.../xlf/CodeFixResources.tr.xlf | 5 +
.../xlf/CodeFixResources.zh-Hans.xlf | 5 +
.../xlf/CodeFixResources.zh-Hant.xlf | 5 +
...SystemDescriptionAttributeAnalyzerTests.cs | 52 +++++-
16 files changed, 277 insertions(+), 3 deletions(-)
create mode 100644 src/Analyzers/MSTest.Analyzers.CodeFixes/DoNotUseSystemDescriptionAttributeFixer.cs
diff --git a/src/Analyzers/MSTest.Analyzers.CodeFixes/CodeFixResources.resx b/src/Analyzers/MSTest.Analyzers.CodeFixes/CodeFixResources.resx
index e21d53f3df..679cb9c2fd 100644
--- a/src/Analyzers/MSTest.Analyzers.CodeFixes/CodeFixResources.resx
+++ b/src/Analyzers/MSTest.Analyzers.CodeFixes/CodeFixResources.resx
@@ -213,4 +213,7 @@
Remove 'out' and 'ref' modifiers
+
+ Use 'DisplayName' property on '[TestMethod]' instead of '[Description]' attribute
+
\ 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..88ff9e4988
--- /dev/null
+++ b/src/Analyzers/MSTest.Analyzers.CodeFixes/DoNotUseSystemDescriptionAttributeFixer.cs
@@ -0,0 +1,160 @@
+// 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.Editing;
+
+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.UseTestMethodDisplayNameInsteadOfDescriptionAttributeFix,
+ createChangedDocument: c => ReplaceDescriptionAttributeAsync(context.Document, methodDeclaration, c),
+ equivalenceKey: nameof(DoNotUseSystemDescriptionAttributeFixer)),
+ diagnostic);
+ }
+
+ private static async Task ReplaceDescriptionAttributeAsync(Document document, MethodDeclarationSyntax methodDeclaration, CancellationToken cancellationToken)
+ {
+ SemanticModel semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
+
+ INamedTypeSymbol? testMethodAttributeSymbol = semanticModel.Compilation.GetTypeByMetadataName(WellKnownTypeNames.MicrosoftVisualStudioTestToolsUnitTestingTestMethodAttribute);
+ INamedTypeSymbol? descriptionAttributeSymbol = semanticModel.Compilation.GetTypeByMetadataName(WellKnownTypeNames.SystemDescriptionAttribute);
+
+ if (testMethodAttributeSymbol is null || descriptionAttributeSymbol is null)
+ {
+ return document;
+ }
+
+ AttributeSyntax? descriptionAttribute = null;
+ AttributeSyntax? testMethodAttribute = null;
+
+ foreach (AttributeListSyntax attributeList in methodDeclaration.AttributeLists)
+ {
+ foreach (AttributeSyntax attribute in attributeList.Attributes)
+ {
+ if (semanticModel.GetSymbolInfo(attribute, cancellationToken).Symbol is IMethodSymbol { ContainingType: { } containingType })
+ {
+ if (SymbolEqualityComparer.Default.Equals(containingType, descriptionAttributeSymbol))
+ {
+ descriptionAttribute = attribute;
+ }
+ else if (IsOrInheritsFrom(containingType, testMethodAttributeSymbol))
+ {
+ testMethodAttribute = attribute;
+ }
+ }
+ }
+ }
+
+ if (descriptionAttribute is null || testMethodAttribute is null)
+ {
+ return document;
+ }
+
+ DocumentEditor editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false);
+
+ // Add DisplayName = "text" to the [TestMethod] attribute (only if it doesn't already have DisplayName)
+ bool hasDisplayName = testMethodAttribute.ArgumentList?.Arguments.Any(
+ a => a.NameEquals?.Name.Identifier.ValueText == "DisplayName") == true;
+
+ if (!hasDisplayName && descriptionAttribute.ArgumentList?.Arguments.Count > 0)
+ {
+ ExpressionSyntax descriptionExpression = descriptionAttribute.ArgumentList.Arguments[0].Expression;
+
+ AttributeArgumentSyntax displayNameArg = SyntaxFactory.AttributeArgument(
+ SyntaxFactory.NameEquals(SyntaxFactory.IdentifierName("DisplayName")),
+ nameColon: null,
+ descriptionExpression);
+
+ AttributeSyntax newTestMethodAttribute = testMethodAttribute.ArgumentList is null
+ ? testMethodAttribute.WithArgumentList(
+ SyntaxFactory.AttributeArgumentList(
+ SyntaxFactory.SingletonSeparatedList(displayNameArg)))
+ : testMethodAttribute.WithArgumentList(
+ testMethodAttribute.ArgumentList.AddArguments(displayNameArg));
+
+ editor.ReplaceNode(testMethodAttribute, newTestMethodAttribute);
+ }
+
+ // Remove the [Description] attribute
+ if (descriptionAttribute.Parent is AttributeListSyntax containingAttributeList)
+ {
+ if (containingAttributeList.Attributes.Count == 1)
+ {
+ // Remove the entire attribute list
+ editor.RemoveNode(containingAttributeList);
+ }
+ else
+ {
+ // Remove just the attribute from the list
+ editor.ReplaceNode(
+ containingAttributeList,
+ containingAttributeList.RemoveNode(descriptionAttribute, SyntaxRemoveOptions.KeepLeadingTrivia)!);
+ }
+ }
+
+ return editor.GetChangedDocument();
+ }
+
+ private static bool IsOrInheritsFrom(INamedTypeSymbol? type, INamedTypeSymbol baseType)
+ {
+ INamedTypeSymbol? current = type;
+ while (current is not null)
+ {
+ if (SymbolEqualityComparer.Default.Equals(current, baseType))
+ {
+ return true;
+ }
+
+ current = current.BaseType;
+ }
+
+ return false;
+ }
+}
diff --git a/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.cs.xlf b/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.cs.xlf
index 68192b0c4e..c1c1a4c778 100644
--- a/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.cs.xlf
+++ b/src/Analyzers/MSTest.Analyzers.CodeFixes/xlf/CodeFixResources.cs.xlf
@@ -162,6 +162,11 @@
Použít atribut [OSCondition]
+
+ Use 'DisplayName' property on '[TestMethod]' instead of '[Description]' attribute
+ Use 'DisplayName' property on '[TestMethod]' instead of '[Description]' attribute
+
+