Skip to content

Commit 36b11db

Browse files
Updated SA1402 to be able to report other types beside classes.
1 parent 9ab315d commit 36b11db

22 files changed

Lines changed: 754 additions & 205 deletions

StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/MaintainabilityRules/SA1402CodeFixProvider.cs

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ namespace StyleCop.Analyzers.MaintainabilityRules
1616
using StyleCop.Analyzers.Helpers;
1717

1818
/// <summary>
19-
/// Implements a code fix for <see cref="SA1402FileMayOnlyContainASingleClass"/>.
19+
/// Implements a code fix for <see cref="SA1402FileMayOnlyContainASingleType"/>.
2020
/// </summary>
2121
/// <remarks>
2222
/// <para>To fix a violation of this rule, move each class into its own file.</para>
@@ -27,7 +27,7 @@ internal class SA1402CodeFixProvider : CodeFixProvider
2727
{
2828
/// <inheritdoc/>
2929
public override ImmutableArray<string> FixableDiagnosticIds { get; } =
30-
ImmutableArray.Create(SA1402FileMayOnlyContainASingleClass.DiagnosticId);
30+
ImmutableArray.Create(SA1402FileMayOnlyContainASingleType.DiagnosticId);
3131

3232
/// <inheritdoc/>
3333
public override FixAllProvider GetFixAllProvider()
@@ -56,32 +56,14 @@ private static async Task<Solution> GetTransformedSolutionAsync(Document documen
5656
{
5757
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
5858
SyntaxNode node = root.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true);
59-
BaseTypeDeclarationSyntax baseTypeDeclarationSyntax = node as BaseTypeDeclarationSyntax;
60-
DelegateDeclarationSyntax delegateDeclarationSyntax = node as DelegateDeclarationSyntax;
61-
if (baseTypeDeclarationSyntax == null && delegateDeclarationSyntax == null)
59+
var memberDeclarationSyntax = node as MemberDeclarationSyntax;
60+
if (memberDeclarationSyntax == null)
6261
{
6362
return document.Project.Solution;
6463
}
6564

6665
DocumentId extractedDocumentId = DocumentId.CreateNewId(document.Project.Id);
67-
string extractedDocumentName = baseTypeDeclarationSyntax.Identifier.ValueText;
68-
if (baseTypeDeclarationSyntax != null)
69-
{
70-
TypeDeclarationSyntax typeDeclarationSyntax = baseTypeDeclarationSyntax as TypeDeclarationSyntax;
71-
if (typeDeclarationSyntax?.TypeParameterList?.Parameters.Count > 0)
72-
{
73-
extractedDocumentName += "`" + typeDeclarationSyntax.TypeParameterList.Parameters.Count;
74-
}
75-
}
76-
else
77-
{
78-
if (delegateDeclarationSyntax.TypeParameterList?.Parameters.Count > 0)
79-
{
80-
extractedDocumentName += "`" + delegateDeclarationSyntax.TypeParameterList.Parameters.Count;
81-
}
82-
}
83-
84-
extractedDocumentName += ".cs";
66+
var extractedDocumentName = GetExtractedDocumentName(memberDeclarationSyntax) + ".cs";
8567

8668
List<SyntaxNode> nodesToRemoveFromExtracted = new List<SyntaxNode>();
8769
SyntaxNode previous = node;
@@ -127,5 +109,24 @@ private static async Task<Solution> GetTransformedSolutionAsync(Document documen
127109

128110
return updatedSolution;
129111
}
112+
113+
private static string GetExtractedDocumentName(MemberDeclarationSyntax memberDeclarationSyntax)
114+
{
115+
string extractedDocumentName = NamedTypeHelpers.GetNameOrIdentifier(memberDeclarationSyntax);
116+
117+
var delegateDeclarationSyntax = memberDeclarationSyntax as DelegateDeclarationSyntax;
118+
if (delegateDeclarationSyntax?.TypeParameterList?.Parameters.Count > 0)
119+
{
120+
extractedDocumentName += "`" + delegateDeclarationSyntax.TypeParameterList.Parameters.Count;
121+
}
122+
123+
var typeDeclarationSyntax = memberDeclarationSyntax as TypeDeclarationSyntax;
124+
if (typeDeclarationSyntax?.TypeParameterList?.Parameters.Count > 0)
125+
{
126+
extractedDocumentName += "`" + typeDeclarationSyntax.TypeParameterList.Parameters.Count;
127+
}
128+
129+
return extractedDocumentName;
130+
}
130131
}
131132
}

StyleCop.Analyzers/StyleCop.Analyzers.Test/MaintainabilityRules/FileMayOnlyContainTestBase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public abstract class FileMayOnlyContainTestBase : CodeFixVerifier
1313
{
1414
public abstract string Keyword { get; }
1515

16-
public virtual bool SupportsCodeFix => false;
16+
public abstract bool SupportsCodeFix { get; }
1717

1818
[Fact]
1919
public async Task TestOneElementAsync()

StyleCop.Analyzers/StyleCop.Analyzers.Test/MaintainabilityRules/SA1402UnitTests.cs renamed to StyleCop.Analyzers/StyleCop.Analyzers.Test/MaintainabilityRules/SA1402ForBlockDeclarationUnitTestsBase.cs

Lines changed: 84 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -4,115 +4,149 @@
44
namespace StyleCop.Analyzers.Test.MaintainabilityRules
55
{
66
using System.Collections.Generic;
7+
using System.Linq;
78
using System.Threading;
89
using System.Threading.Tasks;
10+
using Analyzers.MaintainabilityRules;
911
using Microsoft.CodeAnalysis.CodeFixes;
1012
using Microsoft.CodeAnalysis.Diagnostics;
11-
using StyleCop.Analyzers.MaintainabilityRules;
1213
using TestHelper;
1314
using Xunit;
1415

15-
public class SA1402UnitTests : FileMayOnlyContainTestBase
16+
public abstract class SA1402ForBlockDeclarationUnitTestsBase : FileMayOnlyContainTestBase
1617
{
17-
public override string Keyword
18-
{
19-
get
20-
{
21-
return "class";
22-
}
23-
}
24-
2518
public override bool SupportsCodeFix => true;
2619

20+
private bool ConfigureAsNonTopLevelType { get; set; } = false;
21+
2722
[Fact]
28-
public async Task TestPartialClassesAsync()
23+
public async Task TestTwoElementsWithRuleDisabledAsync()
2924
{
30-
var testCode = @"public partial class Foo
25+
this.ConfigureAsNonTopLevelType = true;
26+
27+
var testCode = @"%1 Foo
3128
{
3229
}
33-
public partial class Foo
30+
%1 Bar
3431
{
35-
3632
}";
3733

34+
testCode = testCode.Replace("%1", this.Keyword);
35+
3836
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
3937
}
4038

4139
[Fact]
42-
public async Task TestDifferentPartialClassesAsync()
40+
public async Task TestPartialTypesAsync()
4341
{
44-
var testCode = @"public partial class Foo
45-
{
46-
}
47-
public partial class Bar
48-
{
42+
var testCode = $@"public partial {this.Keyword} Foo
43+
{{
44+
}}
45+
public partial {this.Keyword} Foo
46+
{{
4947
50-
}";
48+
}}";
49+
50+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
51+
}
52+
53+
[Fact]
54+
public async Task TestDifferentPartialTypesAsync()
55+
{
56+
var testCode = $@"public partial {this.Keyword} Foo
57+
{{
58+
}}
59+
public partial {this.Keyword} Bar
60+
{{
61+
62+
}}";
5163

5264
var fixedCode = new[]
5365
{
54-
@"public partial class Foo
55-
{
56-
}
66+
$@"public partial {this.Keyword} Foo
67+
{{
68+
}}
5769
",
58-
@"public partial class Bar
59-
{
70+
$@"public partial {this.Keyword} Bar
71+
{{
6072
61-
}"
73+
}}"
6274
};
6375

64-
DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(4, 22);
76+
DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(4, 17 + this.Keyword.Length);
6577

6678
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
6779
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
6880
await this.VerifyCSharpFixAsync(new[] { testCode }, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
6981
}
7082

7183
[Fact]
72-
public async Task TestPreferFilenameClassAsync()
84+
public async Task TestPreferFilenameTypeAsync()
7385
{
74-
var testCode = @"public class Foo
75-
{
76-
}
77-
public class Test0
78-
{
79-
}";
86+
var testCode = $@"public {this.Keyword} Foo
87+
{{
88+
}}
89+
public {this.Keyword} Test0
90+
{{
91+
}}";
8092

8193
var fixedCode = new[]
8294
{
83-
@"public class Test0
84-
{
85-
}",
86-
@"public class Foo
87-
{
88-
}
95+
$@"public {this.Keyword} Test0
96+
{{
97+
}}",
98+
$@"public {this.Keyword} Foo
99+
{{
100+
}}
89101
"
90102
};
91103

92-
DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(1, 14);
104+
DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(1, 9 + this.Keyword.Length);
93105

94106
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
95107
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
96108
await this.VerifyCSharpFixAsync(new[] { testCode }, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
97109
}
98110

99111
[Fact]
100-
public async Task TestNestedClassesAsync()
112+
public async Task TestNestedTypesAsync()
101113
{
102-
var testCode = @"public class Foo
103-
{
104-
public class Bar
105-
{
114+
var testCode = $@"public class Foo
115+
{{
116+
public {this.Keyword} Bar
117+
{{
106118
107-
}
108-
}";
119+
}}
120+
}}";
109121

110122
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
111123
}
112124

125+
protected override string GetSettings()
126+
{
127+
var keywords = new List<string> { "class", "interface", "struct", "enum", "delegate" };
128+
if (this.ConfigureAsNonTopLevelType)
129+
{
130+
keywords.Remove(this.Keyword);
131+
}
132+
133+
var keywordsStr = string.Join(", ", keywords.Select(x => "\"" + x + "\""));
134+
135+
var settings = $@"
136+
{{
137+
""settings"": {{
138+
""maintainabilityRules"": {{
139+
""topLevelTypes"": [{keywordsStr}]
140+
}}
141+
}}
142+
}}";
143+
144+
return settings;
145+
}
146+
113147
protected override IEnumerable<DiagnosticAnalyzer> GetCSharpDiagnosticAnalyzers()
114148
{
115-
yield return new SA1402FileMayOnlyContainASingleClass();
149+
yield return new SA1402FileMayOnlyContainASingleType();
116150
}
117151

118152
protected override CodeFixProvider GetCSharpCodeFixProvider()
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
2+
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
3+
4+
namespace StyleCop.Analyzers.Test.MaintainabilityRules
5+
{
6+
public class SA1402ForClassUnitTests : SA1402ForBlockDeclarationUnitTestsBase
7+
{
8+
public override string Keyword => "class";
9+
}
10+
}

0 commit comments

Comments
 (0)