Skip to content

Commit

Permalink
MSTEST0034: Use CleanupBehavior.EndOfClass on ClassCleanupAttribute (#…
Browse files Browse the repository at this point in the history
…3289)

Co-authored-by: Amaury Levé <[email protected]>
  • Loading branch information
engyebrahim and Evangelink authored Jul 23, 2024
1 parent 850d057 commit 7bccb49
Show file tree
Hide file tree
Showing 21 changed files with 486 additions and 0 deletions.
5 changes: 5 additions & 0 deletions src/Analyzers/MSTest.Analyzers/AnalyzerReleases.Unshipped.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
; Unshipped analyzer release
; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md
### New Rules

Rule ID | Category | Severity | Notes
--------|----------|----------|-------
MSTEST0034 | Usage | Info | UseClassCleanupBehaviorEndOfClassAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/core/testing/mstest-analyzers/mstest0034)
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 @@ -37,4 +37,5 @@ internal static class DiagnosticIds
public const string DoNotUseSystemDescriptionAttributeRuleId = "MSTEST0031";
public const string ReviewAlwaysTrueAssertConditionAnalyzerRuleId = "MSTEST0032";
public const string NonNullableReferenceNotInitializedSuppressorRuleId = "MSTEST0033";
public const string UseClassCleanupBehaviorEndOfClassRuleId = "MSTEST0034";
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ internal static class WellKnownTypeNames
public const string MicrosoftVisualStudioTestToolsUnitTestingAssemblyInitializeAttribute = "Microsoft.VisualStudio.TestTools.UnitTesting.AssemblyInitializeAttribute";
public const string MicrosoftVisualStudioTestToolsUnitTestingAssert = "Microsoft.VisualStudio.TestTools.UnitTesting.Assert";
public const string MicrosoftVisualStudioTestToolsUnitTestingClassCleanupAttribute = "Microsoft.VisualStudio.TestTools.UnitTesting.ClassCleanupAttribute";
public const string MicrosoftVisualStudioTestToolsUnitTestingClassCleanupBehavior = "Microsoft.VisualStudio.TestTools.UnitTesting.ClassCleanupBehavior";
public const string MicrosoftVisualStudioTestToolsUnitTestingClassInitializeAttribute = "Microsoft.VisualStudio.TestTools.UnitTesting.ClassInitializeAttribute";
public const string MicrosoftVisualStudioTestToolsUnitTestingCollectionAssert = "Microsoft.VisualStudio.TestTools.UnitTesting.CollectionAssert";
public const string MicrosoftVisualStudioTestToolsUnitTestingCssIterationAttribute = "Microsoft.VisualStudio.TestTools.UnitTesting.CssIterationAttribute";
Expand Down
4 changes: 4 additions & 0 deletions src/Analyzers/MSTest.Analyzers/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
#nullable enable
MSTest.Analyzers.NonNullableReferenceNotInitializedSuppressor
MSTest.Analyzers.NonNullableReferenceNotInitializedSuppressor.NonNullableReferenceNotInitializedSuppressor() -> void
MSTest.Analyzers.UseClassCleanupBehaviorEndOfClassAnalyzer
MSTest.Analyzers.UseClassCleanupBehaviorEndOfClassAnalyzer.UseClassCleanupBehaviorEndOfClassAnalyzer() -> void
override MSTest.Analyzers.NonNullableReferenceNotInitializedSuppressor.ReportSuppressions(Microsoft.CodeAnalysis.Diagnostics.SuppressionAnalysisContext context) -> void
override MSTest.Analyzers.NonNullableReferenceNotInitializedSuppressor.SupportedSuppressions.get -> System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.SuppressionDescriptor!>
override MSTest.Analyzers.UseClassCleanupBehaviorEndOfClassAnalyzer.Initialize(Microsoft.CodeAnalysis.Diagnostics.AnalysisContext! context) -> void
override MSTest.Analyzers.UseClassCleanupBehaviorEndOfClassAnalyzer.SupportedDiagnostics.get -> System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.DiagnosticDescriptor!>
27 changes: 27 additions & 0 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.

9 changes: 9 additions & 0 deletions src/Analyzers/MSTest.Analyzers/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -460,4 +460,13 @@
<data name="ReviewAlwaysTrueAssertConditionAnalyzerMessageFormat" xml:space="preserve">
<value>Review or remove the assertion as its condition is known to be always true</value>
</data>
<data name="UseClassCleanupBehaviorEndOfClassTitle" xml:space="preserve">
<value>Use 'ClassCleanupBehavior.EndOfClass' with the '[ClassCleanup]'</value>
</data>
<data name="UseClassCleanupBehaviorEndOfClassDescription" xml:space="preserve">
<value>Without using 'ClassCleanupBehavior.EndOfClass', the '[ClassCleanup]' will by default be run at the end of the assembly and not at the end of the class.</value>
</data>
<data name="UseClassCleanupBehaviorEndOfClassMessageFormat" xml:space="preserve">
<value>Use 'ClassCleanupBehavior.EndOfClass' with the '[ClassCleanup]'</value>
</data>
</root>
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// 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>
/// MSTEST0034: <inheritdoc cref="Resources.UseClassCleanupBehaviorEndOfClassTitle"/>.
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
public sealed class UseClassCleanupBehaviorEndOfClassAnalyzer : DiagnosticAnalyzer
{
private static readonly LocalizableResourceString Title = new(nameof(Resources.UseClassCleanupBehaviorEndOfClassTitle), Resources.ResourceManager, typeof(Resources));
private static readonly LocalizableResourceString Description = new(nameof(Resources.UseClassCleanupBehaviorEndOfClassDescription), Resources.ResourceManager, typeof(Resources));
private static readonly LocalizableResourceString MessageFormat = new(nameof(Resources.UseClassCleanupBehaviorEndOfClassMessageFormat), Resources.ResourceManager, typeof(Resources));

internal static readonly DiagnosticDescriptor UseClassCleanupBehaviorEndOfClassRule = DiagnosticDescriptorHelper.Create(
id: DiagnosticIds.UseClassCleanupBehaviorEndOfClassRuleId,
Title,
MessageFormat,
Description,
Category.Usage,
DiagnosticSeverity.Info,
isEnabledByDefault: true);

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

public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();

context.RegisterCompilationStartAction(context =>
{
if (context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftVisualStudioTestToolsUnitTestingClassCleanupAttribute, out INamedTypeSymbol? classCleanupAttributeSymbol)
&& context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftVisualStudioTestToolsUnitTestingTestClassAttribute, out INamedTypeSymbol? testClassAttributeSymbol)
&& context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftVisualStudioTestToolsUnitTestingClassCleanupBehavior, out INamedTypeSymbol? classCleanupBehaviorSymbol))
{
context.RegisterSymbolAction(
context => AnalyzeSymbol(context, classCleanupAttributeSymbol, testClassAttributeSymbol, classCleanupBehaviorSymbol),
SymbolKind.Method);
}
});
}

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

if (!methodSymbol.ContainingType.GetAttributes().Any(x => x.AttributeClass.Inherits(testClassAttributeSymbol)))
{
return;
}

ImmutableArray<AttributeData> methodAttributes = methodSymbol.GetAttributes();
bool hasCleanupAttr = false;
bool hasCleanupEndOClassBehavior = false;
foreach (AttributeData methodAttribute in methodAttributes)
{
if (SymbolEqualityComparer.Default.Equals(methodAttribute.AttributeClass, classCleanupAttributeSymbol))
{
hasCleanupAttr = true;
foreach (TypedConstant arg in methodAttribute.ConstructorArguments)
{
// one is the value for EndOFClass behavior in the CleanupBehavior enum.
if (SymbolEqualityComparer.Default.Equals(arg.Type, classCleanupBehaviorSymbol)
&& 1.Equals(arg.Value))
{
hasCleanupEndOClassBehavior = true;
}
}
}
}

if (hasCleanupAttr && !hasCleanupEndOClassBehavior)
{
context.ReportDiagnostic(methodSymbol.CreateDiagnostic(UseClassCleanupBehaviorEndOfClassRule));
}
}
}
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 @@ -316,6 +316,21 @@
<target state="translated">Veřejné typy by měly být testovací třídy.</target>
<note />
</trans-unit>
<trans-unit id="UseClassCleanupBehaviorEndOfClassDescription">
<source>Without using 'ClassCleanupBehavior.EndOfClass', the '[ClassCleanup]' will by default be run at the end of the assembly and not at the end of the class.</source>
<target state="new">Without using 'ClassCleanupBehavior.EndOfClass', the '[ClassCleanup]' will by default be run at the end of the assembly and not at the end of the class.</target>
<note />
</trans-unit>
<trans-unit id="UseClassCleanupBehaviorEndOfClassMessageFormat">
<source>Use 'ClassCleanupBehavior.EndOfClass' with the '[ClassCleanup]'</source>
<target state="new">Use 'ClassCleanupBehavior.EndOfClass' with the '[ClassCleanup]'</target>
<note />
</trans-unit>
<trans-unit id="UseClassCleanupBehaviorEndOfClassTitle">
<source>Use 'ClassCleanupBehavior.EndOfClass' with the '[ClassCleanup]'</source>
<target state="new">Use 'ClassCleanupBehavior.EndOfClass' with the '[ClassCleanup]'</target>
<note />
</trans-unit>
<trans-unit id="TestClassShouldBeValidDescription">
<source>Test classes, classes marked with the '[TestClass]' attribute, should respect the following layout to be considered valid by MSTest:
- it should be 'public' (or 'internal' if '[assembly: DiscoverInternals]' attribute is set)
Expand Down
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 @@ -314,6 +314,21 @@
<target state="translated">Öffentliche Typen sollten Testklassen sein.</target>
<note />
</trans-unit>
<trans-unit id="UseClassCleanupBehaviorEndOfClassDescription">
<source>Without using 'ClassCleanupBehavior.EndOfClass', the '[ClassCleanup]' will by default be run at the end of the assembly and not at the end of the class.</source>
<target state="new">Without using 'ClassCleanupBehavior.EndOfClass', the '[ClassCleanup]' will by default be run at the end of the assembly and not at the end of the class.</target>
<note />
</trans-unit>
<trans-unit id="UseClassCleanupBehaviorEndOfClassMessageFormat">
<source>Use 'ClassCleanupBehavior.EndOfClass' with the '[ClassCleanup]'</source>
<target state="new">Use 'ClassCleanupBehavior.EndOfClass' with the '[ClassCleanup]'</target>
<note />
</trans-unit>
<trans-unit id="UseClassCleanupBehaviorEndOfClassTitle">
<source>Use 'ClassCleanupBehavior.EndOfClass' with the '[ClassCleanup]'</source>
<target state="new">Use 'ClassCleanupBehavior.EndOfClass' with the '[ClassCleanup]'</target>
<note />
</trans-unit>
<trans-unit id="TestClassShouldBeValidDescription">
<source>Test classes, classes marked with the '[TestClass]' attribute, should respect the following layout to be considered valid by MSTest:
- it should be 'public' (or 'internal' if '[assembly: DiscoverInternals]' attribute is set)
Expand Down
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 @@ -314,6 +314,21 @@
<target state="translated">Los tipos públicos deben ser clases de prueba</target>
<note />
</trans-unit>
<trans-unit id="UseClassCleanupBehaviorEndOfClassDescription">
<source>Without using 'ClassCleanupBehavior.EndOfClass', the '[ClassCleanup]' will by default be run at the end of the assembly and not at the end of the class.</source>
<target state="new">Without using 'ClassCleanupBehavior.EndOfClass', the '[ClassCleanup]' will by default be run at the end of the assembly and not at the end of the class.</target>
<note />
</trans-unit>
<trans-unit id="UseClassCleanupBehaviorEndOfClassMessageFormat">
<source>Use 'ClassCleanupBehavior.EndOfClass' with the '[ClassCleanup]'</source>
<target state="new">Use 'ClassCleanupBehavior.EndOfClass' with the '[ClassCleanup]'</target>
<note />
</trans-unit>
<trans-unit id="UseClassCleanupBehaviorEndOfClassTitle">
<source>Use 'ClassCleanupBehavior.EndOfClass' with the '[ClassCleanup]'</source>
<target state="new">Use 'ClassCleanupBehavior.EndOfClass' with the '[ClassCleanup]'</target>
<note />
</trans-unit>
<trans-unit id="TestClassShouldBeValidDescription">
<source>Test classes, classes marked with the '[TestClass]' attribute, should respect the following layout to be considered valid by MSTest:
- it should be 'public' (or 'internal' if '[assembly: DiscoverInternals]' attribute is set)
Expand Down
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 @@ -314,6 +314,21 @@
<target state="translated">Les types publics doivent être des classes de test</target>
<note />
</trans-unit>
<trans-unit id="UseClassCleanupBehaviorEndOfClassDescription">
<source>Without using 'ClassCleanupBehavior.EndOfClass', the '[ClassCleanup]' will by default be run at the end of the assembly and not at the end of the class.</source>
<target state="new">Without using 'ClassCleanupBehavior.EndOfClass', the '[ClassCleanup]' will by default be run at the end of the assembly and not at the end of the class.</target>
<note />
</trans-unit>
<trans-unit id="UseClassCleanupBehaviorEndOfClassMessageFormat">
<source>Use 'ClassCleanupBehavior.EndOfClass' with the '[ClassCleanup]'</source>
<target state="new">Use 'ClassCleanupBehavior.EndOfClass' with the '[ClassCleanup]'</target>
<note />
</trans-unit>
<trans-unit id="UseClassCleanupBehaviorEndOfClassTitle">
<source>Use 'ClassCleanupBehavior.EndOfClass' with the '[ClassCleanup]'</source>
<target state="new">Use 'ClassCleanupBehavior.EndOfClass' with the '[ClassCleanup]'</target>
<note />
</trans-unit>
<trans-unit id="TestClassShouldBeValidDescription">
<source>Test classes, classes marked with the '[TestClass]' attribute, should respect the following layout to be considered valid by MSTest:
- it should be 'public' (or 'internal' if '[assembly: DiscoverInternals]' attribute is set)
Expand Down
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 @@ -314,6 +314,21 @@
<target state="translated">I tipi di pubblico dovrebbero essere classi di test</target>
<note />
</trans-unit>
<trans-unit id="UseClassCleanupBehaviorEndOfClassDescription">
<source>Without using 'ClassCleanupBehavior.EndOfClass', the '[ClassCleanup]' will by default be run at the end of the assembly and not at the end of the class.</source>
<target state="new">Without using 'ClassCleanupBehavior.EndOfClass', the '[ClassCleanup]' will by default be run at the end of the assembly and not at the end of the class.</target>
<note />
</trans-unit>
<trans-unit id="UseClassCleanupBehaviorEndOfClassMessageFormat">
<source>Use 'ClassCleanupBehavior.EndOfClass' with the '[ClassCleanup]'</source>
<target state="new">Use 'ClassCleanupBehavior.EndOfClass' with the '[ClassCleanup]'</target>
<note />
</trans-unit>
<trans-unit id="UseClassCleanupBehaviorEndOfClassTitle">
<source>Use 'ClassCleanupBehavior.EndOfClass' with the '[ClassCleanup]'</source>
<target state="new">Use 'ClassCleanupBehavior.EndOfClass' with the '[ClassCleanup]'</target>
<note />
</trans-unit>
<trans-unit id="TestClassShouldBeValidDescription">
<source>Test classes, classes marked with the '[TestClass]' attribute, should respect the following layout to be considered valid by MSTest:
- it should be 'public' (or 'internal' if '[assembly: DiscoverInternals]' attribute is set)
Expand Down
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 @@ -314,6 +314,21 @@
<target state="translated">パブリック型はテスト クラスである必要があります</target>
<note />
</trans-unit>
<trans-unit id="UseClassCleanupBehaviorEndOfClassDescription">
<source>Without using 'ClassCleanupBehavior.EndOfClass', the '[ClassCleanup]' will by default be run at the end of the assembly and not at the end of the class.</source>
<target state="new">Without using 'ClassCleanupBehavior.EndOfClass', the '[ClassCleanup]' will by default be run at the end of the assembly and not at the end of the class.</target>
<note />
</trans-unit>
<trans-unit id="UseClassCleanupBehaviorEndOfClassMessageFormat">
<source>Use 'ClassCleanupBehavior.EndOfClass' with the '[ClassCleanup]'</source>
<target state="new">Use 'ClassCleanupBehavior.EndOfClass' with the '[ClassCleanup]'</target>
<note />
</trans-unit>
<trans-unit id="UseClassCleanupBehaviorEndOfClassTitle">
<source>Use 'ClassCleanupBehavior.EndOfClass' with the '[ClassCleanup]'</source>
<target state="new">Use 'ClassCleanupBehavior.EndOfClass' with the '[ClassCleanup]'</target>
<note />
</trans-unit>
<trans-unit id="TestClassShouldBeValidDescription">
<source>Test classes, classes marked with the '[TestClass]' attribute, should respect the following layout to be considered valid by MSTest:
- it should be 'public' (or 'internal' if '[assembly: DiscoverInternals]' attribute is set)
Expand Down
Loading

0 comments on commit 7bccb49

Please sign in to comment.