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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,8 @@ If you are already using other analyzers, you can check [which rules are duplica
|[MA0134](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0134.md)|Usage|Observe result of async calls|⚠️|✔️|❌|
|[MA0135](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0135.md)|Design|The log parameter has no configured type|⚠️|❌|❌|
|[MA0136](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0136.md)|Usage|Raw String contains an implicit end of line character|👻|✔️|❌|
|[MA0137](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0137.md)|Design|Use 'Async' suffix when a method returns an awaitable type|⚠️|❌||
|[MA0138](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0138.md)|Design|Do not use 'Async' suffix when a method does not return an awaitable type|⚠️|❌||
|[MA0137](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0137.md)|Design|Use 'Async' suffix when a method returns an awaitable type|⚠️|❌|✔️|
|[MA0138](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0138.md)|Design|Do not use 'Async' suffix when a method does not return an awaitable type|⚠️|❌|✔️|
|[MA0139](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0139.md)|Design|Log parameter type is not valid|⚠️|✔️|❌|
|[MA0140](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0140.md)|Design|Both if and else branch have identical code|⚠️|✔️|❌|
|[MA0141](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0141.md)|Usage|Use pattern matching instead of inequality operators for null check|ℹ️|❌|✔️|
Expand Down
4 changes: 2 additions & 2 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,8 @@
|[MA0134](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0134.md)|Usage|Observe result of async calls|<span title='Warning'>⚠️</span>|✔️|❌|
|[MA0135](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0135.md)|Design|The log parameter has no configured type|<span title='Warning'>⚠️</span>|❌|❌|
|[MA0136](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0136.md)|Usage|Raw String contains an implicit end of line character|<span title='Hidden'>👻</span>|✔️|❌|
|[MA0137](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0137.md)|Design|Use 'Async' suffix when a method returns an awaitable type|<span title='Warning'>⚠️</span>|❌||
|[MA0138](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0138.md)|Design|Do not use 'Async' suffix when a method does not return an awaitable type|<span title='Warning'>⚠️</span>|❌||
|[MA0137](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0137.md)|Design|Use 'Async' suffix when a method returns an awaitable type|<span title='Warning'>⚠️</span>|❌|✔️|
|[MA0138](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0138.md)|Design|Do not use 'Async' suffix when a method does not return an awaitable type|<span title='Warning'>⚠️</span>|❌|✔️|
|[MA0139](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0139.md)|Design|Log parameter type is not valid|<span title='Warning'>⚠️</span>|✔️|❌|
|[MA0140](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0140.md)|Design|Both if and else branch have identical code|<span title='Warning'>⚠️</span>|✔️|❌|
|[MA0141](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0141.md)|Usage|Use pattern matching instead of inequality operators for null check|<span title='Info'>ℹ️</span>|❌|✔️|
Expand Down
2 changes: 1 addition & 1 deletion docs/Rules/MA0137.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# MA0137 - Use 'Async' suffix when a method returns an awaitable type
<!-- sources -->
Source: [MethodsReturningAnAwaitableTypeMustHaveTheAsyncSuffixAnalyzer.cs](https://github.com/meziantou/Meziantou.Analyzer/blob/main/src/Meziantou.Analyzer/Rules/MethodsReturningAnAwaitableTypeMustHaveTheAsyncSuffixAnalyzer.cs)
Sources: [MethodsReturningAnAwaitableTypeMustHaveTheAsyncSuffixAnalyzer.cs](https://github.com/meziantou/Meziantou.Analyzer/blob/main/src/Meziantou.Analyzer/Rules/MethodsReturningAnAwaitableTypeMustHaveTheAsyncSuffixAnalyzer.cs), [MethodsReturningAnAwaitableTypeMustHaveTheAsyncSuffixFixer.cs](https://github.com/meziantou/Meziantou.Analyzer/blob/main/src/Meziantou.Analyzer.CodeFixers/Rules/MethodsReturningAnAwaitableTypeMustHaveTheAsyncSuffixFixer.cs)
<!-- sources -->

Methods that return awaitable types such as `Task` or `ValueTask` should have an Async suffix.
Expand Down
2 changes: 1 addition & 1 deletion docs/Rules/MA0138.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# MA0138 - Do not use 'Async' suffix when a method does not return an awaitable type
<!-- sources -->
Source: [MethodsReturningAnAwaitableTypeMustHaveTheAsyncSuffixAnalyzer.cs](https://github.com/meziantou/Meziantou.Analyzer/blob/main/src/Meziantou.Analyzer/Rules/MethodsReturningAnAwaitableTypeMustHaveTheAsyncSuffixAnalyzer.cs)
Sources: [MethodsReturningAnAwaitableTypeMustHaveTheAsyncSuffixAnalyzer.cs](https://github.com/meziantou/Meziantou.Analyzer/blob/main/src/Meziantou.Analyzer/Rules/MethodsReturningAnAwaitableTypeMustHaveTheAsyncSuffixAnalyzer.cs), [MethodsReturningAnAwaitableTypeMustHaveTheAsyncSuffixFixer.cs](https://github.com/meziantou/Meziantou.Analyzer/blob/main/src/Meziantou.Analyzer.CodeFixers/Rules/MethodsReturningAnAwaitableTypeMustHaveTheAsyncSuffixFixer.cs)
<!-- sources -->

Methods that does not return an awaitable type such as `Task` or `ValueTask` should not have an 'Async' suffix.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using System.Collections.Immutable;
using System.Composition;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Rename;

namespace Meziantou.Analyzer.Rules;

[ExportCodeFixProvider(LanguageNames.CSharp), Shared]
public sealed class MethodsReturningAnAwaitableTypeMustHaveTheAsyncSuffixFixer : CodeFixProvider
{
public override ImmutableArray<string> FixableDiagnosticIds =>
ImmutableArray.Create(
RuleIdentifiers.MethodsReturningAnAwaitableTypeMustHaveTheAsyncSuffix,
RuleIdentifiers.MethodsNotReturningAnAwaitableTypeMustNotHaveTheAsyncSuffix);

public override FixAllProvider? GetFixAllProvider() => null;

public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
if (root is null)
return;

var nodeToFix = root.FindNode(context.Span, getInnermostNodeForTie: true);
if (nodeToFix is null)
return;

var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false);
if (semanticModel is null)
return;

IMethodSymbol? methodSymbol = null;

// Try to get the method symbol: either from a method declaration or a local function
var declarationNode = nodeToFix.AncestorsAndSelf().FirstOrDefault(n => n is MethodDeclarationSyntax or LocalFunctionStatementSyntax);
if (declarationNode is MethodDeclarationSyntax methodDeclaration)
{
methodSymbol = semanticModel.GetDeclaredSymbol(methodDeclaration, context.CancellationToken) as IMethodSymbol;
}
else if (declarationNode is LocalFunctionStatementSyntax localFunctionStatement)
{
methodSymbol = semanticModel.GetDeclaredSymbol(localFunctionStatement, context.CancellationToken) as IMethodSymbol;
}

if (methodSymbol is null)
return;

foreach (var diagnostic in context.Diagnostics)
{
string newName;
string title;
if (diagnostic.Id == RuleIdentifiers.MethodsReturningAnAwaitableTypeMustHaveTheAsyncSuffix)
{
newName = methodSymbol.Name + "Async";
title = $"Rename to '{newName}'";
}
else
{
if (!methodSymbol.Name.EndsWith("Async", StringComparison.Ordinal))
continue;

newName = methodSymbol.Name[..^"Async".Length];
title = $"Rename to '{newName}'";
}

context.RegisterCodeFix(
CodeAction.Create(
title,
ct => RenameMethodAsync(context.Document, methodSymbol, newName, ct),
equivalenceKey: title),
diagnostic);
}
}

private static async Task<Solution> RenameMethodAsync(Document document, IMethodSymbol methodSymbol, string newName, CancellationToken cancellationToken)
{
var solution = document.Project.Solution;
return await Renamer.RenameSymbolAsync(solution, methodSymbol, new SymbolRenameOptions(), newName, cancellationToken).ConfigureAwait(false);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public sealed class MethodsReturningAnAwaitableTypeMustHaveTheAsyncSuffixAnalyze
private static ProjectBuilder CreateProjectBuilder()
=> new ProjectBuilder()
.WithAnalyzer<MethodsReturningAnAwaitableTypeMustHaveTheAsyncSuffixAnalyzer>()
.WithCodeFixProvider<MethodsReturningAnAwaitableTypeMustHaveTheAsyncSuffixFixer>()
.WithTargetFramework(TargetFramework.Net8_0)
.WithLanguageVersion(Microsoft.CodeAnalysis.CSharp.LanguageVersion.Preview);

Expand Down Expand Up @@ -171,6 +172,69 @@ class TypeName
.AddXUnitApi()
.ValidateAsync();

[Fact]
public Task AsyncMethodWithoutSuffix_CodeFix_AddsAsyncSuffix()
=> CreateProjectBuilder()
.WithSourceCode("""
class TypeName
{
System.Threading.Tasks.Task {|MA0137:Test|}() => throw null;
void Caller() { _ = Test(); }
}
""")
.ShouldFixCodeWith("""
class TypeName
{
System.Threading.Tasks.Task TestAsync() => throw null;
void Caller() { _ = TestAsync(); }
}
""")
.ValidateAsync();

[Fact]
public Task MethodNotReturningAwaitableTypeWithSuffix_CodeFix_RemovesAsyncSuffix()
=> CreateProjectBuilder()
.WithSourceCode("""
class TypeName
{
void {|MA0138:TestAsync|}() => throw null;
void Caller() { TestAsync(); }
}
""")
.ShouldFixCodeWith("""
class TypeName
{
void Test() => throw null;
void Caller() { Test(); }
}
""")
.ValidateAsync();

[Fact]
public Task VoidLocalFunctionWithSuffix_CodeFix_RemovesAsyncSuffix()
=> CreateProjectBuilder()
.WithSourceCode("""
class TypeName
{
void Test()
{
void {|MA0138:FooAsync|}() => throw null;
FooAsync();
}
}
""")
.ShouldFixCodeWith("""
class TypeName
{
void Test()
{
void Foo() => throw null;
Foo();
}
}
""")
.ValidateAsync();

[Fact]
public Task IgnoreTestMethods_ExcludeTestMethodsTrue()
=> CreateProjectBuilder()
Expand Down
Loading