Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suggest CA2007 for await foreach without specified cancellation #6683

Merged
merged 13 commits into from
Oct 16, 2023
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.

using System;
using Analyzer.Utilities.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
using Microsoft.CodeQuality.Analyzers.ApiDesignGuidelines;

namespace Microsoft.CodeQuality.CSharp.Analyzers.ApiDesignGuidelines
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class CSharpDoNotDirectlyAwaitATask : DoNotDirectlyAwaitATaskAnalyzer
{
public override (Action<OperationAnalysisContext> Analysis, OperationKind OperationKind) AnalyzeAwaitForEachLoopOperation(INamedTypeSymbol configuredAsyncEnumerable)
{
return (ctx => AnalyzeAwaitForEachLoopOperation(ctx, configuredAsyncEnumerable), OperationKind.Loop);
}

private static void AnalyzeAwaitForEachLoopOperation(OperationAnalysisContext context, INamedTypeSymbol configuredAsyncEnumerable)
{
if (context.Operation is IForEachLoopOperation {Syntax: ForEachStatementSyntax {AwaitKeyword.RawKind: not (int)SyntaxKind.None}} forEachOperation
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should upgrade to newer version of Microsoft.CodeAnalysis package reference for the NetAnalyzers project. We don't really need to support this analyzer package on very old versions of VS/compiler. I'll create a separate PR for this, and remove any old lightup code that was added to work around this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

&& !forEachOperation.Collection.Type.OriginalDefinition.Equals(configuredAsyncEnumerable, SymbolEqualityComparer.Default))
{
context.ReportDiagnostic(forEachOperation.Collection.Syntax.CreateDiagnostic(Rule));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
context.ReportDiagnostic(forEachOperation.Collection.Syntax.CreateDiagnostic(Rule));
context.ReportDiagnostic(forEachOperation.Collection.CreateDiagnostic(Rule));

}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.

using System;
using System.Collections.Immutable;
using System.Linq;
using Analyzer.Utilities;
Expand All @@ -16,8 +17,8 @@
/// <summary>
/// CA2007: <inheritdoc cref="DoNotDirectlyAwaitATaskTitle"/>
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
public sealed class DoNotDirectlyAwaitATaskAnalyzer : DiagnosticAnalyzer
[DiagnosticAnalyzer(LanguageNames.VisualBasic)]

Check failure on line 20 in src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/ApiDesignGuidelines/DoNotDirectlyAwaitATask.cs

View check run for this annotation

Azure Pipelines / roslyn-analyzers-CI (Ubuntu Debug)

src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/ApiDesignGuidelines/DoNotDirectlyAwaitATask.cs#L20

src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/ApiDesignGuidelines/DoNotDirectlyAwaitATask.cs(20,6): error RS1004: (NETCORE_ENGINEERING_TELEMETRY=Build) Diagnostic analyzer 'DoNotDirectlyAwaitATaskAnalyzer' may be able to support both C# and Visual Basic. Consider adding an argument to DiagnosticAnalyzerAttribute for 'C#' language support.

Check failure on line 20 in src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/ApiDesignGuidelines/DoNotDirectlyAwaitATask.cs

View check run for this annotation

Azure Pipelines / roslyn-analyzers-CI (Ubuntu Release)

src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/ApiDesignGuidelines/DoNotDirectlyAwaitATask.cs#L20

src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/ApiDesignGuidelines/DoNotDirectlyAwaitATask.cs(20,6): error RS1004: (NETCORE_ENGINEERING_TELEMETRY=Build) Diagnostic analyzer 'DoNotDirectlyAwaitATaskAnalyzer' may be able to support both C# and Visual Basic. Consider adding an argument to DiagnosticAnalyzerAttribute for 'C#' language support.

Check failure on line 20 in src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/ApiDesignGuidelines/DoNotDirectlyAwaitATask.cs

View check run for this annotation

Azure Pipelines / roslyn-analyzers-CI

src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/ApiDesignGuidelines/DoNotDirectlyAwaitATask.cs#L20

src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/ApiDesignGuidelines/DoNotDirectlyAwaitATask.cs(20,6): error RS1004: (NETCORE_ENGINEERING_TELEMETRY=Build) Diagnostic analyzer 'DoNotDirectlyAwaitATaskAnalyzer' may be able to support both C# and Visual Basic. Consider adding an argument to DiagnosticAnalyzerAttribute for 'C#' language support.
public class DoNotDirectlyAwaitATaskAnalyzer : DiagnosticAnalyzer
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd make this abstract, and have VB-derived class.

{
internal const string RuleId = "CA2007";

Expand Down Expand Up @@ -52,7 +53,9 @@
return;
}

var configuredAsyncDisposable = context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemRuntimeCompilerServicesConfiguredAsyncDisposable);
var wellKnownTypeProvider = WellKnownTypeProvider.GetOrCreate(context.Compilation);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be a good idea to move this variable before TryGetTaskTypes, and pass it instead of passing context.Compilation.

var configuredAsyncDisposable = wellKnownTypeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemRuntimeCompilerServicesConfiguredAsyncDisposable);
var configuredAsyncEnumerable = wellKnownTypeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemRuntimeCompilerServicesConfiguredCancelableAsyncEnumerable);

context.RegisterOperationBlockStartAction(context =>
{
Expand All @@ -77,11 +80,22 @@
context.RegisterOperationAction(context => AnalyzeUsingOperation(context, configuredAsyncDisposable), OperationKind.Using);
context.RegisterOperationAction(context => AnalyzeUsingDeclarationOperation(context, configuredAsyncDisposable), OperationKindEx.UsingDeclaration);
}

if (configuredAsyncEnumerable is not null)
{
var (analysis, operationKind) = AnalyzeAwaitForEachLoopOperation(configuredAsyncEnumerable);
context.RegisterOperationAction(analysis, operationKind);
}
}
});
});
}

public virtual (Action<OperationAnalysisContext> Analysis, OperationKind OperationKind) AnalyzeAwaitForEachLoopOperation(INamedTypeSymbol configuredAsyncEnumerable)
{
return (_ => { }, OperationKind.None);
}
Copy link
Member

@Youssef1313 Youssef1313 Jun 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to make this method return void and do the actual registeration.

protected virtual void RegisterLanguageSpecificChecks(OperationBlockStartAnalysisContext context, INamedTypeSymbol configuredAsyncEnumerable)
{
}

and in C# implementation:

protected override void RegisterLanguageSpecificChecks(OperationBlockStartAnalysisContext context, INamedTypeSymbol configuredAsyncEnumerable)
{
    context.RegisterOperationAction(context => AnalyzeAwaitForEachLoopOperation(context, configuredAsyncEnumerable), OperationKind.Loop);
}

Note: I'm writing directly in GitHub, so I could have made an error.


private static void AnalyzeAwaitOperation(OperationAnalysisContext context, ImmutableArray<INamedTypeSymbol> taskTypes)
{
var awaitExpression = (IAwaitOperation)context.Operation;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
using Xunit;
using CSharpLanguageVersion = Microsoft.CodeAnalysis.CSharp.LanguageVersion;
using VerifyCS = Test.Utilities.CSharpCodeFixVerifier<
Microsoft.CodeQuality.Analyzers.ApiDesignGuidelines.DoNotDirectlyAwaitATaskAnalyzer,
Microsoft.CodeQuality.CSharp.Analyzers.ApiDesignGuidelines.CSharpDoNotDirectlyAwaitATask,
Microsoft.CodeQuality.Analyzers.ApiDesignGuidelines.DoNotDirectlyAwaitATaskFixer>;
using VerifyVB = Test.Utilities.VisualBasicCodeFixVerifier<
Microsoft.CodeQuality.Analyzers.ApiDesignGuidelines.DoNotDirectlyAwaitATaskAnalyzer,
Expand Down Expand Up @@ -741,5 +741,86 @@ async Task CoreAsync()

await VerifyCS.VerifyCodeFixAsync(code, fixedCode);
}

[Fact, WorkItem(6652, "https://github.com/dotnet/roslyn-analyzers/issues/6652")]
public Task CsharpAwaitIAsyncEnumerable_DiagnosticAsync()
{
return new VerifyCS.Test
{
TestCode = @"
using System.Collections.Generic;
using System.Threading.Tasks;

public class C
{
public async Task Test(IAsyncEnumerable<int> enumerable)
{
await foreach(var i in [|enumerable|])
{
}
}
}",
FixedCode = @"
using System.Collections.Generic;
using System.Threading.Tasks;

public class C
{
public async Task Test(IAsyncEnumerable<int> enumerable)
{
await foreach(var i in enumerable.ConfigureAwait(false))
{
}
}
}",
LanguageVersion = CSharpLanguageVersion.CSharp8
}.RunAsync();
}

[Theory, WorkItem(6652, "https://github.com/dotnet/roslyn-analyzers/issues/6652")]
[InlineData("true")]
[InlineData("false")]
public Task CsharpAwaitIAsyncEnumerable_NoDiagnosticAsync(string continueOnCapturedContext)
{
return new VerifyCS.Test
{
TestCode = @$"
using System.Collections.Generic;
using System.Threading.Tasks;

public class C
{{
public async Task Test(IAsyncEnumerable<int> enumerable)
{{
await foreach(var i in enumerable.ConfigureAwait({continueOnCapturedContext}))
{{
}}
}}
}}",
LanguageVersion = CSharpLanguageVersion.CSharp8
}.RunAsync();
}

[Fact, WorkItem(6652, "https://github.com/dotnet/roslyn-analyzers/issues/6652")]
public Task CsharpForEachEnumerable_NoDiagnosticAsync()
{
return new VerifyCS.Test
{
TestCode = @"
using System.Collections.Generic;
using System.Threading.Tasks;

public class C
{
public void Test(IEnumerable<int> enumerable)
{
foreach(var i in enumerable)
{
}
}
}",
LanguageVersion = CSharpLanguageVersion.CSharp8
}.RunAsync();
}
}
}
1 change: 1 addition & 0 deletions src/Utilities/Compiler/WellKnownTypeNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@ internal static class WellKnownTypeNames
public const string SystemRuntimeCompilerServicesCallerArgumentExpressionAttribute = "System.Runtime.CompilerServices.CallerArgumentExpressionAttribute";
public const string SystemRuntimeCompilerServicesCompilerGeneratedAttribute = "System.Runtime.CompilerServices.CompilerGeneratedAttribute";
public const string SystemRuntimeCompilerServicesConfiguredAsyncDisposable = "System.Runtime.CompilerServices.ConfiguredAsyncDisposable";
public const string SystemRuntimeCompilerServicesConfiguredCancelableAsyncEnumerable = "System.Runtime.CompilerServices.ConfiguredCancelableAsyncEnumerable`1";
public const string SystemRuntimeCompilerServicesConfiguredValueTaskAwaitable1 = "System.Runtime.CompilerServices.ConfiguredValueTaskAwaitable`1";
public const string SystemRuntimeCompilerServicesDisableRuntimeMarshallingAttribute = "System.Runtime.CompilerServices.DisableRuntimeMarshallingAttribute";
public const string SystemRuntimeCompilerServicesICriticalNotifyCompletion = "System.Runtime.CompilerServices.ICriticalNotifyCompletion";
Expand Down
Loading