Skip to content

Commit

Permalink
Implement analyzer to warn for use of Assert methods in async void
Browse files Browse the repository at this point in the history
  • Loading branch information
Youssef1313 committed Jan 14, 2025
1 parent 2f59819 commit 8646dec
Show file tree
Hide file tree
Showing 20 changed files with 460 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ Rule ID | Category | Severity | Notes
--------|----------|----------|-------
MSTEST0038 | Usage | Warning | AvoidAssertAreSameWithValueTypesAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/core/testing/mstest-analyzers/mstest0038)
MSTEST0039 | Usage | Info | UseNewerAssertThrowsAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/core/testing/mstest-analyzers/mstest0039)
MSTEST0040 | Usage | Warning | AvoidUsingAssertsInAsyncVoidContextAnalyzer
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 Microsoft.CodeAnalysis.Operations;

using MSTest.Analyzers.Helpers;

namespace MSTest.Analyzers;

/// <summary>
/// MSTEST0040: <inheritdoc cref="Resources.AvoidUsingAssertsInAsyncVoidContextTitle"/>.
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
public sealed class AvoidUsingAssertsInAsyncVoidContextAnalyzer : DiagnosticAnalyzer
{
private static readonly LocalizableResourceString Title = new(nameof(Resources.AvoidUsingAssertsInAsyncVoidContextTitle), Resources.ResourceManager, typeof(Resources));
private static readonly LocalizableResourceString MessageFormat = new(nameof(Resources.AvoidUsingAssertsInAsyncVoidContextMessageFormat), Resources.ResourceManager, typeof(Resources));
private static readonly LocalizableResourceString Description = new(nameof(Resources.AvoidUsingAssertsInAsyncVoidContextDescription), Resources.ResourceManager, typeof(Resources));

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

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

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

context.RegisterCompilationStartAction(context =>
{
Compilation compilation = context.Compilation;
INamedTypeSymbol? assertSymbol = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftVisualStudioTestToolsUnitTestingAssert);
if (assertSymbol is not null)
{
context.RegisterOperationAction(context => AnalyzeOperation(context, assertSymbol), OperationKind.Invocation);
}
});
}

private static void AnalyzeOperation(OperationAnalysisContext context, INamedTypeSymbol assertSymbol)
{
var operation = (IInvocationOperation)context.Operation;
if (!IsAsyncVoidContext(operation, context.ContainingSymbol) ||
!assertSymbol.Equals(operation.TargetMethod.ContainingType, SymbolEqualityComparer.Default))
{
return;
}

context.ReportDiagnostic(operation.CreateDiagnostic(Rule));
}

private static bool IsAsyncVoidContext(IInvocationOperation invocationOperation, ISymbol containingSymbol)
{
if (containingSymbol is IMethodSymbol { IsAsync: true, ReturnsVoid: true })
{
return true;
}

// For the case of anonymous functions or local functions, the ContainingSymbol is the method that contains the anonymous function.
// So, we need to special case this.
IOperation? operation = invocationOperation;
while (operation is not null)
{
if (operation is IAnonymousFunctionOperation { Symbol.IsAsync: true, Symbol.ReturnsVoid: true } or
ILocalFunctionOperation { Symbol.IsAsync: true, Symbol.ReturnsVoid: true })
{
return true;
}

operation = operation.Parent;
}

return false;
}
}
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 @@ -44,4 +44,5 @@ internal static class DiagnosticIds
public const string UseProperAssertMethodsRuleId = "MSTEST0037";
public const string AvoidAssertAreSameWithValueTypesRuleId = "MSTEST0038";
public const string UseNewerAssertThrowsRuleId = "MSTEST0039";
public const string AvoidUsingAssertsInAsyncVoidContextRuleId = "MSTEST0040";
}
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.AvoidAssertAreSameWithValueTypesAnalyzer
MSTest.Analyzers.AvoidAssertAreSameWithValueTypesAnalyzer.AvoidAssertAreSameWithValueTypesAnalyzer() -> void
MSTest.Analyzers.AvoidUsingAssertsInAsyncVoidContextAnalyzer
MSTest.Analyzers.AvoidUsingAssertsInAsyncVoidContextAnalyzer.AvoidUsingAssertsInAsyncVoidContextAnalyzer() -> void
override MSTest.Analyzers.AvoidAssertAreSameWithValueTypesAnalyzer.Initialize(Microsoft.CodeAnalysis.Diagnostics.AnalysisContext! context) -> void
override MSTest.Analyzers.AvoidAssertAreSameWithValueTypesAnalyzer.SupportedDiagnostics.get -> System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.DiagnosticDescriptor!>
override MSTest.Analyzers.AvoidUsingAssertsInAsyncVoidContextAnalyzer.Initialize(Microsoft.CodeAnalysis.Diagnostics.AnalysisContext! context) -> void
override MSTest.Analyzers.AvoidUsingAssertsInAsyncVoidContextAnalyzer.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 @@ -558,4 +558,13 @@ The type declaring these methods should also respect the following rules:
<data name="AvoidAssertAreSameWithValueTypesDescription" xml:space="preserve">
<value>Use 'Assert.AreEqual'/'Assert.AreNotEqual' instead of 'Assert.AreSame'/'Assert.AreNotSame' when comparing value types. Passing a value type to 'Assert.AreSame'/'Assert.AreNotSame' will be boxed (creating a new object). Because 'Assert.AreSame'/'Assert.AreNotSame' does the comparison by reference, 'Assert.AreSame' will fail when boxing happens, and 'Assert.AreNotSame' will always pass.</value>
</data>
<data name="AvoidUsingAssertsInAsyncVoidContextTitle" xml:space="preserve">
<value>Avoid doing assertions inside 'async void' contexts</value>
</data>
<data name="AvoidUsingAssertsInAsyncVoidContextMessageFormat" xml:space="preserve">
<value>Avoid doing assertions inside 'async void' methods or lambdas because they may not fail the test</value>
</data>
<data name="AvoidUsingAssertsInAsyncVoidContextDescription" xml:space="preserve">
<value>Avoid doing assertions in 'async void' methods or lambdas. Exceptions that are thrown in this context will be unobserved exceptions.</value>
</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 @@ -147,6 +147,21 @@ Typ deklarující tyto metody by měl také respektovat následující pravidla:
<target state="translated">Vyhněte se [ExpectedException]</target>
<note />
</trans-unit>
<trans-unit id="AvoidUsingAssertsInAsyncVoidContextDescription">
<source>Avoid doing assertions in 'async void' methods or lambdas. Exceptions that are thrown in this context will be unobserved exceptions.</source>
<target state="new">Avoid doing assertions in 'async void' methods or lambdas. Exceptions that are thrown in this context will be unobserved exceptions.</target>
<note />
</trans-unit>
<trans-unit id="AvoidUsingAssertsInAsyncVoidContextMessageFormat">
<source>Avoid doing assertions inside 'async void' methods or lambdas because they may not fail the test</source>
<target state="new">Avoid doing assertions inside 'async void' methods or lambdas because they may not fail the test</target>
<note />
</trans-unit>
<trans-unit id="AvoidUsingAssertsInAsyncVoidContextTitle">
<source>Avoid doing assertions inside 'async void' contexts</source>
<target state="new">Avoid doing assertions inside 'async void' contexts</target>
<note />
</trans-unit>
<trans-unit id="ClassCleanupShouldBeValidDescription">
<source>Methods marked with '[ClassCleanup]' should follow the following layout to be valid:
-it can't be declared on a generic class without the 'InheritanceBehavior' mode 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 @@ -147,6 +147,21 @@ Der Typ, der diese Methoden deklariert, sollte auch die folgenden Regeln beachte
<target state="translated">„[ExpectedException]“ vermeiden</target>
<note />
</trans-unit>
<trans-unit id="AvoidUsingAssertsInAsyncVoidContextDescription">
<source>Avoid doing assertions in 'async void' methods or lambdas. Exceptions that are thrown in this context will be unobserved exceptions.</source>
<target state="new">Avoid doing assertions in 'async void' methods or lambdas. Exceptions that are thrown in this context will be unobserved exceptions.</target>
<note />
</trans-unit>
<trans-unit id="AvoidUsingAssertsInAsyncVoidContextMessageFormat">
<source>Avoid doing assertions inside 'async void' methods or lambdas because they may not fail the test</source>
<target state="new">Avoid doing assertions inside 'async void' methods or lambdas because they may not fail the test</target>
<note />
</trans-unit>
<trans-unit id="AvoidUsingAssertsInAsyncVoidContextTitle">
<source>Avoid doing assertions inside 'async void' contexts</source>
<target state="new">Avoid doing assertions inside 'async void' contexts</target>
<note />
</trans-unit>
<trans-unit id="ClassCleanupShouldBeValidDescription">
<source>Methods marked with '[ClassCleanup]' should follow the following layout to be valid:
-it can't be declared on a generic class without the 'InheritanceBehavior' mode 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 @@ -147,6 +147,21 @@ El tipo que declara estos métodos también debe respetar las reglas siguientes:
<target state="translated">Evitar '[ExpectedException]'</target>
<note />
</trans-unit>
<trans-unit id="AvoidUsingAssertsInAsyncVoidContextDescription">
<source>Avoid doing assertions in 'async void' methods or lambdas. Exceptions that are thrown in this context will be unobserved exceptions.</source>
<target state="new">Avoid doing assertions in 'async void' methods or lambdas. Exceptions that are thrown in this context will be unobserved exceptions.</target>
<note />
</trans-unit>
<trans-unit id="AvoidUsingAssertsInAsyncVoidContextMessageFormat">
<source>Avoid doing assertions inside 'async void' methods or lambdas because they may not fail the test</source>
<target state="new">Avoid doing assertions inside 'async void' methods or lambdas because they may not fail the test</target>
<note />
</trans-unit>
<trans-unit id="AvoidUsingAssertsInAsyncVoidContextTitle">
<source>Avoid doing assertions inside 'async void' contexts</source>
<target state="new">Avoid doing assertions inside 'async void' contexts</target>
<note />
</trans-unit>
<trans-unit id="ClassCleanupShouldBeValidDescription">
<source>Methods marked with '[ClassCleanup]' should follow the following layout to be valid:
-it can't be declared on a generic class without the 'InheritanceBehavior' mode 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 @@ -147,6 +147,21 @@ Le type doit être une classe
<target state="translated">Éviter « [ExpectedException] »</target>
<note />
</trans-unit>
<trans-unit id="AvoidUsingAssertsInAsyncVoidContextDescription">
<source>Avoid doing assertions in 'async void' methods or lambdas. Exceptions that are thrown in this context will be unobserved exceptions.</source>
<target state="new">Avoid doing assertions in 'async void' methods or lambdas. Exceptions that are thrown in this context will be unobserved exceptions.</target>
<note />
</trans-unit>
<trans-unit id="AvoidUsingAssertsInAsyncVoidContextMessageFormat">
<source>Avoid doing assertions inside 'async void' methods or lambdas because they may not fail the test</source>
<target state="new">Avoid doing assertions inside 'async void' methods or lambdas because they may not fail the test</target>
<note />
</trans-unit>
<trans-unit id="AvoidUsingAssertsInAsyncVoidContextTitle">
<source>Avoid doing assertions inside 'async void' contexts</source>
<target state="new">Avoid doing assertions inside 'async void' contexts</target>
<note />
</trans-unit>
<trans-unit id="ClassCleanupShouldBeValidDescription">
<source>Methods marked with '[ClassCleanup]' should follow the following layout to be valid:
-it can't be declared on a generic class without the 'InheritanceBehavior' mode 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 @@ -147,6 +147,21 @@ Anche il tipo che dichiara questi metodi deve rispettare le regole seguenti:
<target state="translated">Evita '[ExpectedException]'</target>
<note />
</trans-unit>
<trans-unit id="AvoidUsingAssertsInAsyncVoidContextDescription">
<source>Avoid doing assertions in 'async void' methods or lambdas. Exceptions that are thrown in this context will be unobserved exceptions.</source>
<target state="new">Avoid doing assertions in 'async void' methods or lambdas. Exceptions that are thrown in this context will be unobserved exceptions.</target>
<note />
</trans-unit>
<trans-unit id="AvoidUsingAssertsInAsyncVoidContextMessageFormat">
<source>Avoid doing assertions inside 'async void' methods or lambdas because they may not fail the test</source>
<target state="new">Avoid doing assertions inside 'async void' methods or lambdas because they may not fail the test</target>
<note />
</trans-unit>
<trans-unit id="AvoidUsingAssertsInAsyncVoidContextTitle">
<source>Avoid doing assertions inside 'async void' contexts</source>
<target state="new">Avoid doing assertions inside 'async void' contexts</target>
<note />
</trans-unit>
<trans-unit id="ClassCleanupShouldBeValidDescription">
<source>Methods marked with '[ClassCleanup]' should follow the following layout to be valid:
-it can't be declared on a generic class without the 'InheritanceBehavior' mode 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 @@ -147,6 +147,21 @@ The type declaring these methods should also respect the following rules:
<target state="translated">'[ExpectedException]' を回避する</target>
<note />
</trans-unit>
<trans-unit id="AvoidUsingAssertsInAsyncVoidContextDescription">
<source>Avoid doing assertions in 'async void' methods or lambdas. Exceptions that are thrown in this context will be unobserved exceptions.</source>
<target state="new">Avoid doing assertions in 'async void' methods or lambdas. Exceptions that are thrown in this context will be unobserved exceptions.</target>
<note />
</trans-unit>
<trans-unit id="AvoidUsingAssertsInAsyncVoidContextMessageFormat">
<source>Avoid doing assertions inside 'async void' methods or lambdas because they may not fail the test</source>
<target state="new">Avoid doing assertions inside 'async void' methods or lambdas because they may not fail the test</target>
<note />
</trans-unit>
<trans-unit id="AvoidUsingAssertsInAsyncVoidContextTitle">
<source>Avoid doing assertions inside 'async void' contexts</source>
<target state="new">Avoid doing assertions inside 'async void' contexts</target>
<note />
</trans-unit>
<trans-unit id="ClassCleanupShouldBeValidDescription">
<source>Methods marked with '[ClassCleanup]' should follow the following layout to be valid:
-it can't be declared on a generic class without the 'InheritanceBehavior' mode is set
Expand Down
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 @@ -147,6 +147,21 @@ The type declaring these methods should also respect the following rules:
<target state="translated">'[ExpectedException]' 사용 지양</target>
<note />
</trans-unit>
<trans-unit id="AvoidUsingAssertsInAsyncVoidContextDescription">
<source>Avoid doing assertions in 'async void' methods or lambdas. Exceptions that are thrown in this context will be unobserved exceptions.</source>
<target state="new">Avoid doing assertions in 'async void' methods or lambdas. Exceptions that are thrown in this context will be unobserved exceptions.</target>
<note />
</trans-unit>
<trans-unit id="AvoidUsingAssertsInAsyncVoidContextMessageFormat">
<source>Avoid doing assertions inside 'async void' methods or lambdas because they may not fail the test</source>
<target state="new">Avoid doing assertions inside 'async void' methods or lambdas because they may not fail the test</target>
<note />
</trans-unit>
<trans-unit id="AvoidUsingAssertsInAsyncVoidContextTitle">
<source>Avoid doing assertions inside 'async void' contexts</source>
<target state="new">Avoid doing assertions inside 'async void' contexts</target>
<note />
</trans-unit>
<trans-unit id="ClassCleanupShouldBeValidDescription">
<source>Methods marked with '[ClassCleanup]' should follow the following layout to be valid:
-it can't be declared on a generic class without the 'InheritanceBehavior' mode is set
Expand Down
Loading

0 comments on commit 8646dec

Please sign in to comment.