Skip to content

Commit 9246f1b

Browse files
committed
Update SA1513 for pattern matching
Fixes #3288 Fixes #3319 Closes #3320 Fixes #3692
1 parent 1ae44e2 commit 9246f1b

File tree

3 files changed

+310
-0
lines changed

3 files changed

+310
-0
lines changed

StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp8/LayoutRules/SA1513CSharp8UnitTests.cs

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,286 @@
33

44
namespace StyleCop.Analyzers.Test.CSharp8.LayoutRules
55
{
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
using Microsoft.CodeAnalysis.Testing;
69
using StyleCop.Analyzers.Test.CSharp7.LayoutRules;
10+
using Xunit;
11+
using static StyleCop.Analyzers.Test.Verifiers.StyleCopCodeFixVerifier<
12+
StyleCop.Analyzers.LayoutRules.SA1513ClosingBraceMustBeFollowedByBlankLine,
13+
StyleCop.Analyzers.LayoutRules.SA1513CodeFixProvider>;
714

815
public partial class SA1513CSharp8UnitTests : SA1513CSharp7UnitTests
916
{
17+
[Fact]
18+
[WorkItem(3003, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3003")]
19+
public async Task TestSwitchExpressionArmPropertyPatternNoBlankLineRequiredAsync()
20+
{
21+
var testCode = @"
22+
public class TestClass
23+
{
24+
public int Test(Point value) =>
25+
value switch
26+
{
27+
{ X: 1, Y: 2 } => 1,
28+
{ X: 3, Y: 4 } => 2,
29+
_ => 0,
30+
};
31+
}
32+
33+
public class Point
34+
{
35+
public int X { get; set; }
36+
public int Y { get; set; }
37+
}
38+
";
39+
40+
await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
41+
}
42+
43+
[Fact]
44+
[WorkItem(3003, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3003")]
45+
public async Task TestPropertyPatternInsideSwitchExpressionArmNoBlankLineRequiredAsync()
46+
{
47+
var testCode = @"
48+
public class TestClass
49+
{
50+
public string Test(Wrapper wrapper) =>
51+
wrapper.Value switch
52+
{
53+
{ Length: 0 } => ""empty"",
54+
{ Length: var length } when length > 0 => ""nonempty"",
55+
_ => ""unknown"",
56+
};
57+
}
58+
59+
public class Wrapper
60+
{
61+
public string Value { get; set; }
62+
}
63+
";
64+
65+
await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
66+
}
67+
68+
[Fact]
69+
[WorkItem(3288, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3288")]
70+
public async Task TestNestedPropertyPatternWithContinuationTokensAsync()
71+
{
72+
var testCode = @"
73+
public class TestClass
74+
{
75+
public int Test(Container value) =>
76+
value switch
77+
{
78+
{
79+
Inner:
80+
{
81+
Count: 1,
82+
},
83+
} => 1,
84+
_ => 0,
85+
};
86+
}
87+
88+
public class Container
89+
{
90+
public Item Inner { get; set; }
91+
}
92+
93+
public class Item
94+
{
95+
public int Count { get; set; }
96+
}
97+
";
98+
99+
await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
100+
}
101+
102+
[Fact]
103+
[WorkItem(3288, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3288")]
104+
public async Task TestPropertyPatternFollowedByCommaAndCloseParenAsync()
105+
{
106+
var testCode = @"
107+
public class TestClass
108+
{
109+
public (bool, int) Test(Wrapper wrapper)
110+
{
111+
return (
112+
wrapper.Value is
113+
{
114+
Length: var length,
115+
},
116+
1);
117+
}
118+
}
119+
120+
public class Wrapper
121+
{
122+
public string Value { get; set; }
123+
}
124+
";
125+
126+
await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
127+
}
128+
129+
[Fact]
130+
[WorkItem(3319, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3319")]
131+
public async Task TestRecursivePatternDesignationInSwitchLabelAsync()
132+
{
133+
var testCode = @"
134+
using Microsoft.CodeAnalysis;
135+
using Microsoft.CodeAnalysis.CSharp.Syntax;
136+
137+
public class TestClass
138+
{
139+
public void Test(SyntaxNode syntaxNode)
140+
{
141+
switch (syntaxNode)
142+
{
143+
case MethodDeclarationSyntax
144+
{
145+
Parent: ClassDeclarationSyntax { Identifier: { ValueText: ""ResultExtensions"" } },
146+
Modifiers: var modifiers,
147+
ParameterList: { Parameters: var parameters },
148+
} methodDeclarationSyntax:
149+
_ = modifiers;
150+
break;
151+
}
152+
}
153+
}
154+
";
155+
156+
await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
157+
}
158+
159+
[Fact]
160+
[WorkItem(3319, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3319")]
161+
public async Task TestRecursivePatternDesignationInIsExpressionAsync()
162+
{
163+
var testCode = @"
164+
using Microsoft.CodeAnalysis;
165+
using Microsoft.CodeAnalysis.CSharp.Syntax;
166+
167+
public class TestClass
168+
{
169+
public bool Test(SyntaxNode syntaxNode)
170+
{
171+
if (syntaxNode is MethodDeclarationSyntax
172+
{
173+
Parent: ClassDeclarationSyntax { Identifier: { ValueText: ""ResultExtensions"" } },
174+
Modifiers: var modifiers,
175+
ParameterList: { Parameters: var parameters },
176+
} methodDeclarationSyntax)
177+
{
178+
return methodDeclarationSyntax is object;
179+
}
180+
181+
return false;
182+
}
183+
}
184+
";
185+
186+
await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
187+
}
188+
189+
[Fact]
190+
[WorkItem(3320, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3320")]
191+
public async Task TestLargePropertyPatternInSwitchExpressionAsync()
192+
{
193+
var testCode = @"
194+
public class TestClass
195+
{
196+
public static int DummyMethodUsingSwitch(Outer foo) =>
197+
foo switch
198+
{
199+
{
200+
Foo: { Bar: ""foo bar"", Baz: true },
201+
Supercalifragilisticexpialidocious: { Something: int smth, SomethingElse: int smthElse }
202+
} => smth + smthElse,
203+
_ => 0,
204+
};
205+
}
206+
207+
public class Outer
208+
{
209+
public Inner Foo { get; set; }
210+
211+
public Nested Supercalifragilisticexpialidocious { get; set; }
212+
}
213+
214+
public class Inner
215+
{
216+
public string Bar { get; set; }
217+
218+
public bool Baz { get; set; }
219+
}
220+
221+
public class Nested
222+
{
223+
public int Something { get; set; }
224+
225+
public int SomethingElse { get; set; }
226+
}
227+
";
228+
229+
await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
230+
}
231+
232+
[Fact]
233+
[WorkItem(3692, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3692")]
234+
public async Task TestSwitchExpressionWithMultilinePropertyPatternArmAsync()
235+
{
236+
var testCode = @"
237+
public class TestClass
238+
{
239+
public string Test(TestClass value) =>
240+
value switch
241+
{
242+
{
243+
thisIsAReallyLongMemberNameAndItIsReallyReallyLongEvenLongerThanYouMayThink:
244+
42
245+
} => ""extremely unusual result"",
246+
_ => ""expected result"",
247+
};
248+
249+
public int HighlyUnexpectedLargerThanLifeInteger { get; }
250+
251+
public int thisIsAReallyLongMemberNameAndItIsReallyReallyLongEvenLongerThanYouMayThink { get; }
252+
}
253+
";
254+
255+
await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
256+
}
257+
258+
[Fact]
259+
[WorkItem(3692, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3692")]
260+
public async Task TestIsPatternWithMultilinePropertyPatternAsync()
261+
{
262+
var testCode = @"
263+
public class TestClass
264+
{
265+
public string Test(TestClass value)
266+
{
267+
if (value is
268+
{
269+
thisIsAReallyLongMemberNameAndItIsReallyReallyLongEvenLongerThanYouMayThink:
270+
42
271+
} && 1 == 1)
272+
{
273+
return ""expected result"";
274+
}
275+
276+
return ""other"";
277+
}
278+
279+
public int HighlyUnexpectedLargerThanLifeInteger { get; }
280+
281+
public int thisIsAReallyLongMemberNameAndItIsReallyReallyLongEvenLongerThanYouMayThink { get; }
282+
}
283+
";
284+
285+
await VerifyCSharpDiagnosticAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
286+
}
10287
}
11288
}

StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1513ClosingBraceMustBeFollowedByBlankLine.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,12 @@ private static bool StartsWithDirectiveTrivia(SyntaxTriviaList triviaList)
165165
return false;
166166
}
167167

168+
private static RecursivePatternSyntaxWrapper FindRecursivePattern(SyntaxToken token)
169+
{
170+
var recursivePatternSyntax = token.Parent.FirstAncestorOrSelf<SyntaxNode>(static node => RecursivePatternSyntaxWrapper.IsInstance(node));
171+
return (RecursivePatternSyntaxWrapper)recursivePatternSyntax;
172+
}
173+
168174
private static bool IsPartOf<T>(SyntaxToken token)
169175
{
170176
var result = false;
@@ -273,6 +279,29 @@ private void AnalyzeCloseBrace(SyntaxToken token)
273279
return;
274280
}
275281

282+
if (nextToken.IsKind(SyntaxKind.EqualsGreaterThanToken))
283+
{
284+
// the close brace is followed by a switch expression arm arrow
285+
return;
286+
}
287+
288+
if (nextToken.IsKind(SyntaxKind.AmpersandAmpersandToken)
289+
|| nextToken.IsKind(SyntaxKind.BarBarToken))
290+
{
291+
// the close brace is followed by a logical operator continuing the same expression
292+
return;
293+
}
294+
295+
var recursivePattern = FindRecursivePattern(token);
296+
var nextRecursivePattern = FindRecursivePattern(nextToken);
297+
if (recursivePattern.SyntaxNode != null
298+
&& nextRecursivePattern.SyntaxNode == recursivePattern.SyntaxNode
299+
&& nextToken.IsKind(SyntaxKind.IdentifierToken))
300+
{
301+
// the close brace is part of a recursive pattern that continues with a designation
302+
return;
303+
}
304+
276305
if (nextToken.IsKind(SyntaxKind.CloseBracketToken))
277306
{
278307
// the close brace is for example in an object initializer at the end of a collection expression.

documentation/SA1513.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ if (condition)
3737
return value;
3838
```
3939

40+
Braces that are part of an expression or pattern (for example, switch expressions or property patterns introduced in
41+
C# 8.0) do not require a blank line when the brace is immediately followed by continuation tokens like `=>`, `,`, or
42+
`;`. The same applies when the brace is directly followed by a logical operator (such as `&&` or `||`) or by a pattern
43+
designation (e.g., `} patternVar`) that continues the same expression.
4044

4145
## How to fix violations
4246

0 commit comments

Comments
 (0)