-
Notifications
You must be signed in to change notification settings - Fork 470
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1367 from chborl/nameof
analyzer/fixer for nameof
- Loading branch information
Showing
18 changed files
with
1,129 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
65 changes: 65 additions & 0 deletions
65
src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/UseNameofInPlaceOfString.Fixer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} | ||
} |
151 changes: 151 additions & 0 deletions
151
src/Microsoft.CodeQuality.Analyzers/Core/Maintainability/UseNameofInPlaceOfString.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.