Skip to content
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

MSTEST0002: Add code fix #3554

Merged
merged 14 commits into from
Aug 15, 2024

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -135,4 +135,7 @@
<data name="UseAttributeOnTestMethodFix" xml:space="preserve">
<value>Add '[TestMethod]'</value>
</data>
<data name="TestClassShouldBeValidFix" xml:space="preserve">
<value>Fix test class signature</value>
</data>
</root>
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// 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 Microsoft.CodeAnalysis.Text;

using MSTest.Analyzers.Helpers;

namespace MSTest.Analyzers;

[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(TestClassShouldBeValidFixer))]
[Shared]
public sealed class TestClassShouldBeValidFixer : CodeFixProvider
{
public sealed override ImmutableArray<string> FixableDiagnosticIds { get; }
= ImmutableArray.Create(DiagnosticIds.TestClassShouldBeValidRuleId);

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];
TextSpan diagnosticSpan = diagnostic.Location.SourceSpan;

SyntaxToken syntaxToken = root.FindToken(diagnosticSpan.Start);
if (syntaxToken.Parent is null)
{
return;
}

ClassDeclarationSyntax declaration = syntaxToken.Parent.AncestorsAndSelf().OfType<ClassDeclarationSyntax>().FirstOrDefault();

// Register a code action that will invoke the fix.
context.RegisterCodeFix(
CodeAction.Create(
title: CodeFixResources.TestClassShouldBeValidFix,
createChangedDocument: c => FixClassDeclarationAsync(context.Document, root, declaration, c),
equivalenceKey: nameof(TestClassShouldBeValidFixer)),
diagnostic);
}

public static async Task<Document> FixClassDeclarationAsync(Document document, SyntaxNode root, ClassDeclarationSyntax classDeclaration, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();

// Get the SemanticModel and Compilation
SemanticModel semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false)
?? throw new InvalidOperationException("SemanticModel cannot be null.");
bool canDiscoverInternals = IsDiscoverInternalsAttributePresent(semanticModel);

DocumentEditor editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false);

// Remove the static modifier if it exists
SyntaxTokenList modifiers = SyntaxFactory.TokenList(
classDeclaration.Modifiers.Where(modifier => !modifier.IsKind(SyntaxKind.StaticKeyword)));

if (!classDeclaration.Modifiers.Any(SyntaxKind.PublicKeyword))
{
// Determine the visibility modifier
SyntaxToken visibilityModifier = canDiscoverInternals
? SyntaxFactory.Token(SyntaxKind.InternalKeyword)
: SyntaxFactory.Token(SyntaxKind.PublicKeyword);

modifiers = SyntaxFactory.TokenList(
modifiers.Where(modifier => !modifier.IsKind(SyntaxKind.PrivateKeyword) && !modifier.IsKind(SyntaxKind.InternalKeyword))).Add(visibilityModifier);
}

// Create a new class declaration with the updated modifiers.
ClassDeclarationSyntax newClassDeclaration = classDeclaration.WithModifiers(modifiers);
editor.ReplaceNode(classDeclaration, newClassDeclaration);
SyntaxNode newRoot = editor.GetChangedRoot();

return document.WithSyntaxRoot(newRoot);
}

private static bool IsDiscoverInternalsAttributePresent(SemanticModel semanticModel)
{
Compilation compilation = semanticModel.Compilation;
IAssemblySymbol assemblySymbol = compilation.Assembly;
var wellKnownTypeProvider = WellKnownTypeProvider.GetOrCreate(semanticModel.Compilation);
INamedTypeSymbol? discoverInternalsAttribute = wellKnownTypeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftVisualStudioTestToolsUnitTestingDiscoverInternalsAttribute);
foreach (AttributeData attribute in assemblySymbol.GetAttributes())
{
if (SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, discoverInternalsAttribute))
{
return true;
}
}

return false;
}
}
Original file line number Diff line number Diff line change
@@ -22,6 +22,11 @@
<target state="translated">Přidat [TestClass]</target>
<note />
</trans-unit>
<trans-unit id="TestClassShouldBeValidFix">
<source>Fix test class signature</source>
<target state="new">Fix test class signature</target>
<note />
</trans-unit>
<trans-unit id="TestMethodShouldBeValidFix">
<source>Fix test method signature</source>
<target state="translated">Opravit podpis testovací metody</target>
Original file line number Diff line number Diff line change
@@ -22,6 +22,11 @@
<target state="translated">„[TestClass]“ hinzufügen</target>
<note />
</trans-unit>
<trans-unit id="TestClassShouldBeValidFix">
<source>Fix test class signature</source>
<target state="new">Fix test class signature</target>
<note />
</trans-unit>
<trans-unit id="TestMethodShouldBeValidFix">
<source>Fix test method signature</source>
<target state="translated">Korrektur der Signatur der Testmethode</target>
Original file line number Diff line number Diff line change
@@ -22,6 +22,11 @@
<target state="translated">Agregar '[TestClass]'</target>
<note />
</trans-unit>
<trans-unit id="TestClassShouldBeValidFix">
<source>Fix test class signature</source>
<target state="new">Fix test class signature</target>
<note />
</trans-unit>
<trans-unit id="TestMethodShouldBeValidFix">
<source>Fix test method signature</source>
<target state="translated">Corregir firma del método de prueba</target>
Original file line number Diff line number Diff line change
@@ -22,6 +22,11 @@
<target state="translated">Ajouter « [TestClass] »</target>
<note />
</trans-unit>
<trans-unit id="TestClassShouldBeValidFix">
<source>Fix test class signature</source>
<target state="new">Fix test class signature</target>
<note />
</trans-unit>
<trans-unit id="TestMethodShouldBeValidFix">
<source>Fix test method signature</source>
<target state="translated">Corriger la signature de la méthode de test</target>
Original file line number Diff line number Diff line change
@@ -22,6 +22,11 @@
<target state="translated">Aggiungi '[TestClass]'</target>
<note />
</trans-unit>
<trans-unit id="TestClassShouldBeValidFix">
<source>Fix test class signature</source>
<target state="new">Fix test class signature</target>
<note />
</trans-unit>
<trans-unit id="TestMethodShouldBeValidFix">
<source>Fix test method signature</source>
<target state="translated">Correggi la firma del metodo di test</target>
Original file line number Diff line number Diff line change
@@ -22,6 +22,11 @@
<target state="translated">'[TestClass]' の追加</target>
<note />
</trans-unit>
<trans-unit id="TestClassShouldBeValidFix">
<source>Fix test class signature</source>
<target state="new">Fix test class signature</target>
<note />
</trans-unit>
<trans-unit id="TestMethodShouldBeValidFix">
<source>Fix test method signature</source>
<target state="translated">テスト メソッドのシグネチャを修正する</target>
Original file line number Diff line number Diff line change
@@ -22,6 +22,11 @@
<target state="translated">'[TestClass]' 추가</target>
<note />
</trans-unit>
<trans-unit id="TestClassShouldBeValidFix">
<source>Fix test class signature</source>
<target state="new">Fix test class signature</target>
<note />
</trans-unit>
<trans-unit id="TestMethodShouldBeValidFix">
<source>Fix test method signature</source>
<target state="translated">테스트 메서드 시그니처 수정</target>
Original file line number Diff line number Diff line change
@@ -22,6 +22,11 @@
<target state="translated">Dodaj element „[TestClass]”</target>
<note />
</trans-unit>
<trans-unit id="TestClassShouldBeValidFix">
<source>Fix test class signature</source>
<target state="new">Fix test class signature</target>
<note />
</trans-unit>
<trans-unit id="TestMethodShouldBeValidFix">
<source>Fix test method signature</source>
<target state="translated">Napraw podpis metody testowej</target>
Original file line number Diff line number Diff line change
@@ -22,6 +22,11 @@
<target state="translated">Adicionar '[TestClass]'</target>
<note />
</trans-unit>
<trans-unit id="TestClassShouldBeValidFix">
<source>Fix test class signature</source>
<target state="new">Fix test class signature</target>
<note />
</trans-unit>
<trans-unit id="TestMethodShouldBeValidFix">
<source>Fix test method signature</source>
<target state="translated">Corrigir a assinatura do método de teste</target>
Original file line number Diff line number Diff line change
@@ -22,6 +22,11 @@
<target state="translated">Добавить '[TestClass]'</target>
<note />
</trans-unit>
<trans-unit id="TestClassShouldBeValidFix">
<source>Fix test class signature</source>
<target state="new">Fix test class signature</target>
<note />
</trans-unit>
<trans-unit id="TestMethodShouldBeValidFix">
<source>Fix test method signature</source>
<target state="translated">Исправить подпись метода теста</target>
Original file line number Diff line number Diff line change
@@ -22,6 +22,11 @@
<target state="translated">'[TestClass]' Ekle</target>
<note />
</trans-unit>
<trans-unit id="TestClassShouldBeValidFix">
<source>Fix test class signature</source>
<target state="new">Fix test class signature</target>
<note />
</trans-unit>
<trans-unit id="TestMethodShouldBeValidFix">
<source>Fix test method signature</source>
<target state="translated">Test yöntemi imzasını düzeltin</target>
Original file line number Diff line number Diff line change
@@ -22,6 +22,11 @@
<target state="translated">添加“[TestClass]”</target>
<note />
</trans-unit>
<trans-unit id="TestClassShouldBeValidFix">
<source>Fix test class signature</source>
<target state="new">Fix test class signature</target>
<note />
</trans-unit>
<trans-unit id="TestMethodShouldBeValidFix">
<source>Fix test method signature</source>
<target state="translated">修复测试方法签名</target>
Original file line number Diff line number Diff line change
@@ -22,6 +22,11 @@
<target state="translated">新增 '[TestClass]'</target>
<note />
</trans-unit>
<trans-unit id="TestClassShouldBeValidFix">
<source>Fix test class signature</source>
<target state="new">Fix test class signature</target>
<note />
</trans-unit>
<trans-unit id="TestMethodShouldBeValidFix">
<source>Fix test method signature</source>
<target state="translated">修正測試方法簽章</target>
24 changes: 3 additions & 21 deletions src/Analyzers/MSTest.Analyzers/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 2 additions & 8 deletions src/Analyzers/MSTest.Analyzers/Resources.resx
Original file line number Diff line number Diff line change
@@ -325,14 +325,8 @@ The type declaring these methods should also respect the following rules:
- it should not be 'static' (except if it contains only 'AssemblyInitialize' and/or 'AssemblyCleanup' methods)
- it should not be generic.</value>
</data>
<data name="TestClassShouldBeValidMessageFormat_NotStatic" xml:space="preserve">
<value>Test class '{0}' should not be 'static'</value>
</data>
<data name="TestClassShouldBeValidMessageFormat_Public" xml:space="preserve">
<value>Test class '{0}' should be 'public'</value>
</data>
<data name="TestClassShouldBeValidMessageFormat_PublicOrInternal" xml:space="preserve">
<value>Test class '{0}' should be 'public' or 'internal'</value>
<data name="TestClassShouldBeValidMessageFormat" xml:space="preserve">
<value>Test class '{0}' should be valid</value>
</data>
<data name="TestClassShouldBeValidTitle" xml:space="preserve">
<value>Test classes should have valid layout</value>
19 changes: 9 additions & 10 deletions src/Analyzers/MSTest.Analyzers/TestClassShouldBeValidAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -20,9 +20,9 @@ public sealed class TestClassShouldBeValidAnalyzer : DiagnosticAnalyzer
{
private static readonly LocalizableResourceString Title = new(nameof(Resources.TestClassShouldBeValidTitle), Resources.ResourceManager, typeof(Resources));
private static readonly LocalizableResourceString Description = new(nameof(Resources.TestClassShouldBeValidDescription), Resources.ResourceManager, typeof(Resources));
private static readonly LocalizableResourceString MessageFormat = new(nameof(Resources.TestClassShouldBeValidMessageFormat_Public), Resources.ResourceManager, typeof(Resources));
private static readonly LocalizableResourceString MessageFormat = new(nameof(Resources.TestClassShouldBeValidMessageFormat), Resources.ResourceManager, typeof(Resources));

internal static readonly DiagnosticDescriptor PublicRule = DiagnosticDescriptorHelper.Create(
internal static readonly DiagnosticDescriptor TestClassShouldBeValidRule = DiagnosticDescriptorHelper.Create(
DiagnosticIds.TestClassShouldBeValidRuleId,
Title,
MessageFormat,
@@ -31,11 +31,8 @@ public sealed class TestClassShouldBeValidAnalyzer : DiagnosticAnalyzer
DiagnosticSeverity.Warning,
isEnabledByDefault: true);

internal static readonly DiagnosticDescriptor PublicOrInternalRule = PublicRule.WithMessage(new(nameof(Resources.TestClassShouldBeValidMessageFormat_PublicOrInternal), Resources.ResourceManager, typeof(Resources)));
internal static readonly DiagnosticDescriptor NotStaticRule = PublicRule.WithMessage(new(nameof(Resources.TestClassShouldBeValidMessageFormat_NotStatic), Resources.ResourceManager, typeof(Resources)));

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; }
= ImmutableArray.Create(PublicRule);
= ImmutableArray.Create(TestClassShouldBeValidRule);

public override void Initialize(AnalysisContext context)
{
@@ -75,11 +72,13 @@ private static void AnalyzeSymbol(SymbolAnalysisContext context, INamedTypeSymbo
{
if (!canDiscoverInternals && resultantVisibility != SymbolVisibility.Public)
{
context.ReportDiagnostic(namedTypeSymbol.CreateDiagnostic(PublicRule, namedTypeSymbol.Name));
context.ReportDiagnostic(namedTypeSymbol.CreateDiagnostic(TestClassShouldBeValidRule, namedTypeSymbol.Name));
return;
}
else if (canDiscoverInternals && resultantVisibility == SymbolVisibility.Private)
{
context.ReportDiagnostic(namedTypeSymbol.CreateDiagnostic(PublicOrInternalRule, namedTypeSymbol.Name));
context.ReportDiagnostic(namedTypeSymbol.CreateDiagnostic(TestClassShouldBeValidRule, namedTypeSymbol.Name));
return;
}
}

@@ -106,10 +105,10 @@ private static void AnalyzeSymbol(SymbolAnalysisContext context, INamedTypeSymbo
|| SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, classCleanupAttributeSymbol)
|| attribute.AttributeClass.Inherits(testMethodAttributeSymbol))
{
context.ReportDiagnostic(namedTypeSymbol.CreateDiagnostic(NotStaticRule, namedTypeSymbol.Name));
context.ReportDiagnostic(namedTypeSymbol.CreateDiagnostic(TestClassShouldBeValidRule, namedTypeSymbol.Name));

// We only need to report once per class.
break;
return;
}
}
}
Loading