From 6673e09060ba191f14de6b140898242e0ef71baa Mon Sep 17 00:00:00 2001 From: Matt Chaulklin Date: Wed, 26 Jun 2024 11:06:04 -0400 Subject: [PATCH 1/3] Added new logic to skip SA1514 when documenting types declared in the global namespace --- .../LayoutRules/SA1514UnitTests.cs | 18 ++++++++++++++++++ ...mentationHeaderMustBePrecededByBlankLine.cs | 12 ++++++++++++ 2 files changed, 30 insertions(+) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/LayoutRules/SA1514UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/LayoutRules/SA1514UnitTests.cs index 582223d19..0ecb3d387 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/LayoutRules/SA1514UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/LayoutRules/SA1514UnitTests.cs @@ -1151,5 +1151,23 @@ public enum TestEnum await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(false); } + + [Fact] + [WorkItem(3849, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3849")] + public async Task TestClassInGlobalNamespaceAsync() + { + var testCode = @" +/// +/// X. +/// +public class TestClass +{ +} +"; + + var expected = DiagnosticResult.EmptyDiagnosticResults; + + await VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1514ElementDocumentationHeaderMustBePrecededByBlankLine.cs b/StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1514ElementDocumentationHeaderMustBePrecededByBlankLine.cs index 377dfe327..c56bad3b4 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1514ElementDocumentationHeaderMustBePrecededByBlankLine.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1514ElementDocumentationHeaderMustBePrecededByBlankLine.cs @@ -170,6 +170,18 @@ private static void HandleDeclaration(SyntaxNodeAnalysisContext context) // no leading blank line necessary at start of scope. return; } + + // Logic to handle global namespace case + if (prevToken.IsKind(SyntaxKind.None)) + { + // Check if the node is the first non-trivia element in the file + var firstToken = context.Node.SyntaxTree.GetRoot().GetFirstToken(); + if (firstToken == context.Node.GetFirstToken()) + { + // Node is the first element in the global namespace + return; + } + } } context.ReportDiagnostic(Diagnostic.Create(Descriptor, GetDiagnosticLocation(documentationHeader))); From 1ba0ce31e5bcbcfaa429e3fd9e229cc41c0ab1ec Mon Sep 17 00:00:00 2001 From: Matt Chaulklin Date: Mon, 1 Jul 2024 22:04:53 -0400 Subject: [PATCH 2/3] Updated code and tests. --- .../LayoutRules/SA1514UnitTests.cs | 59 +++++++++++++++++++ ...entationHeaderMustBePrecededByBlankLine.cs | 9 +-- 2 files changed, 61 insertions(+), 7 deletions(-) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/LayoutRules/SA1514UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/LayoutRules/SA1514UnitTests.cs index 0ecb3d387..e3d8faef7 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/LayoutRules/SA1514UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/LayoutRules/SA1514UnitTests.cs @@ -1169,5 +1169,64 @@ public class TestClass await VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); } + + [Fact] + public async Task TestClassInGlobalNamespaceWithoutNewlineAsync() + { + var testCode = @"/// +/// X. +/// +public class TestClass +{ +} +"; + + var expected = DiagnosticResult.EmptyDiagnosticResults; + + await VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + public async Task TestClassInGlobalNamespaceWithCommentAsync() + { + var testCode = @" +// Normal comment +{|#0:///|} +/// X. +/// +public class TestClass +{ +} +"; + + var expected = new[] + { + Diagnostic().WithLocation(0).WithArguments(" not", "preceded"), + }; + + await VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + public async Task TestClassInGlobalNamespaceWithPreprocessorDirectiveAsync() + { + var testCode = @" +#if DEBUG +#endif +{|#0:///|} +/// X. +/// +public class TestClass +{ +} +"; + + var expected = new[] + { + Diagnostic().WithLocation(0).WithArguments(" not", "preceded"), + }; + + await VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1514ElementDocumentationHeaderMustBePrecededByBlankLine.cs b/StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1514ElementDocumentationHeaderMustBePrecededByBlankLine.cs index c56bad3b4..932349990 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1514ElementDocumentationHeaderMustBePrecededByBlankLine.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/LayoutRules/SA1514ElementDocumentationHeaderMustBePrecededByBlankLine.cs @@ -174,13 +174,8 @@ private static void HandleDeclaration(SyntaxNodeAnalysisContext context) // Logic to handle global namespace case if (prevToken.IsKind(SyntaxKind.None)) { - // Check if the node is the first non-trivia element in the file - var firstToken = context.Node.SyntaxTree.GetRoot().GetFirstToken(); - if (firstToken == context.Node.GetFirstToken()) - { - // Node is the first element in the global namespace - return; - } + // Node is the first element in the global namespace + return; } } From cd99e2f9542459cdb28f3e9f350b685b87245568 Mon Sep 17 00:00:00 2001 From: Matt Chaulklin Date: Tue, 2 Jul 2024 13:25:29 -0400 Subject: [PATCH 3/3] Added more tests --- .../LayoutRules/SA1514UnitTests.cs | 123 +++++++++++++++++- 1 file changed, 120 insertions(+), 3 deletions(-) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/LayoutRules/SA1514UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/LayoutRules/SA1514UnitTests.cs index e3d8faef7..a71a69b66 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/LayoutRules/SA1514UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/LayoutRules/SA1514UnitTests.cs @@ -1186,6 +1186,64 @@ public class TestClass await VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); } + [Fact] + public async Task TestClassInNamespaceAsync() + { + var testCode = @" +namespace TestNamespace +{ + /// + /// X. + /// + public class TestClass + { + } +} +"; + + var expected = DiagnosticResult.EmptyDiagnosticResults; + + await VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + public async Task TestClassInNamespaceWithCommentAsync() + { + var testCode = @" +namespace TestNamespace +{ + // Normal comment + {|#0:///|} + /// X. + /// + public class TestClass + { + } +} +"; + + var fixedCode = @" +namespace TestNamespace +{ + // Normal comment + + /// + /// X. + /// + public class TestClass + { + } +} +"; + + var expected = new[] + { + Diagnostic().WithLocation(0).WithArguments(" not", "preceded"), + }; + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(false); + } + [Fact] public async Task TestClassInGlobalNamespaceWithCommentAsync() { @@ -1197,6 +1255,17 @@ public async Task TestClassInGlobalNamespaceWithCommentAsync() public class TestClass { } +"; + + var fixedCode = @" +// Normal comment + +/// +/// X. +/// +public class TestClass +{ +} "; var expected = new[] @@ -1204,7 +1273,7 @@ public class TestClass Diagnostic().WithLocation(0).WithArguments(" not", "preceded"), }; - await VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(false); } [Fact] @@ -1219,14 +1288,62 @@ public async Task TestClassInGlobalNamespaceWithPreprocessorDirectiveAsync() public class TestClass { } +"; + + var fixedCode = @" +#if DEBUG +#endif + +/// +/// X. +/// +public class TestClass +{ +} "; var expected = new[] { - Diagnostic().WithLocation(0).WithArguments(" not", "preceded"), + Diagnostic().WithLocation(0).WithArguments(" not", "preceded"), }; - await VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(false); + } + + [Fact] + public async Task TestClassInGlobalNamespaceWithMultilineCommentAsync() + { + var testCode = @" +/* Normal + * multiline + * comment */ +{|#0:///|} +/// X. +/// +public class TestClass +{ +} +"; + + var fixedCode = @" +/* Normal + * multiline + * comment */ + +/// +/// X. +/// +public class TestClass +{ +} +"; + + var expected = new[] + { + Diagnostic().WithLocation(0).WithArguments(" not", "preceded"), + }; + + await VerifyCSharpFixAsync(testCode, expected, fixedCode, CancellationToken.None).ConfigureAwait(false); } } }