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

MSTEST00012: AssemblyInitialize should be valid #2328

Merged
merged 17 commits into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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 @@ -7,3 +7,4 @@ Rule ID | Category | Severity | Notes
MSTEST0007 | Usage | Info | UseAttributeOnTestMethodAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/core/testing/unit-testing-mstest-analyzers-MSTEST0007)
MSTEST0008 | Usage | Warning | TestInitializeShouldBeValidAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/core/testing/unit-testing-mstest-analyzers-MSTEST0008)
MSTEST0009 | Usage | Warning | TestCleanupShouldBeValidAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/core/testing/unit-testing-mstest-analyzers-MSTEST0009)
MSTEST0012 | Usage | Warning | AssemblyInitializeShouldBeValidAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/core/testing/unit-testing-mstest-analyzers-MSTEST0012)
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// 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;

[DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
public sealed class AssemblyInitializeShouldBeValidAnalyzer : DiagnosticAnalyzer
{
private static readonly LocalizableResourceString Title = new(nameof(Resources.AssemblyInitializeShouldBeValidTitle), Resources.ResourceManager, typeof(Resources));
private static readonly LocalizableResourceString Description = new(nameof(Resources.AssemblyInitializeShouldBeValidDescription), Resources.ResourceManager, typeof(Resources));
private static readonly LocalizableResourceString MessageFormat = new(nameof(Resources.AssemblyInitializeShouldBeValidMessageFormat_Public), Resources.ResourceManager, typeof(Resources));

internal static readonly DiagnosticDescriptor PublicRule = DiagnosticDescriptorHelper.Create(
DiagnosticIds.AssemblyInitializeShouldBeValidRuleId,
Title,
MessageFormat,
Description,
Category.Usage,
DiagnosticSeverity.Warning,
isEnabledByDefault: true);

internal static readonly DiagnosticDescriptor StaticRule = PublicRule.WithMessage(new(nameof(Resources.AssemblyInitializeShouldBeValidMessageFormat_Static), Resources.ResourceManager, typeof(Resources)));
internal static readonly DiagnosticDescriptor SingleContextParameterRule = PublicRule.WithMessage(new(nameof(Resources.AssemblyInitializeShouldBeValidMessageFormat_SingleContextParameter), Resources.ResourceManager, typeof(Resources)));
internal static readonly DiagnosticDescriptor ReturnTypeRule = PublicRule.WithMessage(new(nameof(Resources.AssemblyInitializeShouldBeValidMessageFormat_ReturnType), Resources.ResourceManager, typeof(Resources)));
internal static readonly DiagnosticDescriptor NotAsyncVoidRule = PublicRule.WithMessage(new(nameof(Resources.AssemblyInitializeShouldBeValidMessageFormat_NotAsyncVoid), Resources.ResourceManager, typeof(Resources)));
internal static readonly DiagnosticDescriptor NotGenericRule = PublicRule.WithMessage(new(nameof(Resources.AssemblyInitializeShouldBeValidMessageFormat_NotGeneric), Resources.ResourceManager, typeof(Resources)));
internal static readonly DiagnosticDescriptor OrdinaryRule = PublicRule.WithMessage(new(nameof(Resources.AssemblyInitializeShouldBeValidMessageFormat_Ordinary), Resources.ResourceManager, typeof(Resources)));

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

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

context.RegisterCompilationStartAction(context =>
{
if (context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftVisualStudioTestToolsUnitTestingAssemblyInitializeAttribute, out var assemblyInitializeAttributeSymbol))
{
var taskSymbol = context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingTasksTask);
var valueTaskSymbol = context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingTasksValueTask);
var testContextSymbol = context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftVisualStudioTestToolsUnitTestingTestContext);
engyebrahim marked this conversation as resolved.
Show resolved Hide resolved
context.RegisterSymbolAction(
context => AnalyzeSymbol(context, assemblyInitializeAttributeSymbol, taskSymbol, valueTaskSymbol, testContextSymbol),
SymbolKind.Method);
}
});
}

private static void AnalyzeSymbol(SymbolAnalysisContext context, INamedTypeSymbol assemblyInitializeAttributeSymbol, INamedTypeSymbol? taskSymbol,
INamedTypeSymbol? valueTaskSymbol, INamedTypeSymbol? testContextSymbol)
{
var methodSymbol = (IMethodSymbol)context.Symbol;
if (!methodSymbol.GetAttributes().Any(attr => SymbolEqualityComparer.Default.Equals(attr.AttributeClass, assemblyInitializeAttributeSymbol)))
{
return;
}

if (methodSymbol.MethodKind != MethodKind.Ordinary)
{
context.ReportDiagnostic(methodSymbol.CreateDiagnostic(OrdinaryRule, methodSymbol.Name));

// Do not check the other criteria, users should fix the method kind first.
return;
}

if (methodSymbol.Parameters.Length != 1 || testContextSymbol is null ||
!SymbolEqualityComparer.Default.Equals(methodSymbol.Parameters[0].Type, testContextSymbol))
{
context.ReportDiagnostic(methodSymbol.CreateDiagnostic(SingleContextParameterRule, methodSymbol.Name));
}

if (methodSymbol.IsGenericMethod)
{
context.ReportDiagnostic(methodSymbol.CreateDiagnostic(NotGenericRule, methodSymbol.Name));
}

if (!methodSymbol.IsStatic)
{
context.ReportDiagnostic(methodSymbol.CreateDiagnostic(StaticRule, methodSymbol.Name));
}

if (methodSymbol.ReturnsVoid && methodSymbol.IsAsync)
{
context.ReportDiagnostic(methodSymbol.CreateDiagnostic(NotAsyncVoidRule, methodSymbol.Name));
}

if (methodSymbol.DeclaredAccessibility != Accessibility.Public || methodSymbol.GetResultantVisibility() != SymbolVisibility.Public)
engyebrahim marked this conversation as resolved.
Show resolved Hide resolved
{
context.ReportDiagnostic(methodSymbol.CreateDiagnostic(PublicRule, methodSymbol.Name));
}

if (!methodSymbol.ReturnsVoid
&& (taskSymbol is null || !SymbolEqualityComparer.Default.Equals(methodSymbol.ReturnType, taskSymbol))
&& (valueTaskSymbol is null || !SymbolEqualityComparer.Default.Equals(methodSymbol.ReturnType, valueTaskSymbol)))
{
context.ReportDiagnostic(methodSymbol.CreateDiagnostic(ReturnTypeRule, methodSymbol.Name));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ internal static class WellKnownTypeNames
public const string MicrosoftVisualStudioTestToolsUnitTestingTestCleanupAttribute = "Microsoft.VisualStudio.TestTools.UnitTesting.TestCleanupAttribute";
public const string MicrosoftVisualStudioTestToolsUnitTestingTestContext = "Microsoft.VisualStudio.TestTools.UnitTesting.TestContext";
public const string MicrosoftVisualStudioTestToolsUnitTestingTestInitializeAttribute = "Microsoft.VisualStudio.TestTools.UnitTesting.TestInitializeAttribute";
public const string MicrosoftVisualStudioTestToolsUnitTestingAssemblyInitializeAttribute = "Microsoft.VisualStudio.TestTools.UnitTesting.AssemblyInitializeAttribute";
engyebrahim marked this conversation as resolved.
Show resolved Hide resolved
public const string MicrosoftVisualStudioTestToolsUnitTestingTestMethodAttribute = "Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute";
public const string MicrosoftVisualStudioTestToolsUnitTestingTestPropertyAttribute = "Microsoft.VisualStudio.TestTools.UnitTesting.TestPropertyAttribute";
public const string MicrosoftVisualStudioTestToolsUnitTestingWorkItemAttribute = "Microsoft.VisualStudio.TestTools.UnitTesting.WorkItemAttribute";
Expand Down
90 changes: 89 additions & 1 deletion src/Analyzers/MSTest.Analyzers/Resources.Designer.cs

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

36 changes: 35 additions & 1 deletion src/Analyzers/MSTest.Analyzers/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,40 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="AssemblyInitializeShouldBeValidDescription" xml:space="preserve">
<value>Assembly Initialize attribute should follow the following layout to be valid:
engyebrahim marked this conversation as resolved.
Show resolved Hide resolved
- it should be 'public'
- it should be 'static'
- it should not be generic
- it should take one parameter of type TestContext
- return type should be 'void', 'Task' or 'ValueTask'
- it should not be 'async void'
- it should not be a special method (finalizer, operator...).</value>
</data>
<data name="AssemblyInitializeShouldBeValidMessageFormat_NotAsyncVoid" xml:space="preserve">
<value>Assembly Initialize '{0}' should return 'void', 'Task' or 'ValueTask'</value>
engyebrahim marked this conversation as resolved.
Show resolved Hide resolved
</data>
<data name="AssemblyInitializeShouldBeValidMessageFormat_NotGeneric" xml:space="preserve">
<value>Assembly Initialize '{0}' should not be generic</value>
engyebrahim marked this conversation as resolved.
Show resolved Hide resolved
</data>
<data name="AssemblyInitializeShouldBeValidMessageFormat_Ordinary" xml:space="preserve">
<value>Assembly Initialize '{0}' should be an 'ordinary' method</value>
engyebrahim marked this conversation as resolved.
Show resolved Hide resolved
</data>
<data name="AssemblyInitializeShouldBeValidMessageFormat_Public" xml:space="preserve">
<value>Assembly Initialize '{0}' should be 'public'</value>
engyebrahim marked this conversation as resolved.
Show resolved Hide resolved
</data>
<data name="AssemblyInitializeShouldBeValidMessageFormat_ReturnType" xml:space="preserve">
<value>Assembly Initialize '{0}' should return 'void', 'Task' or 'ValueTask'</value>
engyebrahim marked this conversation as resolved.
Show resolved Hide resolved
</data>
<data name="AssemblyInitializeShouldBeValidMessageFormat_SingleContextParameter" xml:space="preserve">
<value>Assembly Initialize '{0}' should take a single parameter of type TestContext</value>
engyebrahim marked this conversation as resolved.
Show resolved Hide resolved
</data>
<data name="AssemblyInitializeShouldBeValidMessageFormat_Static" xml:space="preserve">
<value>Assembly Initialize '{0}' should be 'static'</value>
engyebrahim marked this conversation as resolved.
Show resolved Hide resolved
</data>
<data name="AssemblyInitializeShouldBeValidTitle" xml:space="preserve">
<value>Assembly Initializes method should have valid layout</value>
engyebrahim marked this conversation as resolved.
Show resolved Hide resolved
</data>
<data name="AvoidExpectedExceptionAttributeDescription" xml:space="preserve">
<value>Prefer 'Assert.ThrowsException' or 'Assert.ThrowsExceptionAsync' over '[ExpectedException]' as it ensures that only the expected call throws the expected exception. The assert APIs also provide more flexibility and allow you to assert extra properties of the exeption.</value>
</data>
Expand Down Expand Up @@ -255,7 +289,7 @@
<value>Test Initialize '{0}' should return 'void', 'Task' or 'ValueTask'</value>
</data>
<data name="TestInitializeShouldBeValidTitle" xml:space="preserve">
<value>Test methods should have valid layout</value>
<value>Test method method should have valid layout</value>
engyebrahim marked this conversation as resolved.
Show resolved Hide resolved
</data>
<data name="TestMethodShouldBeValidDescription" xml:space="preserve">
<value>Test methods, methods marked with the '[TestMethod]' attribute, should respect the following layout to be considered valid by MSTest:
Expand Down
Loading
Loading