diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/MaintainabilityRules/SA1402ForClassCSharp11UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/MaintainabilityRules/SA1402ForClassCSharp11UnitTests.cs index e57806802..95d26d21a 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/MaintainabilityRules/SA1402ForClassCSharp11UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/MaintainabilityRules/SA1402ForClassCSharp11UnitTests.cs @@ -3,9 +3,61 @@ namespace StyleCop.Analyzers.Test.CSharp11.MaintainabilityRules { + using System; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; using StyleCop.Analyzers.Test.CSharp10.MaintainabilityRules; + using Xunit; public partial class SA1402ForClassCSharp11UnitTests : SA1402ForClassCSharp10UnitTests { + /// + /// Verifies that SA1402 is not reported for file-local class types. + /// + /// A representing the asynchronous unit test. + [Fact] + [WorkItem(3803, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3803")] + public async Task VerifyFileLocalClassExemptionAsync() + { + var testCode = $@"namespace TestNamespace; + +public class TestClass1 {{ }} + +file class TestClass2 {{ }} +"; + + var expectedDiagnostic = this.Diagnostic().WithLocation(0); + + await this.VerifyCSharpDiagnosticAsync( + testCode, + this.GetSettings(), + Array.Empty(), + CancellationToken.None).ConfigureAwait(false); + } + + /// + /// Verifies that SA1402 is not reported for file-local static class types. + /// + /// A representing the asynchronous unit test. + [Fact] + [WorkItem(3803, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3803")] + public async Task VerifyFileLocalStaticClassExemptionAsync() + { + var testCode = $@"namespace TestNamespace; + +public class TestClass1 {{ }} + +file static class TestClass2 {{ }} +"; + + var expectedDiagnostic = this.Diagnostic().WithLocation(0); + + await this.VerifyCSharpDiagnosticAsync( + testCode, + this.GetSettings(), + Array.Empty(), + CancellationToken.None).ConfigureAwait(false); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/MaintainabilityRules/SA1402ForDelegateCSharp11UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/MaintainabilityRules/SA1402ForDelegateCSharp11UnitTests.cs index d832ca32a..64302c7e4 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/MaintainabilityRules/SA1402ForDelegateCSharp11UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/MaintainabilityRules/SA1402ForDelegateCSharp11UnitTests.cs @@ -3,9 +3,37 @@ namespace StyleCop.Analyzers.Test.CSharp11.MaintainabilityRules { + using System; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; using StyleCop.Analyzers.Test.CSharp10.MaintainabilityRules; + using Xunit; public partial class SA1402ForDelegateCSharp11UnitTests : SA1402ForDelegateCSharp10UnitTests { + /// + /// Verifies that SA1402 is not reported for file-local delegate types. + /// + /// A representing the asynchronous unit test. + [Fact] + [WorkItem(3803, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3803")] + public async Task VerifyFileLocalDelegateExemptionAsync() + { + var testCode = $@"namespace TestNamespace; + +public class TestClass {{ }} + +file delegate void TestDelegate(); +"; + + var expectedDiagnostic = Diagnostic().WithLocation(0); + + await VerifyCSharpDiagnosticAsync( + testCode, + this.GetSettings(), + Array.Empty(), + CancellationToken.None).ConfigureAwait(false); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/MaintainabilityRules/SA1402ForEnumCSharp11UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/MaintainabilityRules/SA1402ForEnumCSharp11UnitTests.cs index c8704cd43..132274ec3 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/MaintainabilityRules/SA1402ForEnumCSharp11UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/MaintainabilityRules/SA1402ForEnumCSharp11UnitTests.cs @@ -3,9 +3,37 @@ namespace StyleCop.Analyzers.Test.CSharp11.MaintainabilityRules { + using System; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; using StyleCop.Analyzers.Test.CSharp10.MaintainabilityRules; + using Xunit; public partial class SA1402ForEnumCSharp11UnitTests : SA1402ForEnumCSharp10UnitTests { + /// + /// Verifies that SA1402 is not reported for file-local enum types. + /// + /// A representing the asynchronous unit test. + [Fact] + [WorkItem(3803, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3803")] + public async Task VerifyFileLocalEnumExemptionAsync() + { + var testCode = $@"namespace TestNamespace; + +public class TestClass1 {{ }} + +file enum TestEnum {{ }} +"; + + var expectedDiagnostic = Diagnostic().WithLocation(0); + + await VerifyCSharpDiagnosticAsync( + testCode, + this.GetSettings(), + Array.Empty(), + CancellationToken.None).ConfigureAwait(false); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/MaintainabilityRules/SA1402ForInterfaceCSharp11UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/MaintainabilityRules/SA1402ForInterfaceCSharp11UnitTests.cs index 231b4a135..0a4949a53 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/MaintainabilityRules/SA1402ForInterfaceCSharp11UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/MaintainabilityRules/SA1402ForInterfaceCSharp11UnitTests.cs @@ -3,9 +3,37 @@ namespace StyleCop.Analyzers.Test.CSharp11.MaintainabilityRules { + using System; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; using StyleCop.Analyzers.Test.CSharp10.MaintainabilityRules; + using Xunit; public partial class SA1402ForInterfaceCSharp11UnitTests : SA1402ForInterfaceCSharp10UnitTests { + /// + /// Verifies that SA1402 is not reported for file-local interface types. + /// + /// A representing the asynchronous unit test. + [Fact] + [WorkItem(3803, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3803")] + public async Task VerifyFileLocalInterfaceExemptionAsync() + { + var testCode = $@"namespace TestNamespace; + +public interface TestInterface1 {{ }} + +file interface TestInterface2 {{ }} +"; + + var expectedDiagnostic = this.Diagnostic().WithLocation(0); + + await this.VerifyCSharpDiagnosticAsync( + testCode, + this.GetSettings(), + Array.Empty(), + CancellationToken.None).ConfigureAwait(false); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/MaintainabilityRules/SA1402ForRecordCSharp11UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/MaintainabilityRules/SA1402ForRecordCSharp11UnitTests.cs index dde412102..d267c9ffe 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/MaintainabilityRules/SA1402ForRecordCSharp11UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/MaintainabilityRules/SA1402ForRecordCSharp11UnitTests.cs @@ -3,9 +3,37 @@ namespace StyleCop.Analyzers.Test.CSharp11.MaintainabilityRules { + using System; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; using StyleCop.Analyzers.Test.CSharp10.MaintainabilityRules; + using Xunit; public partial class SA1402ForRecordCSharp11UnitTests : SA1402ForRecordCSharp10UnitTests { + /// + /// Verifies that SA1402 is not reported for file-local record types. + /// + /// A representing the asynchronous unit test. + [Fact] + [WorkItem(3803, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3803")] + public async Task VerifyFileLocalRecordExemptionAsync() + { + var testCode = $@"namespace TestNamespace; + +public record TestRecord1 {{ }} + +file record TestRecord2 {{ }} +"; + + var expectedDiagnostic = this.Diagnostic().WithLocation(0); + + await this.VerifyCSharpDiagnosticAsync( + testCode, + this.GetSettings(), + Array.Empty(), + CancellationToken.None).ConfigureAwait(false); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/MaintainabilityRules/SA1402ForRecordClassCSharp11UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/MaintainabilityRules/SA1402ForRecordClassCSharp11UnitTests.cs index 33b95102b..72684190e 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/MaintainabilityRules/SA1402ForRecordClassCSharp11UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/MaintainabilityRules/SA1402ForRecordClassCSharp11UnitTests.cs @@ -3,9 +3,37 @@ namespace StyleCop.Analyzers.Test.CSharp11.MaintainabilityRules { + using System; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; using StyleCop.Analyzers.Test.CSharp10.MaintainabilityRules; + using Xunit; public partial class SA1402ForRecordClassCSharp11UnitTests : SA1402ForRecordClassCSharp10UnitTests { + /// + /// Verifies that SA1402 is not reported for file-local record class types. + /// + /// A representing the asynchronous unit test. + [Fact] + [WorkItem(3803, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3803")] + public async Task VerifyFileLocalRecordClassExemptionAsync() + { + var testCode = $@"namespace TestNamespace; + +public class TestClass {{ }} + +file record class TestRecordClass {{ }} +"; + + var expectedDiagnostic = this.Diagnostic().WithLocation(0); + + await this.VerifyCSharpDiagnosticAsync( + testCode, + this.GetSettings(), + Array.Empty(), + CancellationToken.None).ConfigureAwait(false); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/MaintainabilityRules/SA1402ForRecordStructCSharp11UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/MaintainabilityRules/SA1402ForRecordStructCSharp11UnitTests.cs index 687501446..149eec957 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/MaintainabilityRules/SA1402ForRecordStructCSharp11UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/MaintainabilityRules/SA1402ForRecordStructCSharp11UnitTests.cs @@ -3,9 +3,37 @@ namespace StyleCop.Analyzers.Test.CSharp11.MaintainabilityRules { + using System; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; using StyleCop.Analyzers.Test.CSharp10.MaintainabilityRules; + using Xunit; public partial class SA1402ForRecordStructCSharp11UnitTests : SA1402ForRecordStructCSharp10UnitTests { + /// + /// Verifies that SA1402 is not reported for file-local record struct types. + /// + /// A representing the asynchronous unit test. + [Fact] + [WorkItem(3803, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3803")] + public async Task VerifyFileLocalRecordStructExemptionAsync() + { + var testCode = $@"namespace TestNamespace; + +public class TestClass {{ }} + +file record struct TestRecordStruct {{ }} +"; + + var expectedDiagnostic = this.Diagnostic().WithLocation(0); + + await this.VerifyCSharpDiagnosticAsync( + testCode, + this.GetSettings(), + Array.Empty(), + CancellationToken.None).ConfigureAwait(false); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/MaintainabilityRules/SA1402ForStructCSharp11UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/MaintainabilityRules/SA1402ForStructCSharp11UnitTests.cs index 44d5b33c3..92a06210d 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/MaintainabilityRules/SA1402ForStructCSharp11UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/MaintainabilityRules/SA1402ForStructCSharp11UnitTests.cs @@ -3,9 +3,37 @@ namespace StyleCop.Analyzers.Test.CSharp11.MaintainabilityRules { + using System; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.Testing; using StyleCop.Analyzers.Test.CSharp10.MaintainabilityRules; + using Xunit; public partial class SA1402ForStructCSharp11UnitTests : SA1402ForStructCSharp10UnitTests { + /// + /// Verifies that SA1402 is not reported for file-local struct types. + /// + /// A representing the asynchronous unit test. + [Fact] + [WorkItem(3803, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3803")] + public async Task VerifyFileLocalStructExemptionAsync() + { + var testCode = $@"namespace TestNamespace; + +public struct TestStruct1 {{ }} + +file struct TestStruct2 {{ }} +"; + + var expectedDiagnostic = this.Diagnostic().WithLocation(0); + + await this.VerifyCSharpDiagnosticAsync( + testCode, + this.GetSettings(), + Array.Empty(), + CancellationToken.None).ConfigureAwait(false); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/MaintainabilityRules/SA1402FileMayOnlyContainASingleType.cs b/StyleCop.Analyzers/StyleCop.Analyzers/MaintainabilityRules/SA1402FileMayOnlyContainASingleType.cs index b9ca8c9e2..a76c56c93 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/MaintainabilityRules/SA1402FileMayOnlyContainASingleType.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/MaintainabilityRules/SA1402FileMayOnlyContainASingleType.cs @@ -99,7 +99,7 @@ private static void HandleSyntaxTree(SyntaxTreeAnalysisContext context, StyleCop private static IEnumerable GetTopLevelTypeDeclarations(SyntaxNode root, StyleCopSettings settings) { var allTypeDeclarations = root.DescendantNodes(descendIntoChildren: node => ContainsTopLevelTypeDeclarations(node)).OfType().ToList(); - var relevantTypeDeclarations = allTypeDeclarations.Where(x => IsRelevantType(x, settings)).ToList(); + var relevantTypeDeclarations = allTypeDeclarations.Where(x => IsRelevantType(x, settings)).Where(x => !IsFileLocalType(x)).ToList(); return relevantTypeDeclarations; } @@ -136,5 +136,19 @@ private static bool IsRelevantType(SyntaxNode node, StyleCopSettings settings) return isRelevant; } + + private static bool IsFileLocalType(SyntaxNode node) + { + const SyntaxKind FileKeyword = (SyntaxKind)8449; + + var modifiers = node switch + { + BaseTypeDeclarationSyntax x => x.Modifiers, + DelegateDeclarationSyntax x => x.Modifiers, + _ => default, + }; + + return modifiers.Any(FileKeyword); + } } } diff --git a/documentation/SA1402.md b/documentation/SA1402.md index 2a09c0ee5..a313b3c99 100644 --- a/documentation/SA1402.md +++ b/documentation/SA1402.md @@ -27,6 +27,8 @@ It is possible to configure which kind of types this rule should affect. By defa It is also possible to place multiple parts of the same partial type within the same file. +File-local types declared using `file` modifier are exempt from this rule. + ## How to fix violations To fix an instance of this violation, move each type into its own file.