-
Notifications
You must be signed in to change notification settings - Fork 388
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
Trigger AvoidPositionalParameters rule for function defined and called inside a script. #963
Trigger AvoidPositionalParameters rule for function defined and called inside a script. #963
Conversation
Thanks for contributing. 😃 |
Rules/AvoidPositionalParameters.cs
Outdated
@@ -54,7 +54,6 @@ public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName) | |||
if ((Helper.Instance.IsCmdlet(cmdAst) || declaredFunctionNames.Contains(cmdAst.GetCommandName())) && | |||
(Helper.Instance.PositionalParameterUsed(cmdAst))) | |||
{ | |||
Console.WriteLine("Cmdlet or function call"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you know that you can just attach the debugger to it and step through it by attaching to the PowerShell process? In Debug, the cmdlet even has an -AttachAndDebug
switch to automatically attach to Visual Studio.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't know that. Thanks.
I removed debug line.
if (HasSplattedVariable(cmdAst)) | ||
{ | ||
return false; | ||
} | ||
|
||
if (commandInfo != null && commandInfo.CommandType == System.Management.Automation.CommandTypes.Cmdlet) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As in this function we probably should care only about the number of arguments which have no corresponding parameters, analyzing switch parameters seems unnecessary in here...
this code triggers the rule : $sb={
Function Get-MyCommand {
param(
$A
)
"Test"
}
Get-MyCommand -A 'Get-ChildItem'
Get-MyCommand 'Get-ChildItem'
}
Invoke-ScriptAnalyzer -ScriptDefinition "$sb" this code too : $sb={
Function Get-MyCommand {
param(
[Parameter(Mandatory=$true,Position=1)]
$A,
[Parameter(Position=2)]
$B,
[Parameter(Position=3)]
$C
)
"Test"
}
Get-MyCommand -A 'Get-ChildItem' -B 'Microsoft.PowerShell.Management' -C 'System.Management.Automation.Cmdlet'
}
Invoke-ScriptAnalyzer -ScriptDefinition "$sb" |
Counting positional parameters should be fixed. |
@kalgiz Thank you. $sb={
Function Get-MyCommand {
param(
[Parameter(Mandatory=$true,Position=1)]
$A,
[Parameter(Position=2)]
$B,
[Parameter(Position=3)]
$C
)
"Test"
}
Get-MyCommand P1 P2 P3 #OK -> Error
Get-MyCommand -A P1 P2 P3 #OK -> No error
'Test' | Get-MyCommand -A P1 P2 P3 #OK -> No error
'Test' | Get-MyCommand P1 P2 P3 #NOK ?? -> No error
}
Invoke-ScriptAnalyzer -ScriptDefinition "$sb" |
@kalgiz Thank you for your efforts. If the PR is ready for review and test, please remove |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall, looks very good! Only some minor comments. All tests showed no regression. For the record, the result were:
- Windows PowerShell 3 (WinServer 2012): 10 (known) test failures
- Windows PowerShell 4 (WinServer 2012R2): 0 test failures
- Windows PowerShell 5.1 (WinServer 2016 and Win10): 0 test failures
- PowerShell Core 6.0.2 (Windows 10): 9 (known) test failures
- PowerShell Core 6.0.2 (Hosted Linux agent on VSTS (Ubuntu 16)): 9 (known) test failures
Engine/Helper.cs
Outdated
// Because of the way we count, we will also count the cmdlet as an argument so we have to -1 | ||
int arguments = -1; | ||
int argumentsWithoutParameters = -1; | ||
bool wasParameter = false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are argumentsWithoutPositionalParameters
and wasPositionalParameter
more descriptive? (optional comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed names for more descriptive: argumentsWithoutProcedingParameters, parameterPreceding
Engine/Helper.cs
Outdated
continue; | ||
wasParameter = true; | ||
} else { | ||
if (!wasParameter) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use consistent bracing style of the surrounding code. For C#
code, braces should be on their own line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
Rules/AvoidPositionalParameters.cs
Outdated
|
||
foreach (Ast foundAst in functionDefinitionAsts) { | ||
FunctionDefinitionAst functionDefinitionAst = (FunctionDefinitionAst)foundAst; | ||
if (string.IsNullOrEmpty(functionDefinitionAst.Name)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
brace style as per comment above
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
Rules/AvoidPositionalParameters.cs
Outdated
HashSet<String> declaredFunctionNames = new HashSet<String>(); | ||
|
||
foreach (Ast foundAst in functionDefinitionAsts) { | ||
FunctionDefinitionAst functionDefinitionAst = (FunctionDefinitionAst)foundAst; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can inline the cast in the foreach loop: foreach (FunctionDefinitionAst functionDefinitionAst in functionDefinitionAsts)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
Rules/UseCmdletCorrectly.cs
Outdated
@@ -129,7 +129,7 @@ private bool MandatoryParameterExists(CommandAst cmdAst) | |||
return true; | |||
} | |||
|
|||
if (mandParams.Count() == 0 || Helper.Instance.PositionalParameterUsed(cmdAst)) | |||
if (mandParams.Count() == 0 || (Helper.Instance.IsCmdlet(cmdAst) && Helper.Instance.PositionalParameterUsed(cmdAst))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
!mandParams.Any()
performs in general better than mandParams.Count() == 0
for IEnumerable
types because the former stops on the first element but the latter enumerates over all elements. But in this case it is a List
, therefore we can even just use the property mandParams.Count == 0
, which does not need to enumerate at all. I know, this code was already like that before, I am not criticising you but we should take the opportunity and make this faster for free.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, fixed.
} | ||
$warnings = Invoke-ScriptAnalyzer -ScriptDefinition "$sb" | ||
$warnings.Count | Should -BeGreaterThan 0 | ||
$warnings.RuleName | Should -Contain "PSAvoidUsingPositionalParameters" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the variable $violationName = "PSAvoidUsingPositionalParameters"
defined at the top should be re-used
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
Foo "a" "b" "c" | ||
} | ||
$warnings = Invoke-ScriptAnalyzer -ScriptDefinition "$sb" | ||
$warnings.Count | Should -BeGreaterThan 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not -Be 1
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem is that the other violation appears PSAvoidTrailingWhitespace if I write the script like this:
<$sb=
{
Function Foo {
param(
[Parameter(Mandatory=$true,Position=1)] $A,
[Parameter(Position=2)]$B,
[Parameter(Position=3)]$C)
}
Foo "a" "b" "c"
} >
The warning doesn't show up when the script looks like:
<$sb=
{
Function Foo {
param(
[Parameter(Mandatory=$true,Position=1)] $A,
[Parameter(Position=2)]$B,
[Parameter(Position=3)]$C)
}
Foo "a" "b" "c"}>
But I don't like its readability.
I am not sure if PSAvoidTrailingWhitespace is an expected behaviour in here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The trailing whitespace is because of the indentation of the last brace, if you were to put the brace to the very left the rule would start to appear. Therefore this is by design and expected because it would be the same as if the last line in a script had space characters. Asserting against the exact number is more important than minor optical issues. I would be ok to just call Invoke-ScriptAnalyzer
with -ExcludeRule PSAvoidTrailingWhitespace
instead as an alternative. You should then also use the -Be
operators for the other assertions. I proposed in older PRs to filter out only the relevant rules using Invoke-ScriptAnalyzer -ScriptDefintion $scriptDefinition | Where-Object { $_.RuleName -eq 'PSAvoidUsingPositionalParameters' }
but Jim did not like this idea either (and he is also not in favour of testing the error message itself).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I got rid of AvoidTrailingWhitespaces trigger and of testing error message.
Engine/Helper.cs
Outdated
/// <param name="cmdAst"></param> | ||
/// <param name="moreThanThreePositional">only return true if more than three positional parameters are used</param> | ||
/// <returns></returns> | ||
public bool PositionalParameterUsed(CommandAst cmdAst, bool moreThanThreePositional = false) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this be moreThanTwoPositionalParameters
since the rule is >=3
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me. Thanks!
But please do not rebase during a PR. When the PR gets merged, it gets squashed anyway. It is OK to get upstream changes to avoid future merge conflicts but please use a git pull
instead for that because as you can see the diff on GitHub gets confused by that. P.S. When the PR builds run, they run on a PR branch to emulate the behaviour as if your branch was merged into development (to detect possible integration issues if the branch is behind).
Ok, sorry for that, I wanted to make git history cleaner. |
8775306
to
717bccd
Compare
717bccd
to
8775306
Compare
Engine/Helper.cs
Outdated
// Because of the way we count, we will also count the cmdlet as an argument so we have to -1 | ||
int arguments = -1; | ||
int argumentsWithoutProcedingParameters = -1; | ||
bool parameterPreceding = false; | ||
|
||
foreach (CommandElementAst ceAst in cmdAst.CommandElements) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this can all be written more simply:
var commandElementCollection = cmdAst.CommandElements;
if ( commandElementCollection.Count = 1 ) { return false; }
for(int i = 1; i < commandElementCollection.Length; i++) {
if ( commandElementCollection[i] isnot CommandParameterAst && commandElementCollection[i-1] isnot CommandParameterAst ) {
argumentsWithoutProcedingParameters++;
}
}
this should catch things like test-cmd a -a -b c,f d,z $a $b -q $() -e -f @() @{ one = 1 } -z "this is a test" "this is a test"
it doesn't validate that there may be missing parameter arguments, but it should catch any positional parameters
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
…d inside a sript given as argument to Script Analyzer.
…d inside a sript given as argument to Script Analyzer.
401b195
to
d760aae
Compare
d760aae
to
479fbc4
Compare
It seems this PR introduced a regression for a test case that is not in the test suite, which is that |
…ereby PSAvoidUsingPositionalParameters was not taking aliases into account any more (#1175) * Allow aliases or external script definitions as well so that positionalparameters triggers on them as well * add test
…d inside a script. (PowerShell#963) * Trigger AvoidPositionalParameters rule for function defined and called inside a sript given as argument to Script Analyzer. * Get rid of debug print. * Tests fpr PSAvoidPOsitionalParameters rule. * Tests for AvoidPositionalParameters rule fixes. * Counting positional parameters fix. * Code cleanup for AvoidPositionalParameter rule fix. * Trigger AvoidPositionalParameters rule for function defined and called inside a sript given as argument to Script Analyzer. * Tests fpr PSAvoidPOsitionalParameters rule. * Counting positional parameters fix. * Tests fixes for AvoidPositionalParameters rule. * Code cleanup
… 1.18.0 whereby PSAvoidUsingPositionalParameters was not taking aliases into account any more (PowerShell#1175) * Allow aliases or external script definitions as well so that positionalparameters triggers on them as well * add test
PR Summary
Trigger AvoidPositionalParameters rule for function defined and called inside a script.
fixes: #893
The rule doesn't trigger for the function defined and called in the script, because the script is not run and the function definition is not added to the powershell session runspace. Hence, the called function is not recognized as Cmdlet.
For the temporary solution I pull the function definitions from the script, store them and search these function calls for positional parameters.
PR Checklist
Note: Tick the boxes below that apply to this pull request by putting an
x
between the square brackets. Please mark anything not applicable to this PRNA
.WIP:
to the beginning of the title and remove the prefix when the PR is ready