From e1f52bfe2659521ab6e53d007c9f0f45b1bf4e07 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Fri, 14 Jan 2022 09:18:50 -0800 Subject: [PATCH] Support file-scoped namespaces in SA1649 and SA1402 Fixes #3435 --- .../SA1649CSharp10UnitTests.cs | 38 +++++++++++++++++++ .../SA1402CSharp10ForClassUnitTests.cs | 32 ++++++++++++++++ .../DocumentationRules/SA1649UnitTests.cs | 12 +++--- .../SA1649FileNameMustMatchTypeName.cs | 5 ++- .../SA1402FileMayOnlyContainASingleType.cs | 3 +- 5 files changed, 81 insertions(+), 9 deletions(-) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp10/DocumentationRules/SA1649CSharp10UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp10/DocumentationRules/SA1649CSharp10UnitTests.cs index 3ae105437..e63b2cd68 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp10/DocumentationRules/SA1649CSharp10UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp10/DocumentationRules/SA1649CSharp10UnitTests.cs @@ -3,9 +3,47 @@ namespace StyleCop.Analyzers.Test.CSharp10.DocumentationRules { + using System.Threading; + using System.Threading.Tasks; using StyleCop.Analyzers.Test.CSharp9.DocumentationRules; + using StyleCop.Analyzers.Test.Helpers; + using Xunit; + using static StyleCop.Analyzers.Test.Verifiers.CustomDiagnosticVerifier< + StyleCop.Analyzers.DocumentationRules.SA1649FileNameMustMatchTypeName>; public class SA1649CSharp10UnitTests : SA1649CSharp9UnitTests { + /// + /// Verifies that the file name is based on the first type. + /// + /// The type keyword to use during the test. + /// A representing the asynchronous unit test. + [Theory] + [MemberData(nameof(CommonMemberData.TypeDeclarationKeywords), MemberType = typeof(CommonMemberData))] + [WorkItem(3435, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3435")] + public async Task VerifyFirstTypeIsUsedWithFileScopedNamespacesAsync(string typeKeyword) + { + var testCode = $@"namespace TestNamespace; + +public enum IgnoredEnum {{ }} +public delegate void IgnoredDelegate(); + +{GetTypeDeclaration(typeKeyword, "TestType", diagnosticKey: 0)} + +{GetTypeDeclaration(typeKeyword, "TestType2")} +"; + var fixedCode = $@"namespace TestNamespace; + +public enum IgnoredEnum {{ }} +public delegate void IgnoredDelegate(); + +{GetTypeDeclaration(typeKeyword, "TestType")} + +{GetTypeDeclaration(typeKeyword, "TestType2")} +"; + + var expectedDiagnostic = Diagnostic().WithLocation(0); + await VerifyCSharpFixAsync("TestType2.cs", testCode, StyleCopSettings, expectedDiagnostic, "TestType.cs", fixedCode, CancellationToken.None).ConfigureAwait(false); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp10/MaintainabilityRules/SA1402CSharp10ForClassUnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp10/MaintainabilityRules/SA1402CSharp10ForClassUnitTests.cs index cfade8dbc..41e3c74a8 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp10/MaintainabilityRules/SA1402CSharp10ForClassUnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp10/MaintainabilityRules/SA1402CSharp10ForClassUnitTests.cs @@ -3,9 +3,41 @@ namespace StyleCop.Analyzers.Test.CSharp10.MaintainabilityRules { + using System.Threading; + using System.Threading.Tasks; using StyleCop.Analyzers.Test.CSharp9.MaintainabilityRules; + using Xunit; public class SA1402CSharp10ForClassUnitTests : SA1402CSharp9ForClassUnitTests { + [Fact] + [WorkItem(3435, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3435")] + public async Task VerifyTwoClassesWithFileScopedNamespacesAsync() + { + var testCode = $@"namespace TestNamespace; + +public class TestClass1 {{ }} +public class {{|#0:TestClass2|}} {{ }} +"; + var fixedCode1 = $@"namespace TestNamespace; + +public class TestClass1 {{ }} +"; + var fixedCode2 = $@"namespace TestNamespace; +public class TestClass2 {{ }} +"; + + var expectedDiagnostic = this.Diagnostic().WithLocation(0); + await this.VerifyCSharpFixAsync( + testCode, + this.GetSettings(), + expectedDiagnostic, + new[] + { + ("/0/Test0.cs", fixedCode1), + ("TestClass2.cs", fixedCode2), + }, + CancellationToken.None).ConfigureAwait(false); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1649UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1649UnitTests.cs index ccb105e31..5f2bf72bb 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1649UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1649UnitTests.cs @@ -17,7 +17,7 @@ namespace StyleCop.Analyzers.Test.DocumentationRules /// public class SA1649UnitTests { - private const string MetadataSettings = @" + protected const string MetadataSettings = @" { ""settings"": { ""documentationRules"": { @@ -27,7 +27,7 @@ public class SA1649UnitTests } "; - private const string StyleCopSettings = @" + protected const string StyleCopSettings = @" { ""settings"": { ""documentationRules"": { @@ -331,7 +331,7 @@ public class Class2 await VerifyCSharpDiagnosticAsync("Class1.cs", testCode, DiagnosticResult.EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); } - private static string GetTypeDeclaration(string typeKind, string typeName, int? diagnosticKey = null) + protected static string GetTypeDeclaration(string typeKind, string typeName, int? diagnosticKey = null) { if (diagnosticKey is not null) { @@ -345,7 +345,7 @@ private static string GetTypeDeclaration(string typeKind, string typeName, int? }; } - private static Task VerifyCSharpDiagnosticAsync(string fileName, string source, DiagnosticResult[] expected, CancellationToken cancellationToken) + protected static Task VerifyCSharpDiagnosticAsync(string fileName, string source, DiagnosticResult[] expected, CancellationToken cancellationToken) { var test = new StyleCopCodeFixVerifier.CSharpTest() { @@ -356,10 +356,10 @@ private static Task VerifyCSharpDiagnosticAsync(string fileName, string source, return test.RunAsync(cancellationToken); } - private static Task VerifyCSharpFixAsync(string oldFileName, string source, string testSettings, DiagnosticResult expected, string newFileName, string fixedSource, CancellationToken cancellationToken) + protected static Task VerifyCSharpFixAsync(string oldFileName, string source, string testSettings, DiagnosticResult expected, string newFileName, string fixedSource, CancellationToken cancellationToken) => VerifyCSharpFixAsync(oldFileName, source, testSettings, new[] { expected }, newFileName, fixedSource, cancellationToken); - private static Task VerifyCSharpFixAsync(string oldFileName, string source, string testSettings, DiagnosticResult[] expected, string newFileName, string fixedSource, CancellationToken cancellationToken) + protected static Task VerifyCSharpFixAsync(string oldFileName, string source, string testSettings, DiagnosticResult[] expected, string newFileName, string fixedSource, CancellationToken cancellationToken) { var test = new StyleCopCodeFixVerifier.CSharpTest() { diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/SA1649FileNameMustMatchTypeName.cs b/StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/SA1649FileNameMustMatchTypeName.cs index 125fc6e4d..b087bb726 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/SA1649FileNameMustMatchTypeName.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/SA1649FileNameMustMatchTypeName.cs @@ -11,6 +11,7 @@ namespace StyleCop.Analyzers.DocumentationRules using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using StyleCop.Analyzers.Helpers; + using StyleCop.Analyzers.Lightup; using StyleCop.Analyzers.Settings.ObjectModel; /// @@ -97,13 +98,13 @@ public static void HandleSyntaxTree(SyntaxTreeAnalysisContext context, StyleCopS private static MemberDeclarationSyntax GetFirstTypeDeclaration(SyntaxNode root) { // Prefer to find the first type which is a true TypeDeclarationSyntax - MemberDeclarationSyntax firstTypeDeclaration = root.DescendantNodes(descendIntoChildren: node => node.IsKind(SyntaxKind.CompilationUnit) || node.IsKind(SyntaxKind.NamespaceDeclaration)) + MemberDeclarationSyntax firstTypeDeclaration = root.DescendantNodes(descendIntoChildren: node => node.IsKind(SyntaxKind.CompilationUnit) || node.IsKind(SyntaxKind.NamespaceDeclaration) || node.IsKind(SyntaxKindEx.FileScopedNamespaceDeclaration)) .OfType() .FirstOrDefault(); // If no TypeDeclarationSyntax is found, expand the search to any type declaration as long as only one // is present - var expandedTypeDeclarations = root.DescendantNodes(descendIntoChildren: node => node.IsKind(SyntaxKind.CompilationUnit) || node.IsKind(SyntaxKind.NamespaceDeclaration)) + var expandedTypeDeclarations = root.DescendantNodes(descendIntoChildren: node => node.IsKind(SyntaxKind.CompilationUnit) || node.IsKind(SyntaxKind.NamespaceDeclaration) || node.IsKind(SyntaxKindEx.FileScopedNamespaceDeclaration)) .OfType() .Where(node => node is BaseTypeDeclarationSyntax || node.IsKind(SyntaxKind.DelegateDeclaration)) .ToList(); diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/MaintainabilityRules/SA1402FileMayOnlyContainASingleType.cs b/StyleCop.Analyzers/StyleCop.Analyzers/MaintainabilityRules/SA1402FileMayOnlyContainASingleType.cs index 35e153783..18194e065 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/MaintainabilityRules/SA1402FileMayOnlyContainASingleType.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/MaintainabilityRules/SA1402FileMayOnlyContainASingleType.cs @@ -12,6 +12,7 @@ namespace StyleCop.Analyzers.MaintainabilityRules using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using StyleCop.Analyzers.Helpers; + using StyleCop.Analyzers.Lightup; using StyleCop.Analyzers.Settings.ObjectModel; /// @@ -99,7 +100,7 @@ private static IEnumerable GetTopLevelTypeDeclarations( private static bool ContainsTopLevelTypeDeclarations(SyntaxNode node) { - return node.IsKind(SyntaxKind.CompilationUnit) || node.IsKind(SyntaxKind.NamespaceDeclaration); + return node.IsKind(SyntaxKind.CompilationUnit) || node.IsKind(SyntaxKind.NamespaceDeclaration) || node.IsKind(SyntaxKindEx.FileScopedNamespaceDeclaration); } private static bool IsRelevantType(SyntaxNode node, StyleCopSettings settings)