Skip to content

Commit

Permalink
EPS05 code fix should fix implementations and callers
Browse files Browse the repository at this point in the history
Fixes #122
Fixes #123
  • Loading branch information
sharwell committed Apr 17, 2020
1 parent 3145e3e commit b41c446
Show file tree
Hide file tree
Showing 3 changed files with 446 additions and 9 deletions.
19 changes: 19 additions & 0 deletions src/ErrorProne.NET.Core/KeyValuePairExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// --------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// --------------------------------------------------------------------

using System.Collections.Generic;

namespace ErrorProne.NET.Core
{
public static class KeyValuePairExtensions
{
public static void Deconstruct<TKey, TValue>(this KeyValuePair<TKey, TValue> pair, out TKey key, out TValue value)
{
key = pair.Key;
value = pair.Value;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
using System.Collections.Immutable;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using ErrorProne.NET.Core;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.Text;

namespace ErrorProne.NET.StructAnalyzers
{
Expand Down Expand Up @@ -39,7 +43,7 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
context.RegisterCodeFix(
CodeAction.Create(
title: Title,
createChangedDocument: c => AddInModifier(context.Document, declaration, c),
createChangedSolution: c => AddInModifier(context.Document, declaration, c),
equivalenceKey: Title),
diagnostic);
}
Expand Down Expand Up @@ -115,17 +119,233 @@ private async Task<bool> ParameterIsUsedInNonInFriendlyManner(ParameterSyntax pa
return false;
}

private async Task<Document> AddInModifier(Document document, ParameterSyntax paramSyntax, CancellationToken cancellationToken)
private async Task<Solution> AddInModifier(Document document, ParameterSyntax paramSyntax, CancellationToken cancellationToken)
{
SyntaxTriviaList trivia = paramSyntax.GetLeadingTrivia(); ;
var arguments = new Dictionary<DocumentId, List<TextSpan>>();
var parameters = new Dictionary<DocumentId, List<TextSpan>> { { document.Id, new List<TextSpan> { paramSyntax.Span } } };

var newType = paramSyntax
.WithModifiers(paramSyntax.Modifiers.Insert(0, SyntaxFactory.Token(SyntaxKind.InKeyword)))
.WithLeadingTrivia(trivia);
var semanticModel = await document.GetSemanticModelAsync(cancellationToken);
var parameterSymbol = semanticModel.GetDeclaredSymbol(paramSyntax, cancellationToken);
if (parameterSymbol?.ContainingSymbol is IMethodSymbol containingMethod)
{
var parameterIndex = containingMethod.Parameters.IndexOf(parameterSymbol);
var parameterName = parameterSymbol.Name;

var callers = await SymbolFinder.FindCallersAsync(containingMethod, document.Project.Solution, cancellationToken).ConfigureAwait(false);
foreach (var caller in callers)
{
foreach (var location in caller.Locations)
{
if (!location.IsInSource)
{
continue;
}

var locationRoot = await location.SourceTree.GetRootAsync(cancellationToken).ConfigureAwait(false);
var node = locationRoot.FindNode(location.SourceSpan, getInnermostNodeForTie: true);

var invocationExpression = node.Parent as InvocationExpressionSyntax;
if (invocationExpression is null)
{
invocationExpression = (node.Parent as MemberAccessExpressionSyntax)?.Parent as InvocationExpressionSyntax;
}

if (invocationExpression is object)
{
ArgumentSyntax? argument = null;
var positionalArgument = TryGetArgumentAtPosition(invocationExpression.ArgumentList, parameterIndex);
if (positionalArgument is object && (positionalArgument.NameColon is null || positionalArgument.NameColon.Name.Identifier.Text == parameterName))
{
argument = positionalArgument;
}
else
{
foreach (var argumentSyntax in invocationExpression.ArgumentList.Arguments)
{
if (argumentSyntax?.NameColon.Name.Identifier.Text != parameterName)
{
continue;
}

argument = argumentSyntax;
break;
}
}

if (argument is null)
{
continue;
}

var documentId = document.Project.Solution.GetDocument(argument.SyntaxTree)?.Id;
if (documentId is null)
{
continue;
}

if (!arguments.TryGetValue(documentId, out var argumentSpans))
{
argumentSpans = new List<TextSpan>();
arguments[documentId] = argumentSpans;
}

argumentSpans.Add(argument.Span);
}
}
}

var implementations = await SymbolFinder.FindImplementationsAsync(containingMethod, document.Project.Solution, projects: null, cancellationToken).ConfigureAwait(false);
foreach (var implementation in implementations)
{
foreach (var location in implementation.Locations)
{
var locationRoot = await location.SourceTree.GetRootAsync(cancellationToken).ConfigureAwait(false);
var node = locationRoot.FindNode(location.SourceSpan, getInnermostNodeForTie: true);
if (node is MethodDeclarationSyntax methodDeclaration)
{
var parameterSyntax = TryGetParameterAtPosition(methodDeclaration.ParameterList, parameterIndex);
if (parameterSyntax is null)
{
continue;
}

var documentId = document.Project.Solution.GetDocument(parameterSyntax.SyntaxTree)?.Id;
if (documentId is null)
{
continue;
}

if (!parameters.TryGetValue(documentId, out var parameterSpans))
{
parameterSpans = new List<TextSpan>();
parameters[documentId] = parameterSpans;
}

parameterSpans.Add(parameterSyntax.Span);
}
}
}

var overrides = await SymbolFinder.FindOverridesAsync(containingMethod, document.Project.Solution, projects: null, cancellationToken).ConfigureAwait(false);
foreach (var @override in overrides)
{
foreach (var location in @override.Locations)
{
var locationRoot = await location.SourceTree.GetRootAsync(cancellationToken).ConfigureAwait(false);
var node = locationRoot.FindNode(location.SourceSpan, getInnermostNodeForTie: true);
if (node is MethodDeclarationSyntax methodDeclaration)
{
var parameterSyntax = TryGetParameterAtPosition(methodDeclaration.ParameterList, parameterIndex);
if (parameterSyntax is null)
{
continue;
}

var documentId = document.Project.Solution.GetDocument(methodDeclaration.SyntaxTree)?.Id;
if (documentId is null)
{
continue;
}

if (!parameters.TryGetValue(documentId, out var parameterSpans))
{
parameterSpans = new List<TextSpan>();
parameters[documentId] = parameterSpans;
}

parameterSpans.Add(parameterSyntax.Span);
}
}
}
}

var result = document.Project.Solution;
foreach (var (documentId, spans) in arguments)
{
var originalDocument = result.GetDocument(documentId);
var root = await originalDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var argumentsToReplace = spans.Select(span => root.FindNode(span, getInnermostNodeForTie: true)).Select(node => node.FirstAncestorOrSelf<ArgumentSyntax>());
if (!parameters.TryGetValue(documentId, out var parameterSpans))
{
parameterSpans = new List<TextSpan>();
}

var parametersToReplace = parameterSpans.Select(span => root.FindNode(span, getInnermostNodeForTie: true)).Select(node => node.FirstAncestorOrSelf<ParameterSyntax>());
var newRoot = root.ReplaceNodes(
argumentsToReplace.Cast<SyntaxNode>().Concat(parametersToReplace),
(originalNode, rewrittenNode) =>
{
if (rewrittenNode is ArgumentSyntax argument)
{
return ((ArgumentSyntax)rewrittenNode).WithRefKindKeyword(SyntaxFactory.Token(SyntaxKind.InKeyword));
}
else
{
Debug.Assert(rewrittenNode is ParameterSyntax);
var trivia = rewrittenNode.GetLeadingTrivia();
return ((ParameterSyntax)rewrittenNode)
.WithModifiers(((ParameterSyntax)rewrittenNode).Modifiers.Insert(0, SyntaxFactory.Token(SyntaxKind.InKeyword)))
.WithLeadingTrivia(trivia);
}
});

result = result.WithDocumentSyntaxRoot(documentId, newRoot, PreservationMode.PreserveValue);
}

foreach (var (documentId, spans) in parameters)
{
if (arguments.ContainsKey(documentId))
{
continue;
}

var originalDocument = result.GetDocument(documentId);
var root = await originalDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var parametersToReplace = spans.Select(span => root.FindNode(span, getInnermostNodeForTie: true)).Select(node => node.FirstAncestorOrSelf<ParameterSyntax>());
var newRoot = root.ReplaceNodes(
parametersToReplace,
(originalNode, rewrittenNode) =>
{
var trivia = rewrittenNode.GetLeadingTrivia();
return rewrittenNode
.WithModifiers(rewrittenNode.Modifiers.Insert(0, SyntaxFactory.Token(SyntaxKind.InKeyword)))
.WithLeadingTrivia(trivia);
});

var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
result = result.WithDocumentSyntaxRoot(documentId, newRoot, PreservationMode.PreserveValue);
}

return result;
}

private static ParameterSyntax? TryGetParameterAtPosition(BaseParameterListSyntax? parameterList, int index)
{
if (parameterList is null)
{
return null;
}

if (parameterList.Parameters.Count < index)
{
return null;
}

return parameterList.Parameters[index];
}

private static ArgumentSyntax? TryGetArgumentAtPosition(BaseArgumentListSyntax? argumentList, int index)
{
if (argumentList is null)
{
return null;
}

if (argumentList.Arguments.Count < index)
{
return null;
}

return document.WithSyntaxRoot(root.ReplaceNode(paramSyntax, newType));
return argumentList.Arguments[index];
}
}
}
Loading

0 comments on commit b41c446

Please sign in to comment.