Skip to content

Commit 6415242

Browse files
committed
Fix collection expression handling
1 parent 7874fe6 commit 6415242

File tree

2 files changed

+298
-253
lines changed

2 files changed

+298
-253
lines changed

src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Tasks/DoNotUseNonCancelableTaskDelayWithWhenAny.cs

Lines changed: 22 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,16 @@
11
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.
22

33
using System.Collections.Immutable;
4-
using System.Linq;
54
using Analyzer.Utilities;
65
using Analyzer.Utilities.Extensions;
76
using Analyzer.Utilities.Lightup;
87
using Microsoft.CodeAnalysis;
98
using Microsoft.CodeAnalysis.Diagnostics;
109
using Microsoft.CodeAnalysis.Operations;
10+
using static Microsoft.NetCore.Analyzers.MicrosoftNetCoreAnalyzersResources;
1111

1212
namespace Microsoft.NetCore.Analyzers.Tasks
1313
{
14-
using static MicrosoftNetCoreAnalyzersResources;
15-
1614
/// <summary>
1715
/// CA2027: <inheritdoc cref="DoNotUseNonCancelableTaskDelayWithWhenAnyTitle"/>
1816
/// </summary>
@@ -53,55 +51,57 @@ public override void Initialize(AnalysisContext context)
5351
var invocation = (IInvocationOperation)context.Operation;
5452

5553
// Check if this is a call to Task.WhenAny
56-
if (!IsTaskWhenAny(invocation.TargetMethod, taskType))
54+
var method = invocation.TargetMethod;
55+
if (!SymbolEqualityComparer.Default.Equals(method.ContainingType, taskType) ||
56+
!method.IsStatic ||
57+
method.Name != nameof(Task.WhenAny))
5758
{
5859
return;
5960
}
6061

6162
// Count the total number of tasks passed to WhenAny
6263
int taskCount = 0;
63-
System.Collections.Generic.List<IOperation>? taskDelayOperations = null;
64+
List<IOperation>? taskDelayOperations = null;
6465

6566
// Task.WhenAny has params parameters, so arguments are often implicitly wrapped in an array
6667
// We need to check inside the array initializer or collection expression
67-
foreach (var argument in invocation.Arguments)
68+
for (int i = 0; i < invocation.Arguments.Length; i++)
6869
{
69-
// Check if this is an array creation (implicit params expansion, explicit array, or collection expression)
70-
if (argument.Value is IArrayCreationOperation { Initializer: not null } arrayCreation)
70+
var argument = invocation.Arguments[i].Value.WalkDownConversion();
71+
72+
// Check if this is an array creation
73+
if (argument is IArrayCreationOperation { Initializer: not null } arrayCreation)
7174
{
7275
// Check each element in the array
7376
foreach (var element in arrayCreation.Initializer.ElementValues)
7477
{
7578
taskCount++;
7679
if (IsNonCancelableTaskDelay(element, taskType, cancellationTokenType))
7780
{
78-
taskDelayOperations ??= new System.Collections.Generic.List<IOperation>();
79-
taskDelayOperations.Add(element);
81+
(taskDelayOperations ??= []).Add(element);
8082
}
8183
}
8284
}
83-
else if (ICollectionExpressionOperationWrapper.IsInstance(argument.Value))
85+
else if (ICollectionExpressionOperationWrapper.IsInstance(argument))
8486
{
8587
// Check each element in the collection expression
86-
var collectionExpression = ICollectionExpressionOperationWrapper.FromOperation(argument.Value);
88+
var collectionExpression = ICollectionExpressionOperationWrapper.FromOperation(argument);
8789
foreach (var element in collectionExpression.Elements)
8890
{
8991
taskCount++;
9092
if (IsNonCancelableTaskDelay(element, taskType, cancellationTokenType))
9193
{
92-
taskDelayOperations ??= new System.Collections.Generic.List<IOperation>();
93-
taskDelayOperations.Add(element);
94+
(taskDelayOperations ??= []).Add(element);
9495
}
9596
}
9697
}
9798
else
9899
{
99100
// Direct argument (not params or array)
100101
taskCount++;
101-
if (IsNonCancelableTaskDelay(argument.Value, taskType, cancellationTokenType))
102+
if (IsNonCancelableTaskDelay(argument, taskType, cancellationTokenType))
102103
{
103-
taskDelayOperations ??= new System.Collections.Generic.List<IOperation>();
104-
taskDelayOperations.Add(argument.Value);
104+
(taskDelayOperations ??= []).Add(argument);
105105
}
106106
}
107107
}
@@ -119,42 +119,30 @@ public override void Initialize(AnalysisContext context)
119119
});
120120
}
121121

122-
private static bool IsTaskWhenAny(IMethodSymbol method, INamedTypeSymbol taskType)
123-
{
124-
return SymbolEqualityComparer.Default.Equals(method.ContainingType, taskType) &&
125-
method.IsStatic &&
126-
method.Name == nameof(System.Threading.Tasks.Task.WhenAny);
127-
}
128-
129122
private static bool IsNonCancelableTaskDelay(IOperation operation, INamedTypeSymbol taskType, INamedTypeSymbol cancellationTokenType)
130123
{
131-
// Unwrap conversions to get to the actual invocation
132-
if (operation is IConversionOperation conversion)
133-
{
134-
operation = conversion.Operand;
135-
}
124+
operation = operation.WalkDownConversion();
136125

137126
if (operation is not IInvocationOperation invocation)
138127
{
139128
return false;
140129
}
141130

142-
var method = invocation.TargetMethod;
143-
144131
// Check if this is Task.Delay
132+
var method = invocation.TargetMethod;
145133
if (!SymbolEqualityComparer.Default.Equals(method.ContainingType, taskType) ||
146134
!method.IsStatic ||
147-
method.Name != nameof(System.Threading.Tasks.Task.Delay))
135+
method.Name != nameof(Task.Delay))
148136
{
149137
return false;
150138
}
151139

152-
// Check if any parameter is a CancellationToken
140+
// Check if any parameter is a CancellationToken, in which case we consider it cancelable
153141
foreach (var parameter in method.Parameters)
154142
{
155143
if (SymbolEqualityComparer.Default.Equals(parameter.Type, cancellationTokenType))
156144
{
157-
return false; // Has a CancellationToken parameter, so it's cancelable
145+
return false;
158146
}
159147
}
160148

0 commit comments

Comments
 (0)