Skip to content

Commit 3628987

Browse files
authored
Merge pull request #4396 from BZngr/4319_RemoveParamInvalidCode
Fixes possible dangling trailing comma with refactor/remote-parameter.
2 parents 166e749 + be724ac commit 3628987

File tree

2 files changed

+543
-651
lines changed

2 files changed

+543
-651
lines changed

Rubberduck.Refactorings/RemoveParameters/RemoveParametersRefactoring.cs

Lines changed: 115 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,10 @@ public void Refactor(Declaration target)
9090
public void QuickFix(RubberduckParserState state, QualifiedSelection selection)
9191
{
9292
_model = new RemoveParametersModel(state, selection, new MessageBox());
93-
93+
9494
var target = _model.Parameters.SingleOrDefault(p => selection.Selection.Contains(p.Declaration.QualifiedSelection.Selection));
9595
Debug.Assert(target != null, "Target was not found");
96-
96+
9797
if (target != null)
9898
{
9999
_model.RemoveParameters.Add(target);
@@ -165,6 +165,7 @@ private void RemoveCallArguments(VBAParser.ArgumentListContext argList, Qualifie
165165
var rewriter = _model.State.GetRewriter(module);
166166
_rewriters.Add(rewriter);
167167

168+
var usesNamedArguments = false;
168169
var args = argList.children.OfType<VBAParser.ArgumentContext>().ToList();
169170
for (var i = 0; i < _model.Parameters.Count; i++)
170171
{
@@ -173,7 +174,7 @@ private void RemoveCallArguments(VBAParser.ArgumentListContext argList, Qualifie
173174
{
174175
continue;
175176
}
176-
177+
177178
if (_model.Parameters[i].IsParamArray)
178179
{
179180
//The following code works because it is neither allowed to use both named arguments
@@ -192,6 +193,7 @@ private void RemoveCallArguments(VBAParser.ArgumentListContext argList, Qualifie
192193
}
193194
else
194195
{
196+
usesNamedArguments = true;
195197
var arg = args.Where(a => a.namedArgument() != null)
196198
.SingleOrDefault(a =>
197199
a.namedArgument().unrestrictedIdentifier().GetText() ==
@@ -203,6 +205,8 @@ private void RemoveCallArguments(VBAParser.ArgumentListContext argList, Qualifie
203205
}
204206
}
205207
}
208+
209+
RemoveTrailingComma(rewriter, argList, usesNamedArguments);
206210
}
207211

208212
private void AdjustSignatures()
@@ -251,23 +255,127 @@ private void AdjustSignatures()
251255

252256
private Declaration GetLetterOrSetter(Declaration declaration, DeclarationType declarationType)
253257
{
254-
return _model.Declarations.FirstOrDefault(item => item.QualifiedModuleName.Equals(declaration.QualifiedModuleName)
255-
&& item.IdentifierName == declaration.IdentifierName
258+
return _model.Declarations.FirstOrDefault(item => item.QualifiedModuleName.Equals(declaration.QualifiedModuleName)
259+
&& item.IdentifierName == declaration.IdentifierName
256260
&& item.DeclarationType == declarationType);
257261
}
258262

259263
private void RemoveSignatureParameters(Declaration target)
260264
{
261265
var rewriter = _model.State.GetRewriter(target);
262266

263-
var parameters = ((IParameterizedDeclaration) target).Parameters.OrderBy(o => o.Selection).ToList();
264-
267+
var parameters = ((IParameterizedDeclaration)target).Parameters.OrderBy(o => o.Selection).ToList();
268+
265269
foreach (var index in _model.RemoveParameters.Select(rem => _model.Parameters.IndexOf(rem)))
266270
{
267271
rewriter.Remove(parameters[index]);
268272
}
269273

274+
RemoveTrailingComma(rewriter);
270275
_rewriters.Add(rewriter);
271276
}
277+
278+
//Issue 4319. If there are 3 or more arguments and the user elects to remove 2 or more of
279+
//the last arguments, then we need to specifically remove the trailing comma from
280+
//the last 'kept' argument.
281+
private void RemoveTrailingComma(IModuleRewriter rewriter, VBAParser.ArgumentListContext argList = null, bool usesNamedParams = false)
282+
{
283+
var commaLocator = RetrieveTrailingCommaInfo(_model.RemoveParameters, _model.Parameters);
284+
if (!commaLocator.RequiresTrailingCommaRemoval)
285+
{
286+
return;
287+
}
288+
289+
var tokenStart = 0;
290+
var tokenStop = 0;
291+
292+
if (argList is null)
293+
{
294+
//Handle Signatures only
295+
tokenStart = commaLocator.LastRetainedArg.Param.Declaration.Context.Stop.TokenIndex + 1;
296+
tokenStop = commaLocator.FirstOfRemovedArgSeries.Param.Declaration.Context.Start.TokenIndex - 1;
297+
rewriter.RemoveRange(tokenStart, tokenStop);
298+
return;
299+
}
300+
301+
302+
//Handles References
303+
var args = argList.children.OfType<VBAParser.ArgumentContext>().ToList();
304+
305+
if (usesNamedParams)
306+
{
307+
var lastKeptArg = args.Where(a => a.namedArgument() != null)
308+
.SingleOrDefault(a => a.namedArgument().unrestrictedIdentifier().GetText() ==
309+
commaLocator.LastRetainedArg.Identifier);
310+
311+
var firstOfRemovedArgSeries = args.Where(a => a.namedArgument() != null)
312+
.SingleOrDefault(a => a.namedArgument().unrestrictedIdentifier().GetText() ==
313+
commaLocator.FirstOfRemovedArgSeries.Identifier);
314+
315+
tokenStart = lastKeptArg.Stop.TokenIndex + 1;
316+
tokenStop = firstOfRemovedArgSeries.Start.TokenIndex - 1;
317+
rewriter.RemoveRange(tokenStart, tokenStop);
318+
return;
319+
}
320+
tokenStart = args[commaLocator.LastRetainedArg.Index].Stop.TokenIndex + 1;
321+
tokenStop = args[commaLocator.FirstOfRemovedArgSeries.Index].Start.TokenIndex - 1;
322+
rewriter.RemoveRange(tokenStart, tokenStop);
323+
}
324+
325+
private CommaLocator RetrieveTrailingCommaInfo(List<Parameter> toRemove, List<Parameter> allParams)
326+
{
327+
if (toRemove.Count == allParams.Count || allParams.Count < 3)
328+
{
329+
return new CommaLocator();
330+
}
331+
332+
var reversedAllParams = allParams.OrderByDescending(tr => tr.Declaration.Selection);
333+
var rangeRemoval = new List<Parameter>();
334+
for (var idx = 0; idx < reversedAllParams.Count(); idx++)
335+
{
336+
if (toRemove.Contains(reversedAllParams.ElementAt(idx)))
337+
{
338+
rangeRemoval.Add(reversedAllParams.ElementAt(idx));
339+
continue;
340+
}
341+
342+
if (rangeRemoval.Count >= 2)
343+
{
344+
var startIndex = allParams.FindIndex(par => par == reversedAllParams.ElementAt(idx));
345+
var stopIndex = allParams.FindIndex(par => par == rangeRemoval.First());
346+
347+
return new CommaLocator()
348+
{
349+
RequiresTrailingCommaRemoval = true,
350+
LastRetainedArg = new CommaBoundary()
351+
{
352+
Param = reversedAllParams.ElementAt(idx),
353+
Index = startIndex,
354+
},
355+
FirstOfRemovedArgSeries = new CommaBoundary()
356+
{
357+
Param = rangeRemoval.First(),
358+
Index = stopIndex,
359+
}
360+
};
361+
}
362+
break;
363+
}
364+
return new CommaLocator();
365+
}
366+
367+
private struct CommaLocator
368+
{
369+
public bool RequiresTrailingCommaRemoval;
370+
public CommaBoundary LastRetainedArg;
371+
public CommaBoundary FirstOfRemovedArgSeries;
372+
}
373+
374+
private struct CommaBoundary
375+
{
376+
public Parameter Param;
377+
public int Index;
378+
public string Identifier => Param.Declaration.IdentifierName;
379+
}
272380
}
273381
}

0 commit comments

Comments
 (0)