Skip to content

Commit 1dc9d91

Browse files
committed
Fix up Splat function finding, all tests pass
1 parent c4b6fab commit 1dc9d91

File tree

2 files changed

+59
-26
lines changed

2 files changed

+59
-26
lines changed

src/PowerShellEditorServices/Services/TextDocument/RenameService.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,11 @@ private TextEdit GetRenameVariableEdit(Ast ast)
481481
NewText = '-' + NewName,
482482
Range = new ScriptExtentAdapter(param.Extent)
483483
},
484+
StringConstantExpressionAst stringAst => new TextEdit
485+
{
486+
NewText = NewName,
487+
Range = new ScriptExtentAdapter(stringAst.Extent)
488+
},
484489
_ => throw new InvalidOperationException($"GetRenameVariableEdit was called on an Ast that was not the target. This is a bug and you should file an issue.")
485490
};
486491
}

src/PowerShellEditorServices/Utility/AstExtensions.cs

Lines changed: 54 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,37 @@ public static Ast[] FindParents(this Ast ast, params Type[] type)
280280
typeof(ForStatementAst)
281281
);
282282

283+
public static VariableExpressionAst? FindClosestParameterInFunction(this Ast target, string functionName, string parameterName)
284+
{
285+
Ast? scope = target.GetScopeBoundary();
286+
while (scope is not null)
287+
{
288+
FunctionDefinitionAst? funcDef = scope.FindAll
289+
(
290+
ast => ast is FunctionDefinitionAst funcDef
291+
&& funcDef.StartsBefore(target)
292+
&& funcDef.Name.ToLower() == functionName.ToLower()
293+
&& (funcDef.Parameters ?? funcDef.Body.ParamBlock.Parameters)
294+
.SingleOrDefault(
295+
param => param.Name.GetUnqualifiedName().ToLower() == parameterName.ToLower()
296+
) is not null
297+
, false
298+
).LastOrDefault() as FunctionDefinitionAst;
299+
300+
if (funcDef is not null)
301+
{
302+
return (funcDef.Parameters ?? funcDef.Body.ParamBlock.Parameters)
303+
.SingleOrDefault
304+
(
305+
param => param.Name.GetUnqualifiedName().ToLower() == parameterName.ToLower()
306+
)?.Name; //Should not be null at this point
307+
}
308+
309+
scope = scope.GetScopeBoundary();
310+
}
311+
return null;
312+
}
313+
283314
/// <summary>
284315
/// Returns true if the Expression is part of a variable assignment
285316
/// </summary>
@@ -333,9 +364,9 @@ public static bool IsScopedVariableAssignment(this VariableExpressionAst var)
333364
}
334365

335366
/// <summary>
336-
/// For a given string constant, determine if it is a splat, and there is at least one splat reference. If so, return a tuple of the variable assignment and the name of the splat reference. If not, return null.
367+
/// For a given string constant, determine if it is a splat, and there is at least one splat reference. If so, return the location of the splat assignment.
337368
/// </summary>
338-
public static VariableExpressionAst? FindSplatVariableAssignment(this StringConstantExpressionAst stringConstantAst)
369+
public static VariableExpressionAst? FindSplatParameterReference(this StringConstantExpressionAst stringConstantAst)
339370
{
340371
if (stringConstantAst.Parent is not HashtableAst hashtableAst) { return null; }
341372
if (hashtableAst.Parent is not CommandExpressionAst commandAst) { return null; }
@@ -359,7 +390,7 @@ ast is VariableExpressionAst var
359390
return varAst.FindBefore(ast =>
360391
ast is StringConstantExpressionAst stringAst
361392
&& stringAst.Value == varAst.GetUnqualifiedName()
362-
&& stringAst.FindSplatVariableAssignment() == varAst,
393+
&& stringAst.FindSplatParameterReference() == varAst,
363394
crossScopeBoundaries: true) as StringConstantExpressionAst;
364395
}
365396

@@ -389,11 +420,18 @@ public static bool TryGetFunction(this ParameterAst ast, out FunctionDefinitionA
389420
// Splats are special, we will treat them as a top variable assignment and search both above for a parameter assignment and below for a splat reference, but we don't require a command definition within the same scope for the splat.
390421
if (reference is StringConstantExpressionAst stringConstant)
391422
{
392-
VariableExpressionAst? splat = stringConstant.FindSplatVariableAssignment();
393-
if (splat is not null)
423+
VariableExpressionAst? splat = stringConstant.FindSplatParameterReference();
424+
if (splat is null) { return null; }
425+
// Find the function associated with the splat parameter reference
426+
string? commandName = (splat.Parent as CommandAst)?.GetCommandName().ToLower();
427+
if (commandName is null) { return null; }
428+
VariableExpressionAst? splatParamReference = splat.FindClosestParameterInFunction(commandName, stringConstant.Value);
429+
430+
if (splatParamReference is not null)
394431
{
395-
return reference;
432+
return splatParamReference;
396433
}
434+
397435
}
398436

399437
// If nothing found, search parent scopes for a variable assignment until we hit the top of the document
@@ -432,27 +470,17 @@ public static bool TryGetFunction(this ParameterAst ast, out FunctionDefinitionA
432470
// TODO: This could be less complicated
433471
if (reference is CommandParameterAst parameterAst)
434472
{
435-
FunctionDefinitionAst? closestFunctionMatch = scope.FindAll(
436-
ast => ast is FunctionDefinitionAst funcDef
437-
&& funcDef.Name.ToLower() == (parameterAst.Parent as CommandAst)?.GetCommandName()?.ToLower()
438-
&& (funcDef.Parameters ?? funcDef.Body.ParamBlock.Parameters).SingleOrDefault(
439-
param => param.Name.GetUnqualifiedName().ToLower() == name.ToLower()
440-
) is not null
441-
, false
442-
).LastOrDefault() as FunctionDefinitionAst;
443-
444-
if (closestFunctionMatch is not null)
445-
{
446-
//TODO: This should not ever be null but should probably be sure.
447-
return
448-
(closestFunctionMatch.Parameters ?? closestFunctionMatch.Body.ParamBlock.Parameters)
449-
.SingleOrDefault
450-
(
451-
param => param.Name.GetUnqualifiedName().ToLower() == name.ToLower()
452-
)?.Name;
453-
};
454-
};
473+
string? commandName = (parameterAst.Parent as CommandAst)?.GetCommandName()?.ToLower();
455474

475+
if (commandName is not null)
476+
{
477+
VariableExpressionAst? paramDefinition = parameterAst.FindClosestParameterInFunction(commandName, parameterAst.ParameterName);
478+
if (paramDefinition is not null)
479+
{
480+
return paramDefinition;
481+
}
482+
}
483+
}
456484
// Will find the outermost assignment that matches the reference.
457485
varAssignment = reference switch
458486
{

0 commit comments

Comments
 (0)