diff --git a/src/Analyzers/MSTest.Analyzers/Helpers/WellKnownTypeNames.cs b/src/Analyzers/MSTest.Analyzers/Helpers/WellKnownTypeNames.cs index 06f9b4abf4..f0b44c998d 100644 --- a/src/Analyzers/MSTest.Analyzers/Helpers/WellKnownTypeNames.cs +++ b/src/Analyzers/MSTest.Analyzers/Helpers/WellKnownTypeNames.cs @@ -11,6 +11,7 @@ internal static class WellKnownTypeNames 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 MicrosoftVisualStudioTestToolsUnitTestingClassCleanupExecutionAttribute = "Microsoft.VisualStudio.TestTools.UnitTesting.ClassCleanupExecutionAttribute"; 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"; diff --git a/src/Analyzers/MSTest.Analyzers/UseClassCleanupBehaviorEndOfClassAnalyzer.cs b/src/Analyzers/MSTest.Analyzers/UseClassCleanupBehaviorEndOfClassAnalyzer.cs index 79ff80a84e..4785438321 100644 --- a/src/Analyzers/MSTest.Analyzers/UseClassCleanupBehaviorEndOfClassAnalyzer.cs +++ b/src/Analyzers/MSTest.Analyzers/UseClassCleanupBehaviorEndOfClassAnalyzer.cs @@ -43,16 +43,17 @@ public override void Initialize(AnalysisContext 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.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftVisualStudioTestToolsUnitTestingClassCleanupBehavior, out INamedTypeSymbol? classCleanupBehaviorSymbol) + && context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftVisualStudioTestToolsUnitTestingClassCleanupExecutionAttribute, out INamedTypeSymbol? classCleanupExecutionAttributeSymbol)) { context.RegisterSymbolAction( - context => AnalyzeSymbol(context, classCleanupAttributeSymbol, testClassAttributeSymbol, classCleanupBehaviorSymbol), + context => AnalyzeSymbol(context, classCleanupAttributeSymbol, testClassAttributeSymbol, classCleanupBehaviorSymbol, classCleanupExecutionAttributeSymbol), SymbolKind.Method); } }); } - private static void AnalyzeSymbol(SymbolAnalysisContext context, INamedTypeSymbol classCleanupAttributeSymbol, INamedTypeSymbol testClassAttributeSymbol, INamedTypeSymbol classCleanupBehaviorSymbol) + private static void AnalyzeSymbol(SymbolAnalysisContext context, INamedTypeSymbol classCleanupAttributeSymbol, INamedTypeSymbol testClassAttributeSymbol, INamedTypeSymbol classCleanupBehaviorSymbol, INamedTypeSymbol classCleanupExecutionAttributeSymbol) { var methodSymbol = (IMethodSymbol)context.Symbol; @@ -61,9 +62,19 @@ private static void AnalyzeSymbol(SymbolAnalysisContext context, INamedTypeSymbo return; } + // Check if the assembly has the ClassCleanupExecutionAttribute with EndOfClass behavior + bool assemblyHasEndOfClassCleanup = context.Compilation.Assembly + .GetAttributes() + .Any(attr => SymbolEqualityComparer.Default.Equals(attr.AttributeClass, classCleanupExecutionAttributeSymbol) + && attr.ConstructorArguments.Length == 1 + && SymbolEqualityComparer.Default.Equals(attr.ConstructorArguments[0].Type, classCleanupBehaviorSymbol) + && 1.Equals(attr.ConstructorArguments[0].Value)); + ImmutableArray methodAttributes = methodSymbol.GetAttributes(); bool hasCleanupAttr = false; bool hasCleanupEndOClassBehavior = false; + bool hasClassCleanupBehavior = false; + foreach (AttributeData methodAttribute in methodAttributes) { if (SymbolEqualityComparer.Default.Equals(methodAttribute.AttributeClass, classCleanupAttributeSymbol)) @@ -71,19 +82,27 @@ private static void AnalyzeSymbol(SymbolAnalysisContext context, INamedTypeSymbo 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)) + if (SymbolEqualityComparer.Default.Equals(arg.Type, classCleanupBehaviorSymbol)) { - hasCleanupEndOClassBehavior = true; + hasClassCleanupBehavior = true; + + // one is the value for EndOFClass behavior in the CleanupBehavior enum. + if (1.Equals(arg.Value)) + { + hasCleanupEndOClassBehavior = true; + } } } } } - if (hasCleanupAttr && !hasCleanupEndOClassBehavior) + if (!hasCleanupAttr || + (!hasClassCleanupBehavior && assemblyHasEndOfClassCleanup) + || (hasClassCleanupBehavior && hasCleanupEndOClassBehavior)) { - context.ReportDiagnostic(methodSymbol.CreateDiagnostic(UseClassCleanupBehaviorEndOfClassRule)); + return; } + + context.ReportDiagnostic(methodSymbol.CreateDiagnostic(UseClassCleanupBehaviorEndOfClassRule)); } } diff --git a/test/UnitTests/MSTest.Analyzers.UnitTests/UseClassCleanupBehaviorEndOfClassAnalyzerTests.cs b/test/UnitTests/MSTest.Analyzers.UnitTests/UseClassCleanupBehaviorEndOfClassAnalyzerTests.cs index c94c685d2d..531bf1886c 100644 --- a/test/UnitTests/MSTest.Analyzers.UnitTests/UseClassCleanupBehaviorEndOfClassAnalyzerTests.cs +++ b/test/UnitTests/MSTest.Analyzers.UnitTests/UseClassCleanupBehaviorEndOfClassAnalyzerTests.cs @@ -152,4 +152,80 @@ public static void ClassCleanup() await VerifyCS.VerifyAnalyzerAsync(code); } + + public async Task UsingClassCleanup_InsideTestClass_WithClassCleanupExecutionWithEndOfClassBehavior_NoDiagnostic() + { + string code = """ + using Microsoft.VisualStudio.TestTools.UnitTesting; + [assembly: ClassCleanupExecutionAttribute(ClassCleanupBehavior.EndOfClass)] + + [TestClass] + public class MyTestClass + { + [ClassCleanup] + public static void ClassCleanup() + { + } + } + """; + + await VerifyCS.VerifyAnalyzerAsync(code); + } + + public async Task UsingClassCleanup_InsideTestClass_WithClassCleanupExecutionWithEndOfAsseblyBehavior_Diagnostic() + { + string code = """ + using Microsoft.VisualStudio.TestTools.UnitTesting; + [assembly: ClassCleanupExecutionAttribute(ClassCleanupBehavior.EndOfAssembly)] + + [TestClass] + public class MyTestClass + { + [ClassCleanup] + public static void [|ClassCleanup|]() + { + } + } + """; + + await VerifyCS.VerifyAnalyzerAsync(code); + } + + public async Task UsingClassCleanup_InsideTestClass_WithClassCleanupExecutionWithEndOfClassBehavior_WithCleanupBehaviorEndOfAssembly_Diagnostic() + { + string code = """ + using Microsoft.VisualStudio.TestTools.UnitTesting; + [assembly: ClassCleanupExecutionAttribute(ClassCleanupBehavior.EndOfClass)] + + [TestClass] + public class MyTestClass + { + [ClassCleanup(ClassCleanupBehavior.EndOfAssembly)] + public static void [|ClassCleanup|]() + { + } + } + """; + + await VerifyCS.VerifyAnalyzerAsync(code); + } + + public async Task UsingClassCleanup_InsideTestClass_WithClassCleanupExecutionWithEndOfAssemblyBehavior_WithCleanupBehaviorEndOfClass_NoDiagnostic() + { + string code = """ + using Microsoft.VisualStudio.TestTools.UnitTesting; + [assembly: ClassCleanupExecutionAttribute(ClassCleanupBehavior.EndOfAssembly)] + + [TestClass] + public class MyTestClass + { + [ClassCleanup(ClassCleanupBehavior.EndOfClass)] + public static void ClassCleanup() + { + } + } + """; + + await VerifyCS.VerifyAnalyzerAsync(code); + } }