Skip to content

Commit

Permalink
Merge pull request #1982 from mavasani/MakeStatic
Browse files Browse the repository at this point in the history
Add code fix for MarkMembersStatic (CA1822)
  • Loading branch information
mavasani committed Jan 8, 2019
2 parents acd28e3 + 5bb5a4b commit 60535a6
Show file tree
Hide file tree
Showing 21 changed files with 1,442 additions and 13 deletions.
@@ -1,8 +1,10 @@
// 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.Composition;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeQuality.Analyzers.QualityGuidelines;

namespace Microsoft.CodeQuality.CSharp.Analyzers.QualityGuidelines
Expand All @@ -13,5 +15,10 @@ namespace Microsoft.CodeQuality.CSharp.Analyzers.QualityGuidelines
[ExportCodeFixProvider(LanguageNames.CSharp), Shared]
public sealed class CSharpMarkMembersAsStaticFixer : MarkMembersAsStaticFixer
{
protected override IEnumerable<SyntaxNode> GetTypeArguments(SyntaxNode node)
=> (node as GenericNameSyntax)?.TypeArgumentList.Arguments;

protected override SyntaxNode GetExpressionOfInvocation(SyntaxNode invocation)
=> (invocation as InvocationExpressionSyntax)?.Expression;
}
}
@@ -1,9 +1,20 @@
// 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;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Analyzer.Utilities;
using Analyzer.Utilities.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Operations;

namespace Microsoft.CodeQuality.Analyzers.QualityGuidelines
{
Expand All @@ -12,19 +23,176 @@ namespace Microsoft.CodeQuality.Analyzers.QualityGuidelines
/// </summary>
public abstract class MarkMembersAsStaticFixer : CodeFixProvider
{
protected abstract IEnumerable<SyntaxNode> GetTypeArguments(SyntaxNode node);
protected abstract SyntaxNode GetExpressionOfInvocation(SyntaxNode invocation);
protected virtual SyntaxNode GetSyntaxNodeToReplace(IMemberReferenceOperation memberReference)
=> memberReference.Syntax;

public sealed override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(MarkMembersAsStaticAnalyzer.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 Task RegisterCodeFixesAsync(CodeFixContext context)
public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
var node = root.FindNode(context.Span);
if (node == null)
{
return;
}

context.RegisterCodeFix(
new MarkMembersAsStaticAction(
MicrosoftQualityGuidelinesAnalyzersResources.MarkMembersAsStaticCodeFix,
ct => MakeStaticAsync(context.Document, root, node, ct)),
context.Diagnostics);
}

private async Task<Solution> MakeStaticAsync(Document document, SyntaxNode root, SyntaxNode node, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();

// Update definition to add static modifier.
var syntaxGenerator = SyntaxGenerator.GetGenerator(document);
var madeStatic = syntaxGenerator.WithModifiers(node, DeclarationModifiers.Static);
document = document.WithSyntaxRoot(root.ReplaceNode(node, madeStatic));
var solution = document.Project.Solution;

// Update references, if any.
root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
node = root.DescendantNodes().Single(n => n.SpanStart == node.SpanStart && n.Span.Length == madeStatic.Span.Length);
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var symbol = semanticModel.GetDeclaredSymbol(node, cancellationToken);
if (symbol != null)
{
solution = await UpdateReferencesAsync(symbol, solution, cancellationToken).ConfigureAwait(false);
}

return solution;
}

private async Task<Solution> UpdateReferencesAsync(ISymbol symbol, Solution solution, CancellationToken cancellationToken)
{
// Fixer not yet implemented.
return Task.CompletedTask;
cancellationToken.ThrowIfCancellationRequested();

var references = await SymbolFinder.FindReferencesAsync(symbol, solution, cancellationToken).ConfigureAwait(false);
if (references.Count() != 1)
{
return solution;
}

// Group references by document and fix references in each document.
foreach (var referenceLocationGroup in references.Single().Locations.GroupBy(r => r.Document))
{
// Get document in current solution
var document = solution.GetDocument(referenceLocationGroup.Key.Id);

// Skip references in projects with different language.
// https://github.com/dotnet/roslyn-analyzers/issues/1986 tracks handling them.
if (!document.Project.Language.Equals(symbol.Language, StringComparison.Ordinal))
{
continue;
}

var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);

// Compute replacements
var editor = new SyntaxEditor(root, solution.Workspace);
foreach (var referenceLocation in referenceLocationGroup)
{
cancellationToken.ThrowIfCancellationRequested();

var referenceNode = root.FindNode(referenceLocation.Location.SourceSpan, getInnermostNodeForTie: true);
if (referenceNode != null)
{
var operation = semanticModel.GetOperationWalkingUpParentChain(referenceNode, cancellationToken);
SyntaxNode nodeToReplaceOpt = null;
switch (operation)
{
case IMemberReferenceOperation memberReference:
if (IsReplacableOperation(memberReference.Instance))
{
nodeToReplaceOpt = GetSyntaxNodeToReplace(memberReference);
}

break;

case IInvocationOperation invocation:
if (IsReplacableOperation(invocation.Instance))
{
nodeToReplaceOpt = GetExpressionOfInvocation(invocation.Syntax);
}

break;
}

if (nodeToReplaceOpt != null)
{
// Fetch the symbol for the node to replace - note that this might be
// different from the original symbol due to generic type arguments.
var symbolInfo = semanticModel.GetSymbolInfo(nodeToReplaceOpt, cancellationToken);
if (symbolInfo.Symbol == null)
{
continue;
}

SyntaxNode memberName;
var typeArgumentsOpt = GetTypeArguments(referenceNode);
memberName = typeArgumentsOpt != null ?
editor.Generator.GenericName(symbolInfo.Symbol.Name, typeArgumentsOpt) :
editor.Generator.IdentifierName(symbolInfo.Symbol.Name);

var newNode = editor.Generator.MemberAccessExpression(
expression: editor.Generator.TypeExpression(symbolInfo.Symbol.ContainingType),
memberName: memberName)
.WithLeadingTrivia(nodeToReplaceOpt.GetLeadingTrivia())
.WithTrailingTrivia(nodeToReplaceOpt.GetTrailingTrivia())
.WithAdditionalAnnotations(Formatter.Annotation);

editor.ReplaceNode(nodeToReplaceOpt, newNode);
}
}
}

document = document.WithSyntaxRoot(editor.GetChangedRoot());
solution = document.Project.Solution;
}

return solution;

// Local functions.
bool IsReplacableOperation(IOperation operation)
{
// We only replace reference operations whose removal cannot change semantics.
if (operation != null)
{
switch (operation.Kind)
{
case OperationKind.InstanceReference:
case OperationKind.ParameterReference:
case OperationKind.LocalReference:
return true;

case OperationKind.FieldReference:
case OperationKind.PropertyReference:
return IsReplacableOperation(((IMemberReferenceOperation)operation).Instance);
}
}

return false;
}
}

private class MarkMembersAsStaticAction : SolutionChangeAction
{
public MarkMembersAsStaticAction(string title, Func<CancellationToken, Task<Solution>> createChangedSolution)
: base(title, createChangedSolution, equivalenceKey: title)
{
}
}
}
}
Expand Up @@ -78,6 +78,15 @@ public override void Initialize(AnalysisContext analysisContext)
isInstanceReferenced = true;
}, OperationKind.InstanceReference);
blockStartContext.RegisterOperationAction(operationContext =>
{
var invocation = (IInvocationOperation)operationContext.Operation;
if (!invocation.TargetMethod.IsExternallyVisible())
{
invokedInternalMethods.Add(invocation.TargetMethod);
}
}, OperationKind.Invocation);
blockStartContext.RegisterOperationBlockEndAction(blockEndContext =>
{
// Methods referenced by other non static methods
Expand All @@ -101,15 +110,6 @@ public override void Initialize(AnalysisContext analysisContext)
});
});
compilationContext.RegisterOperationAction(operationContext =>
{
var invocation = (IInvocationOperation)operationContext.Operation;
if (!invocation.TargetMethod.IsExternallyVisible())
{
invokedInternalMethods.Add(invocation.TargetMethod);
}
}, OperationKind.Invocation);
compilationContext.RegisterCompilationEndAction(compilationEndContext =>
{
foreach (var candidate in internalCandidates)
Expand Down
Expand Up @@ -231,4 +231,7 @@
<data name="AvoidDuplicateElementInitializationTitle" xml:space="preserve">
<value>Do not duplicate indexed element initializations</value>
</data>
<data name="MarkMembersAsStaticCodeFix" xml:space="preserve">
<value>Make static</value>
</data>
</root>
Expand Up @@ -2,6 +2,11 @@
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en" target-language="cs" original="../MicrosoftQualityGuidelinesAnalyzersResources.resx">
<body>
<trans-unit id="MarkMembersAsStaticCodeFix">
<source>Make static</source>
<target state="new">Make static</target>
<note />
</trans-unit>
<trans-unit id="UseLiteralsWhereAppropriateTitle">
<source>Use literals where appropriate</source>
<target state="translated">Tam, kde je to vhodné, použijte literály</target>
Expand Down
Expand Up @@ -2,6 +2,11 @@
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en" target-language="de" original="../MicrosoftQualityGuidelinesAnalyzersResources.resx">
<body>
<trans-unit id="MarkMembersAsStaticCodeFix">
<source>Make static</source>
<target state="new">Make static</target>
<note />
</trans-unit>
<trans-unit id="UseLiteralsWhereAppropriateTitle">
<source>Use literals where appropriate</source>
<target state="translated">Nach Möglichkeit Literale verwenden</target>
Expand Down
Expand Up @@ -2,6 +2,11 @@
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en" target-language="es" original="../MicrosoftQualityGuidelinesAnalyzersResources.resx">
<body>
<trans-unit id="MarkMembersAsStaticCodeFix">
<source>Make static</source>
<target state="new">Make static</target>
<note />
</trans-unit>
<trans-unit id="UseLiteralsWhereAppropriateTitle">
<source>Use literals where appropriate</source>
<target state="translated">Usar literales cuando resulte apropiado</target>
Expand Down
Expand Up @@ -2,6 +2,11 @@
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en" target-language="fr" original="../MicrosoftQualityGuidelinesAnalyzersResources.resx">
<body>
<trans-unit id="MarkMembersAsStaticCodeFix">
<source>Make static</source>
<target state="new">Make static</target>
<note />
</trans-unit>
<trans-unit id="UseLiteralsWhereAppropriateTitle">
<source>Use literals where appropriate</source>
<target state="translated">Utiliser des littéraux quand cela est approprié</target>
Expand Down
Expand Up @@ -2,6 +2,11 @@
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en" target-language="it" original="../MicrosoftQualityGuidelinesAnalyzersResources.resx">
<body>
<trans-unit id="MarkMembersAsStaticCodeFix">
<source>Make static</source>
<target state="new">Make static</target>
<note />
</trans-unit>
<trans-unit id="UseLiteralsWhereAppropriateTitle">
<source>Use literals where appropriate</source>
<target state="translated">Usa valori letterali dove appropriato</target>
Expand Down
Expand Up @@ -2,6 +2,11 @@
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en" target-language="ja" original="../MicrosoftQualityGuidelinesAnalyzersResources.resx">
<body>
<trans-unit id="MarkMembersAsStaticCodeFix">
<source>Make static</source>
<target state="new">Make static</target>
<note />
</trans-unit>
<trans-unit id="UseLiteralsWhereAppropriateTitle">
<source>Use literals where appropriate</source>
<target state="translated">適切な場所にリテラルを使用します</target>
Expand Down
Expand Up @@ -2,6 +2,11 @@
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en" target-language="ko" original="../MicrosoftQualityGuidelinesAnalyzersResources.resx">
<body>
<trans-unit id="MarkMembersAsStaticCodeFix">
<source>Make static</source>
<target state="new">Make static</target>
<note />
</trans-unit>
<trans-unit id="UseLiteralsWhereAppropriateTitle">
<source>Use literals where appropriate</source>
<target state="translated">적합한 리터럴을 사용하세요.</target>
Expand Down
Expand Up @@ -2,6 +2,11 @@
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en" target-language="pl" original="../MicrosoftQualityGuidelinesAnalyzersResources.resx">
<body>
<trans-unit id="MarkMembersAsStaticCodeFix">
<source>Make static</source>
<target state="new">Make static</target>
<note />
</trans-unit>
<trans-unit id="UseLiteralsWhereAppropriateTitle">
<source>Use literals where appropriate</source>
<target state="translated">Używaj literałów w odpowiednich miejscach</target>
Expand Down
Expand Up @@ -2,6 +2,11 @@
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en" target-language="pt-BR" original="../MicrosoftQualityGuidelinesAnalyzersResources.resx">
<body>
<trans-unit id="MarkMembersAsStaticCodeFix">
<source>Make static</source>
<target state="new">Make static</target>
<note />
</trans-unit>
<trans-unit id="UseLiteralsWhereAppropriateTitle">
<source>Use literals where appropriate</source>
<target state="translated">Usar literais sempre que apropriado</target>
Expand Down
Expand Up @@ -2,6 +2,11 @@
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en" target-language="ru" original="../MicrosoftQualityGuidelinesAnalyzersResources.resx">
<body>
<trans-unit id="MarkMembersAsStaticCodeFix">
<source>Make static</source>
<target state="new">Make static</target>
<note />
</trans-unit>
<trans-unit id="UseLiteralsWhereAppropriateTitle">
<source>Use literals where appropriate</source>
<target state="translated">Используйте литералы, когда это уместно</target>
Expand Down

0 comments on commit 60535a6

Please sign in to comment.