Skip to content

Commit a761031

Browse files
author
Kapil Borle
committed
Fix test failures caused by StaticParameterBinder
Two rules in the CommunityAnalzyerRules module use StaticParameterBinder class. This class was introduced in PowerShell version 4.0.
1 parent afa54df commit a761031

File tree

2 files changed

+171
-157
lines changed

2 files changed

+171
-157
lines changed

Tests/Engine/CommunityAnalyzerRules/CommunityAnalyzerRules.psm1

Lines changed: 162 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -224,84 +224,167 @@ function Measure-RequiresModules
224224
}
225225
}
226226

227-
<#
228-
.SYNOPSIS
229-
You can store the type name in a variable or using -f operator to reduce the amount of redundant information in your script.
230-
.DESCRIPTION
231-
When interacting with classes that have long type names, you want to reduce the amount of redundant information in your script.
232-
To fix a violation of this rule, please store the type name in a variable or using -f operator. For example:
233-
$namespace = "System.Collections.{0}"; $arrayList = New-Object ($namespace -f "ArrayList"); $queue = New-Object ($namespace -f "Queue")
234-
.EXAMPLE
235-
Measure-LongClassName -CommandAst $CommandAst
236-
.INPUTS
237-
[System.Management.Automation.Language.CommandAst]
238-
.OUTPUTS
239-
[Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]]
240-
.NOTES
241-
Reference: 3.11. Reduce Typying for Long Class Names, Windows PowerShell Cookbook, Third Edition
242-
#>
243-
function Measure-LongClassName
244-
{
245-
[CmdletBinding()]
246-
[OutputType([Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]])]
247-
Param
248-
(
249-
[Parameter(Mandatory = $true)]
250-
[ValidateNotNullOrEmpty()]
251-
[System.Management.Automation.Language.CommandAst]
252-
$CommandAst
253-
)
254-
255-
Process
256-
{
257-
$results = @()
258227

259-
# The StaticParameterBinder help us to find the argument of TypeName.
260-
$spBinder = [System.Management.Automation.Language.StaticParameterBinder]
261-
262-
# Checks New-Object without ComObject parameter command only.
263-
if ($null -ne $CommandAst.GetCommandName())
264-
{
265-
if ($CommandAst.GetCommandName() -ne "new-object")
266-
{
267-
return $results
268-
}
269-
}
270-
else
271-
{
272-
return $results
273-
}
274-
275-
try
276-
{
277-
[System.Management.Automation.Language.StaticBindingResult]$sbResults = $spBinder::BindCommand($CommandAst, $true)
278-
foreach ($sbResult in $sbResults)
279-
{
280-
# TypeName cannot be found if user run command like, New-Object -ComObject Scripting.FileSystemObject.
281-
if ($null -eq $sbResult.BoundParameters["TypeName"].ConstantValue) { continue }
282-
283-
if ($sbResult.BoundParameters["TypeName"].ConstantValue.ToString().Split('.').Length -ge 3)
284-
{
285-
# $sbResult.BoundParameters["TypeName"].Value is a CommandElementAst, so we can return an extent.
286-
$result = New-Object `
287-
-Typename "Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord" `
288-
-ArgumentList $Messages.MeasureLongClassName,$sbResult.BoundParameters["TypeName"].Value.Extent,$PSCmdlet.MyInvocation.InvocationName,Information,$null
289-
290-
$results += $result
291-
}
292-
}
293-
294-
return $results
295-
}
296-
catch
297-
{
298-
$PSCmdlet.ThrowTerminatingError($PSItem)
299-
}
228+
# The two rules in the following if block use StaticParameterBinder class.
229+
# StaticParameterBinder class was introduced in PSv4.
230+
if ($PSVersionTable.PSVersion -ge [Version]'4.0')
231+
{
232+
<#
233+
.SYNOPSIS
234+
You can store the type name in a variable or using -f operator to reduce the amount of redundant information in your script.
235+
.DESCRIPTION
236+
When interacting with classes that have long type names, you want to reduce the amount of redundant information in your script.
237+
To fix a violation of this rule, please store the type name in a variable or using -f operator. For example:
238+
$namespace = "System.Collections.{0}"; $arrayList = New-Object ($namespace -f "ArrayList"); $queue = New-Object ($namespace -f "Queue")
239+
.EXAMPLE
240+
Measure-LongClassName -CommandAst $CommandAst
241+
.INPUTS
242+
[System.Management.Automation.Language.CommandAst]
243+
.OUTPUTS
244+
[Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]]
245+
.NOTES
246+
Reference: 3.11. Reduce Typying for Long Class Names, Windows PowerShell Cookbook, Third Edition
247+
#>
248+
function Measure-LongClassName
249+
{
250+
[CmdletBinding()]
251+
[OutputType([Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]])]
252+
Param
253+
(
254+
[Parameter(Mandatory = $true)]
255+
[ValidateNotNullOrEmpty()]
256+
[System.Management.Automation.Language.CommandAst]
257+
$CommandAst
258+
)
259+
260+
Process
261+
{
262+
$results = @()
263+
264+
# The StaticParameterBinder help us to find the argument of TypeName.
265+
$spBinder = [System.Management.Automation.Language.StaticParameterBinder]
266+
267+
# Checks New-Object without ComObject parameter command only.
268+
if ($null -ne $CommandAst.GetCommandName())
269+
{
270+
if ($CommandAst.GetCommandName() -ne "new-object")
271+
{
272+
return $results
273+
}
274+
}
275+
else
276+
{
277+
return $results
278+
}
279+
280+
try
281+
{
282+
[System.Management.Automation.Language.StaticBindingResult]$sbResults = $spBinder::BindCommand($CommandAst, $true)
283+
foreach ($sbResult in $sbResults)
284+
{
285+
# TypeName cannot be found if user run command like, New-Object -ComObject Scripting.FileSystemObject.
286+
if ($null -eq $sbResult.BoundParameters["TypeName"].ConstantValue) { continue }
287+
288+
if ($sbResult.BoundParameters["TypeName"].ConstantValue.ToString().Split('.').Length -ge 3)
289+
{
290+
# $sbResult.BoundParameters["TypeName"].Value is a CommandElementAst, so we can return an extent.
291+
$result = New-Object `
292+
-Typename "Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord" `
293+
-ArgumentList $Messages.MeasureLongClassName,$sbResult.BoundParameters["TypeName"].Value.Extent,$PSCmdlet.MyInvocation.InvocationName,Information,$null
294+
295+
$results += $result
296+
}
297+
}
298+
299+
return $results
300+
}
301+
catch
302+
{
303+
$PSCmdlet.ThrowTerminatingError($PSItem)
304+
}
305+
306+
307+
}
308+
}
309+
310+
<#
311+
.SYNOPSIS
312+
Please do not use COM objects when calling New-Object.
313+
.DESCRIPTION
314+
If you can't use just PowerShell, use .NET, external commands or COM objects, in that order of preference. COM objects are rarely well-documented, making them harder for someone else to research and understand.
315+
They do not always work flawlessly in PowerShell, as they must be used through .NET's Interop layer, which isn't 100% perfect.
316+
To fix a violation of this rule, please do not use COM objects when calling New-Object.
317+
.EXAMPLE
318+
Measure-ComObject -CommandAst $CommandAst
319+
.INPUTS
320+
[System.Management.Automation.Language.CommandAst]
321+
.OUTPUTS
322+
[Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]]
323+
.NOTES
324+
Reference: The Purity Laws, The Community Book of PowerShell Practices.
325+
#>
326+
function Measure-ComObject
327+
{
328+
[CmdletBinding()]
329+
[OutputType([Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]])]
330+
Param
331+
(
332+
[Parameter(Mandatory = $true)]
333+
[ValidateNotNullOrEmpty()]
334+
[System.Management.Automation.Language.CommandAst]
335+
$CommandAst
336+
)
337+
338+
Process
339+
{
340+
$results = @()
341+
342+
# The StaticParameterBinder help us to find the argument of TypeName.
343+
$spBinder = [System.Management.Automation.Language.StaticParameterBinder]
344+
345+
# Checks New-Object without ComObject parameter command only.
346+
if ($null -ne $CommandAst.GetCommandName())
347+
{
348+
if ($CommandAst.GetCommandName() -ne "new-object")
349+
{
350+
return $results
351+
}
352+
}
353+
else
354+
{
355+
return $results
356+
}
357+
358+
try
359+
{
360+
[System.Management.Automation.Language.StaticBindingResult]$sbResults = $spBinder::BindCommand($CommandAst, $true)
361+
foreach ($sbResult in $sbResults)
362+
{
363+
if ($sbResults.BoundParameters.ContainsKey("ComObject"))
364+
{
365+
# $sbResult.BoundParameters["TypeName"].Value is a CommandElementAst, so we can return an extent.
366+
$result = New-Object `
367+
-Typename "Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord" `
368+
-ArgumentList $Messages.MeasureComObject,$sbResult.BoundParameters["ComObject"].Value.Extent,$PSCmdlet.MyInvocation.InvocationName,Warning,$null
369+
370+
$results += $result
371+
}
372+
}
373+
374+
return $results
375+
}
376+
catch
377+
{
378+
$PSCmdlet.ThrowTerminatingError($PSItem)
379+
}
380+
381+
382+
}
383+
}
384+
385+
} # end if ($PSVersionTable.PSVersion -ge [Version]'4.0')
300386

301387

302-
}
303-
}
304-
305388
<#
306389
.SYNOPSIS
307390
Do not use deprecated WMI class in your script.
@@ -357,81 +440,6 @@ function Measure-DeprecatedWMIClass
357440
}
358441
}
359442

360-
<#
361-
.SYNOPSIS
362-
Please do not use COM objects when calling New-Object.
363-
.DESCRIPTION
364-
If you can't use just PowerShell, use .NET, external commands or COM objects, in that order of preference. COM objects are rarely well-documented, making them harder for someone else to research and understand.
365-
They do not always work flawlessly in PowerShell, as they must be used through .NET's Interop layer, which isn't 100% perfect.
366-
To fix a violation of this rule, please do not use COM objects when calling New-Object.
367-
.EXAMPLE
368-
Measure-ComObject -CommandAst $CommandAst
369-
.INPUTS
370-
[System.Management.Automation.Language.CommandAst]
371-
.OUTPUTS
372-
[Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]]
373-
.NOTES
374-
Reference: The Purity Laws, The Community Book of PowerShell Practices.
375-
#>
376-
function Measure-ComObject
377-
{
378-
[CmdletBinding()]
379-
[OutputType([Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]])]
380-
Param
381-
(
382-
[Parameter(Mandatory = $true)]
383-
[ValidateNotNullOrEmpty()]
384-
[System.Management.Automation.Language.CommandAst]
385-
$CommandAst
386-
)
387-
388-
Process
389-
{
390-
$results = @()
391-
392-
# The StaticParameterBinder help us to find the argument of TypeName.
393-
$spBinder = [System.Management.Automation.Language.StaticParameterBinder]
394-
395-
# Checks New-Object without ComObject parameter command only.
396-
if ($null -ne $CommandAst.GetCommandName())
397-
{
398-
if ($CommandAst.GetCommandName() -ne "new-object")
399-
{
400-
return $results
401-
}
402-
}
403-
else
404-
{
405-
return $results
406-
}
407-
408-
try
409-
{
410-
[System.Management.Automation.Language.StaticBindingResult]$sbResults = $spBinder::BindCommand($CommandAst, $true)
411-
foreach ($sbResult in $sbResults)
412-
{
413-
if ($sbResults.BoundParameters.ContainsKey("ComObject"))
414-
{
415-
# $sbResult.BoundParameters["TypeName"].Value is a CommandElementAst, so we can return an extent.
416-
$result = New-Object `
417-
-Typename "Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord" `
418-
-ArgumentList $Messages.MeasureComObject,$sbResult.BoundParameters["ComObject"].Value.Extent,$PSCmdlet.MyInvocation.InvocationName,Warning,$null
419-
420-
$results += $result
421-
}
422-
}
423-
424-
return $results
425-
}
426-
catch
427-
{
428-
$PSCmdlet.ThrowTerminatingError($PSItem)
429-
}
430-
431-
432-
}
433-
}
434-
435443
<#
436444
.SYNOPSIS
437445
Adds end-of-line comment after closing curly bracket for deeply nested structures.
@@ -498,8 +506,8 @@ function Measure-CurlyBracket
498506
foreach ($ast in $asts)
499507
{
500508
# Checks nesting structures
501-
$nestingASTs = $asts.Where({($PSItem.Extent.StartLineNumber -gt $ast.Extent.StartLineNumber) -and
502-
($PSItem.Extent.EndLineNumber -lt $ast.Extent.EndLineNumber)})
509+
$nestingASTs = $asts | Where-Object {($PSItem.Extent.StartLineNumber -gt $ast.Extent.StartLineNumber) -and
510+
($PSItem.Extent.EndLineNumber -lt $ast.Extent.EndLineNumber)}
503511

504512
# If one AST have end-of-line comments, we should skip it.
505513
[bool]$needComment = $ast.Extent.EndScriptPosition.Line.Trim().EndsWith("}")
@@ -629,7 +637,7 @@ function Measure-Backtick
629637
try
630638
{
631639
# Finds LineContinuation tokens
632-
$lcTokens = $Token.Where({$PSItem.Kind -eq [System.Management.Automation.Language.TokenKind]::LineContinuation})
640+
$lcTokens = $Token | Where-Object {$PSItem.Kind -eq [System.Management.Automation.Language.TokenKind]::LineContinuation}
633641

634642
foreach ($lcToken in $lcTokens)
635643
{
@@ -830,7 +838,7 @@ function Measure-QuestionVariable
830838
$sbAst = [System.Management.Automation.Language.Parser]::ParseInput($ScriptBlockAst, [ref]$tokens, [ref]$errors)
831839

832840
# Gets question variables
833-
$questionVariables = $tokens.Where({$PSItem.Name -eq '?'})
841+
$questionVariables = $tokens | Where-Object {$PSItem.Name -eq '?'}
834842

835843
foreach ($questionVariable in $questionVariables)
836844
{

Tests/Engine/GetScriptAnalyzerRule.tests.ps1

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,20 +83,26 @@ Describe "Test RuleExtension" {
8383
$community = "CommunityAnalyzerRules"
8484
$measureRequired = "Measure-RequiresModules"
8585
Context "When used correctly" {
86+
87+
$expectedNumCommunityRules = 10
88+
if ($PSVersionTable.PSVersion -ge [Version]'4.0')
89+
{
90+
$expectedNumCommunityRules = 12
91+
}
8692
It "with the module folder path" {
8793
$ruleExtension = Get-ScriptAnalyzerRule -CustomizedRulePath $directory\CommunityAnalyzerRules | Where-Object {$_.SourceName -eq $community}
88-
$ruleExtension.Count | Should Be 12
94+
$ruleExtension.Count | Should Be $expectedNumCommunityRules
8995
}
9096

9197
It "with the psd1 path" {
9298
$ruleExtension = Get-ScriptAnalyzerRule -CustomizedRulePath $directory\CommunityAnalyzerRules\CommunityAnalyzerRules.psd1 | Where-Object {$_.SourceName -eq $community}
93-
$ruleExtension.Count | Should Be 12
99+
$ruleExtension.Count | Should Be $expectedNumCommunityRules
94100

95101
}
96102

97103
It "with the psm1 path" {
98104
$ruleExtension = Get-ScriptAnalyzerRule -CustomizedRulePath $directory\CommunityAnalyzerRules\CommunityAnalyzerRules.psm1 | Where-Object {$_.SourceName -eq $community}
99-
$ruleExtension.Count | Should Be 12
105+
$ruleExtension.Count | Should Be $expectedNumCommunityRules
100106
}
101107

102108
It "with Name of a built-in rules" {

0 commit comments

Comments
 (0)