1- using System . Collections . Immutable ;
1+ using System ;
2+ using System . Collections . Generic ;
3+ using System . Collections . Immutable ;
24using System . Composition ;
35using System . Diagnostics ;
46using System . Linq ;
57using System . Threading ;
68using System . Threading . Tasks ;
9+ using ErrorProne . NET . Core ;
710using Microsoft . CodeAnalysis ;
811using Microsoft . CodeAnalysis . CodeActions ;
912using Microsoft . CodeAnalysis . CodeFixes ;
1013using Microsoft . CodeAnalysis . CSharp ;
1114using Microsoft . CodeAnalysis . CSharp . Syntax ;
1215using Microsoft . CodeAnalysis . FindSymbols ;
16+ using Microsoft . CodeAnalysis . Text ;
1317
1418namespace 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