Skip to content

Commit

Permalink
Merge pull request #1367 from chborl/nameof
Browse files Browse the repository at this point in the history
analyzer/fixer for nameof
  • Loading branch information
chborl committed Dec 1, 2017
2 parents b71632e + d31f650 commit 6f3a651
Show file tree
Hide file tree
Showing 18 changed files with 1,129 additions and 0 deletions.
Expand Up @@ -192,4 +192,13 @@
<data name="DoNotIgnoreMethodResultsMessagePureMethod" xml:space="preserve">
<value>{0} calls {1} but does not use the value the method returns. Because {1} is marked as a Pure method, it cannot have side effects. Use the result in a conditional statement, assign the result to a variable, or pass it as an argument to another method.</value>
</data>
<data name="UseNameOfInPlaceOfStringDescription" xml:space="preserve">
<value>Using nameof helps keep your code valid when refactoring.</value>
</data>
<data name="UseNameOfInPlaceOfStringMessage" xml:space="preserve">
<value>Use nameof in place of string literal '{0}'</value>
</data>
<data name="UseNameOfInPlaceOfStringTitle" xml:space="preserve">
<value>Use nameof to express symbol names</value>
</data>
</root>
@@ -0,0 +1,65 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Collections.Immutable;
using System.Composition;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Editing;

namespace Microsoft.CodeQuality.Analyzers.Maintainability
{
/// <summary>
/// CA1507 Use nameof to express symbol names
/// </summary>
[ExportCodeFixProvider(LanguageNames.VisualBasic, LanguageNames.CSharp), Shared]
public class UseNameOfInPlaceOfStringFixer : CodeFixProvider
{
public sealed override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(UseNameofInPlaceOfStringAnalyzer.RuleId);

public sealed override FixAllProvider GetFixAllProvider()
{
// See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/FixAllProvider.md for more information on Fix All Providers'
return WellKnownFixAllProviders.BatchFixer;
}

public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
var diagnostics = context.Diagnostics;
var diagnosticSpan = context.Span;
// getInnerModeNodeForTie = true so we are replacing the string literal node and not the whole argument node
var nodeToReplace = root.FindNode(diagnosticSpan, getInnermostNodeForTie: true);

Debug.Assert(nodeToReplace != null);
var stringText = nodeToReplace.FindToken(diagnosticSpan.Start).ValueText;
context.RegisterCodeFix(CodeAction.Create(
MicrosoftMaintainabilityAnalyzersResources.UseNameOfInPlaceOfStringTitle,
c => ReplaceWithNameOf(context.Document, nodeToReplace, stringText, c),
equivalenceKey: nameof(UseNameOfInPlaceOfStringFixer)),
context.Diagnostics);
}

private async Task<Document> ReplaceWithNameOf(Document document, SyntaxNode nodeToReplace,
string stringText, CancellationToken cancellationToken)
{
var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false);
var generator = editor.Generator;

var trailingTrivia = nodeToReplace.GetTrailingTrivia();
var leadingTrivia = nodeToReplace.GetLeadingTrivia();
var nameOfExpression = generator.NameOfExpression(generator.IdentifierName(stringText))
.WithTrailingTrivia(trailingTrivia)
.WithLeadingTrivia(leadingTrivia);

var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var newRoot = root.ReplaceNode(nodeToReplace, nameOfExpression);

return document.WithSyntaxRoot(newRoot);
}
}
}
@@ -0,0 +1,151 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Collections.Generic;
using System.Collections.Immutable;
using Analyzer.Utilities;
using Analyzer.Utilities.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;

namespace Microsoft.CodeQuality.Analyzers.Maintainability
{
/// <summary>
/// CA1507 Use nameof to express symbol names
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
public sealed class UseNameofInPlaceOfStringAnalyzer : DiagnosticAnalyzer
{
internal const string RuleId = "CA1507";
private const string ParamName = "paramName";
private const string PropertyName = "propertyName";
internal const string StringText = "StringText";

private static readonly LocalizableString s_localizableTitle = new LocalizableResourceString(nameof(MicrosoftMaintainabilityAnalyzersResources.UseNameOfInPlaceOfStringTitle), MicrosoftMaintainabilityAnalyzersResources.ResourceManager, typeof(MicrosoftMaintainabilityAnalyzersResources));
private static readonly LocalizableString s_localizableMessage = new LocalizableResourceString(nameof(MicrosoftMaintainabilityAnalyzersResources.UseNameOfInPlaceOfStringMessage), MicrosoftMaintainabilityAnalyzersResources.ResourceManager, typeof(MicrosoftMaintainabilityAnalyzersResources));
private static readonly LocalizableString s_localizableDescription = new LocalizableResourceString(nameof(MicrosoftMaintainabilityAnalyzersResources.UseNameOfInPlaceOfStringDescription), MicrosoftMaintainabilityAnalyzersResources.ResourceManager, typeof(MicrosoftMaintainabilityAnalyzersResources));

internal static DiagnosticDescriptor RuleWithSuggestion = new DiagnosticDescriptor(RuleId,
s_localizableTitle,
s_localizableMessage,
DiagnosticCategory.Maintainability,
DiagnosticHelpers.DefaultDiagnosticSeverity,
isEnabledByDefault: true,
description: s_localizableDescription,
helpLinkUri: "https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeQuality.Analyzers/Microsoft.CodeQuality.Analyzers.md#maintainability",
customTags: WellKnownDiagnosticTags.Telemetry);

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(RuleWithSuggestion);

public override void Initialize(AnalysisContext analysisContext)
{
analysisContext.EnableConcurrentExecution();
analysisContext.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);

analysisContext.RegisterOperationAction(AnalyzeArgument, OperationKind.Argument);
}

private void AnalyzeArgument(OperationAnalysisContext context)
{
var argument = (IArgumentOperation)context.Operation;
if ((argument.Value.Kind != OperationKind.Literal
|| argument.Value.Type.SpecialType != SpecialType.System_String))
{
return;
}

if (argument.Parameter == null)
{
return;
}

var stringText = (string)argument.Value.ConstantValue.Value;

var matchingParameter = argument.Parameter;

switch (matchingParameter.Name)
{
case ParamName:
var parametersInScope = GetParametersInScope(context);
if (HasAMatchInScope(stringText, parametersInScope))
{
context.ReportDiagnostic(Diagnostic.Create(
RuleWithSuggestion, argument.Value.Syntax.GetLocation(), stringText ));
}
return;
case PropertyName:
var propertiesInScope = GetPropertiesInScope(context);
if (HasAMatchInScope(stringText, propertiesInScope))
{
context.ReportDiagnostic(Diagnostic.Create(
RuleWithSuggestion, argument.Value.Syntax.GetLocation(), stringText));
}
return;
default:
return;
}
}

private IEnumerable<string> GetPropertiesInScope(OperationAnalysisContext context)
{
var containingType = context.ContainingSymbol.ContainingType;
// look for all of the properties in the containing type and return the property names
if (containingType != null)
{
foreach (var property in containingType.GetMembers().OfType<IPropertySymbol>())
{
yield return property.Name;
}
}
}

internal IEnumerable<string> GetParametersInScope(OperationAnalysisContext context)
{
// get the parameters for the containing method
foreach (var parameter in context.ContainingSymbol.GetParameters())
{
yield return parameter.Name;
}

// and loop through the ancestors to find parameters of anonymous functions and local functions
var parentOperation = context.Operation.Parent;
while (parentOperation != null)
{
if (parentOperation.Kind == OperationKind.AnonymousFunction)
{
var lambdaSymbol = ((IAnonymousFunctionOperation)parentOperation).Symbol;
if (lambdaSymbol != null)
{
foreach (var lambdaParameter in lambdaSymbol.Parameters)
{
yield return lambdaParameter.Name;
}
}
}
else if (parentOperation.Kind == OperationKind.LocalFunction)
{
var localFunction = ((ILocalFunctionOperation)parentOperation).Symbol;
foreach (var localFunctionParameter in localFunction.Parameters)
{
yield return localFunctionParameter.Name;
}
}

parentOperation = parentOperation.Parent;
}
}

private static bool HasAMatchInScope(string stringText, IEnumerable<string> searchCollection)
{
foreach (var name in searchCollection)
{
if (stringText == name)
{
return true;
}
}

return false;
}
}
}
Expand Up @@ -127,6 +127,21 @@
<target state="translated">{0} volá {1}, ale nepoužívá hodnotu, kterou tato metoda vrací. Protože {1} je označená jako metoda Pure, nemůže mít vedlejší účinky. Použijte výsledek v podmíněném příkazu, přiřaďte výsledek proměnné nebo ho předejte jako argument jiné metodě.</target>
<note />
</trans-unit>
<trans-unit id="UseNameOfInPlaceOfStringDescription">
<source>Using nameof helps keep your code valid when refactoring.</source>
<target state="new">Using nameof helps keep your code valid when refactoring.</target>
<note />
</trans-unit>
<trans-unit id="UseNameOfInPlaceOfStringMessage">
<source>Use nameof in place of string literal '{0}'</source>
<target state="new">Use nameof in place of string literal '{0}'</target>
<note />
</trans-unit>
<trans-unit id="UseNameOfInPlaceOfStringTitle">
<source>Use nameof to express symbol names</source>
<target state="new">Use nameof to express symbol names</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
Expand Up @@ -127,6 +127,21 @@
<target state="translated">"{0}" ruft "{1}" auf, verwendet jedoch nicht den von der Methode zurückgegebenen Wert. Weil "{1}" als Pure-Methode markiert ist, kann sie keine Nebeneffekte haben. Verwenden Sie das Ergebnis in einer Bedingungsanweisung, weisen Sie das Ergebnis einer Variablen zu, oder übergeben Sie es als Argument an eine andere Methode.</target>
<note />
</trans-unit>
<trans-unit id="UseNameOfInPlaceOfStringDescription">
<source>Using nameof helps keep your code valid when refactoring.</source>
<target state="new">Using nameof helps keep your code valid when refactoring.</target>
<note />
</trans-unit>
<trans-unit id="UseNameOfInPlaceOfStringMessage">
<source>Use nameof in place of string literal '{0}'</source>
<target state="new">Use nameof in place of string literal '{0}'</target>
<note />
</trans-unit>
<trans-unit id="UseNameOfInPlaceOfStringTitle">
<source>Use nameof to express symbol names</source>
<target state="new">Use nameof to express symbol names</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
Expand Up @@ -127,6 +127,21 @@
<target state="translated">{0} llama a {1} pero no usa el valor que el método devuelve. {1} se ha marcado como método Pure, por lo que no puede tener efectos secundarios. Use el resultado en una instrucción condicional, asigne el resultado a una variable o páselo como argumento a otro método.</target>
<note />
</trans-unit>
<trans-unit id="UseNameOfInPlaceOfStringDescription">
<source>Using nameof helps keep your code valid when refactoring.</source>
<target state="new">Using nameof helps keep your code valid when refactoring.</target>
<note />
</trans-unit>
<trans-unit id="UseNameOfInPlaceOfStringMessage">
<source>Use nameof in place of string literal '{0}'</source>
<target state="new">Use nameof in place of string literal '{0}'</target>
<note />
</trans-unit>
<trans-unit id="UseNameOfInPlaceOfStringTitle">
<source>Use nameof to express symbol names</source>
<target state="new">Use nameof to express symbol names</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
Expand Up @@ -127,6 +127,21 @@
<target state="translated">{0} appelle {1} mais n'utilise pas la valeur retournée par la méthode. Dans la mesure où {1} est marqué en tant que méthode Pure, il ne peut pas avoir d'effets secondaires. Utilisez le résultat dans une instruction conditionnelle, assignez-le à une variable ou passez-le en tant qu'argument à une autre méthode.</target>
<note />
</trans-unit>
<trans-unit id="UseNameOfInPlaceOfStringDescription">
<source>Using nameof helps keep your code valid when refactoring.</source>
<target state="new">Using nameof helps keep your code valid when refactoring.</target>
<note />
</trans-unit>
<trans-unit id="UseNameOfInPlaceOfStringMessage">
<source>Use nameof in place of string literal '{0}'</source>
<target state="new">Use nameof in place of string literal '{0}'</target>
<note />
</trans-unit>
<trans-unit id="UseNameOfInPlaceOfStringTitle">
<source>Use nameof to express symbol names</source>
<target state="new">Use nameof to express symbol names</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
Expand Up @@ -127,6 +127,21 @@
<target state="translated">{0} chiama {1} ma non usa il valore restituito dal metodo. Dal momento che {1} è contrassegnato come metodo Pure, non può avere effetti collaterali. Usare il risultato in un'istruzione condizionale, assegnare il risultato a una variabile o passarlo come argomento a un altro metodo.</target>
<note />
</trans-unit>
<trans-unit id="UseNameOfInPlaceOfStringDescription">
<source>Using nameof helps keep your code valid when refactoring.</source>
<target state="new">Using nameof helps keep your code valid when refactoring.</target>
<note />
</trans-unit>
<trans-unit id="UseNameOfInPlaceOfStringMessage">
<source>Use nameof in place of string literal '{0}'</source>
<target state="new">Use nameof in place of string literal '{0}'</target>
<note />
</trans-unit>
<trans-unit id="UseNameOfInPlaceOfStringTitle">
<source>Use nameof to express symbol names</source>
<target state="new">Use nameof to express symbol names</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
Expand Up @@ -127,6 +127,21 @@
<target state="translated">{0} は {1} を呼び出しますが、メソッドから返される値は使用されません。{1} は Pure メソッドとしてマークされているため、副作用は発生しません。条件ステートメントで結果を使用するか、結果を変数に割り当てるか、引数として他のメソッドに渡してください。</target>
<note />
</trans-unit>
<trans-unit id="UseNameOfInPlaceOfStringDescription">
<source>Using nameof helps keep your code valid when refactoring.</source>
<target state="new">Using nameof helps keep your code valid when refactoring.</target>
<note />
</trans-unit>
<trans-unit id="UseNameOfInPlaceOfStringMessage">
<source>Use nameof in place of string literal '{0}'</source>
<target state="new">Use nameof in place of string literal '{0}'</target>
<note />
</trans-unit>
<trans-unit id="UseNameOfInPlaceOfStringTitle">
<source>Use nameof to express symbol names</source>
<target state="new">Use nameof to express symbol names</target>
<note />
</trans-unit>
</body>
</file>
</xliff>
Expand Up @@ -127,6 +127,21 @@
<target state="translated">{0}이(가) {1}을(를) 호출하지만 메서드가 반환하는 값을 사용하지 않습니다. {1}이(가) 순수 메서드로 표시되어 있으므로 의도하지 않은 결과가 발생하지 않습니다. 결과를 조건문에 사용하거나 변수에 할당하거나 다른 메서드에 인수로 전달하세요.</target>
<note />
</trans-unit>
<trans-unit id="UseNameOfInPlaceOfStringDescription">
<source>Using nameof helps keep your code valid when refactoring.</source>
<target state="new">Using nameof helps keep your code valid when refactoring.</target>
<note />
</trans-unit>
<trans-unit id="UseNameOfInPlaceOfStringMessage">
<source>Use nameof in place of string literal '{0}'</source>
<target state="new">Use nameof in place of string literal '{0}'</target>
<note />
</trans-unit>
<trans-unit id="UseNameOfInPlaceOfStringTitle">
<source>Use nameof to express symbol names</source>
<target state="new">Use nameof to express symbol names</target>
<note />
</trans-unit>
</body>
</file>
</xliff>

0 comments on commit 6f3a651

Please sign in to comment.