Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
141 changes: 141 additions & 0 deletions TUnit.Analyzers.Tests/AsyncVoidAnalyzerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,145 @@ public async Task Test()
"""
);
}

[Test]
public async Task Async_Void_Lambda_Raises_Error()
{
await Verifier
.VerifyAnalyzerAsync(
"""
using System;
using System.Threading.Tasks;
using TUnit.Core;

public class MyClass
{
[Test]
public void Test()
{
Action action = {|#0:async () =>
{
await Task.Delay(1);
}|};
}
}
""",

Verifier
.Diagnostic(Rules.AsyncVoidMethod)
.WithLocation(0)
);
}

[Test]
public async Task Async_Task_Lambda_Raises_No_Error()
{
await Verifier
.VerifyAnalyzerAsync(
"""
using System;
using System.Threading.Tasks;
using TUnit.Core;

public class MyClass
{
[Test]
public void Test()
{
Func<Task> action = async () =>
{
await Task.Delay(1);
};
}
}
"""
);
}

[Test]
public async Task Async_Void_AnonymousDelegate_Raises_Error()
{
await Verifier
.VerifyAnalyzerAsync(
"""
using System;
using System.Threading.Tasks;
using TUnit.Core;

public class MyClass
{
[Test]
public void Test()
{
Action action = {|#0:async delegate
{
await Task.Delay(1);
}|};
}
}
""",

Verifier
.Diagnostic(Rules.AsyncVoidMethod)
.WithLocation(0)
);
}

[Test]
public async Task Async_Void_Lambda_With_Parameter_Raises_Error()
{
await Verifier
.VerifyAnalyzerAsync(
"""
using System;
using System.Threading.Tasks;
using TUnit.Core;

public class MyClass
{
[Test]
public void Test()
{
Action<int> action = {|#0:async (x) =>
{
await Task.Delay(1);
}|};
}
}
""",

Verifier
.Diagnostic(Rules.AsyncVoidMethod)
.WithLocation(0)
);
}

[Test]
public async Task Async_Void_SimpleLambda_Raises_Error()
{
await Verifier
.VerifyAnalyzerAsync(
"""
using System;
using System.Threading.Tasks;
using TUnit.Core;

public class MyClass
{
[Test]
public void Test()
{
Action<int> action = {|#0:async x =>
{
await Task.Delay(1);
}|};
}
}
""",

Verifier
.Diagnostic(Rules.AsyncVoidMethod)
.WithLocation(0)
);
}
}
2 changes: 1 addition & 1 deletion TUnit.Analyzers/AnalyzerReleases.Shipped.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

### New Rules

#### Test Method and Structure Rules

Check warning on line 5 in TUnit.Analyzers/AnalyzerReleases.Shipped.md

View workflow job for this annotation

GitHub Actions / modularpipeline (fr-FR)

Le fichier de version d'analyseur 'AnalyzerReleases.Shipped.md' a un en-tête de version '#### Test Method and Structure Rules' non valide, ou celui-ci est manquant (https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md)
Rule ID | Category | Severity | Notes
--------|----------|----------|------------------------------------------------
TUnit0001 | Usage | Error | Test argument types don't match method parameters
Expand Down Expand Up @@ -62,7 +62,7 @@
Rule ID | Category | Severity | Notes
--------|----------|----------|------------------------------------------------
TUnit0015 | Usage | Error | Methods with [Timeout] must have a CancellationToken parameter
TUnit0031 | Usage | Error | Async void methods not allowed - return Task instead
TUnit0031 | Usage | Error | Async void methods and lambdas not allowed - return Task instead

#### AOT Compatibility Rules
Rule ID | Category | Severity | Notes
Expand Down
32 changes: 30 additions & 2 deletions TUnit.Analyzers/AsyncVoidAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;

namespace TUnit.Analyzers;
Expand All @@ -12,10 +14,14 @@ public class AsyncVoidAnalyzer : ConcurrentDiagnosticAnalyzer

protected override void InitializeInternal(AnalysisContext context)
{
context.RegisterSymbolAction(AnalyzeSyntax, SymbolKind.Method);
context.RegisterSymbolAction(AnalyzeMethod, SymbolKind.Method);
context.RegisterSyntaxNodeAction(AnalyzeLambda,
SyntaxKind.ParenthesizedLambdaExpression,
SyntaxKind.SimpleLambdaExpression,
SyntaxKind.AnonymousMethodExpression);
}

private void AnalyzeSyntax(SymbolAnalysisContext context)
private void AnalyzeMethod(SymbolAnalysisContext context)
{
if (context.Symbol is not IMethodSymbol methodSymbol)
{
Expand All @@ -29,4 +35,26 @@ private void AnalyzeSyntax(SymbolAnalysisContext context)
);
}
}

private void AnalyzeLambda(SyntaxNodeAnalysisContext context)
{
if (context.Node is not AnonymousFunctionExpressionSyntax anonymousFunction)
{
return;
}

if (!anonymousFunction.Modifiers.Any(SyntaxKind.AsyncKeyword))
{
return;
}

var symbol = context.SemanticModel.GetSymbolInfo(anonymousFunction).Symbol;

if (symbol is IMethodSymbol { ReturnsVoid: true })
{
context.ReportDiagnostic(Diagnostic.Create(Rules.AsyncVoidMethod,
anonymousFunction.GetLocation())
);
}
}
}
6 changes: 3 additions & 3 deletions TUnit.Analyzers/Resources.Designer.cs

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

6 changes: 3 additions & 3 deletions TUnit.Analyzers/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -217,13 +217,13 @@
<value>Class doesn't pick up tests from the base class</value>
</data>
<data name="TUnit0031Description" xml:space="preserve">
<value>Async void methods are not allowed.</value>
<value>Async void methods and lambdas are not allowed.</value>
</data>
<data name="TUnit0031MessageFormat" xml:space="preserve">
<value>Async void methods are not allowed</value>
<value>Async void methods and lambdas are not allowed</value>
</data>
<data name="TUnit0031Title" xml:space="preserve">
<value>Async void methods are not allowed</value>
<value>Async void methods and lambdas are not allowed</value>
</data>
<data name="TUnit0032Description" xml:space="preserve">
<value>Conflicting DependsOn and NotInParallel attributes.</value>
Expand Down
Loading