-
-
Notifications
You must be signed in to change notification settings - Fork 52
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
New rules: MA0137 and MA0138 Use 'Async' suffix when a method returns…
… an awaitable type
- Loading branch information
Showing
9 changed files
with
328 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# MA0137 - Use 'Async' suffix when a method returns an awaitable type | ||
|
||
Methods that return awaitable types such as `Task` or `ValueTask` should have an Async suffix. | ||
|
||
````c# | ||
// compliant | ||
Task FooAsync() => Task.CompletedTask; | ||
|
||
// non-compliant | ||
Task Foo() => Task.CompletedTask; | ||
```` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# MA0138 - Do not use 'Async' suffix when a method does not return an awaitable type | ||
|
||
Methods that does not return an awaitable type such as `Task` or `ValueTask` should not have an 'Async' suffix. | ||
|
||
````c# | ||
// compliant | ||
void Foo() { } | ||
|
||
// non-compliant | ||
void FooAsync() { } | ||
```` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
101 changes: 101 additions & 0 deletions
101
...Meziantou.Analyzer/Rules/MethodsReturningAnAwaitableTypeMustHaveTheAsyncSuffixAnalyzer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
using System; | ||
using System.Collections.Immutable; | ||
using Meziantou.Analyzer.Internals; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
using Microsoft.CodeAnalysis.Operations; | ||
|
||
namespace Meziantou.Analyzer.Rules; | ||
|
||
[DiagnosticAnalyzer(LanguageNames.CSharp)] | ||
public sealed class MethodsReturningAnAwaitableTypeMustHaveTheAsyncSuffixAnalyzer : DiagnosticAnalyzer | ||
{ | ||
private static readonly DiagnosticDescriptor s_asyncSuffixRule = new( | ||
RuleIdentifiers.MethodsReturningAnAwaitableTypeMustHaveTheAsyncSuffix, | ||
title: "Use 'Async' suffix when a method returns an awaitable type", | ||
messageFormat: "Method returning an awaitable type must use the 'Async' suffix", | ||
RuleCategories.Design, | ||
DiagnosticSeverity.Warning, | ||
isEnabledByDefault: false, | ||
description: "", | ||
helpLinkUri: RuleIdentifiers.GetHelpUri(RuleIdentifiers.MethodsReturningAnAwaitableTypeMustHaveTheAsyncSuffix)); | ||
|
||
private static readonly DiagnosticDescriptor s_notAsyncSuffixRule = new( | ||
RuleIdentifiers.MethodsNotReturningAnAwaitableTypeMustNotHaveTheAsyncSuffix, | ||
title: "Do not use 'Async' suffix when a method does not return an awaitable type", | ||
messageFormat: "Method not returning an awaitable type must not use the 'Async' suffix", | ||
RuleCategories.Design, | ||
DiagnosticSeverity.Warning, | ||
isEnabledByDefault: false, | ||
description: "", | ||
helpLinkUri: RuleIdentifiers.GetHelpUri(RuleIdentifiers.MethodsNotReturningAnAwaitableTypeMustNotHaveTheAsyncSuffix)); | ||
|
||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(s_asyncSuffixRule, s_notAsyncSuffixRule); | ||
|
||
public override void Initialize(AnalysisContext context) | ||
{ | ||
context.EnableConcurrentExecution(); | ||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); | ||
context.RegisterCompilationStartAction(ctx => | ||
{ | ||
var context = new AnalyzerContext(ctx.Compilation); | ||
ctx.RegisterSymbolAction(context.AnalyzeSymbol, SymbolKind.Method); | ||
ctx.RegisterOperationAction(context.AnalyzeLocalFunction, OperationKind.LocalFunction); | ||
}); | ||
} | ||
|
||
private sealed class AnalyzerContext | ||
{ | ||
private readonly AwaitableTypes _awaitableTypes; | ||
|
||
public AnalyzerContext(Compilation compilation) | ||
{ | ||
_awaitableTypes = new AwaitableTypes(compilation); | ||
} | ||
|
||
public void AnalyzeSymbol(SymbolAnalysisContext context) | ||
{ | ||
var method = (IMethodSymbol)context.Symbol; | ||
if (method.IsOverrideOrInterfaceImplementation()) | ||
return; | ||
|
||
var hasAsyncSuffix = method.Name.EndsWith("Async", StringComparison.Ordinal); | ||
if (_awaitableTypes.IsAwaitable(method.ReturnType, context.Compilation)) | ||
{ | ||
if (!hasAsyncSuffix) | ||
{ | ||
context.ReportDiagnostic(s_asyncSuffixRule, method); | ||
} | ||
} | ||
else | ||
{ | ||
if (hasAsyncSuffix) | ||
{ | ||
context.ReportDiagnostic(s_notAsyncSuffixRule, method); | ||
} | ||
} | ||
} | ||
|
||
public void AnalyzeLocalFunction(OperationAnalysisContext context) | ||
{ | ||
var operation = (ILocalFunctionOperation)context.Operation; | ||
var method = operation.Symbol; | ||
|
||
var hasAsyncSuffix = method.Name.EndsWith("Async", StringComparison.Ordinal); | ||
if (_awaitableTypes.IsAwaitable(method.ReturnType, context.Compilation)) | ||
{ | ||
if (!hasAsyncSuffix) | ||
{ | ||
context.ReportDiagnostic(s_asyncSuffixRule, properties: default, operation, DiagnosticReportOptions.ReportOnMethodName); | ||
} | ||
} | ||
else | ||
{ | ||
if (hasAsyncSuffix) | ||
{ | ||
context.ReportDiagnostic(s_notAsyncSuffixRule, properties: default, operation, DiagnosticReportOptions.ReportOnMethodName); | ||
} | ||
} | ||
} | ||
} | ||
} |
139 changes: 139 additions & 0 deletions
139
...Analyzer.Test/Rules/MethodsReturningAnAwaitableTypeMustHaveTheAsyncSuffixAnalyzerTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
using System.Threading.Tasks; | ||
using Meziantou.Analyzer.Rules; | ||
using TestHelper; | ||
using Xunit; | ||
|
||
namespace Meziantou.Analyzer.Test.Rules; | ||
public sealed class MethodsReturningAnAwaitableTypeMustHaveTheAsyncSuffixAnalyzerTests | ||
{ | ||
private static ProjectBuilder CreateProjectBuilder() | ||
{ | ||
return new ProjectBuilder() | ||
.WithAnalyzer<MethodsReturningAnAwaitableTypeMustHaveTheAsyncSuffixAnalyzer>() | ||
.WithLanguageVersion(Microsoft.CodeAnalysis.CSharp.LanguageVersion.Preview); | ||
} | ||
|
||
[Fact] | ||
public async Task AsyncMethodWithSuffix() | ||
{ | ||
const string SourceCode = """ | ||
class TypeName | ||
{ | ||
System.Threading.Tasks.Task TestAsync() => throw null; | ||
} | ||
"""; | ||
await CreateProjectBuilder() | ||
.WithSourceCode(SourceCode) | ||
.ValidateAsync(); | ||
} | ||
|
||
[Fact] | ||
public async Task AsyncMethodWithoutSuffix() | ||
{ | ||
const string SourceCode = """ | ||
class TypeName | ||
{ | ||
System.Threading.Tasks.Task [|Test|]() => throw null; | ||
} | ||
"""; | ||
await CreateProjectBuilder() | ||
.WithSourceCode(SourceCode) | ||
.ValidateAsync(); | ||
} | ||
[Fact] | ||
public async Task VoidMethodWithSuffix() | ||
{ | ||
const string SourceCode = """ | ||
class TypeName | ||
{ | ||
void [|TestAsync|]() => throw null; | ||
} | ||
"""; | ||
await CreateProjectBuilder() | ||
.WithSourceCode(SourceCode) | ||
.ValidateAsync(); | ||
} | ||
|
||
[Fact] | ||
public async Task VoidMethodWithoutSuffix() | ||
{ | ||
const string SourceCode = """ | ||
class TypeName | ||
{ | ||
void Test() => throw null; | ||
} | ||
"""; | ||
await CreateProjectBuilder() | ||
.WithSourceCode(SourceCode) | ||
.ValidateAsync(); | ||
} | ||
|
||
[Fact] | ||
public async Task VoidLocalFunctionWithSuffix() | ||
{ | ||
const string SourceCode = """ | ||
class TypeName | ||
{ | ||
void Test() | ||
{ | ||
void [|FooAsync|]() => throw null; | ||
} | ||
} | ||
"""; | ||
await CreateProjectBuilder() | ||
.WithSourceCode(SourceCode) | ||
.ValidateAsync(); | ||
} | ||
|
||
[Fact] | ||
public async Task VoidLocalFunctionWithoutSuffix() | ||
{ | ||
const string SourceCode = """ | ||
class TypeName | ||
{ | ||
void Test() | ||
{ | ||
void Foo() => throw null; | ||
} | ||
} | ||
"""; | ||
await CreateProjectBuilder() | ||
.WithSourceCode(SourceCode) | ||
.ValidateAsync(); | ||
} | ||
|
||
[Fact] | ||
public async Task AwaitableLocalFunctionWithoutSuffix() | ||
{ | ||
const string SourceCode = """ | ||
class TypeName | ||
{ | ||
void Test() | ||
{ | ||
_ = Foo(); | ||
System.Threading.Tasks.Task [|Foo|]() => throw null; | ||
} | ||
} | ||
"""; | ||
await CreateProjectBuilder() | ||
.WithSourceCode(SourceCode) | ||
.ValidateAsync(); | ||
} | ||
|
||
[Fact] | ||
public async Task AwaitableLocalFunctionWithSuffix() | ||
{ | ||
const string SourceCode = """ | ||
class TypeName | ||
{ | ||
void Test() | ||
{ | ||
System.Threading.Tasks.Task FooAsync() => throw null; | ||
} | ||
} | ||
"""; | ||
await CreateProjectBuilder() | ||
.WithSourceCode(SourceCode) | ||
.ValidateAsync(); | ||
} | ||
} |