Skip to content

Commit 366f19f

Browse files
committed
Fix SA1008 handling of tuple type opening parenthesis
Fixes #2472
1 parent c9e111b commit 366f19f

3 files changed

Lines changed: 171 additions & 1 deletion

File tree

StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp7/SpacingRules/SA1008CSharp7UnitTests.cs

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,112 @@ public class TestClass
136136
await this.VerifyCSharpFixAsync(testCode, fixedCode, numberOfFixAllIterations: 2).ConfigureAwait(false);
137137
}
138138

139+
[Fact]
140+
[WorkItem(2472, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2472")]
141+
public async Task TestTupleArrayParameterTypeAsync()
142+
{
143+
var testCode = @"namespace TestNamespace
144+
{
145+
public class TestClass
146+
{
147+
public int TestMethod1( ( int, int)[] arg) => 0;
148+
public int TestMethod2( (int, int)[] arg) => 0;
149+
public int TestMethod3(( int, int)[] arg) => 0;
150+
151+
public int TestMethod4((int, int) arg1, params( int, int)[] arg2) => 0;
152+
public int TestMethod5((int, int) arg1, params(int, int)[] arg2) => 0;
153+
public int TestMethod6((int, int) arg1, params ( int, int)[] arg2) => 0;
154+
}
155+
}
156+
";
157+
158+
var fixedCode = @"namespace TestNamespace
159+
{
160+
public class TestClass
161+
{
162+
public int TestMethod1((int, int)[] arg) => 0;
163+
public int TestMethod2((int, int)[] arg) => 0;
164+
public int TestMethod3((int, int)[] arg) => 0;
165+
166+
public int TestMethod4((int, int) arg1, params (int, int)[] arg2) => 0;
167+
public int TestMethod5((int, int) arg1, params (int, int)[] arg2) => 0;
168+
public int TestMethod6((int, int) arg1, params (int, int)[] arg2) => 0;
169+
}
170+
}
171+
";
172+
173+
DiagnosticResult[] expectedDiagnostic =
174+
{
175+
// TestMethod1
176+
this.CSharpDiagnostic(DescriptorNotFollowed).WithLocation(5, 31),
177+
this.CSharpDiagnostic(DescriptorNotFollowed).WithLocation(5, 33),
178+
179+
// TestMethod2
180+
this.CSharpDiagnostic(DescriptorNotFollowed).WithLocation(6, 31),
181+
182+
// TestMethod3
183+
this.CSharpDiagnostic(DescriptorNotFollowed).WithLocation(7, 32),
184+
185+
// TestMethod4
186+
this.CSharpDiagnostic(DescriptorPreceded).WithLocation(9, 55),
187+
this.CSharpDiagnostic(DescriptorNotFollowed).WithLocation(9, 55),
188+
189+
// TestMethod5
190+
this.CSharpDiagnostic(DescriptorPreceded).WithLocation(10, 55),
191+
192+
// TestMethod6
193+
this.CSharpDiagnostic(DescriptorNotFollowed).WithLocation(11, 56),
194+
};
195+
196+
await this.VerifyCSharpDiagnosticAsync(testCode, expectedDiagnostic, CancellationToken.None).ConfigureAwait(false);
197+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
198+
await this.VerifyCSharpFixAsync(testCode, fixedCode, numberOfFixAllIterations: 2).ConfigureAwait(false);
199+
}
200+
201+
[Fact]
202+
[WorkItem(2472, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2472")]
203+
public async Task TestTupleOutParametersAsync()
204+
{
205+
var testCode = @"namespace TestNamespace
206+
{
207+
public class TestClass
208+
{
209+
public int TestMethod1(out( int, int)[] arg) => throw null;
210+
public int TestMethod2(out(int, int)[] arg) => throw null;
211+
public int TestMethod3(out ( int, int)[] arg) => throw null;
212+
}
213+
}
214+
";
215+
216+
var fixedCode = @"namespace TestNamespace
217+
{
218+
public class TestClass
219+
{
220+
public int TestMethod1(out (int, int)[] arg) => throw null;
221+
public int TestMethod2(out (int, int)[] arg) => throw null;
222+
public int TestMethod3(out (int, int)[] arg) => throw null;
223+
}
224+
}
225+
";
226+
227+
DiagnosticResult[] expectedDiagnostic =
228+
{
229+
// TestMethod1
230+
this.CSharpDiagnostic(DescriptorPreceded).WithLocation(5, 35),
231+
this.CSharpDiagnostic(DescriptorNotFollowed).WithLocation(5, 35),
232+
233+
// TestMethod2
234+
this.CSharpDiagnostic(DescriptorPreceded).WithLocation(6, 35),
235+
236+
// TestMethod3
237+
this.CSharpDiagnostic(DescriptorNotFollowed).WithLocation(7, 36),
238+
};
239+
240+
await this.VerifyCSharpDiagnosticAsync(testCode, expectedDiagnostic, CancellationToken.None).ConfigureAwait(false);
241+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
242+
await this.VerifyCSharpFixAsync(testCode, fixedCode, numberOfFixAllIterations: 2).ConfigureAwait(false);
243+
}
244+
139245
/// <summary>
140246
/// Verifies that spacing around tuple return types is handled properly.
141247
/// </summary>
@@ -273,6 +379,50 @@ public class TestClass
273379
await this.VerifyCSharpFixAsync(testCode, fixedCode, numberOfFixAllIterations: 2).ConfigureAwait(false);
274380
}
275381

382+
[Fact]
383+
[WorkItem(2472, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2472")]
384+
public async Task TestNullableTupleReturnTypeAsync()
385+
{
386+
var testCode = @"namespace TestNamespace
387+
{
388+
public class TestClass
389+
{
390+
public( int, int)? TestMethod1() => default( ( int, int)?);
391+
public(int, int)? TestMethod2() => default( (int, int)?);
392+
public ( int, int)? TestMethod3() => default(( int, int)?);
393+
}
394+
}
395+
";
396+
397+
var fixedCode = @"namespace TestNamespace
398+
{
399+
public class TestClass
400+
{
401+
public (int, int)? TestMethod1() => default((int, int)?);
402+
public (int, int)? TestMethod2() => default((int, int)?);
403+
public (int, int)? TestMethod3() => default((int, int)?);
404+
}
405+
}
406+
";
407+
408+
DiagnosticResult[] expectedDiagnostic =
409+
{
410+
// TestMethod1, TestMethod2, TestMethod3
411+
this.CSharpDiagnostic(DescriptorPreceded).WithLocation(5, 15),
412+
this.CSharpDiagnostic(DescriptorNotFollowed).WithLocation(5, 15),
413+
this.CSharpDiagnostic(DescriptorNotFollowed).WithLocation(5, 52),
414+
this.CSharpDiagnostic(DescriptorNotFollowed).WithLocation(5, 54),
415+
this.CSharpDiagnostic(DescriptorPreceded).WithLocation(6, 15),
416+
this.CSharpDiagnostic(DescriptorNotFollowed).WithLocation(6, 51),
417+
this.CSharpDiagnostic(DescriptorNotFollowed).WithLocation(7, 16),
418+
this.CSharpDiagnostic(DescriptorNotFollowed).WithLocation(7, 54),
419+
};
420+
421+
await this.VerifyCSharpDiagnosticAsync(testCode, expectedDiagnostic, CancellationToken.None).ConfigureAwait(false);
422+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
423+
await this.VerifyCSharpFixAsync(testCode, fixedCode, numberOfFixAllIterations: 2).ConfigureAwait(false);
424+
}
425+
276426
/// <summary>
277427
/// Verifies that spacing for tuple expressions is handled properly.
278428
/// </summary>

StyleCop.Analyzers/StyleCop.Analyzers/Helpers/TypeSyntaxHelper.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,24 @@ namespace StyleCop.Analyzers.Helpers
99

1010
internal static class TypeSyntaxHelper
1111
{
12+
public static TypeSyntax GetContainingNotEnclosingType(this TypeSyntax syntax)
13+
{
14+
while (true)
15+
{
16+
switch (syntax.Parent.Kind())
17+
{
18+
case SyntaxKind.ArrayType:
19+
case SyntaxKind.NullableType:
20+
case SyntaxKind.PointerType:
21+
syntax = (TypeSyntax)syntax.Parent;
22+
break;
23+
24+
default:
25+
return syntax;
26+
}
27+
}
28+
}
29+
1230
public static bool IsReturnType(this TypeSyntax syntax)
1331
{
1432
switch (syntax.Parent.Kind())

StyleCop.Analyzers/StyleCop.Analyzers/SpacingRules/SA1008OpeningParenthesisMustBeSpacedCorrectly.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,9 +237,11 @@ private static void HandleOpenParenToken(SyntaxTreeAnalysisContext context, Synt
237237

238238
case SyntaxKindEx.TupleType:
239239
// Comma covers tuple types in parameters and nested within other tuple types.
240+
// 'out', 'ref', 'in', 'params' parameters are covered by IsKeywordKind.
240241
// Return types are handled by a helper.
241242
haveLeadingSpace = prevToken.IsKind(SyntaxKind.CommaToken)
242-
|| ((TypeSyntax)token.Parent).IsReturnType();
243+
|| SyntaxFacts.IsKeywordKind(prevToken.Kind())
244+
|| ((TypeSyntax)token.Parent).GetContainingNotEnclosingType().IsReturnType();
243245
break;
244246
}
245247

0 commit comments

Comments
 (0)