-
Notifications
You must be signed in to change notification settings - Fork 382
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ReviewUnusedParameter does not capture parameter usage within a scriptblock #1472
Comments
@mattmcnabb @rjmholt It seems we actually discussed this in the PR review with divided opinions: #1382 (comment) |
This caused some of my CI pipelines to suddenly fail today. Since I hadn't pinned the version of PSScriptAnalyzer that was used in the pipelines, and since I hadn't updated to 1.19.0 locally, I thought something was wrong with the pipeline or that I'd inadvertently introduced an error to our scripts. As a user I'd expect to receive a warning only if there really is an issue. I like the idea of the rule, but I'd rather forgo it entirely than have to add workarounds to our scripts or toggle In case I'm not understanding what the (This is my first time commenting on a PSScriptAnalyzer issue or PR. -Even though this new rule has been negative for me, I want to thank you for your work on this tool--it's been a great help not only for ensuring a clear and consistent style for our scripts but also for teaching how to use PowerShell.) |
We can theoretically know in this case that the variable will be used; However, this is going to be undecidable in general, since I can write a program like this: function New-ScriptBlock
{
param($x)
{ "`$x: $x" }
}
$sb = New-ScriptBlock -x 'Hi'
& $sb We can solve the common case problem for commands that pass and invoke scriptblocks, but the blocker there is parameter binding; to properly resolve when a scriptblock corresponds to a parameter that's going to be invoked immediately, we really need a general purpose way to decide which argument corresponds to which parameter. That's where I got to here; it's not just that we need to solve it for |
I think for now it's ok to search nested scriptblocks though |
There is also this proposal in PowerShell to help PSSA: PowerShell/PowerShell#12287 |
@kmbn The idea behind |
Yeah, this proposal would help in the cases we don't know about, but most of the time people use In this case, the simple solution is to also look in the child scriptblock. The better solution is to search the child scriptblock when the command is |
See See PowerShell/PSScriptAnalyzer#1472 Co-authored-by: Christoph Bergmeister [MVP] <[email protected]>
See See PowerShell/PSScriptAnalyzer#1472 Co-authored-by: Christoph Bergmeister [MVP] <[email protected]>
See See PowerShell/PSScriptAnalyzer#1472 Co-authored-by: Christoph Bergmeister [MVP] <[email protected]>
See See PowerShell/PSScriptAnalyzer#1472 Co-authored-by: Christoph Bergmeister [MVP] <[email protected]>
To add to the list, we're also seeing this fail with |
@JPRuskin The rule looks only in the current scope at the moment, which is something we should definitely improve as per above comments. Thanks for the suggestion to also scan for
|
Somehow a number of PSScriptAnalyzer issues snuck in. This fixes them. The main PSScriptAnalyzer issues needing to be fixed were: * `PSReviewUnusedParameter` - This one came up a lot due to our heavy usage of `Resolve-RepositoryElements` and `Resolve-ParameterWithDefaultConfigurationValue` which both end up referencing their parameters by grabbing them off the stack. That means that `NoStatus` and `Uri` are frequently never directly referenced. So, exceptions were added. There were two cases (in GitHubAnalytics) where there was a false positive due to PowerShell/PSScriptAnalyzer#1472 * `PSUseProcessBlockForPipelineCommand` - We had a number of functions that took pipeline input, but didn't actuall use the `process` block. This actually caught a bug with `Group-GitHubIssue` and `Group-GitHubPullRequest`. Added correct `process` block usage for most of the functions, but removed pipeline support for those where it didn't actually make sense anymore. * `PSUseDeclaredVarsMoreThanAssignments` - These are false positives in the Pester tests due to the usage of `BeforeAll`. There wasn't an obvious way to use `SuppressMessageAttribute` in the Pester test, so I used a hacky workaround to "use" the variable in the `BeforeAll` block. I could have added the suppression to the top of the file, but I still want to catch real issues in those files later. Also, it turns out that `Group-GitHubPullRequest` hadn't actually been exported, so I fixed that too.
Somehow a number of PSScriptAnalyzer issues snuck in. This fixes them. The main PSScriptAnalyzer issues needing to be fixed were: * `PSReviewUnusedParameter` - This one came up a lot due to our heavy usage of `Resolve-RepositoryElements` and `Resolve-ParameterWithDefaultConfigurationValue` which both end up referencing their parameters by grabbing them off the stack. That means that `NoStatus` and `Uri` are frequently never directly referenced. So, exceptions were added. There were two cases (in GitHubAnalytics) where there was a false positive due to PowerShell/PSScriptAnalyzer#1472 * `PSUseProcessBlockForPipelineCommand` - We had a number of functions that took pipeline input, but didn't actuall use the `process` block. This actually caught a bug with `Group-GitHubIssue` and `Group-GitHubPullRequest`. Added correct `process` block usage for most of the functions, but removed pipeline support for those where it didn't actually make sense anymore. * `PSUseDeclaredVarsMoreThanAssignments` - These are false positives in the Pester tests due to the usage of `BeforeAll`. There wasn't an obvious way to use `SuppressMessageAttribute` in the Pester test, so I used a hacky workaround to "use" the variable in the `BeforeAll` block. I could have added the suppression to the top of the file, but I still want to catch real issues in those files later. * `PSAvoidOverwritingBuiltInCmdlets` - It turns out that there's a bug with PSDesiredStateConfiguration in PS Core 6.1.0 where it was exporting internal functions. This was thus a false-postive flag for Write-Log. See PowerShell/PowerShell#7209 for more info. Also, it turns out that `Group-GitHubPullRequest` hadn't actually been exported, so I fixed that too.
Somehow a number of PSScriptAnalyzer issues snuck in. This fixes them. The main PSScriptAnalyzer issues needing to be fixed were: * `PSReviewUnusedParameter` - This one came up a lot due to our heavy usage of `Resolve-RepositoryElements` and `Resolve-ParameterWithDefaultConfigurationValue` which both end up referencing their parameters by grabbing them off the stack. That means that `NoStatus` and `Uri` are frequently never directly referenced. So, exceptions were added. There were two cases (in GitHubAnalytics) where there was a false positive due to PowerShell/PSScriptAnalyzer#1472 * `PSUseProcessBlockForPipelineCommand` - We had a number of functions that took pipeline input, but didn't actuall use the `process` block. This actually caught a bug with `Group-GitHubIssue` and `Group-GitHubPullRequest`. Added correct `process` block usage for most of the functions, but removed pipeline support for those where it didn't actually make sense anymore. * `PSUseDeclaredVarsMoreThanAssignments` - These are false positives in the Pester tests due to the usage of `BeforeAll`. There wasn't an obvious way to use `SuppressMessageAttribute` in the Pester test, so I used a hacky workaround to "use" the variable in the `BeforeAll` block. I could have added the suppression to the top of the file, but I still want to catch real issues in those files later. * `PSAvoidOverwritingBuiltInCmdlets` - It turns out that there's a bug with PSDesiredStateConfiguration in PS Core 6.1.0 where it was exporting internal functions. This was thus a false-postive flag for Write-Log. See PowerShell/PowerShell#7209 for more info. Also, it turns out that `Group-GitHubPullRequest` hadn't actually been exported, so I fixed that too.
I have found another way in which this rule incorrectly fires which is related to this issue: Consider this: function get-DoesContainScheme {
[OutputType([boolean])]
param(
[Parameter()]
[string]$SchemeName,
[Parameter()]
[object[]]$Schemes
)
# $null = $SchemeName;
$found = $Schemes | Where-Object { $_.name -eq $SchemeName };
return ($null -ne $found);
} Reports:
❗ : note the commented out $null statement. When this is uncommented, the rule no longer fires. ... and for completeness:
|
This issue is also observed in Pester tests when you directly use Parameter in "It" statement. |
Amazing, thanks for the clarification there. I can confirm it works now :) For anyone else getting stuck like I did:
|
I've noticed that PSScriptAnalyzer doesn't appear to detect conditions where the use of the variable within the script is like so: |
@robinmalik I do not see the behaviour that you see, the following does not report a warning function Test {
Param (
[String]$a
)
if ($a) {
return
}
} |
@bergmeister Thanks, I can't recall what scripts triggered this originally (possibly my mistake) but I'm also unable to see that behaviour with your example given. If I do come across the issue again I'll report back, but disregard it for now :) |
First of all, using the workaround: [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', 'ReplaceWithParameterName', Justification = 'False positive as rule does not scan child scopes')] Is the correct way to suppress this false positive. But in case it concerns a lot of parameters, you might also use this dirty workaround: function foo {
Param(
$MyParameter1,
$MyParameter2,
$MyParameter3
)
$Null = $MyParameter1, $MyParameter2, $MyParameter3 # Prevent PSReviewUnusedParameter false positive
Get-Item| ForEach-Object { Get-ChildItem $MyParameter1 }
} Note that whenever this request #1894 would be implemented, it won't capture this dirty workaround |
FWIW, I have avoided this problem in Pester tests by making any variable that will be used in multiple ScriptBlocks be Just be careful that anything you dot-source doesn't also use a script-scoped variable of the same name or you will cause problems for yourself. Before (analyzer warnings)After (no warnings) |
Heya, as this rule has been bugging me for some time now, I've done a quick PR for a proposed solution, traversing into the scriptblocks passed to whitelisted commands (like Any thoughts on that approach? |
@FriedrichWeinmann The only problem with this suggestion I'm seeing for now is that a whitelisting based approach does not fix the issue where PSSA does trigger this rule for an "unused" parameter that is simply in a sub-scope regardless from some specific command usage, i.e. any locally defined function, for example. |
Same for me and we are apparently not the only one (looking to the visibility of this issue and the duplicates).
false positives are worse than false negatives (knowing that is the default behavior without PSScriptAnalyzer or specific rule). From that view, I think it is better to approach this wit Blacklisted constrains.
might not indeed be a good practice, but the reason for this is not because Anyways, I think that these two rules ( |
Please fix this bug. It is not an "enhancement." Using a variable in a script block = "using a variable." Whether the script block is actually invoked is irrelevant. "Using" a parameter by referencing it from an unused script block is like "using" it by storing it in an unused hashtable. The question is no longer whether the parameter has been used, but whether the new value is used; the original parameter should no longer be linted Here's another replication case: function Invoke-In {
<#
.SYNOPSIS
Runs a block in a pushed directory, popping it afterwards.
#>
param (
[Parameter(Mandatory=$True, Position=0)]
[ValidateScript({Test-Path -PathType Container -LiteralPath $_})]
[string]$Directory,
[Parameter(Mandatory=$True, Position=1)]
[ScriptBlock]$CodeBlock
)
pushd $Directory
try {
return & $CodeBlock
} finally {
popd
}
}
function Build-Proj {
param (
[string] $Platform
)
Invoke-In vp3d {
echo MSBuild -noLogo Proj.vcxproj -p:Configuration=Release -p:Platform=$Platform -consoleLoggerParameters:ForceConsoleColor
}
} If |
Any progress over here, issue reported 3 years back and still no fix? |
@muvijay There has been progress on this, actually. Although https://github.com/PowerShell/PSScriptAnalyzer/blob/master/CHANGELOG.MD has not been updated, apparently. It's still on
Reusing my own old testcase here, this does not work and still triggers a param(
[Parameter(Position = 0)]
[string] $ExampleInput = 'I am just a simple DEMO string',
[switch] $Lower,
[switch] $Upper,
[switch] $Variant,
[switch] $DemoSwitch
)
function Assert-Parameter ([string] $In) {
if ($DemoSwitch) {
return $In.Replace('string', 'string, DemoSwitch is SET! SUCCESS!')
} else {
return $In.Replace('string', 'string, DemoSwitch is NOT SET! ("default" operation)')
}
}
if ($Variant) {
Write-Output (Assert-Parameter -In $ExampleInput)
} elseif ($Lower) {
Write-Output $ExampleInput.ToLower()
} elseif ($Upper) {
Write-Output $ExampleInput.ToUpper()
} else {
Write-Output $ExampleInput
} To be fair, my custom function here is used inside parentheses as a parameter to param(
[Parameter(Position = 0)]
[string] $ExampleInput = 'I am just a simple DEMO string',
[switch] $Lower,
[switch] $Upper,
[switch] $Variant,
[switch] $DemoSwitch
)
function Assert-Parameter ([string] $In) {
if ($DemoSwitch) {
return $In.Replace('string', 'string, DemoSwitch is SET! SUCCESS!')
} else {
return $In.Replace('string', 'string, DemoSwitch is NOT SET! ("default" operation)')
}
}
if ($Variant) {
$Result = Assert-Parameter -In $ExampleInput
Write-Output $Result
} elseif ($Lower) {
Write-Output $ExampleInput.ToLower()
} elseif ($Upper) {
Write-Output $ExampleInput.ToUpper()
} else {
Write-Output $ExampleInput
} It still triggers the I have this in PSReviewUnusedParameter = @{
CommandsToTraverse = @(
'Assert-Parameter'
)
} Seems like it does not do what I think it is supposed to do here... Tagging @FriedrichWeinmann here, maybe he can weigh in? |
Heya :) Assert-Parameter { if ($DemoSwitch) { "Yay" } } I am fundamentally opposed to scope boundary violations, so I did not even consider implementing looking into child-functions for this, sorry. Honestly, I'm also against ever implementing it that way, in order to not encourage people to do so. |
Hey, thanks for weighing in so quickly! 😄 Okay, I see what you mean by scriptblock only. But I'm a but confused, why would you describe this as a scope boundary violation? I mean, it's still a scope issue, this should be clear. I still don't see the fundamental difference here with regard to Also, what exactly would you intent to discourage here? |
The scriptblocks provided to ForEach-Object & Where-Object do not become part of the code implementing the command - they are code provided to the commands, which the commands then execute for the caller (script). The scriptblocks are not executed within the context of those commands. Pipeline usage doesn't really factor in here.
There is a commonly propagated best practice where functions should not directly access variables from their parent scopes - all information a command needs should be provided via parameter, all results returned via output. function Get-Test {
$Test
}
$Test = 42
Get-Test But!! There is one problem with that approach: It's really hard to track, where the information came from and where it's going to. Also, imagine your helper function turns out useful and you want to copy it over to another command/script. Now you need to remember exactly about that variable you used or figure out some other way to track this. If instead you had made it a parameter, you'd have a central location to look up what input the command needs: function Get-Test {
[CmdletBinding()]
param (
$Test
)
$Test
}
$Test = 42
Get-Test -Test $Test I know, it's a slightly greater time investment upfront, but it pays its dividends in easier maintainability for your busy future self. My rant on this topic is definitely not inspired by me having been hired several times to fix legacy code bases and having to figure out just where the heck that crappy piece of information is coming from and why it sometimes isn't what it should be :grml: ... ;) |
I'm actually stuck with my habit of keeping the code without any flags (of whatever color) telling me "something is not right here" in VS Code. Right now my problem is
|
Yeah, I need to patch that rule as well so it can do the same thing with Command Traversal as I did for ReviewUnusedParameter |
But the scriptblocks are passed on to
I see, thanks.
Yeah, I get what you mean. And I agree, although there's one possible difference to make here, and that is, like in my ad-hoc testcase here, if one is doing a read-only access into the parent scope. This is supported just fine by Powershell, without needing to use any scope modifiers. But I see the benefit of always using parameters for the inputs of a function etc. Not sure, I've actually read https://github.com/PoshCode/PowerShellPracticeAndStyle |
Yepp. Technically it works just fine - it's just usually not a great idea (but faster, especially if you need to access a dozen different pieces of data). I'm mostly arguing against expedience in the short term at the cost of long term pain.
They are, but they keep their context - so when they get run from inside the command, they are not in the command context.
What does NOT happen is this:
The script scope (and the functions in that script) are invisible to any commands from modules you call. What is more important however from a PSScriptAnalyzer perspective is the Abstract Syntax Tree - the structured view on the code as processed by the parser. There is a huge difference between a scriptblock and a function definition there. Making sure we do a proper, reliable detection is a lot more iffy when we have to traverse into a FunctionDefinitionAst. And that to support/enable a practice I really don't want to encourage :) |
It is good to see that this issue is finally fixed in
There is although a quiet similar (I think) issue with Is it possible to apply your experience with this issue on that issue as well? |
According to PowerShell/PSScriptAnalyzer#1472 (comment), this rule is now evaluated with less false positive.
Steps to reproduce
Run
Invoke-ScriptAnalyzer
against the following with the new 1.19.0 release.Expected behavior
No rule violations.
Actual behavior
The new
ReviewUnusedParameter
rule doesn't notice the usage. I suspect this is similar to the limitation of theAvoidUsingCmdletAliases
rule though. Not sure if we should relax theReviewUnusedParameter
rule in this case to search nested scriptblocks inside a function scope.cc @mattmcnabb @rjmholt @JamesWTruher
If an unexpected error was thrown then please report the full error details using e.g.
$error[0] | Select-Object *
Environment data
The text was updated successfully, but these errors were encountered: