Skip to content

Commit 16b767b

Browse files
committed
Support awaiting a parenthesized 'switch' or 'with' expression
Fixes #3460
1 parent 49ef1d5 commit 16b767b

3 files changed

Lines changed: 85 additions & 31 deletions

File tree

StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/MaintainabilityRules/SA1119CSharp8UnitTests.cs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,71 @@ public string TestMethod(int n, object a, object b)
7979
await VerifyCSharpFixAsync(LanguageVersion.CSharp8, testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(false);
8080
}
8181

82+
/// <summary>
83+
/// Verifies that <see langword="await"/> followed by a switch expression is handled correctly.
84+
/// </summary>
85+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
86+
[Fact]
87+
[WorkItem(3460, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3460")]
88+
public async Task TestAwaitFollowedBySwitchExpressionIsHandledCorrectlyAsync()
89+
{
90+
const string testCode = @"
91+
using System.Threading.Tasks;
92+
93+
public class Foo
94+
{
95+
public async Task<string> TestMethod(int n, Task<string> a, Task<string> b)
96+
{
97+
return await (n switch { 1 => a, 2 => b });
98+
}
99+
}
100+
";
101+
102+
await VerifyCSharpDiagnosticAsync(LanguageVersion.CSharp8, testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
103+
}
104+
105+
/// <summary>
106+
/// Verifies that <see langword="await"/> followed by a switch expression with unnecessary parenthesis is handled correctly.
107+
/// </summary>
108+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
109+
[Fact]
110+
[WorkItem(3460, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3460")]
111+
public async Task TestAwaitFollowedBySwitchExpressionWithUnnecessaryParenthesisIsHandledCorrectlyAsync()
112+
{
113+
const string testCode = @"
114+
using System.Threading.Tasks;
115+
116+
public class Foo
117+
{
118+
public async Task<string> TestMethod(int n, Task<string> a, Task<string> b)
119+
{
120+
return await {|#0:{|#1:(|}(n switch { 1 => a, 2 => b }){|#2:)|}|};
121+
}
122+
}
123+
";
124+
125+
const string fixedCode = @"
126+
using System.Threading.Tasks;
127+
128+
public class Foo
129+
{
130+
public async Task<string> TestMethod(int n, Task<string> a, Task<string> b)
131+
{
132+
return await (n switch { 1 => a, 2 => b });
133+
}
134+
}
135+
";
136+
137+
DiagnosticResult[] expected =
138+
{
139+
Diagnostic(DiagnosticId).WithLocation(0),
140+
Diagnostic(ParenthesesDiagnosticId).WithLocation(1),
141+
Diagnostic(ParenthesesDiagnosticId).WithLocation(2),
142+
};
143+
144+
await VerifyCSharpFixAsync(LanguageVersion.CSharp8, testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(false);
145+
}
146+
82147
/// <summary>
83148
/// Verifies that a switch expression with unnecessary parenthesis is handled correcly.
84149
/// </summary>

StyleCop.Analyzers/StyleCop.Analyzers/Helpers/ExpressionSyntaxHelpers.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
22
// Licensed under the MIT License. See LICENSE in the project root for license information.
33

4-
#nullable disable
5-
64
namespace StyleCop.Analyzers.Helpers
75
{
86
using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -19,5 +17,16 @@ public static ExpressionSyntax WalkDownParentheses(this ExpressionSyntax express
1917

2018
return result;
2119
}
20+
21+
public static ExpressionSyntax WalkUpParentheses(this ExpressionSyntax expression)
22+
{
23+
var result = expression;
24+
while (result.Parent is ParenthesizedExpressionSyntax parenthesizedExpression)
25+
{
26+
result = parenthesizedExpression;
27+
}
28+
29+
return result;
30+
}
2231
}
2332
}

StyleCop.Analyzers/StyleCop.Analyzers/MaintainabilityRules/SA1119StatementMustNotUseUnnecessaryParenthesis.cs

Lines changed: 9 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -130,12 +130,7 @@ private static void HandleParenthesizedExpression(SyntaxNodeAnalysisContext cont
130130
return;
131131
}
132132

133-
if (IsSwitchOrWithExpressionPrecededByTypeCast(node))
134-
{
135-
return;
136-
}
137-
138-
if (IsSwitchOrWithExpressionExpressionOfMemberAccess(node))
133+
if (IsSwitchOrWithExpressionWithRequiredParentheses(node))
139134
{
140135
return;
141136
}
@@ -219,37 +214,22 @@ private static bool IsConditionalAccessInInterpolation(ExpressionSyntax node)
219214
return false;
220215
}
221216

222-
private static bool IsSwitchOrWithExpressionPrecededByTypeCast(ParenthesizedExpressionSyntax node)
223-
{
224-
if (!node.Expression.IsKind(SyntaxKindEx.SwitchExpression)
225-
&& !node.Expression.IsKind(SyntaxKindEx.WithExpression))
226-
{
227-
return false;
228-
}
229-
230-
var previousToken = node.OpenParenToken.GetPreviousToken();
231-
232-
while (previousToken.IsKind(SyntaxKind.OpenParenToken) && previousToken.Parent.IsKind(SyntaxKind.ParenthesizedExpression))
233-
{
234-
previousToken = previousToken.GetPreviousToken();
235-
}
236-
237-
return previousToken.IsKind(SyntaxKind.CloseParenToken) && previousToken.Parent.IsKind(SyntaxKind.CastExpression);
238-
}
239-
240-
private static bool IsSwitchOrWithExpressionExpressionOfMemberAccess(ParenthesizedExpressionSyntax node)
217+
private static bool IsSwitchOrWithExpressionWithRequiredParentheses(ParenthesizedExpressionSyntax node)
241218
{
242219
if (!node.Expression.IsKind(SyntaxKindEx.SwitchExpression)
243220
&& !node.Expression.IsKind(SyntaxKindEx.WithExpression))
244221
{
245222
return false;
246223
}
247224

248-
return node.Parent switch
225+
var outerExpression = node.WalkUpParentheses();
226+
return outerExpression.Parent switch
249227
{
250-
MemberAccessExpressionSyntax memberAccessExpression => memberAccessExpression.Expression == node,
251-
ConditionalAccessExpressionSyntax conditionalAccessExpression => conditionalAccessExpression.Expression == node,
252-
ElementAccessExpressionSyntax elementAccessExpression => elementAccessExpression.Expression == node,
228+
AwaitExpressionSyntax awaitExpression => awaitExpression.Expression == outerExpression,
229+
CastExpressionSyntax castExpression => castExpression.Expression == outerExpression,
230+
MemberAccessExpressionSyntax memberAccessExpression => memberAccessExpression.Expression == outerExpression,
231+
ConditionalAccessExpressionSyntax conditionalAccessExpression => conditionalAccessExpression.Expression == outerExpression,
232+
ElementAccessExpressionSyntax elementAccessExpression => elementAccessExpression.Expression == outerExpression,
253233
_ => false,
254234
};
255235
}

0 commit comments

Comments
 (0)