Skip to content

Commit 6661c25

Browse files
authored
Merge pull request #2207 from bjornhellander/Issue1756_RemoveExclusionOfHelperTypesInSa1402
Remove exclusion of helper types in SA1402
2 parents 1c9a915 + 7c42f4a commit 6661c25

25 files changed

Lines changed: 967 additions & 279 deletions

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

Lines changed: 10 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,18 @@ 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>
22-
/// <para>To fix a violation of this rule, move each class into its own file.</para>
22+
/// <para>To fix a violation of this rule, move each type into its own file.</para>
2323
/// </remarks>
2424
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(SA1402CodeFixProvider))]
2525
[Shared]
2626
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,17 @@ 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+
string suffix;
67+
FileNameHelpers.GetFileNameAndSuffix(document.Name, out suffix);
68+
var settings = document.Project.AnalyzerOptions.GetStyleCopSettings(cancellationToken);
69+
string extractedDocumentName = FileNameHelpers.GetConventionalFileName(memberDeclarationSyntax, settings.DocumentationRules.FileNamingConvention) + suffix;
8570

8671
List<SyntaxNode> nodesToRemoveFromExtracted = new List<SyntaxNode>();
8772
SyntaxNode previous = node;
@@ -111,8 +96,8 @@ private static async Task<Solution> GetTransformedSolutionAsync(Document documen
11196
}
11297
}
11398

99+
// Add the new file
114100
SyntaxNode extractedDocumentNode = root.RemoveNodes(nodesToRemoveFromExtracted, SyntaxRemoveOptions.KeepUnbalancedDirectives);
115-
116101
Solution updatedSolution = document.Project.Solution.AddDocument(extractedDocumentId, extractedDocumentName, extractedDocumentNode, document.Folders);
117102

118103
// Make sure to also add the file to linked projects

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()
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
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+
using System.Collections.Generic;
7+
using System.Linq;
8+
using System.Threading;
9+
using System.Threading.Tasks;
10+
using Analyzers.MaintainabilityRules;
11+
using Microsoft.CodeAnalysis.CodeFixes;
12+
using Microsoft.CodeAnalysis.Diagnostics;
13+
using TestHelper;
14+
using Xunit;
15+
16+
public abstract class SA1402ForBlockDeclarationUnitTestsBase : FileMayOnlyContainTestBase
17+
{
18+
public override bool SupportsCodeFix => true;
19+
20+
protected SA1402SettingsConfiguration SettingsConfiguration { get; set; } = SA1402SettingsConfiguration.ConfigureAsTopLevelType;
21+
22+
protected abstract bool IsConfiguredAsTopLevelTypeByDefault { get; }
23+
24+
[Fact]
25+
public async Task TestTwoGenericElementsAsync()
26+
{
27+
var testCode = @"%1 Foo<T1>
28+
{
29+
}
30+
%1 Bar<T2, T3>
31+
{
32+
}";
33+
34+
var fixedCode = new[]
35+
{
36+
@"%1 Foo<T1>
37+
{
38+
}
39+
",
40+
@"%1 Bar<T2, T3>
41+
{
42+
}"
43+
};
44+
45+
testCode = testCode.Replace("%1", this.Keyword);
46+
fixedCode = fixedCode.Select(c => c.Replace("%1", this.Keyword)).ToArray();
47+
48+
DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(4, this.Keyword.Length + 2);
49+
50+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
51+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
52+
await this.VerifyCSharpFixAsync(new[] { testCode }, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
53+
}
54+
55+
[Fact]
56+
public async Task TestTwoElementsWithRuleDisabledAsync()
57+
{
58+
this.SettingsConfiguration = SA1402SettingsConfiguration.ConfigureAsNonTopLevelType;
59+
60+
var testCode = @"%1 Foo
61+
{
62+
}
63+
%1 Bar
64+
{
65+
}";
66+
67+
testCode = testCode.Replace("%1", this.Keyword);
68+
69+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
70+
}
71+
72+
[Fact]
73+
public async Task TestTwoElementsWithDefaultRuleConfigurationAsync()
74+
{
75+
this.SettingsConfiguration = SA1402SettingsConfiguration.KeepDefaultConfiguration;
76+
77+
var testCode = @"%1 Foo
78+
{
79+
}
80+
%1 Bar
81+
{
82+
}";
83+
84+
var fixedCode = new[]
85+
{
86+
@"%1 Foo
87+
{
88+
}
89+
",
90+
@"%1 Bar
91+
{
92+
}"
93+
};
94+
95+
testCode = testCode.Replace("%1", this.Keyword);
96+
fixedCode = fixedCode.Select(c => c.Replace("%1", this.Keyword)).ToArray();
97+
98+
if (this.IsConfiguredAsTopLevelTypeByDefault)
99+
{
100+
DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(4, this.Keyword.Length + 2);
101+
102+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
103+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
104+
await this.VerifyCSharpFixAsync(new[] { testCode }, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
105+
}
106+
else
107+
{
108+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
109+
}
110+
}
111+
112+
[Fact]
113+
public async Task TestPartialTypesAsync()
114+
{
115+
var testCode = $@"public partial {this.Keyword} Foo
116+
{{
117+
}}
118+
public partial {this.Keyword} Foo
119+
{{
120+
121+
}}";
122+
123+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
124+
}
125+
126+
[Fact]
127+
public async Task TestDifferentPartialTypesAsync()
128+
{
129+
var testCode = $@"public partial {this.Keyword} Foo
130+
{{
131+
}}
132+
public partial {this.Keyword} Bar
133+
{{
134+
135+
}}";
136+
137+
var fixedCode = new[]
138+
{
139+
$@"public partial {this.Keyword} Foo
140+
{{
141+
}}
142+
",
143+
$@"public partial {this.Keyword} Bar
144+
{{
145+
146+
}}"
147+
};
148+
149+
DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(4, 17 + this.Keyword.Length);
150+
151+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
152+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
153+
await this.VerifyCSharpFixAsync(new[] { testCode }, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
154+
}
155+
156+
[Fact]
157+
public async Task TestPreferFilenameTypeAsync()
158+
{
159+
var testCode = $@"public {this.Keyword} Foo
160+
{{
161+
}}
162+
public {this.Keyword} Test0
163+
{{
164+
}}";
165+
166+
var fixedCode = new[]
167+
{
168+
$@"public {this.Keyword} Test0
169+
{{
170+
}}",
171+
$@"public {this.Keyword} Foo
172+
{{
173+
}}
174+
"
175+
};
176+
177+
DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(1, 9 + this.Keyword.Length);
178+
179+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
180+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
181+
await this.VerifyCSharpFixAsync(new[] { testCode }, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
182+
}
183+
184+
[Fact]
185+
public async Task TestNestedTypesAsync()
186+
{
187+
var testCode = $@"public class Foo
188+
{{
189+
public {this.Keyword} Bar
190+
{{
191+
192+
}}
193+
}}";
194+
195+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
196+
}
197+
198+
protected override string GetSettings()
199+
{
200+
return this.SettingsConfiguration.GetSettings(this.Keyword);
201+
}
202+
203+
protected override IEnumerable<DiagnosticAnalyzer> GetCSharpDiagnosticAnalyzers()
204+
{
205+
yield return new SA1402FileMayOnlyContainASingleType();
206+
}
207+
208+
protected override CodeFixProvider GetCSharpCodeFixProvider()
209+
{
210+
return new SA1402CodeFixProvider();
211+
}
212+
}
213+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
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+
protected override bool IsConfiguredAsTopLevelTypeByDefault => true;
11+
}
12+
}

0 commit comments

Comments
 (0)