-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
261 additions
and
43 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
39 changes: 39 additions & 0 deletions
39
src/EcoCode.Analyzers/Analyzers/EC84_AvoidAsyncVoidMethodsAnalyzer.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,39 @@ | ||
namespace EcoCode.Analyzers; | ||
|
||
/// <summary>Analyzer for avoid async void methods.</summary> | ||
[DiagnosticAnalyzer(LanguageNames.CSharp)] | ||
public sealed class AvoidAsyncVoidMethodsAnalyzer : DiagnosticAnalyzer | ||
{ | ||
/// <summary>The diagnostic descriptor.</summary> | ||
public static DiagnosticDescriptor Descriptor { get; } = new( | ||
Rule.Ids.EC84_AvoidAsyncVoidMethods, | ||
title: "Avoid async void methods", | ||
messageFormat: "Avoid async void methods", | ||
Rule.Categories.Design, | ||
DiagnosticSeverity.Warning, | ||
isEnabledByDefault: true, | ||
description: null, | ||
helpLinkUri: Rule.GetHelpUri(Rule.Ids.EC84_AvoidAsyncVoidMethods)); | ||
|
||
/// <inheritdoc/> | ||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [Descriptor]; | ||
|
||
/// <inheritdoc/> | ||
public override void Initialize(AnalysisContext context) | ||
{ | ||
context.EnableConcurrentExecution(); | ||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); | ||
context.RegisterSyntaxNodeAction(static context => AnalyzeMethod(context), SyntaxKind.MethodDeclaration); | ||
} | ||
|
||
private static void AnalyzeMethod(SyntaxNodeAnalysisContext context) | ||
{ | ||
var methodDeclaration = (MethodDeclarationSyntax)context.Node; | ||
if (methodDeclaration.Modifiers.Any(SyntaxKind.AsyncKeyword) && | ||
methodDeclaration.ReturnType is PredefinedTypeSyntax returnType && | ||
returnType.Keyword.IsKind(SyntaxKind.VoidKeyword)) | ||
{ | ||
context.ReportDiagnostic(Diagnostic.Create(Descriptor, methodDeclaration.Identifier.GetLocation())); | ||
} | ||
} | ||
} |
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
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
56 changes: 56 additions & 0 deletions
56
src/EcoCode.CodeFixes/CodeFixes/EC84_AvoidAsyncVoidMethodsCodeFixProvider.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,56 @@ | ||
namespace EcoCode.CodeFixes; | ||
|
||
/// <summary>The code fix provider for avoid async void methods.</summary> | ||
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(AvoidAsyncVoidMethodsCodeFixProvider)), Shared] | ||
public sealed class AvoidAsyncVoidMethodsCodeFixProvider : CodeFixProvider | ||
{ | ||
/// <inheritdoc/> | ||
public override ImmutableArray<string> FixableDiagnosticIds => [AvoidAsyncVoidMethodsAnalyzer.Descriptor.Id]; | ||
|
||
/// <inheritdoc/> | ||
public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; | ||
|
||
/// <inheritdoc/> | ||
public override async Task RegisterCodeFixesAsync(CodeFixContext context) | ||
{ | ||
if (context.Diagnostics.Length == 0) return; | ||
|
||
var document = context.Document; | ||
var root = await document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); | ||
if (root is null) return; | ||
|
||
foreach (var diagnostic in context.Diagnostics) | ||
{ | ||
var parent = root.FindToken(diagnostic.Location.SourceSpan.Start).Parent; | ||
if (parent is null) continue; | ||
|
||
foreach (var node in parent.AncestorsAndSelf()) | ||
{ | ||
if (node is not MethodDeclarationSyntax declaration) continue; | ||
context.RegisterCodeFix( | ||
CodeAction.Create( | ||
title: "Convert to async Task", | ||
createChangedDocument: token => RefactorAsync(document, declaration, token), | ||
equivalenceKey: "Convert to async Task"), | ||
diagnostic); | ||
break; | ||
} | ||
} | ||
} | ||
|
||
private static async Task<Document> RefactorAsync(Document document, MethodDeclarationSyntax methodDecl, CancellationToken token) | ||
{ | ||
// Note : it may seem a good idea to add the System.Thread.Tasks using directive if it isn't present yet | ||
// However it isn't properly doable because : | ||
// - It could be added as a global using in a different file, but Roslyn doesn't give easy access to those | ||
// - The user could have enabled the ImplicitUsings option, which makes the using directives both global and invisible to the analyzer | ||
// So as a result, we simply don't handle it | ||
|
||
var root = await document.GetSyntaxRootAsync(token).ConfigureAwait(false); | ||
return root is null ? document : document.WithSyntaxRoot( | ||
root.ReplaceNode(methodDecl, methodDecl.WithReturnType( | ||
SyntaxFactory.IdentifierName("Task") // Change the return type of the method to Task | ||
.WithLeadingTrivia(methodDecl.ReturnType.GetLeadingTrivia()) | ||
.WithTrailingTrivia(methodDecl.ReturnType.GetTrailingTrivia())))); | ||
} | ||
} |
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 |
---|---|---|
@@ -1,8 +1,14 @@ | ||
global using EcoCode.Analyzers; | ||
global using EcoCode.Shared; | ||
global using Microsoft.CodeAnalysis; | ||
global using Microsoft.CodeAnalysis.CodeActions; | ||
global using Microsoft.CodeAnalysis.CodeFixes; | ||
global using Microsoft.CodeAnalysis.CSharp; | ||
global using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
global using Microsoft.CodeAnalysis.Editing; | ||
global using Microsoft.CodeAnalysis.Simplification; | ||
global using System.Collections.Immutable; | ||
global using System.Composition; | ||
global using System.Runtime.InteropServices; | ||
global using System.Threading; | ||
global using System.Threading.Tasks; |
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 |
---|---|---|
@@ -1,3 +1,2 @@ | ||
global using Microsoft.CodeAnalysis; | ||
global using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
global using System.Linq; |
91 changes: 91 additions & 0 deletions
91
src/EcoCode.Tests/Tests/EC84_AvoidAsyncVoidMethodsUnitTests.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,91 @@ | ||
namespace EcoCode.Tests; | ||
|
||
[TestClass] | ||
public class AvoidAsyncVoidMethodsUnitTests | ||
{ | ||
private static readonly VerifyDlg VerifyAsync = CodeFixVerifier.VerifyAsync< | ||
AvoidAsyncVoidMethodsAnalyzer, | ||
AvoidAsyncVoidMethodsCodeFixProvider>; | ||
|
||
[TestMethod] | ||
public async Task EmptyCodeAsync() => await VerifyAsync("").ConfigureAwait(false); | ||
|
||
[TestMethod] | ||
public async Task AvoidAsyncVoidMethodAsync() => await VerifyAsync(""" | ||
using System; | ||
using System.Threading.Tasks; | ||
public static class Program | ||
{ | ||
public static async void [|Main|]() | ||
{ | ||
await Task.Delay(1000).ConfigureAwait(false); | ||
Console.WriteLine(); | ||
} | ||
} | ||
""", | ||
fixedSource: """ | ||
using System; | ||
using System.Threading.Tasks; | ||
public static class Program | ||
{ | ||
public static async Task Main() | ||
{ | ||
await Task.Delay(1000).ConfigureAwait(false); | ||
Console.WriteLine(); | ||
} | ||
} | ||
""").ConfigureAwait(false); | ||
|
||
[TestMethod] | ||
public async Task AvoidAsyncVoidMethodWithMissingUsingAsync() => await VerifyAsync(""" | ||
using System; | ||
using System.Net.Http; | ||
public static class Program | ||
{ | ||
public static async void [|Main|]() | ||
{ | ||
using var httpClient = new HttpClient(); | ||
_ = await httpClient.GetAsync(new Uri("URL")).ConfigureAwait(false); | ||
} | ||
} | ||
""", | ||
fixedSource: """ | ||
using System; | ||
using System.Net.Http; | ||
public static class Program | ||
{ | ||
public static async {|CS0246:Task|} {|CS0161:Main|}() | ||
{ | ||
using var httpClient = new HttpClient(); | ||
_ = await httpClient.GetAsync(new Uri("URL")).ConfigureAwait(false); | ||
} | ||
} | ||
""").ConfigureAwait(false); | ||
|
||
[TestMethod] | ||
public async Task AsyncTaskMethodIsOkAsync() => await VerifyAsync(""" | ||
using System; | ||
using System.Threading.Tasks; | ||
public static class Program | ||
{ | ||
public static async Task Main() | ||
{ | ||
await Task.Delay(1000).ConfigureAwait(false); | ||
Console.WriteLine(); | ||
} | ||
} | ||
""").ConfigureAwait(false); | ||
|
||
[TestMethod] | ||
public async Task AsyncGenericTaskMethodIsOkAsync() => await VerifyAsync(""" | ||
using System.Threading.Tasks; | ||
public static class Program | ||
{ | ||
public static async Task<int> Main() | ||
{ | ||
await Task.Delay(1000).ConfigureAwait(false); | ||
return 1; | ||
} | ||
} | ||
""").ConfigureAwait(false); | ||
} |
Oops, something went wrong.