Skip to content

Commit d0fab43

Browse files
committed
Implement SA1654 GenericParameterNamesMustBeginWithT
1 parent 72da7cb commit d0fab43

11 files changed

Lines changed: 459 additions & 1 deletion

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 any type parameters
58+
// of the containing type.
59+
var parentSymbol = containingSymbol?.ContainingSymbol as INamedTypeSymbol;
60+
if (parentSymbol == null)
61+
{
62+
return true;
63+
}
64+
65+
bool conflict = parentSymbol.TypeParameters.Any(t => t.Name == name);
66+
return !conflict;
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="SA1654GenericParameterNamesMustBeginWithT"/>.
17+
/// </summary>
18+
/// <remarks>
19+
/// <para>To fix a violation of this rule, add the capital letter T to the front of the generic parameter name.</para>
20+
/// </remarks>
21+
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(SA1654CodeFixProvider))]
22+
[Shared]
23+
internal class SA1654CodeFixProvider : CodeFixProvider
24+
{
25+
/// <inheritdoc/>
26+
public override ImmutableArray<string> FixableDiagnosticIds { get; } =
27+
ImmutableArray.Create(SA1654GenericParameterNamesMustBeginWithT.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.SA1654CodeFix,
37+
cancellationToken => CreateChangedSolutionAsync(context.Document, diagnostic, cancellationToken),
38+
nameof(SA1654CodeFixProvider)),
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
@@ -101,6 +101,7 @@
101101
<Compile Include="NamingRules\SA1308CodeFixProvider.cs" />
102102
<Compile Include="NamingRules\SA1309CodeFixProvider.cs" />
103103
<Compile Include="NamingRules\SA1310CodeFixProvider.cs" />
104+
<Compile Include="NamingRules\SA1654CodeFixProvider.cs" />
104105
<Compile Include="NamingRules\SX1309CodeFixProvider.cs" />
105106
<Compile Include="OrderingRules\ElementOrderCodeFixProvider.cs" />
106107
<Compile Include="OrderingRules\SA1205CodeFixProvider.cs" />
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
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 SA1654UnitTests : CodeFixVerifier
16+
{
17+
[Fact]
18+
public async Task TestGenericParameterDoesNotStartWithTAsync()
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 TestGenericParameterDoesNotStartWithTPlusParameterUsedAsync()
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 TestGenericParameterStartsWithLowerTAsync()
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 TestInnerGenericParameterDoesNotStartWithTAsync()
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 TestGenericParameterDoesStartWithTAsync()
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 TestInnerGenericParameterDoesStartWithTAsync()
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 TestGenericParameterDoesNotStartWithTWithMemberMatchingTargetTypeAsync()
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 TestNestedGenericParameterDoesNotStartWithTWithConflictAsync()
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 TestNestedGenericParameterDoesNotStartWithTWithMemberConflictAsync()
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+
protected override IEnumerable<DiagnosticAnalyzer> GetCSharpDiagnosticAnalyzers()
212+
{
213+
yield return new SA1654GenericParameterNamesMustBeginWithT();
214+
}
215+
216+
protected override CodeFixProvider GetCSharpCodeFixProvider()
217+
{
218+
return new SA1654CodeFixProvider();
219+
}
220+
}
221+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@
266266
<Compile Include="NamingRules\SA1311UnitTests.cs" />
267267
<Compile Include="NamingRules\SA1312UnitTests.cs" />
268268
<Compile Include="NamingRules\SA1313UnitTests.cs" />
269+
<Compile Include="NamingRules\SA1654UnitTests.cs" />
269270
<Compile Include="NamingRules\SX1309SUnitTests.cs" />
270271
<Compile Include="NamingRules\SX1309UnitTests.cs" />
271272
<Compile Include="OrderingRules\SA1200OutsideNamespaceUnitTests.cs" />

StyleCop.Analyzers/StyleCop.Analyzers/NamingRules/NamingResources.Designer.cs

Lines changed: 36 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

StyleCop.Analyzers/StyleCop.Analyzers/NamingRules/NamingResources.resx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,4 +141,16 @@
141141
<data name="SA1313Title" xml:space="preserve">
142142
<value>Parameter names must begin with lower-case letter</value>
143143
</data>
144+
<data name="SA1654CodeFix" xml:space="preserve">
145+
<value>Prefix generic parameter name with 'T'</value>
146+
</data>
147+
<data name="SA1654Description" xml:space="preserve">
148+
<value>The name of a C# generic parameter does not begin with the capital letter T.</value>
149+
</data>
150+
<data name="SA1654MessageFormat" xml:space="preserve">
151+
<value>Generic parameter names must begin with T</value>
152+
</data>
153+
<data name="SA1654Title" xml:space="preserve">
154+
<value>Generic parameter names must begin with T</value>
155+
</data>
144156
</root>

0 commit comments

Comments
 (0)