Skip to content

Commit

Permalink
New rule MA0129: Await task in using statements (#459)
Browse files Browse the repository at this point in the history
  • Loading branch information
meziantou authored Mar 14, 2023
1 parent ca5ef33 commit 46f9027
Show file tree
Hide file tree
Showing 6 changed files with 204 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,5 +144,6 @@ If you are already using other analyzers, you can check [which rules are duplica
|[MA0126](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0126.md)|Design|The list of log parameter types contains a duplicate|⚠️|✔️||
|[MA0127](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0127.md)|Usage|Use String.Equals instead of is pattern|⚠️|||
|[MA0128](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0128.md)|Usage|Use 'is' operator instead of SequenceEqual|ℹ️|✔️|✔️|
|[MA0129](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0129.md)|Usage|Await task in using statement|⚠️|✔️||

<!-- rules -->
7 changes: 7 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@
|[MA0126](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0126.md)|Design|The list of log parameter types contains a duplicate|<span title='Warning'>⚠️</span>|✔️||
|[MA0127](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0127.md)|Usage|Use String.Equals instead of is pattern|<span title='Warning'>⚠️</span>|||
|[MA0128](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0128.md)|Usage|Use 'is' operator instead of SequenceEqual|<span title='Info'>ℹ️</span>|✔️|✔️|
|[MA0129](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0129.md)|Usage|Await task in using statement|<span title='Warning'>⚠️</span>|✔️||

# .editorconfig - default values

Expand Down Expand Up @@ -512,6 +513,9 @@ dotnet_diagnostic.MA0127.severity = none
# MA0128: Use 'is' operator instead of SequenceEqual
dotnet_diagnostic.MA0128.severity = suggestion
# MA0129: Await task in using statement
dotnet_diagnostic.MA0129.severity = warning
```

# .editorconfig - all rules disabled
Expand Down Expand Up @@ -897,4 +901,7 @@ dotnet_diagnostic.MA0127.severity = none
# MA0128: Use 'is' operator instead of SequenceEqual
dotnet_diagnostic.MA0128.severity = none
# MA0129: Await task in using statement
dotnet_diagnostic.MA0129.severity = none
```
10 changes: 10 additions & 0 deletions docs/Rules/MA0129.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# MA0129 - Await task in using statement

A `Task` doesn't need to be disposed. When used in a `using` statement, most of the time, developers forgot to await it.

````
Task<IDiposable> t = ...;
using(t) { } // non-compliant
using(await t) { } // ok
````
1 change: 1 addition & 0 deletions src/Meziantou.Analyzer/RuleIdentifiers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ internal static class RuleIdentifiers
public const string LoggerParameterType_DuplicateRule = "MA0126";
public const string UseStringEqualsInsteadOfIsPattern = "MA0127";
public const string UseIsPatternInsteadOfSequenceEqual = "MA0128";
public const string TaskInUsing = "MA0129";

public static string GetHelpUri(string identifier)
{
Expand Down
93 changes: 93 additions & 0 deletions src/Meziantou.Analyzer/Rules/TaskInUsingAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
using System;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;

namespace Meziantou.Analyzer.Rules;

[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class TaskInUsingAnalyzer : DiagnosticAnalyzer
{
private static readonly DiagnosticDescriptor s_rule = new(
RuleIdentifiers.TaskInUsing,
title: "Await task in using statement",
messageFormat: "Await task in using statement",
RuleCategories.Usage,
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
description: "",
helpLinkUri: RuleIdentifiers.GetHelpUri(RuleIdentifiers.TaskInUsing));

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

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

context.RegisterCompilationStartAction(ctx =>
{
var taskSymbol = ctx.Compilation.GetBestTypeByMetadataName("System.Threading.Tasks.Task");
if (taskSymbol == null)
return;

var analyzerContext = new AnalyzerContext(taskSymbol);
ctx.RegisterOperationAction(analyzerContext.AnalyzeUsing, OperationKind.Using);
ctx.RegisterOperationAction(analyzerContext.AnalyzeUsingDeclaration, OperationKind.UsingDeclaration);
});
}

private sealed class AnalyzerContext
{
private INamedTypeSymbol _taskSymbol;

public AnalyzerContext(INamedTypeSymbol taskSymbol)
{
_taskSymbol = taskSymbol;
}

public void AnalyzeUsing(OperationAnalysisContext context)
{
var operation = (IUsingOperation)context.Operation;
AnalyzeResource(context, operation.Resources);
}

internal void AnalyzeUsingDeclaration(OperationAnalysisContext context)
{
var operation = (IUsingDeclarationOperation)context.Operation;
AnalyzeResource(context, operation.DeclarationGroup);
}

private void AnalyzeResource(OperationAnalysisContext context, IOperation? operation)
{
if (operation == null)
return;

if (operation is IVariableDeclarationGroupOperation variableDeclarationGroupOperation)
{
foreach (var declaration in variableDeclarationGroupOperation.Declarations)
{
AnalyzeResource(context, declaration);
}

return;
}

if (operation is IVariableDeclarationOperation variableDeclarationOperation)
{
foreach (var declarator in variableDeclarationOperation.Declarators)
{
AnalyzeResource(context, declarator.Initializer?.Value);
}
return;
}

operation = operation.UnwrapImplicitConversionOperations();
if (operation.Type != null && operation.Type.IsOrInheritFrom(_taskSymbol))
{
context.ReportDiagnostic(s_rule, operation);
}
}
}
}
92 changes: 92 additions & 0 deletions tests/Meziantou.Analyzer.Test/Rules/TaskInUsingAnalyzerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
using System.Threading.Tasks;
using Meziantou.Analyzer.Rules;
using Microsoft.CodeAnalysis;
using TestHelper;
using Xunit;

namespace Meziantou.Analyzer.Test.Rules;
public sealed class TaskInUsingAnalyzerTests
{
private static ProjectBuilder CreateProjectBuilder()
{
return new ProjectBuilder()
.WithOutputKind(OutputKind.ConsoleApplication)
.WithAnalyzer<TaskInUsingAnalyzer>();
}

[Fact]
public async Task SingleTaskInUsing()
{
const string SourceCode = """
using System.Threading.Tasks;
Task t = null;
using ([|t|]) { }
""";

await CreateProjectBuilder()
.WithSourceCode(SourceCode)
.ValidateAsync();
}

[Fact]
public async Task SingleTaskAssignedInUsing()
{
const string SourceCode = """
using System.Threading.Tasks;
Task t = null;
using (var a = [|t|]) { }
""";

await CreateProjectBuilder()
.WithSourceCode(SourceCode)
.ValidateAsync();
}

[Fact]
public async Task MultipleTasksInUsing()
{
const string SourceCode = """
using System.Threading.Tasks;
Task t1 = null;
Task t2 = null;
using (Task a = [|t1|], b = [|t2|]) { }
""";

await CreateProjectBuilder()
.WithSourceCode(SourceCode)
.ValidateAsync();
}

[Fact]
public async Task TaskOfTInUsing()
{
const string SourceCode = """
using System.Threading.Tasks;
Task<System.IDisposable> t1 = null;
using ([|t1|]) { }
""";

await CreateProjectBuilder()
.WithSourceCode(SourceCode)
.ValidateAsync();
}

[Fact]
public async Task TaskOfTInUsingStatement()
{
const string SourceCode = """
using System.Threading.Tasks;
Task<System.IDisposable> t1 = null;
using var a = [|t1|];
""";

await CreateProjectBuilder()
.WithSourceCode(SourceCode)
.ValidateAsync();
}
}

0 comments on commit 46f9027

Please sign in to comment.