-
Notifications
You must be signed in to change notification settings - Fork 509
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement SA1314 (TypeParameterNamesMustBeginWithT) #1925
Changes from all commits
d0fab43
3ed63ac
7e8d738
3b18b2f
23a9178
1f72cb7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. | ||
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. | ||
|
||
namespace StyleCop.Analyzers.NamingRules | ||
{ | ||
using System.Collections.Immutable; | ||
using System.Composition; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Helpers; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CodeActions; | ||
using Microsoft.CodeAnalysis.CodeFixes; | ||
|
||
/// <summary> | ||
/// Implements a code fix for <see cref="SA1314TypeParameterNamesMustBeginWithT"/>. | ||
/// </summary> | ||
/// <remarks> | ||
/// <para>To fix a violation of this rule, add the capital letter T to the front of the type parameter name.</para> | ||
/// </remarks> | ||
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(SA1314CodeFixProvider))] | ||
[Shared] | ||
internal class SA1314CodeFixProvider : CodeFixProvider | ||
{ | ||
/// <inheritdoc/> | ||
public override ImmutableArray<string> FixableDiagnosticIds { get; } = | ||
ImmutableArray.Create(SA1314TypeParameterNamesMustBeginWithT.DiagnosticId); | ||
|
||
/// <inheritdoc/> | ||
public override Task RegisterCodeFixesAsync(CodeFixContext context) | ||
{ | ||
foreach (var diagnostic in context.Diagnostics) | ||
{ | ||
context.RegisterCodeFix( | ||
CodeAction.Create( | ||
NamingResources.SA1314CodeFix, | ||
cancellationToken => CreateChangedSolutionAsync(context.Document, diagnostic, cancellationToken), | ||
nameof(SA1314CodeFixProvider)), | ||
diagnostic); | ||
} | ||
|
||
return SpecializedTasks.CompletedTask; | ||
} | ||
|
||
private static async Task<Solution> CreateChangedSolutionAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken) | ||
{ | ||
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); | ||
var token = root.FindToken(diagnostic.Location.SourceSpan.Start); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we also want to make the first letter after T capital? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I followed the example of SA1302, which simply adds an I and doesn't touch the rest of the token. This makes sense, though; I could implement this change if others agree. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't mind just adding the |
||
var baseName = "T" + token.ValueText; | ||
var index = 0; | ||
var newName = baseName; | ||
|
||
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); | ||
var declaredSymbol = semanticModel.GetDeclaredSymbol(token.Parent, cancellationToken); | ||
while (!await RenameHelper.IsValidNewMemberNameAsync(semanticModel, declaredSymbol, newName, cancellationToken).ConfigureAwait(false)) | ||
{ | ||
index++; | ||
newName = baseName + index; | ||
} | ||
|
||
return await RenameHelper.RenameSymbolAsync(document, root, token, newName, cancellationToken).ConfigureAwait(false); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,274 @@ | ||
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. | ||
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. | ||
|
||
namespace StyleCop.Analyzers.Test.NamingRules | ||
{ | ||
using System.Collections.Generic; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.CodeAnalysis.CodeFixes; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
using StyleCop.Analyzers.NamingRules; | ||
using TestHelper; | ||
using Xunit; | ||
|
||
public class SA1314UnitTests : CodeFixVerifier | ||
{ | ||
[Fact] | ||
public async Task TestTypeParameterDoesNotStartWithTAsync() | ||
{ | ||
var testCode = @" | ||
public interface IFoo<Key> | ||
{ | ||
}"; | ||
|
||
DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(2, 23); | ||
|
||
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); | ||
|
||
var fixedCode = @" | ||
public interface IFoo<TKey> | ||
{ | ||
}"; | ||
|
||
await this.VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false); | ||
} | ||
|
||
[Fact] | ||
public async Task TestTypeParameterDoesNotStartWithTPlusParameterUsedAsync() | ||
{ | ||
var testCode = @" | ||
public class Foo<Key> | ||
{ | ||
void Test() | ||
{ | ||
var key = typeof(Key); | ||
} | ||
} | ||
"; | ||
|
||
DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(2, 18); | ||
|
||
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); | ||
|
||
var fixedCode = @" | ||
public class Foo<TKey> | ||
{ | ||
void Test() | ||
{ | ||
var key = typeof(TKey); | ||
} | ||
} | ||
"; | ||
|
||
await this.VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false); | ||
} | ||
|
||
[Fact] | ||
public async Task TestTypeParameterStartsWithLowerTAsync() | ||
{ | ||
var testCode = @" | ||
public interface IFoo<tKey> | ||
{ | ||
}"; | ||
|
||
DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(2, 23); | ||
|
||
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); | ||
|
||
var fixedCode = @" | ||
public interface IFoo<TtKey> | ||
{ | ||
}"; | ||
|
||
await this.VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false); | ||
} | ||
|
||
[Fact] | ||
public async Task TestInnerTypeParameterDoesNotStartWithTAsync() | ||
{ | ||
var testCode = @" | ||
public class Bar | ||
{ | ||
public class Foo<Key> | ||
{ | ||
} | ||
}"; | ||
|
||
DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(4, 22); | ||
|
||
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); | ||
|
||
var fixedCode = @" | ||
public class Bar | ||
{ | ||
public class Foo<TKey> | ||
{ | ||
} | ||
}"; | ||
|
||
await this.VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false); | ||
} | ||
|
||
[Fact] | ||
public async Task TestTypeParameterDoesStartWithTAsync() | ||
{ | ||
var testCode = @"public interface IFoo<TKey> | ||
{ | ||
}"; | ||
|
||
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); | ||
} | ||
|
||
[Fact] | ||
public async Task TestInnerTypeParameterDoesStartWithTAsync() | ||
{ | ||
var testCode = @" | ||
public class Bar | ||
{ | ||
public class Foo<TKey> | ||
{ | ||
} | ||
}"; | ||
|
||
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); | ||
} | ||
|
||
[Fact] | ||
public async Task TestTypeParameterDoesNotStartWithTWithMemberMatchingTargetTypeAsync() | ||
{ | ||
string testCode = @" | ||
public class Foo<Key> | ||
{ | ||
Key Bar { get; } | ||
}"; | ||
|
||
string fixedCode = @" | ||
public class Foo<TKey> | ||
{ | ||
TKey Bar { get; } | ||
}"; | ||
|
||
DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(2, 18); | ||
|
||
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); | ||
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); | ||
await this.VerifyCSharpFixAsync(testCode, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false); | ||
} | ||
|
||
[Fact] | ||
public async Task TestNestedTypeParameterDoesNotStartWithTWithConflictAsync() | ||
{ | ||
string testCode = @" | ||
public class Outer<TKey> | ||
{ | ||
public class Foo<Key> | ||
{ | ||
} | ||
}"; | ||
string fixedCode = @" | ||
public class Outer<TKey> | ||
{ | ||
public class Foo<TKey1> | ||
{ | ||
} | ||
}"; | ||
|
||
DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(4, 22); | ||
|
||
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); | ||
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); | ||
await this.VerifyCSharpFixAsync(testCode, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false); | ||
} | ||
|
||
[Fact] | ||
public async Task TestNestedTypeParameterDoesNotStartWithTWithMemberConflictAsync() | ||
{ | ||
string testCode = @" | ||
public class Outer<TKey> | ||
{ | ||
public class Foo<Key> | ||
{ | ||
Key Bar { get; } | ||
} | ||
}"; | ||
string fixedCode = @" | ||
public class Outer<TKey> | ||
{ | ||
public class Foo<TKey1> | ||
{ | ||
TKey1 Bar { get; } | ||
} | ||
}"; | ||
|
||
DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(4, 22); | ||
|
||
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); | ||
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); | ||
await this.VerifyCSharpFixAsync(testCode, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false); | ||
} | ||
|
||
[Fact] | ||
public async Task TestTypeParameterDoesNotStartWithTAndTypeConflictAsync() | ||
{ | ||
string testCode = @" | ||
public class TFoo | ||
{ | ||
} | ||
|
||
public class Bar<Foo> | ||
{ | ||
}"; | ||
string fixedCode = @" | ||
public class TFoo | ||
{ | ||
} | ||
|
||
public class Bar<TFoo1> | ||
{ | ||
}"; | ||
|
||
DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(6, 18); | ||
|
||
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); | ||
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); | ||
await this.VerifyCSharpFixAsync(testCode, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false); | ||
} | ||
|
||
[Fact] | ||
public async Task TestTypeParameterInMethodSignatureDoesNotStartWithTAsync() | ||
{ | ||
var testCode = @" | ||
public class Foo | ||
{ | ||
public void Bar<Baz>() | ||
{ | ||
} | ||
}"; | ||
|
||
DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(4, 21); | ||
|
||
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false); | ||
|
||
var fixedCode = @" | ||
public class Foo | ||
{ | ||
public void Bar<TBaz>() | ||
{ | ||
} | ||
}"; | ||
|
||
await this.VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false); | ||
} | ||
|
||
protected override IEnumerable<DiagnosticAnalyzer> GetCSharpDiagnosticAnalyzers() | ||
{ | ||
yield return new SA1314TypeParameterNamesMustBeginWithT(); | ||
} | ||
|
||
protected override CodeFixProvider GetCSharpCodeFixProvider() | ||
{ | ||
return new SA1314CodeFixProvider(); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We cant rename the type parameter if there is already a type with the same name
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mean (for example) if there was already a class named
TKey
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exactly