Skip to content

Commit 4da56fd

Browse files
authored
Merge pull request #1925 from nbarbettini/fix-739
Implement SA1314 (TypeParameterNamesMustBeginWithT)
2 parents b934782 + 1f72cb7 commit 4da56fd

12 files changed

Lines changed: 511 additions & 0 deletions

File tree

StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/Helpers/RenameHelper.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,20 @@ public static async Task<bool> IsValidNewMemberNameAsync(SemanticModel semanticM
5252

5353
var containingSymbol = symbol.ContainingSymbol;
5454

55+
if (symbol.Kind == SymbolKind.TypeParameter)
56+
{
57+
// If the symbol is a type parameter, the name can't be the same as any type parameters of the containing type.
58+
var parentSymbol = containingSymbol?.ContainingSymbol as INamedTypeSymbol;
59+
if (parentSymbol != null
60+
&& parentSymbol.TypeParameters.Any(t => t.Name == name))
61+
{
62+
return false;
63+
}
64+
65+
// Move up one level for the next validation step.
66+
containingSymbol = containingSymbol?.ContainingSymbol;
67+
}
68+
5569
var containingNamespaceOrTypeSymbol = containingSymbol as INamespaceOrTypeSymbol;
5670
if (containingNamespaceOrTypeSymbol != null)
5771
{
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
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.NamingRules
5+
{
6+
using System.Collections.Immutable;
7+
using System.Composition;
8+
using System.Threading;
9+
using System.Threading.Tasks;
10+
using Helpers;
11+
using Microsoft.CodeAnalysis;
12+
using Microsoft.CodeAnalysis.CodeActions;
13+
using Microsoft.CodeAnalysis.CodeFixes;
14+
15+
/// <summary>
16+
/// Implements a code fix for <see cref="SA1314TypeParameterNamesMustBeginWithT"/>.
17+
/// </summary>
18+
/// <remarks>
19+
/// <para>To fix a violation of this rule, add the capital letter T to the front of the type parameter name.</para>
20+
/// </remarks>
21+
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(SA1314CodeFixProvider))]
22+
[Shared]
23+
internal class SA1314CodeFixProvider : CodeFixProvider
24+
{
25+
/// <inheritdoc/>
26+
public override ImmutableArray<string> FixableDiagnosticIds { get; } =
27+
ImmutableArray.Create(SA1314TypeParameterNamesMustBeginWithT.DiagnosticId);
28+
29+
/// <inheritdoc/>
30+
public override Task RegisterCodeFixesAsync(CodeFixContext context)
31+
{
32+
foreach (var diagnostic in context.Diagnostics)
33+
{
34+
context.RegisterCodeFix(
35+
CodeAction.Create(
36+
NamingResources.SA1314CodeFix,
37+
cancellationToken => CreateChangedSolutionAsync(context.Document, diagnostic, cancellationToken),
38+
nameof(SA1314CodeFixProvider)),
39+
diagnostic);
40+
}
41+
42+
return SpecializedTasks.CompletedTask;
43+
}
44+
45+
private static async Task<Solution> CreateChangedSolutionAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken)
46+
{
47+
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
48+
var token = root.FindToken(diagnostic.Location.SourceSpan.Start);
49+
var baseName = "T" + token.ValueText;
50+
var index = 0;
51+
var newName = baseName;
52+
53+
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
54+
var declaredSymbol = semanticModel.GetDeclaredSymbol(token.Parent, cancellationToken);
55+
while (!await RenameHelper.IsValidNewMemberNameAsync(semanticModel, declaredSymbol, newName, cancellationToken).ConfigureAwait(false))
56+
{
57+
index++;
58+
newName = baseName + index;
59+
}
60+
61+
return await RenameHelper.RenameSymbolAsync(document, root, token, newName, cancellationToken).ConfigureAwait(false);
62+
}
63+
}
64+
}

StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/StyleCop.Analyzers.CodeFixes.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@
102102
<Compile Include="NamingRules\SA1308CodeFixProvider.cs" />
103103
<Compile Include="NamingRules\SA1309CodeFixProvider.cs" />
104104
<Compile Include="NamingRules\SA1310CodeFixProvider.cs" />
105+
<Compile Include="NamingRules\SA1314CodeFixProvider.cs" />
105106
<Compile Include="NamingRules\SX1309CodeFixProvider.cs" />
106107
<Compile Include="OrderingRules\ElementOrderCodeFixProvider.cs" />
107108
<Compile Include="OrderingRules\SA1205CodeFixProvider.cs" />
Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
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.NamingRules
5+
{
6+
using System.Collections.Generic;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
using Microsoft.CodeAnalysis.CodeFixes;
10+
using Microsoft.CodeAnalysis.Diagnostics;
11+
using StyleCop.Analyzers.NamingRules;
12+
using TestHelper;
13+
using Xunit;
14+
15+
public class SA1314UnitTests : CodeFixVerifier
16+
{
17+
[Fact]
18+
public async Task TestTypeParameterDoesNotStartWithTAsync()
19+
{
20+
var testCode = @"
21+
public interface IFoo<Key>
22+
{
23+
}";
24+
25+
DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(2, 23);
26+
27+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
28+
29+
var fixedCode = @"
30+
public interface IFoo<TKey>
31+
{
32+
}";
33+
34+
await this.VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false);
35+
}
36+
37+
[Fact]
38+
public async Task TestTypeParameterDoesNotStartWithTPlusParameterUsedAsync()
39+
{
40+
var testCode = @"
41+
public class Foo<Key>
42+
{
43+
void Test()
44+
{
45+
var key = typeof(Key);
46+
}
47+
}
48+
";
49+
50+
DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(2, 18);
51+
52+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
53+
54+
var fixedCode = @"
55+
public class Foo<TKey>
56+
{
57+
void Test()
58+
{
59+
var key = typeof(TKey);
60+
}
61+
}
62+
";
63+
64+
await this.VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false);
65+
}
66+
67+
[Fact]
68+
public async Task TestTypeParameterStartsWithLowerTAsync()
69+
{
70+
var testCode = @"
71+
public interface IFoo<tKey>
72+
{
73+
}";
74+
75+
DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(2, 23);
76+
77+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
78+
79+
var fixedCode = @"
80+
public interface IFoo<TtKey>
81+
{
82+
}";
83+
84+
await this.VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false);
85+
}
86+
87+
[Fact]
88+
public async Task TestInnerTypeParameterDoesNotStartWithTAsync()
89+
{
90+
var testCode = @"
91+
public class Bar
92+
{
93+
public class Foo<Key>
94+
{
95+
}
96+
}";
97+
98+
DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(4, 22);
99+
100+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
101+
102+
var fixedCode = @"
103+
public class Bar
104+
{
105+
public class Foo<TKey>
106+
{
107+
}
108+
}";
109+
110+
await this.VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false);
111+
}
112+
113+
[Fact]
114+
public async Task TestTypeParameterDoesStartWithTAsync()
115+
{
116+
var testCode = @"public interface IFoo<TKey>
117+
{
118+
}";
119+
120+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
121+
}
122+
123+
[Fact]
124+
public async Task TestInnerTypeParameterDoesStartWithTAsync()
125+
{
126+
var testCode = @"
127+
public class Bar
128+
{
129+
public class Foo<TKey>
130+
{
131+
}
132+
}";
133+
134+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
135+
}
136+
137+
[Fact]
138+
public async Task TestTypeParameterDoesNotStartWithTWithMemberMatchingTargetTypeAsync()
139+
{
140+
string testCode = @"
141+
public class Foo<Key>
142+
{
143+
Key Bar { get; }
144+
}";
145+
146+
string fixedCode = @"
147+
public class Foo<TKey>
148+
{
149+
TKey Bar { get; }
150+
}";
151+
152+
DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(2, 18);
153+
154+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
155+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
156+
await this.VerifyCSharpFixAsync(testCode, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
157+
}
158+
159+
[Fact]
160+
public async Task TestNestedTypeParameterDoesNotStartWithTWithConflictAsync()
161+
{
162+
string testCode = @"
163+
public class Outer<TKey>
164+
{
165+
public class Foo<Key>
166+
{
167+
}
168+
}";
169+
string fixedCode = @"
170+
public class Outer<TKey>
171+
{
172+
public class Foo<TKey1>
173+
{
174+
}
175+
}";
176+
177+
DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(4, 22);
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(testCode, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
182+
}
183+
184+
[Fact]
185+
public async Task TestNestedTypeParameterDoesNotStartWithTWithMemberConflictAsync()
186+
{
187+
string testCode = @"
188+
public class Outer<TKey>
189+
{
190+
public class Foo<Key>
191+
{
192+
Key Bar { get; }
193+
}
194+
}";
195+
string fixedCode = @"
196+
public class Outer<TKey>
197+
{
198+
public class Foo<TKey1>
199+
{
200+
TKey1 Bar { get; }
201+
}
202+
}";
203+
204+
DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(4, 22);
205+
206+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
207+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
208+
await this.VerifyCSharpFixAsync(testCode, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
209+
}
210+
211+
[Fact]
212+
public async Task TestTypeParameterDoesNotStartWithTAndTypeConflictAsync()
213+
{
214+
string testCode = @"
215+
public class TFoo
216+
{
217+
}
218+
219+
public class Bar<Foo>
220+
{
221+
}";
222+
string fixedCode = @"
223+
public class TFoo
224+
{
225+
}
226+
227+
public class Bar<TFoo1>
228+
{
229+
}";
230+
231+
DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(6, 18);
232+
233+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
234+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
235+
await this.VerifyCSharpFixAsync(testCode, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
236+
}
237+
238+
[Fact]
239+
public async Task TestTypeParameterInMethodSignatureDoesNotStartWithTAsync()
240+
{
241+
var testCode = @"
242+
public class Foo
243+
{
244+
public void Bar<Baz>()
245+
{
246+
}
247+
}";
248+
249+
DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(4, 21);
250+
251+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
252+
253+
var fixedCode = @"
254+
public class Foo
255+
{
256+
public void Bar<TBaz>()
257+
{
258+
}
259+
}";
260+
261+
await this.VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false);
262+
}
263+
264+
protected override IEnumerable<DiagnosticAnalyzer> GetCSharpDiagnosticAnalyzers()
265+
{
266+
yield return new SA1314TypeParameterNamesMustBeginWithT();
267+
}
268+
269+
protected override CodeFixProvider GetCSharpCodeFixProvider()
270+
{
271+
return new SA1314CodeFixProvider();
272+
}
273+
}
274+
}

StyleCop.Analyzers/StyleCop.Analyzers.Test/StyleCop.Analyzers.Test.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,7 @@
278278
<Compile Include="NamingRules\SA1311UnitTests.cs" />
279279
<Compile Include="NamingRules\SA1312UnitTests.cs" />
280280
<Compile Include="NamingRules\SA1313UnitTests.cs" />
281+
<Compile Include="NamingRules\SA1314UnitTests.cs" />
281282
<Compile Include="NamingRules\SX1309SUnitTests.cs" />
282283
<Compile Include="NamingRules\SX1309UnitTests.cs" />
283284
<Compile Include="OrderingRules\SA1200OutsideNamespaceUnitTests.cs" />

0 commit comments

Comments
 (0)