Skip to content
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

Merged
merged 6 commits into from
Nov 20, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,20 @@ public static async Task<bool> IsValidNewMemberNameAsync(SemanticModel semanticM

var containingSymbol = symbol.ContainingSymbol;

if (symbol.Kind == SymbolKind.TypeParameter)
Copy link
Member

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

Copy link
Contributor Author

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?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly

{
// If the symbol is a type parameter, the name can't be the same as any type parameters of the containing type.
var parentSymbol = containingSymbol?.ContainingSymbol as INamedTypeSymbol;
if (parentSymbol != null
&& parentSymbol.TypeParameters.Any(t => t.Name == name))
{
return false;
}

// Move up one level for the next validation step.
containingSymbol = containingSymbol?.ContainingSymbol;
}

var containingNamespaceOrTypeSymbol = containingSymbol as INamespaceOrTypeSymbol;
if (containingNamespaceOrTypeSymbol != null)
{
Expand Down
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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we also want to make the first letter after T capital?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't mind just adding the T prefix.

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
Expand Up @@ -102,6 +102,7 @@
<Compile Include="NamingRules\SA1308CodeFixProvider.cs" />
<Compile Include="NamingRules\SA1309CodeFixProvider.cs" />
<Compile Include="NamingRules\SA1310CodeFixProvider.cs" />
<Compile Include="NamingRules\SA1314CodeFixProvider.cs" />
<Compile Include="NamingRules\SX1309CodeFixProvider.cs" />
<Compile Include="OrderingRules\ElementOrderCodeFixProvider.cs" />
<Compile Include="OrderingRules\SA1205CodeFixProvider.cs" />
Expand Down
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();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@
<Compile Include="NamingRules\SA1311UnitTests.cs" />
<Compile Include="NamingRules\SA1312UnitTests.cs" />
<Compile Include="NamingRules\SA1313UnitTests.cs" />
<Compile Include="NamingRules\SA1314UnitTests.cs" />
<Compile Include="NamingRules\SX1309SUnitTests.cs" />
<Compile Include="NamingRules\SX1309UnitTests.cs" />
<Compile Include="OrderingRules\SA1200OutsideNamespaceUnitTests.cs" />
Expand Down
Loading