From cc13c5a2e090a5e96cb70fd778cc6f6926c30b99 Mon Sep 17 00:00:00 2001 From: Matt Chaulklin Date: Tue, 29 Jul 2025 08:36:08 -0400 Subject: [PATCH] Do not warn if a comma follows a preprocessor directive --- .../SpacingRules/SA1001UnitTests.cs | 108 ++++++++++++++++++ .../SA1001CommasMustBeSpacedCorrectly.cs | 30 ++++- documentation/SA1001.md | 7 ++ 3 files changed, 144 insertions(+), 1 deletion(-) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/SpacingRules/SA1001UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/SpacingRules/SA1001UnitTests.cs index 3476214f4..5969e9a03 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/SpacingRules/SA1001UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/SpacingRules/SA1001UnitTests.cs @@ -349,6 +349,114 @@ public void TestMethod() await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(false); } + [Fact] + [WorkItem(3816, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3816")] + public async Task TestCommaFollowingPreprocessorDirectiveAsync() + { + var testCode = @" +interface IFormattable {} +interface ISpanFormattable {} +partial struct Money : IFormattable +#if true + , ISpanFormattable +#endif +{ +} +"; + + var expected = DiagnosticResult.EmptyDiagnosticResults; + + await VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + public async Task TestCommaFollowingElifDirectiveAsync() + { + var testCode = @" +interface IFormattable {} +interface ISpanFormattable {} +partial struct Money : IFormattable +#if false +#elif true + , ISpanFormattable +#endif +{ +} +"; + + var expected = DiagnosticResult.EmptyDiagnosticResults; + + await VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + public async Task TestCommaFollowingElseDirectiveAsync() + { + var testCode = @" +interface IFormattable {} +interface ISpanFormattable {} +partial struct Money : IFormattable +#if false +#else + , ISpanFormattable +#endif +{ +} +"; + + var expected = DiagnosticResult.EmptyDiagnosticResults; + + await VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + public async Task TestCommaFollowingEndIfDirectiveAsync() + { + var testCode = @" +interface IFormattable {} +interface ISpanFormattable {} +partial struct Money : IFormattable +#if false +#endif + , ISpanFormattable +{ +} +"; + + var expected = DiagnosticResult.EmptyDiagnosticResults; + + await VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + public async Task TestCommaNotFollowingDirectiveAsync() + { + var testCode = @" +interface IFormattable {} +interface ISpanFormattable {} +partial struct Money : IFormattable + {|#0:,|} ISpanFormattable +{ +} +"; + + var fixedCode = @" +interface IFormattable {} +interface ISpanFormattable {} +partial struct Money : IFormattable, + ISpanFormattable +{ +} +"; + + var expected = new[] + { + Diagnostic().WithLocation(0).WithArguments(" not", "preceded"), + }; + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(false); + } + private Task TestCommaInStatementOrDeclAsync(string originalStatement, DiagnosticResult expected, string fixedStatement) { return this.TestCommaInStatementOrDeclAsync(originalStatement, new[] { expected }, fixedStatement); diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/SpacingRules/SA1001CommasMustBeSpacedCorrectly.cs b/StyleCop.Analyzers/StyleCop.Analyzers/SpacingRules/SA1001CommasMustBeSpacedCorrectly.cs index 8f77a60cf..34796e1f9 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/SpacingRules/SA1001CommasMustBeSpacedCorrectly.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/SpacingRules/SA1001CommasMustBeSpacedCorrectly.cs @@ -76,6 +76,9 @@ private static void HandleCommaToken(SyntaxTreeAnalysisContext context, SyntaxTo return; } + // Check if the comma follows a conditional preprocessor directive. + bool followsDirective = token.HasLeadingTrivia && IsPrecededByDirectiveTrivia(token.LeadingTrivia); + // check for a following space bool missingFollowingSpace = true; @@ -102,7 +105,7 @@ private static void HandleCommaToken(SyntaxTreeAnalysisContext context, SyntaxTo } } - if (token.IsFirstInLine() || token.IsPrecededByWhitespace(context.CancellationToken)) + if (!followsDirective && (token.IsFirstInLine() || token.IsPrecededByWhitespace(context.CancellationToken))) { // comma should{ not} be {preceded} by whitespace context.ReportDiagnostic(Diagnostic.Create(Descriptor, token.GetLocation(), TokenSpacingProperties.RemovePrecedingPreserveLayout, " not", "preceded")); @@ -120,5 +123,30 @@ private static void HandleCommaToken(SyntaxTreeAnalysisContext context, SyntaxTo context.ReportDiagnostic(Diagnostic.Create(Descriptor, token.GetLocation(), TokenSpacingProperties.RemoveFollowing, " not", "followed")); } } + + private static bool IsPrecededByDirectiveTrivia(SyntaxTriviaList triviaList) + { + int triviaIndex = triviaList.Count - 1; + while (triviaIndex >= 0) + { + switch (triviaList[triviaIndex].Kind()) + { + case SyntaxKind.WhitespaceTrivia: + triviaIndex--; + break; + + case SyntaxKind.IfDirectiveTrivia: + case SyntaxKind.ElifDirectiveTrivia: + case SyntaxKind.ElseDirectiveTrivia: + case SyntaxKind.EndIfDirectiveTrivia: + return true; + + default: + return false; + } + } + + return false; + } } } diff --git a/documentation/SA1001.md b/documentation/SA1001.md index 0d68cce9d..74e3024d1 100644 --- a/documentation/SA1001.md +++ b/documentation/SA1001.md @@ -28,6 +28,13 @@ A comma should be followed by a single space, except in the following cases. * A comma may appear at the end of a line * A comma should not be followed by a space when used in an open generic type in a `typeof` expression * A comma is part of a string interpolation alignment component. For example:`$"{x,3}"` +* A comma follows a conditional preprocessor directive (#if, #elif, #else, #endif). For example: + ```csharp + partial struct Money : IFormattable + #if !NETSTANDARD + , ISpanFormattable + #endif + ``` A comma should never be preceded by a space or appear as the first token on a line.