Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
Rule ID | Category | Severity | Notes
--------|----------|----------|-------
MSTEST0058 | Usage | Info | AvoidAssertsInCatchBlocksAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/core/testing/mstest-analyzers/mstest0058)
MSTEST0059 | Usage | Warning | DuplicateTestMethodAttributeAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/core/testing/mstest-analyzers/mstest0059)
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// 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 Analyzer.Utilities.Extensions;

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;

using MSTest.Analyzers.Helpers;

namespace MSTest.Analyzers;

/// <summary>
/// MSTEST0059: <inheritdoc cref="Resources.DuplicateTestMethodAttributeTitle"/>.
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
public sealed class DuplicateTestMethodAttributeAnalyzer : DiagnosticAnalyzer
{
internal static readonly DiagnosticDescriptor Rule = DiagnosticDescriptorHelper.Create(
DiagnosticIds.DuplicateTestMethodAttributeRuleId,
new LocalizableResourceString(nameof(Resources.DuplicateTestMethodAttributeTitle), Resources.ResourceManager, typeof(Resources)),
new LocalizableResourceString(nameof(Resources.DuplicateTestMethodAttributeMessageFormat), Resources.ResourceManager, typeof(Resources)),
new LocalizableResourceString(nameof(Resources.DuplicateTestMethodAttributeDescription), Resources.ResourceManager, typeof(Resources)),
Category.Usage,
DiagnosticSeverity.Warning,
isEnabledByDefault: true);

/// <inheritdoc />
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(Rule);

/// <inheritdoc />
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();

context.RegisterCompilationStartAction(context =>
{
if (context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftVisualStudioTestToolsUnitTestingTestMethodAttribute, out INamedTypeSymbol? testMethodAttributeSymbol))
{
context.RegisterSymbolAction(
context => AnalyzeSymbol(context, testMethodAttributeSymbol),
SymbolKind.Method);
}
});
}

private static void AnalyzeSymbol(SymbolAnalysisContext context, INamedTypeSymbol testMethodAttributeSymbol)
{
var methodSymbol = (IMethodSymbol)context.Symbol;

int testMethodAttributeCount = 0;
foreach (AttributeData attribute in methodSymbol.GetAttributes())
{
if (attribute.AttributeClass is not null && attribute.AttributeClass.Inherits(testMethodAttributeSymbol))
{
testMethodAttributeCount++;
if (testMethodAttributeCount > 1)
{
// Report diagnostic on the method itself
context.ReportDiagnostic(methodSymbol.CreateDiagnostic(Rule, methodSymbol.Name));
return;
}
}
}
}
}
1 change: 1 addition & 0 deletions src/Analyzers/MSTest.Analyzers/Helpers/DiagnosticIds.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,5 @@ internal static class DiagnosticIds
public const string TestMethodAttributeShouldSetDisplayNameCorrectlyRuleId = "MSTEST0056";
public const string TestMethodAttributeShouldPropagateSourceInformationRuleId = "MSTEST0057";
public const string AvoidAssertsInCatchBlocksRuleId = "MSTEST0058";
public const string DuplicateTestMethodAttributeRuleId = "MSTEST0059";
}
9 changes: 9 additions & 0 deletions src/Analyzers/MSTest.Analyzers/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -699,4 +699,13 @@ The type declaring these methods should also respect the following rules:
<data name="AvoidAssertsInCatchBlocksDescription" xml:space="preserve">
<value>Using asserts in catch blocks is problematic because the test will pass even if no exception is thrown and the catch block is never executed. Use 'Assert.Throws', 'Assert.ThrowsExactly', 'Assert.ThrowsAsync' or 'Assert.ThrowsExactlyAsync' to verify that an exception is thrown, and then make additional assertions on the caught exception without using the try-catch block.</value>
</data>
<data name="DuplicateTestMethodAttributeTitle" xml:space="preserve">
<value>Avoid duplicate test method attributes</value>
</data>
<data name="DuplicateTestMethodAttributeMessageFormat" xml:space="preserve">
<value>Test method '{0}' has multiple attributes that inherit from 'TestMethodAttribute'. Only one test method attribute is allowed.</value>
</data>
<data name="DuplicateTestMethodAttributeDescription" xml:space="preserve">
<value>A test method should be marked with exactly one attribute that is or inherits from 'TestMethodAttribute'. Having multiple such attributes can lead to unexpected behavior.</value>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not so much unexpected behavior. The problem is that only one attribute will be called (the first returned by reflection).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot please update the messages.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the messages to clarify that only the first attribute returned by reflection will be used, rather than saying "unexpected behavior". Changes committed in b4de188.

</data>
</root>
15 changes: 15 additions & 0 deletions src/Analyzers/MSTest.Analyzers/xlf/Resources.cs.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -1014,6 +1014,21 @@ Typ deklarující tyto metody by měl také respektovat následující pravidla:
<target state="translated">Používání kontrolních výrazů v blocích catch je problematické, protože test projde, i když se nevyvolá žádná výjimka a blok catch se nikdy nespustí. K ověření, že je vyvolána výjimka, použijte metody Assert.Throws, Assert.ThrowsExactly, Assert.ThrowsAsync nebo Assert.ThrowsExactlyAsync a poté proveďte další kontrolní výrazy nad zachycenou výjimkou bez použití bloku try-catch.</target>
<note />
</trans-unit>
<trans-unit id="DuplicateTestMethodAttributeTitle">
<source>Avoid duplicate test method attributes</source>
<target state="new">Avoid duplicate test method attributes</target>
<note />
</trans-unit>
<trans-unit id="DuplicateTestMethodAttributeMessageFormat">
<source>Test method '{0}' has multiple attributes that inherit from 'TestMethodAttribute'. Only one test method attribute is allowed.</source>
<target state="new">Test method '{0}' has multiple attributes that inherit from 'TestMethodAttribute'. Only one test method attribute is allowed.</target>
<note />
</trans-unit>
<trans-unit id="DuplicateTestMethodAttributeDescription">
<source>A test method should be marked with exactly one attribute that is or inherits from 'TestMethodAttribute'. Having multiple such attributes can lead to unexpected behavior.</source>
<target state="new">A test method should be marked with exactly one attribute that is or inherits from 'TestMethodAttribute'. Having multiple such attributes can lead to unexpected behavior.</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
15 changes: 15 additions & 0 deletions src/Analyzers/MSTest.Analyzers/xlf/Resources.de.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -1015,6 +1015,21 @@ Der Typ, der diese Methoden deklariert, sollte auch die folgenden Regeln beachte
<target state="translated">Die Verwendung von Asserts in Catch-Blöcken ist problematisch, da der Test auch dann erfolgreich ist, wenn keine Ausnahme ausgelöst wird und der Catch-Block nie ausgeführt wird. Verwenden Sie „Assert.Throws“, „Assert.ThrowsExactly“, „Assert.ThrowsAsync“ oder „Assert.ThrowsExactlyAsync“, um zu überprüfen, ob eine Ausnahme ausgelöst wird, und erstellen Sie dann zusätzliche Assertionen für die abgefangene Ausnahme, ohne den „try-catch“-Block zu verwenden.</target>
<note />
</trans-unit>
<trans-unit id="DuplicateTestMethodAttributeTitle">
<source>Avoid duplicate test method attributes</source>
<target state="new">Avoid duplicate test method attributes</target>
<note />
</trans-unit>
<trans-unit id="DuplicateTestMethodAttributeMessageFormat">
<source>Test method '{0}' has multiple attributes that inherit from 'TestMethodAttribute'. Only one test method attribute is allowed.</source>
<target state="new">Test method '{0}' has multiple attributes that inherit from 'TestMethodAttribute'. Only one test method attribute is allowed.</target>
<note />
</trans-unit>
<trans-unit id="DuplicateTestMethodAttributeDescription">
<source>A test method should be marked with exactly one attribute that is or inherits from 'TestMethodAttribute'. Having multiple such attributes can lead to unexpected behavior.</source>
<target state="new">A test method should be marked with exactly one attribute that is or inherits from 'TestMethodAttribute'. Having multiple such attributes can lead to unexpected behavior.</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
15 changes: 15 additions & 0 deletions src/Analyzers/MSTest.Analyzers/xlf/Resources.es.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -1014,6 +1014,21 @@ El tipo que declara estos métodos también debe respetar las reglas siguientes:
<target state="translated">El uso de aserciones en bloques catch es problemático porque la prueba se superará incluso aunque no se produzca ninguna excepción y el bloque catch nunca se ejecuta. Use "Assert.Throws", "Assert.ThrowsExactly", "Assert.ThrowsAsync" o "Assert.ThrowsExactlyAsync" para comprobar que se produzca una excepción y, a continuación, realice aserciones adicionales en la excepción detectada sin usar el bloque try-catch.</target>
<note />
</trans-unit>
<trans-unit id="DuplicateTestMethodAttributeTitle">
<source>Avoid duplicate test method attributes</source>
<target state="new">Avoid duplicate test method attributes</target>
<note />
</trans-unit>
<trans-unit id="DuplicateTestMethodAttributeMessageFormat">
<source>Test method '{0}' has multiple attributes that inherit from 'TestMethodAttribute'. Only one test method attribute is allowed.</source>
<target state="new">Test method '{0}' has multiple attributes that inherit from 'TestMethodAttribute'. Only one test method attribute is allowed.</target>
<note />
</trans-unit>
<trans-unit id="DuplicateTestMethodAttributeDescription">
<source>A test method should be marked with exactly one attribute that is or inherits from 'TestMethodAttribute'. Having multiple such attributes can lead to unexpected behavior.</source>
<target state="new">A test method should be marked with exactly one attribute that is or inherits from 'TestMethodAttribute'. Having multiple such attributes can lead to unexpected behavior.</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
15 changes: 15 additions & 0 deletions src/Analyzers/MSTest.Analyzers/xlf/Resources.fr.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -1014,6 +1014,21 @@ Le type doit être une classe
<target state="translated">L’utilisation d’assertions dans les blocs catch pose problème, car le test réussit même si aucune exception n’est levée et que le bloc catch n’est jamais exécuté. Utilisez « Assert.Throws », « Assert.ThrowsExactly », « Assert.ThrowsAsync » ou « Assert.ThrowsExactlyAsync » pour vérifier qu’une exception est levée, puis effectuez des assertions supplémentaires sur l’exception capturée sans utiliser le bloc try-catch.</target>
<note />
</trans-unit>
<trans-unit id="DuplicateTestMethodAttributeTitle">
<source>Avoid duplicate test method attributes</source>
<target state="new">Avoid duplicate test method attributes</target>
<note />
</trans-unit>
<trans-unit id="DuplicateTestMethodAttributeMessageFormat">
<source>Test method '{0}' has multiple attributes that inherit from 'TestMethodAttribute'. Only one test method attribute is allowed.</source>
<target state="new">Test method '{0}' has multiple attributes that inherit from 'TestMethodAttribute'. Only one test method attribute is allowed.</target>
<note />
</trans-unit>
<trans-unit id="DuplicateTestMethodAttributeDescription">
<source>A test method should be marked with exactly one attribute that is or inherits from 'TestMethodAttribute'. Having multiple such attributes can lead to unexpected behavior.</source>
<target state="new">A test method should be marked with exactly one attribute that is or inherits from 'TestMethodAttribute'. Having multiple such attributes can lead to unexpected behavior.</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
15 changes: 15 additions & 0 deletions src/Analyzers/MSTest.Analyzers/xlf/Resources.it.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -1014,6 +1014,21 @@ Anche il tipo che dichiara questi metodi deve rispettare le regole seguenti:
<target state="translated">L'uso di asserzioni nei blocchi catch è problematico perché il test risulta superato anche se non viene generata alcuna eccezione e il blocco catch non viene mai eseguito. Utilizzare 'Assert.Throws', 'Assert.ThrowsExactly', 'Assert.ThrowsAsync' o 'Assert.ThrowsExactlyAsync' per verificare che venga generata un'eccezione, quindi effettuare ulteriori asserzioni sull'eccezione rilevata senza usare il blocco try-catch.</target>
<note />
</trans-unit>
<trans-unit id="DuplicateTestMethodAttributeTitle">
<source>Avoid duplicate test method attributes</source>
<target state="new">Avoid duplicate test method attributes</target>
<note />
</trans-unit>
<trans-unit id="DuplicateTestMethodAttributeMessageFormat">
<source>Test method '{0}' has multiple attributes that inherit from 'TestMethodAttribute'. Only one test method attribute is allowed.</source>
<target state="new">Test method '{0}' has multiple attributes that inherit from 'TestMethodAttribute'. Only one test method attribute is allowed.</target>
<note />
</trans-unit>
<trans-unit id="DuplicateTestMethodAttributeDescription">
<source>A test method should be marked with exactly one attribute that is or inherits from 'TestMethodAttribute'. Having multiple such attributes can lead to unexpected behavior.</source>
<target state="new">A test method should be marked with exactly one attribute that is or inherits from 'TestMethodAttribute'. Having multiple such attributes can lead to unexpected behavior.</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
15 changes: 15 additions & 0 deletions src/Analyzers/MSTest.Analyzers/xlf/Resources.ja.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -1014,6 +1014,21 @@ The type declaring these methods should also respect the following rules:
<target state="translated">catch ブロックでアサートを使用すると、例外がスローされず、catch ブロックが実行されなくてもテストが成功するため、問題があります。'Assert.Throws'、'Assert.ThrowsExactly'、'Assert.ThrowsAsync'、または 'Assert.ThrowsExactlyAsync' を使用して例外がスローされたことを確認し、try-catch ブロックを使用せずにキャッチされた例外に対して追加のアサートを実行します。</target>
<note />
</trans-unit>
<trans-unit id="DuplicateTestMethodAttributeTitle">
<source>Avoid duplicate test method attributes</source>
<target state="new">Avoid duplicate test method attributes</target>
<note />
</trans-unit>
<trans-unit id="DuplicateTestMethodAttributeMessageFormat">
<source>Test method '{0}' has multiple attributes that inherit from 'TestMethodAttribute'. Only one test method attribute is allowed.</source>
<target state="new">Test method '{0}' has multiple attributes that inherit from 'TestMethodAttribute'. Only one test method attribute is allowed.</target>
<note />
</trans-unit>
<trans-unit id="DuplicateTestMethodAttributeDescription">
<source>A test method should be marked with exactly one attribute that is or inherits from 'TestMethodAttribute'. Having multiple such attributes can lead to unexpected behavior.</source>
<target state="new">A test method should be marked with exactly one attribute that is or inherits from 'TestMethodAttribute'. Having multiple such attributes can lead to unexpected behavior.</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
15 changes: 15 additions & 0 deletions src/Analyzers/MSTest.Analyzers/xlf/Resources.ko.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -1014,6 +1014,21 @@ The type declaring these methods should also respect the following rules:
<target state="translated">예외가 발생하지 않고 catch 블록이 실행되지 않더라도 테스트가 통과하기 때문에 catch 블록에서 어설션을 사용하는 것은 문제가 됩니다. 'Assert.Throws', 'Assert.ThrowsExactly', 'Assert.ThrowsAsync' 또는 'Assert.ThrowsExactlyAsync'를 사용하여 예외가 발생했는지 확인한 다음 try-catch 블록을 사용하지 않고 catch된 예외에 대해 추가 어설션을 만듭니다.</target>
<note />
</trans-unit>
<trans-unit id="DuplicateTestMethodAttributeTitle">
<source>Avoid duplicate test method attributes</source>
<target state="new">Avoid duplicate test method attributes</target>
<note />
</trans-unit>
<trans-unit id="DuplicateTestMethodAttributeMessageFormat">
<source>Test method '{0}' has multiple attributes that inherit from 'TestMethodAttribute'. Only one test method attribute is allowed.</source>
<target state="new">Test method '{0}' has multiple attributes that inherit from 'TestMethodAttribute'. Only one test method attribute is allowed.</target>
<note />
</trans-unit>
<trans-unit id="DuplicateTestMethodAttributeDescription">
<source>A test method should be marked with exactly one attribute that is or inherits from 'TestMethodAttribute'. Having multiple such attributes can lead to unexpected behavior.</source>
<target state="new">A test method should be marked with exactly one attribute that is or inherits from 'TestMethodAttribute'. Having multiple such attributes can lead to unexpected behavior.</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
15 changes: 15 additions & 0 deletions src/Analyzers/MSTest.Analyzers/xlf/Resources.pl.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -1014,6 +1014,21 @@ Typ deklarujący te metody powinien również przestrzegać następujących regu
<target state="translated">Używanie asercji w blokach catch jest problematyczne, ponieważ test zakończy się powodzeniem, nawet jeśli nie zostanie zgłoszony żaden wyjątek i blok catch nigdy nie zostanie wykonany. Użyj instrukcji „Assert.Throws”, „Assert.ThrowsExactly”, „Assert.ThrowsAsync” lub „Assert.ThrowsExactlyAsync”, aby sprawdzić, czy zgłoszono wyjątek, a następnie wykonaj dodatkowe asercje dla przechwyconego wyjątku bez użycia bloku try-catch.</target>
<note />
</trans-unit>
<trans-unit id="DuplicateTestMethodAttributeTitle">
<source>Avoid duplicate test method attributes</source>
<target state="new">Avoid duplicate test method attributes</target>
<note />
</trans-unit>
<trans-unit id="DuplicateTestMethodAttributeMessageFormat">
<source>Test method '{0}' has multiple attributes that inherit from 'TestMethodAttribute'. Only one test method attribute is allowed.</source>
<target state="new">Test method '{0}' has multiple attributes that inherit from 'TestMethodAttribute'. Only one test method attribute is allowed.</target>
<note />
</trans-unit>
<trans-unit id="DuplicateTestMethodAttributeDescription">
<source>A test method should be marked with exactly one attribute that is or inherits from 'TestMethodAttribute'. Having multiple such attributes can lead to unexpected behavior.</source>
<target state="new">A test method should be marked with exactly one attribute that is or inherits from 'TestMethodAttribute'. Having multiple such attributes can lead to unexpected behavior.</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
Loading
Loading