11// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.
22
33using System . Collections . Immutable ;
4- using System . Linq ;
54using Analyzer . Utilities ;
65using Analyzer . Utilities . Extensions ;
76using Analyzer . Utilities . Lightup ;
87using Microsoft . CodeAnalysis ;
98using Microsoft . CodeAnalysis . Diagnostics ;
109using Microsoft . CodeAnalysis . Operations ;
10+ using static Microsoft . NetCore . Analyzers . MicrosoftNetCoreAnalyzersResources ;
1111
1212namespace 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