Skip to content

Commit 75291c8

Browse files
jessehouwingCopilot
andcommitted
UseShouldProcessCorrectly: scope local function shadowing to the call site
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent e9de0f9 commit 75291c8

2 files changed

Lines changed: 49 additions & 3 deletions

File tree

‎Rules/UseShouldProcessCorrectly.cs‎

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -292,14 +292,24 @@ private bool DeclaresSupportsShouldProcess(FunctionDefinitionAst ast)
292292
/// Checks if the given command supports ShouldProcess
293293
/// </summary>
294294
/// <returns>False if input is null. If the input command has declares SupportsShouldProcess attribute, returns true</returns>
295-
private bool SupportsShouldProcess(string cmdName)
295+
private bool SupportsShouldProcess(string cmdName, IEnumerable<Ast> callSites)
296296
{
297297
if (String.IsNullOrWhiteSpace(cmdName))
298298
{
299299
return false;
300300
}
301301

302-
CommandInfo cmdInfo = Helper.Instance.GetCommandInfo(cmdName);
302+
// Credit the name if any invocation of it resolves to a real command rather than a function
303+
// that is only in scope elsewhere.
304+
CommandInfo cmdInfo = null;
305+
foreach (Ast callSite in callSites)
306+
{
307+
cmdInfo = Helper.Instance.GetCommandInfo(cmdName, callSite: callSite);
308+
if (cmdInfo != null)
309+
{
310+
break;
311+
}
312+
}
303313
if (cmdInfo == null)
304314
{
305315
return false;
@@ -410,7 +420,7 @@ private void CheckForSupportShouldProcess()
410420
if (funcDigraph.GetOutDegree(v) == 0)
411421
{
412422
// Member vertices carry a method name, which PowerShell never resolves as a command.
413-
if (v.IsCommand && SupportsShouldProcess(v.Name))
423+
if (v.IsCommand && SupportsShouldProcess(v.Name, v.CallSites))
414424
{
415425
commandsWithSupportShouldProcess.Add(v);
416426
}
@@ -451,6 +461,9 @@ public Ast Ast
451461
/// <summary>True when the vertex was reached through an actual command invocation.</summary>
452462
public bool IsCommand { get; set; }
453463

464+
/// <summary>Every command invocation merged into this name-keyed vertex.</summary>
465+
public List<Ast> CallSites { get; } = new List<Ast>();
466+
454467
private string name;
455468
private Ast ast;
456469
private bool isNestedFunctionDefinition;
@@ -572,6 +585,7 @@ public void AddVertex(Vertex vertex)
572585
// Vertices are keyed on name alone, so a name used both as a command and as a
573586
// member must stay marked as a command however the two are ordered.
574587
v.IsCommand |= vertex.IsCommand;
588+
v.CallSites.AddRange(vertex.CallSites);
575589
if (vertex.Ast != null
576590
&& vertex.Ast is FunctionDefinitionAst)
577591
{
@@ -635,6 +649,7 @@ public override AstVisitAction VisitCommand(CommandAst ast)
635649
}
636650

637651
var vertex = new Vertex (cmdName, ast) { IsCommand = true };
652+
vertex.CallSites.Add(ast);
638653
AddVertex(vertex);
639654
if (IsWithinFunctionDefinition())
640655
{

‎Tests/Rules/UseShouldProcessCorrectly.tests.ps1‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,37 @@ function Foo
280280
}
281281
}
282282

283+
Context "Nested function definitions only shadow commands within their own scope" {
284+
It "credits a call to the real command when a nested function elsewhere shares its name" {
285+
$scriptDef = @'
286+
function Outer
287+
{
288+
function Remove-Item { param($path) }
289+
}
290+
function Invoke-Thing
291+
{
292+
[CmdletBinding(SupportsShouldProcess)]
293+
param($path)
294+
Remove-Item $path
295+
}
296+
'@
297+
Invoke-ScriptAnalyzer -ScriptDefinition $scriptDef -IncludeRule PSShouldProcess | Should -BeNullOrEmpty
298+
}
299+
300+
It "does not credit a call that resolves to a nested function inside its own scope" {
301+
$scriptDef = @'
302+
function Invoke-Thing
303+
{
304+
[CmdletBinding(SupportsShouldProcess)]
305+
param($path)
306+
function Remove-Item { param($path) }
307+
Remove-Item $path
308+
}
309+
'@
310+
@(Invoke-ScriptAnalyzer -ScriptDefinition $scriptDef -IncludeRule PSShouldProcess).Count | Should -Be 1
311+
}
312+
}
313+
283314
Context "Method calls are not command calls" {
284315
# A quoted member name may contain a hyphen, so Remove-Item serves as a member whose name also
285316
# belongs to a cmdlet that declares SupportsShouldProcess on every platform.

0 commit comments

Comments
 (0)