Skip to content

Commit b41c446

Browse files
committed
EPS05 code fix should fix implementations and callers
Fixes #122 Fixes #123
1 parent 3145e3e commit b41c446

3 files changed

Lines changed: 446 additions & 9 deletions

File tree

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// --------------------------------------------------------------------
2+
//
3+
// Copyright (c) Microsoft Corporation. All rights reserved.
4+
//
5+
// --------------------------------------------------------------------
6+
7+
using System.Collections.Generic;
8+
9+
namespace ErrorProne.NET.Core
10+
{
11+
public static class KeyValuePairExtensions
12+
{
13+
public static void Deconstruct<TKey, TValue>(this KeyValuePair<TKey, TValue> pair, out TKey key, out TValue value)
14+
{
15+
key = pair.Key;
16+
value = pair.Value;
17+
}
18+
}
19+
}

src/ErrorProne.NET.StructAnalyzers.CodeFixes/UseInModifierForReadOnlyStructCodeFixProvider.cs

Lines changed: 229 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
1-
using System.Collections.Immutable;
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Collections.Immutable;
24
using System.Composition;
35
using System.Diagnostics;
46
using System.Linq;
57
using System.Threading;
68
using System.Threading.Tasks;
9+
using ErrorProne.NET.Core;
710
using Microsoft.CodeAnalysis;
811
using Microsoft.CodeAnalysis.CodeActions;
912
using Microsoft.CodeAnalysis.CodeFixes;
1013
using Microsoft.CodeAnalysis.CSharp;
1114
using Microsoft.CodeAnalysis.CSharp.Syntax;
1215
using Microsoft.CodeAnalysis.FindSymbols;
16+
using Microsoft.CodeAnalysis.Text;
1317

1418
namespace ErrorProne.NET.StructAnalyzers
1519
{
@@ -39,7 +43,7 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
3943
context.RegisterCodeFix(
4044
CodeAction.Create(
4145
title: Title,
42-
createChangedDocument: c => AddInModifier(context.Document, declaration, c),
46+
createChangedSolution: c => AddInModifier(context.Document, declaration, c),
4347
equivalenceKey: Title),
4448
diagnostic);
4549
}
@@ -115,17 +119,233 @@ private async Task<bool> ParameterIsUsedInNonInFriendlyManner(ParameterSyntax pa
115119
return false;
116120
}
117121

118-
private async Task<Document> AddInModifier(Document document, ParameterSyntax paramSyntax, CancellationToken cancellationToken)
122+
private async Task<Solution> AddInModifier(Document document, ParameterSyntax paramSyntax, CancellationToken cancellationToken)
119123
{
120-
SyntaxTriviaList trivia = paramSyntax.GetLeadingTrivia(); ;
124+
var arguments = new Dictionary<DocumentId, List<TextSpan>>();
125+
var parameters = new Dictionary<DocumentId, List<TextSpan>> { { document.Id, new List<TextSpan> { paramSyntax.Span } } };
121126

122-
var newType = paramSyntax
123-
.WithModifiers(paramSyntax.Modifiers.Insert(0, SyntaxFactory.Token(SyntaxKind.InKeyword)))
124-
.WithLeadingTrivia(trivia);
127+
var semanticModel = await document.GetSemanticModelAsync(cancellationToken);
128+
var parameterSymbol = semanticModel.GetDeclaredSymbol(paramSyntax, cancellationToken);
129+
if (parameterSymbol?.ContainingSymbol is IMethodSymbol containingMethod)
130+
{
131+
var parameterIndex = containingMethod.Parameters.IndexOf(parameterSymbol);
132+
var parameterName = parameterSymbol.Name;
133+
134+
var callers = await SymbolFinder.FindCallersAsync(containingMethod, document.Project.Solution, cancellationToken).ConfigureAwait(false);
135+
foreach (var caller in callers)
136+
{
137+
foreach (var location in caller.Locations)
138+
{
139+
if (!location.IsInSource)
140+
{
141+
continue;
142+
}
143+
144+
var locationRoot = await location.SourceTree.GetRootAsync(cancellationToken).ConfigureAwait(false);
145+
var node = locationRoot.FindNode(location.SourceSpan, getInnermostNodeForTie: true);
146+
147+
var invocationExpression = node.Parent as InvocationExpressionSyntax;
148+
if (invocationExpression is null)
149+
{
150+
invocationExpression = (node.Parent as MemberAccessExpressionSyntax)?.Parent as InvocationExpressionSyntax;
151+
}
152+
153+
if (invocationExpression is object)
154+
{
155+
ArgumentSyntax? argument = null;
156+
var positionalArgument = TryGetArgumentAtPosition(invocationExpression.ArgumentList, parameterIndex);
157+
if (positionalArgument is object && (positionalArgument.NameColon is null || positionalArgument.NameColon.Name.Identifier.Text == parameterName))
158+
{
159+
argument = positionalArgument;
160+
}
161+
else
162+
{
163+
foreach (var argumentSyntax in invocationExpression.ArgumentList.Arguments)
164+
{
165+
if (argumentSyntax?.NameColon.Name.Identifier.Text != parameterName)
166+
{
167+
continue;
168+
}
169+
170+
argument = argumentSyntax;
171+
break;
172+
}
173+
}
174+
175+
if (argument is null)
176+
{
177+
continue;
178+
}
179+
180+
var documentId = document.Project.Solution.GetDocument(argument.SyntaxTree)?.Id;
181+
if (documentId is null)
182+
{
183+
continue;
184+
}
185+
186+
if (!arguments.TryGetValue(documentId, out var argumentSpans))
187+
{
188+
argumentSpans = new List<TextSpan>();
189+
arguments[documentId] = argumentSpans;
190+
}
191+
192+
argumentSpans.Add(argument.Span);
193+
}
194+
}
195+
}
196+
197+
var implementations = await SymbolFinder.FindImplementationsAsync(containingMethod, document.Project.Solution, projects: null, cancellationToken).ConfigureAwait(false);
198+
foreach (var implementation in implementations)
199+
{
200+
foreach (var location in implementation.Locations)
201+
{
202+
var locationRoot = await location.SourceTree.GetRootAsync(cancellationToken).ConfigureAwait(false);
203+
var node = locationRoot.FindNode(location.SourceSpan, getInnermostNodeForTie: true);
204+
if (node is MethodDeclarationSyntax methodDeclaration)
205+
{
206+
var parameterSyntax = TryGetParameterAtPosition(methodDeclaration.ParameterList, parameterIndex);
207+
if (parameterSyntax is null)
208+
{
209+
continue;
210+
}
211+
212+
var documentId = document.Project.Solution.GetDocument(parameterSyntax.SyntaxTree)?.Id;
213+
if (documentId is null)
214+
{
215+
continue;
216+
}
217+
218+
if (!parameters.TryGetValue(documentId, out var parameterSpans))
219+
{
220+
parameterSpans = new List<TextSpan>();
221+
parameters[documentId] = parameterSpans;
222+
}
223+
224+
parameterSpans.Add(parameterSyntax.Span);
225+
}
226+
}
227+
}
228+
229+
var overrides = await SymbolFinder.FindOverridesAsync(containingMethod, document.Project.Solution, projects: null, cancellationToken).ConfigureAwait(false);
230+
foreach (var @override in overrides)
231+
{
232+
foreach (var location in @override.Locations)
233+
{
234+
var locationRoot = await location.SourceTree.GetRootAsync(cancellationToken).ConfigureAwait(false);
235+
var node = locationRoot.FindNode(location.SourceSpan, getInnermostNodeForTie: true);
236+
if (node is MethodDeclarationSyntax methodDeclaration)
237+
{
238+
var parameterSyntax = TryGetParameterAtPosition(methodDeclaration.ParameterList, parameterIndex);
239+
if (parameterSyntax is null)
240+
{
241+
continue;
242+
}
243+
244+
var documentId = document.Project.Solution.GetDocument(methodDeclaration.SyntaxTree)?.Id;
245+
if (documentId is null)
246+
{
247+
continue;
248+
}
249+
250+
if (!parameters.TryGetValue(documentId, out var parameterSpans))
251+
{
252+
parameterSpans = new List<TextSpan>();
253+
parameters[documentId] = parameterSpans;
254+
}
255+
256+
parameterSpans.Add(parameterSyntax.Span);
257+
}
258+
}
259+
}
260+
}
261+
262+
var result = document.Project.Solution;
263+
foreach (var (documentId, spans) in arguments)
264+
{
265+
var originalDocument = result.GetDocument(documentId);
266+
var root = await originalDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
267+
var argumentsToReplace = spans.Select(span => root.FindNode(span, getInnermostNodeForTie: true)).Select(node => node.FirstAncestorOrSelf<ArgumentSyntax>());
268+
if (!parameters.TryGetValue(documentId, out var parameterSpans))
269+
{
270+
parameterSpans = new List<TextSpan>();
271+
}
272+
273+
var parametersToReplace = parameterSpans.Select(span => root.FindNode(span, getInnermostNodeForTie: true)).Select(node => node.FirstAncestorOrSelf<ParameterSyntax>());
274+
var newRoot = root.ReplaceNodes(
275+
argumentsToReplace.Cast<SyntaxNode>().Concat(parametersToReplace),
276+
(originalNode, rewrittenNode) =>
277+
{
278+
if (rewrittenNode is ArgumentSyntax argument)
279+
{
280+
return ((ArgumentSyntax)rewrittenNode).WithRefKindKeyword(SyntaxFactory.Token(SyntaxKind.InKeyword));
281+
}
282+
else
283+
{
284+
Debug.Assert(rewrittenNode is ParameterSyntax);
285+
var trivia = rewrittenNode.GetLeadingTrivia();
286+
return ((ParameterSyntax)rewrittenNode)
287+
.WithModifiers(((ParameterSyntax)rewrittenNode).Modifiers.Insert(0, SyntaxFactory.Token(SyntaxKind.InKeyword)))
288+
.WithLeadingTrivia(trivia);
289+
}
290+
});
291+
292+
result = result.WithDocumentSyntaxRoot(documentId, newRoot, PreservationMode.PreserveValue);
293+
}
294+
295+
foreach (var (documentId, spans) in parameters)
296+
{
297+
if (arguments.ContainsKey(documentId))
298+
{
299+
continue;
300+
}
301+
302+
var originalDocument = result.GetDocument(documentId);
303+
var root = await originalDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
304+
var parametersToReplace = spans.Select(span => root.FindNode(span, getInnermostNodeForTie: true)).Select(node => node.FirstAncestorOrSelf<ParameterSyntax>());
305+
var newRoot = root.ReplaceNodes(
306+
parametersToReplace,
307+
(originalNode, rewrittenNode) =>
308+
{
309+
var trivia = rewrittenNode.GetLeadingTrivia();
310+
return rewrittenNode
311+
.WithModifiers(rewrittenNode.Modifiers.Insert(0, SyntaxFactory.Token(SyntaxKind.InKeyword)))
312+
.WithLeadingTrivia(trivia);
313+
});
125314

126-
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
315+
result = result.WithDocumentSyntaxRoot(documentId, newRoot, PreservationMode.PreserveValue);
316+
}
317+
318+
return result;
319+
}
320+
321+
private static ParameterSyntax? TryGetParameterAtPosition(BaseParameterListSyntax? parameterList, int index)
322+
{
323+
if (parameterList is null)
324+
{
325+
return null;
326+
}
327+
328+
if (parameterList.Parameters.Count < index)
329+
{
330+
return null;
331+
}
332+
333+
return parameterList.Parameters[index];
334+
}
335+
336+
private static ArgumentSyntax? TryGetArgumentAtPosition(BaseArgumentListSyntax? argumentList, int index)
337+
{
338+
if (argumentList is null)
339+
{
340+
return null;
341+
}
342+
343+
if (argumentList.Arguments.Count < index)
344+
{
345+
return null;
346+
}
127347

128-
return document.WithSyntaxRoot(root.ReplaceNode(paramSyntax, newType));
348+
return argumentList.Arguments[index];
129349
}
130350
}
131351
}

0 commit comments

Comments
 (0)