Skip to content

Commit 06ec4fb

Browse files
committed
Exempt file-local types from SA1402 [resolves #3803]
1 parent ff5c432 commit 06ec4fb

10 files changed

+265
-1
lines changed

StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/MaintainabilityRules/SA1402ForClassCSharp11UnitTests.cs

+52
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,61 @@
33

44
namespace StyleCop.Analyzers.Test.CSharp11.MaintainabilityRules
55
{
6+
using System;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
using Microsoft.CodeAnalysis.Testing;
610
using StyleCop.Analyzers.Test.CSharp10.MaintainabilityRules;
11+
using Xunit;
712

813
public partial class SA1402ForClassCSharp11UnitTests : SA1402ForClassCSharp10UnitTests
914
{
15+
/// <summary>
16+
/// Verifies that SA1402 is not reported for file-local class types.
17+
/// </summary>
18+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
19+
[Fact]
20+
[WorkItem(3803, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3803")]
21+
public async Task VerifyFileLocalClassExemptionAsync()
22+
{
23+
var testCode = $@"namespace TestNamespace;
24+
25+
public class TestClass1 {{ }}
26+
27+
file class TestClass2 {{ }}
28+
";
29+
30+
var expectedDiagnostic = this.Diagnostic().WithLocation(0);
31+
32+
await this.VerifyCSharpDiagnosticAsync(
33+
testCode,
34+
this.GetSettings(),
35+
Array.Empty<DiagnosticResult>(),
36+
CancellationToken.None).ConfigureAwait(false);
37+
}
38+
39+
/// <summary>
40+
/// Verifies that SA1402 is not reported for file-local static class types.
41+
/// </summary>
42+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
43+
[Fact]
44+
[WorkItem(3803, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3803")]
45+
public async Task VerifyFileLocalStaticClassExemptionAsync()
46+
{
47+
var testCode = $@"namespace TestNamespace;
48+
49+
public class TestClass1 {{ }}
50+
51+
file static class TestClass2 {{ }}
52+
";
53+
54+
var expectedDiagnostic = this.Diagnostic().WithLocation(0);
55+
56+
await this.VerifyCSharpDiagnosticAsync(
57+
testCode,
58+
this.GetSettings(),
59+
Array.Empty<DiagnosticResult>(),
60+
CancellationToken.None).ConfigureAwait(false);
61+
}
1062
}
1163
}

StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/MaintainabilityRules/SA1402ForDelegateCSharp11UnitTests.cs

+28
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,37 @@
33

44
namespace StyleCop.Analyzers.Test.CSharp11.MaintainabilityRules
55
{
6+
using System;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
using Microsoft.CodeAnalysis.Testing;
610
using StyleCop.Analyzers.Test.CSharp10.MaintainabilityRules;
11+
using Xunit;
712

813
public partial class SA1402ForDelegateCSharp11UnitTests : SA1402ForDelegateCSharp10UnitTests
914
{
15+
/// <summary>
16+
/// Verifies that SA1402 is not reported for file-local delegate types.
17+
/// </summary>
18+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
19+
[Fact]
20+
[WorkItem(3803, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3803")]
21+
public async Task VerifyFileLocalDelegateExemptionAsync()
22+
{
23+
var testCode = $@"namespace TestNamespace;
24+
25+
public class TestClass {{ }}
26+
27+
file delegate void TestDelegate();
28+
";
29+
30+
var expectedDiagnostic = Diagnostic().WithLocation(0);
31+
32+
await VerifyCSharpDiagnosticAsync(
33+
testCode,
34+
this.GetSettings(),
35+
Array.Empty<DiagnosticResult>(),
36+
CancellationToken.None).ConfigureAwait(false);
37+
}
1038
}
1139
}

StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/MaintainabilityRules/SA1402ForEnumCSharp11UnitTests.cs

+28
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,37 @@
33

44
namespace StyleCop.Analyzers.Test.CSharp11.MaintainabilityRules
55
{
6+
using System;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
using Microsoft.CodeAnalysis.Testing;
610
using StyleCop.Analyzers.Test.CSharp10.MaintainabilityRules;
11+
using Xunit;
712

813
public partial class SA1402ForEnumCSharp11UnitTests : SA1402ForEnumCSharp10UnitTests
914
{
15+
/// <summary>
16+
/// Verifies that SA1402 is not reported for file-local enum types.
17+
/// </summary>
18+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
19+
[Fact]
20+
[WorkItem(3803, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3803")]
21+
public async Task VerifyFileLocalEnumExemptionAsync()
22+
{
23+
var testCode = $@"namespace TestNamespace;
24+
25+
public class TestClass1 {{ }}
26+
27+
file enum TestEnum {{ }}
28+
";
29+
30+
var expectedDiagnostic = Diagnostic().WithLocation(0);
31+
32+
await VerifyCSharpDiagnosticAsync(
33+
testCode,
34+
this.GetSettings(),
35+
Array.Empty<DiagnosticResult>(),
36+
CancellationToken.None).ConfigureAwait(false);
37+
}
1038
}
1139
}

StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/MaintainabilityRules/SA1402ForInterfaceCSharp11UnitTests.cs

+28
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,37 @@
33

44
namespace StyleCop.Analyzers.Test.CSharp11.MaintainabilityRules
55
{
6+
using System;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
using Microsoft.CodeAnalysis.Testing;
610
using StyleCop.Analyzers.Test.CSharp10.MaintainabilityRules;
11+
using Xunit;
712

813
public partial class SA1402ForInterfaceCSharp11UnitTests : SA1402ForInterfaceCSharp10UnitTests
914
{
15+
/// <summary>
16+
/// Verifies that SA1402 is not reported for file-local interface types.
17+
/// </summary>
18+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
19+
[Fact]
20+
[WorkItem(3803, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3803")]
21+
public async Task VerifyFileLocalInterfaceExemptionAsync()
22+
{
23+
var testCode = $@"namespace TestNamespace;
24+
25+
public interface TestInterface1 {{ }}
26+
27+
file interface TestInterface2 {{ }}
28+
";
29+
30+
var expectedDiagnostic = this.Diagnostic().WithLocation(0);
31+
32+
await this.VerifyCSharpDiagnosticAsync(
33+
testCode,
34+
this.GetSettings(),
35+
Array.Empty<DiagnosticResult>(),
36+
CancellationToken.None).ConfigureAwait(false);
37+
}
1038
}
1139
}

StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/MaintainabilityRules/SA1402ForRecordCSharp11UnitTests.cs

+28
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,37 @@
33

44
namespace StyleCop.Analyzers.Test.CSharp11.MaintainabilityRules
55
{
6+
using System;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
using Microsoft.CodeAnalysis.Testing;
610
using StyleCop.Analyzers.Test.CSharp10.MaintainabilityRules;
11+
using Xunit;
712

813
public partial class SA1402ForRecordCSharp11UnitTests : SA1402ForRecordCSharp10UnitTests
914
{
15+
/// <summary>
16+
/// Verifies that SA1402 is not reported for file-local record types.
17+
/// </summary>
18+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
19+
[Fact]
20+
[WorkItem(3803, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3803")]
21+
public async Task VerifyFileLocalRecordExemptionAsync()
22+
{
23+
var testCode = $@"namespace TestNamespace;
24+
25+
public record TestRecord1 {{ }}
26+
27+
file record TestRecord2 {{ }}
28+
";
29+
30+
var expectedDiagnostic = this.Diagnostic().WithLocation(0);
31+
32+
await this.VerifyCSharpDiagnosticAsync(
33+
testCode,
34+
this.GetSettings(),
35+
Array.Empty<DiagnosticResult>(),
36+
CancellationToken.None).ConfigureAwait(false);
37+
}
1038
}
1139
}

StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/MaintainabilityRules/SA1402ForRecordClassCSharp11UnitTests.cs

+28
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,37 @@
33

44
namespace StyleCop.Analyzers.Test.CSharp11.MaintainabilityRules
55
{
6+
using System;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
using Microsoft.CodeAnalysis.Testing;
610
using StyleCop.Analyzers.Test.CSharp10.MaintainabilityRules;
11+
using Xunit;
712

813
public partial class SA1402ForRecordClassCSharp11UnitTests : SA1402ForRecordClassCSharp10UnitTests
914
{
15+
/// <summary>
16+
/// Verifies that SA1402 is not reported for file-local record class types.
17+
/// </summary>
18+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
19+
[Fact]
20+
[WorkItem(3803, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3803")]
21+
public async Task VerifyFileLocalRecordClassExemptionAsync()
22+
{
23+
var testCode = $@"namespace TestNamespace;
24+
25+
public class TestClass {{ }}
26+
27+
file record class TestRecordClass {{ }}
28+
";
29+
30+
var expectedDiagnostic = this.Diagnostic().WithLocation(0);
31+
32+
await this.VerifyCSharpDiagnosticAsync(
33+
testCode,
34+
this.GetSettings(),
35+
Array.Empty<DiagnosticResult>(),
36+
CancellationToken.None).ConfigureAwait(false);
37+
}
1038
}
1139
}

StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/MaintainabilityRules/SA1402ForRecordStructCSharp11UnitTests.cs

+28
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,37 @@
33

44
namespace StyleCop.Analyzers.Test.CSharp11.MaintainabilityRules
55
{
6+
using System;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
using Microsoft.CodeAnalysis.Testing;
610
using StyleCop.Analyzers.Test.CSharp10.MaintainabilityRules;
11+
using Xunit;
712

813
public partial class SA1402ForRecordStructCSharp11UnitTests : SA1402ForRecordStructCSharp10UnitTests
914
{
15+
/// <summary>
16+
/// Verifies that SA1402 is not reported for file-local record struct types.
17+
/// </summary>
18+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
19+
[Fact]
20+
[WorkItem(3803, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3803")]
21+
public async Task VerifyFileLocalRecordStructExemptionAsync()
22+
{
23+
var testCode = $@"namespace TestNamespace;
24+
25+
public class TestClass {{ }}
26+
27+
file record struct TestRecordStruct {{ }}
28+
";
29+
30+
var expectedDiagnostic = this.Diagnostic().WithLocation(0);
31+
32+
await this.VerifyCSharpDiagnosticAsync(
33+
testCode,
34+
this.GetSettings(),
35+
Array.Empty<DiagnosticResult>(),
36+
CancellationToken.None).ConfigureAwait(false);
37+
}
1038
}
1139
}

StyleCop.Analyzers/StyleCop.Analyzers.Test.CSharp11/MaintainabilityRules/SA1402ForStructCSharp11UnitTests.cs

+28
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,37 @@
33

44
namespace StyleCop.Analyzers.Test.CSharp11.MaintainabilityRules
55
{
6+
using System;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
using Microsoft.CodeAnalysis.Testing;
610
using StyleCop.Analyzers.Test.CSharp10.MaintainabilityRules;
11+
using Xunit;
712

813
public partial class SA1402ForStructCSharp11UnitTests : SA1402ForStructCSharp10UnitTests
914
{
15+
/// <summary>
16+
/// Verifies that SA1402 is not reported for file-local struct types.
17+
/// </summary>
18+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
19+
[Fact]
20+
[WorkItem(3803, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3803")]
21+
public async Task VerifyFileLocalStructExemptionAsync()
22+
{
23+
var testCode = $@"namespace TestNamespace;
24+
25+
public struct TestStruct1 {{ }}
26+
27+
file struct TestStruct2 {{ }}
28+
";
29+
30+
var expectedDiagnostic = this.Diagnostic().WithLocation(0);
31+
32+
await this.VerifyCSharpDiagnosticAsync(
33+
testCode,
34+
this.GetSettings(),
35+
Array.Empty<DiagnosticResult>(),
36+
CancellationToken.None).ConfigureAwait(false);
37+
}
1038
}
1139
}

StyleCop.Analyzers/StyleCop.Analyzers/MaintainabilityRules/SA1402FileMayOnlyContainASingleType.cs

+15-1
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ private static void HandleSyntaxTree(SyntaxTreeAnalysisContext context, StyleCop
9999
private static IEnumerable<MemberDeclarationSyntax> GetTopLevelTypeDeclarations(SyntaxNode root, StyleCopSettings settings)
100100
{
101101
var allTypeDeclarations = root.DescendantNodes(descendIntoChildren: node => ContainsTopLevelTypeDeclarations(node)).OfType<MemberDeclarationSyntax>().ToList();
102-
var relevantTypeDeclarations = allTypeDeclarations.Where(x => IsRelevantType(x, settings)).ToList();
102+
var relevantTypeDeclarations = allTypeDeclarations.Where(x => IsRelevantType(x, settings)).Where(x => !IsFileLocalType(x)).ToList();
103103
return relevantTypeDeclarations;
104104
}
105105

@@ -136,5 +136,19 @@ private static bool IsRelevantType(SyntaxNode node, StyleCopSettings settings)
136136

137137
return isRelevant;
138138
}
139+
140+
private static bool IsFileLocalType(SyntaxNode node)
141+
{
142+
const SyntaxKind FileKeyword = (SyntaxKind)8449;
143+
144+
var modifiers = node switch
145+
{
146+
BaseTypeDeclarationSyntax x => x.Modifiers,
147+
DelegateDeclarationSyntax x => x.Modifiers,
148+
_ => default,
149+
};
150+
151+
return modifiers.Any(FileKeyword);
152+
}
139153
}
140154
}

documentation/SA1402.md

+2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ It is possible to configure which kind of types this rule should affect. By defa
2727

2828
It is also possible to place multiple parts of the same partial type within the same file.
2929

30+
File-local types declared using `file` modifier are exempt from this rule.
31+
3032
## How to fix violations
3133

3234
To fix an instance of this violation, move each type into its own file.

0 commit comments

Comments
 (0)