Skip to content

Commit 0ccaad4

Browse files
committed
Fix SA1009 handling of tuple type closing parenthesis
Fixes #2476
1 parent 366f19f commit 0ccaad4

2 files changed

Lines changed: 130 additions & 1 deletion

File tree

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

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,68 @@ public class TestClass
453453
await this.VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false);
454454
}
455455

456+
[Fact]
457+
[WorkItem(2476, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2476")]
458+
public async Task TestNullableAndArrayTupleReturnTypeAsync()
459+
{
460+
var testCode = @"namespace TestNamespace
461+
{
462+
public class TestClass
463+
{
464+
public (int, int ) ? TestMethod1() => default((int, int ) ?);
465+
public (int, int )? TestMethod2() => default((int, int )?);
466+
public (int, int) ? TestMethod3() => default((int, int) ?);
467+
468+
public (int, int ) [] TestMethod4() => default((int, int ) []);
469+
public (int, int )[] TestMethod5() => default((int, int )[]);
470+
public (int, int) [] TestMethod6() => default((int, int) []);
471+
}
472+
}
473+
";
474+
475+
var fixedCode = @"namespace TestNamespace
476+
{
477+
public class TestClass
478+
{
479+
public (int, int)? TestMethod1() => default((int, int)?);
480+
public (int, int)? TestMethod2() => default((int, int)?);
481+
public (int, int)? TestMethod3() => default((int, int)?);
482+
483+
public (int, int)[] TestMethod4() => default((int, int)[]);
484+
public (int, int)[] TestMethod5() => default((int, int)[]);
485+
public (int, int)[] TestMethod6() => default((int, int)[]);
486+
}
487+
}
488+
";
489+
490+
DiagnosticResult[] expectedDiagnostic =
491+
{
492+
// TestMethod1, TestMethod2, TestMethod3
493+
this.CSharpDiagnostic().WithArguments(" not", "preceded").WithLocation(5, 26),
494+
this.CSharpDiagnostic().WithArguments(" not", "followed").WithLocation(5, 26),
495+
this.CSharpDiagnostic().WithArguments(" not", "preceded").WithLocation(5, 65),
496+
this.CSharpDiagnostic().WithArguments(" not", "followed").WithLocation(5, 65),
497+
this.CSharpDiagnostic().WithArguments(" not", "preceded").WithLocation(6, 26),
498+
this.CSharpDiagnostic().WithArguments(" not", "preceded").WithLocation(6, 64),
499+
this.CSharpDiagnostic().WithArguments(" not", "followed").WithLocation(7, 25),
500+
this.CSharpDiagnostic().WithArguments(" not", "followed").WithLocation(7, 63),
501+
502+
// TestMethod4, TestMethod5, TestMethod6
503+
this.CSharpDiagnostic().WithArguments(" not", "preceded").WithLocation(9, 26),
504+
this.CSharpDiagnostic().WithArguments(" not", "followed").WithLocation(9, 26),
505+
this.CSharpDiagnostic().WithArguments(" not", "preceded").WithLocation(9, 66),
506+
this.CSharpDiagnostic().WithArguments(" not", "followed").WithLocation(9, 66),
507+
this.CSharpDiagnostic().WithArguments(" not", "preceded").WithLocation(10, 26),
508+
this.CSharpDiagnostic().WithArguments(" not", "preceded").WithLocation(10, 65),
509+
this.CSharpDiagnostic().WithArguments(" not", "followed").WithLocation(11, 25),
510+
this.CSharpDiagnostic().WithArguments(" not", "followed").WithLocation(11, 64),
511+
};
512+
513+
await this.VerifyCSharpDiagnosticAsync(testCode, expectedDiagnostic, CancellationToken.None).ConfigureAwait(false);
514+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
515+
await this.VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false);
516+
}
517+
456518
/// <summary>
457519
/// Verifies that spacing for tuple expressions is handled properly.
458520
/// </summary>
@@ -796,6 +858,72 @@ public class TestClass
796858
await this.VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false);
797859
}
798860

861+
[Fact]
862+
[WorkItem(2476, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2476")]
863+
public async Task TestNullableAndArrayTupleTypesAsGenericArgumentsAsync()
864+
{
865+
var testCode = @"using System;
866+
867+
namespace TestNamespace
868+
{
869+
public class TestClass
870+
{
871+
static Func<(int, int ) ?, (int, int ) ?> Function1;
872+
static Func<(int, int )?, (int, int )?> Function2;
873+
static Func<(int, int) ?, (int, int) ?> Function3;
874+
875+
static Func<(int, int ) [], (int, int ) []> Function4;
876+
static Func<(int, int )[], (int, int )[]> Function5;
877+
static Func<(int, int) [], (int, int) []> Function6;
878+
}
879+
}
880+
";
881+
882+
var fixedCode = @"using System;
883+
884+
namespace TestNamespace
885+
{
886+
public class TestClass
887+
{
888+
static Func<(int, int)?, (int, int)?> Function1;
889+
static Func<(int, int)?, (int, int)?> Function2;
890+
static Func<(int, int)?, (int, int)?> Function3;
891+
892+
static Func<(int, int)[], (int, int)[]> Function4;
893+
static Func<(int, int)[], (int, int)[]> Function5;
894+
static Func<(int, int)[], (int, int)[]> Function6;
895+
}
896+
}
897+
";
898+
899+
DiagnosticResult[] expectedDiagnostics =
900+
{
901+
// Function1, Function2, Function3
902+
this.CSharpDiagnostic().WithArguments(" not", "preceded").WithLocation(7, 31),
903+
this.CSharpDiagnostic().WithArguments(" not", "followed").WithLocation(7, 31),
904+
this.CSharpDiagnostic().WithArguments(" not", "preceded").WithLocation(7, 46),
905+
this.CSharpDiagnostic().WithArguments(" not", "followed").WithLocation(7, 46),
906+
this.CSharpDiagnostic().WithArguments(" not", "preceded").WithLocation(8, 31),
907+
this.CSharpDiagnostic().WithArguments(" not", "preceded").WithLocation(8, 45),
908+
this.CSharpDiagnostic().WithArguments(" not", "followed").WithLocation(9, 30),
909+
this.CSharpDiagnostic().WithArguments(" not", "followed").WithLocation(9, 44),
910+
911+
// Function4, Function5, Function6
912+
this.CSharpDiagnostic().WithArguments(" not", "preceded").WithLocation(11, 31),
913+
this.CSharpDiagnostic().WithArguments(" not", "followed").WithLocation(11, 31),
914+
this.CSharpDiagnostic().WithArguments(" not", "preceded").WithLocation(11, 47),
915+
this.CSharpDiagnostic().WithArguments(" not", "followed").WithLocation(11, 47),
916+
this.CSharpDiagnostic().WithArguments(" not", "preceded").WithLocation(12, 31),
917+
this.CSharpDiagnostic().WithArguments(" not", "preceded").WithLocation(12, 46),
918+
this.CSharpDiagnostic().WithArguments(" not", "followed").WithLocation(13, 30),
919+
this.CSharpDiagnostic().WithArguments(" not", "followed").WithLocation(13, 45),
920+
};
921+
922+
await this.VerifyCSharpDiagnosticAsync(testCode, expectedDiagnostics, CancellationToken.None).ConfigureAwait(false);
923+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
924+
await this.VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false);
925+
}
926+
799927
/// <summary>
800928
/// Verifies that spacing for tuple variable declarations is handled properly.
801929
/// </summary>

StyleCop.Analyzers/StyleCop.Analyzers/SpacingRules/SA1009ClosingParenthesisMustBeSpacedCorrectly.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,8 @@ private static void HandleCloseParenToken(SyntaxTreeAnalysisContext context, Syn
109109
}
110110
else
111111
{
112-
precedesStickyCharacter = false;
112+
// A space follows unless this is a nullable tuple type
113+
precedesStickyCharacter = nextToken.Parent.IsKind(SyntaxKind.NullableType);
113114
}
114115

115116
break;

0 commit comments

Comments
 (0)