Skip to content

Commit

Permalink
New rule: MA0134 Observe the result of awaitable methods (#509)
Browse files Browse the repository at this point in the history
  • Loading branch information
meziantou authored Apr 26, 2023
1 parent 9e5d9e3 commit ae9112c
Show file tree
Hide file tree
Showing 8 changed files with 440 additions and 5 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ If you are already using other analyzers, you can check [which rules are duplica
|[MA0131](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0131.md)|Usage|ArgumentNullException.ThrowIfNull should not be used with non-nullable types|⚠️|✔️||
|[MA0132](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0132.md)|Design|Do not convert implicitly to DateTimeOffset|⚠️|✔️||
|[MA0133](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0133.md)|Design|Use DateTimeOffset instead of relying on the implicit conversion|ℹ️|✔️||
|[MA0134](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0134.md)|Usage|Observe result of async calls|⚠️|✔️||

<!-- rules -->

Expand Down
7 changes: 7 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@
|[MA0131](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0131.md)|Usage|ArgumentNullException.ThrowIfNull should not be used with non-nullable types|<span title='Warning'>⚠️</span>|✔️||
|[MA0132](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0132.md)|Design|Do not convert implicitly to DateTimeOffset|<span title='Warning'>⚠️</span>|✔️||
|[MA0133](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0133.md)|Design|Use DateTimeOffset instead of relying on the implicit conversion|<span title='Info'>ℹ️</span>|✔️||
|[MA0134](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0134.md)|Usage|Observe result of async calls|<span title='Warning'>⚠️</span>|✔️||

|Id|Suppressed rule|Justification|
|--|---------------|-------------|
Expand Down Expand Up @@ -538,6 +539,9 @@ dotnet_diagnostic.MA0132.severity = warning
# MA0133: Use DateTimeOffset instead of relying on the implicit conversion
dotnet_diagnostic.MA0133.severity = suggestion
# MA0134: Observe result of async calls
dotnet_diagnostic.MA0134.severity = warning
```

# .editorconfig - all rules disabled
Expand Down Expand Up @@ -938,4 +942,7 @@ dotnet_diagnostic.MA0132.severity = none
# MA0133: Use DateTimeOffset instead of relying on the implicit conversion
dotnet_diagnostic.MA0133.severity = none
# MA0134: Observe result of async calls
dotnet_diagnostic.MA0134.severity = none
```
15 changes: 15 additions & 0 deletions docs/Rules/MA0134.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# MA0134 - Observe result of async calls

The result of awaitable method should be observed by using `await`, `Result`, `Wait`, or other methods.

Note: [CS4014](https://learn.microsoft.com/en-US/dotnet/csharp/language-reference/compiler-messages/cs4014?WT.mc_id=DT-MVP-5003978) is similar but only operate in `async` methods. MA0134 operates in non-async methods.

````c#
void Sample()
{
Task.Delay(1); // non-compliant
Task.Delay(1).Wait(); // ok
_ = Task.Delay(1); // ok
}
````
1 change: 1 addition & 0 deletions src/Meziantou.Analyzer/RuleIdentifiers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ internal static class RuleIdentifiers
public const string ThrowIfNullWithNonNullableInstance = "MA0131";
public const string DoNotImplicitlyConvertDateTimeToDateTimeOffset = "MA0132";
public const string UseDateTimeOffsetInsteadOfDateTime = "MA0133";
public const string AwaitAwaitableMethodInSyncMethod = "MA0134";

public static string GetHelpUri(string identifier)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
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 AwaitAwaitableMethodInSyncMethodAnalyzer : DiagnosticAnalyzer
{
private static readonly DiagnosticDescriptor s_rule = new(
RuleIdentifiers.AwaitAwaitableMethodInSyncMethod,
title: "Observe result of async calls",
messageFormat: "Observe result of async calls",
RuleCategories.Usage,
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
helpLinkUri: RuleIdentifiers.GetHelpUri(RuleIdentifiers.AwaitAwaitableMethodInSyncMethod));

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(s_rule);

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

context.RegisterCompilationStartAction(context =>
{
var awaitableTypes = new AwaitableTypes(context.Compilation);
context.RegisterSymbolStartAction(context =>
{
if (context.Symbol is IMethodSymbol method && (method.IsAsync || method.IsTopLevelStatementsEntryPointMethod()))
return; // Already handled by CS4014

context.RegisterOperationAction(context => AnalyzeOperation(context, awaitableTypes), OperationKind.Invocation);
}, SymbolKind.Method);
});
}

private static void AnalyzeOperation(OperationAnalysisContext context, AwaitableTypes awaitableTypes)
{
var operation = (IInvocationOperation)context.Operation;

var parent = operation.Parent;
if (parent is null or IBlockOperation or IExpressionStatementOperation or IConditionalAccessOperation)
{
var semanticModel = operation.SemanticModel!;
var position = operation.Syntax.GetLocation().SourceSpan.End;

// While there is a check in RegisterSymbolStartAction, this is needed to handle lambda and delegates
var enclosingSymbol = semanticModel.GetEnclosingSymbol(position, context.CancellationToken);
if (enclosingSymbol is IMethodSymbol method && (method.IsAsync || method.IsTopLevelStatementsEntryPointMethod()))
return;

if (!awaitableTypes.IsAwaitable(operation.Type, semanticModel, position))
return;

context.ReportDiagnostic(s_rule, operation);
}
}
}
4 changes: 2 additions & 2 deletions src/Meziantou.Analyzer/Rules/NamedParameterAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ public override void Initialize(AnalysisContext context)
if (argumentList is null)
return;

var invokedMethodSymbol = syntaxContext.SemanticModel.GetSymbolInfo(invocationExpression).Symbol;
var invokedMethodSymbol = syntaxContext.SemanticModel.GetSymbolInfo(invocationExpression, syntaxContext.CancellationToken).Symbol;
if (invokedMethodSymbol == null && invocationExpression.IsKind(SyntaxKind.ElementAccessExpression))
return; // Skip Array[index]

Expand Down Expand Up @@ -160,7 +160,7 @@ bool IsParams(SyntaxNode node)
if (expression.IsKind(SyntaxKind.NullLiteralExpression))
return false;

var type = syntaxContext.SemanticModel.GetTypeInfo(node).ConvertedType;
var type = syntaxContext.SemanticModel.GetTypeInfo(node, syntaxContext.CancellationToken).ConvertedType;
return !type.IsEqualTo(lastParameter.Type);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
Expand Down Expand Up @@ -197,12 +196,12 @@ private bool IsValidType(ITypeSymbol? symbol)
return true;

var originalDefinition = symbol.OriginalDefinition;
if (ConcreteCollectionSymbols.Any(t => t.IsEqualTo(originalDefinition)))
if (ConcreteCollectionSymbols.Exists(t => t.IsEqualTo(originalDefinition)))
return false;

if (symbol is INamedTypeSymbol namedTypeSymbol)
{
if (TaskSymbols.Any(t => t.IsEqualTo(symbol.OriginalDefinition)))
if (TaskSymbols.Exists(t => t.IsEqualTo(symbol.OriginalDefinition)))
{
return IsValidType(namedTypeSymbol.TypeArguments[0]);
}
Expand Down
Loading

0 comments on commit ae9112c

Please sign in to comment.