Skip to content

Commit 39e7dca

Browse files
fix: 🐛 Update AST search logic to resolve duplicate errors (#8)
* Modified `Measure-BasicWebRequestProperty` and `Measure-InvokeWebRequestWithoutBasic` to fix duplicate errors caused by recursive AST searches. * Updated `CHANGELOG.md` for version 0.3.1 to reflect these fixes. * Added tests to ensure proper detection of method usage with `Invoke-WebRequest`. Fixes #7
1 parent df2f8e0 commit 39e7dca

10 files changed

Lines changed: 77 additions & 14 deletions

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](http://keepachangelog.com/)
66
and this project adheres to [Semantic Versioning](http://semver.org/).
77

8+
## [0.3.1] 2025-12-14
9+
10+
### Fixed
11+
12+
- `Measure-BasicWebRequestProperty`: AST search modified to fix duplicate errors
13+
due to recursive search.
14+
- `Measure-InvokeWebRequestWithoutBasic`: AST search modified to fix duplicate
15+
errors due to recursive search.
16+
817
## [0.3.0] 2025-12-13
918

1019
### Added

GoodEnoughRules/GoodEnoughRules.psd1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
RootModule = 'GoodEnoughRules.psm1'
1313

1414
# Version number of this module.
15-
ModuleVersion = '0.3.0'
15+
ModuleVersion = '0.3.1'
1616

1717
# Supported PSEditions
1818
# CompatiblePSEditions = @()

GoodEnoughRules/Public/Measure-BasicWebRequestProperty.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ function Measure-BasicWebRequestProperty {
7777

7878
process {
7979
# Find all member expression ASTs that match our criteria
80-
[System.Management.Automation.Language.Ast[]]$memberReads = $ScriptBlockAst.FindAll($iwrMemberRead, $true)
80+
[System.Management.Automation.Language.Ast[]]$memberReads = $ScriptBlockAst.FindAll($iwrMemberRead, $false)
8181
foreach ($memberRead in $memberReads) {
8282
# ParenExpression would be the whole line: (iwr -Uri ... -UseBasicParsing).Foo
8383
$parenExpression = $memberRead.Parent.Parent
@@ -89,7 +89,7 @@ function Measure-BasicWebRequestProperty {
8989
}
9090
}
9191
# Find all assignment ASTs that match our criteria
92-
[System.Management.Automation.Language.Ast[]]$assignments = $ScriptBlockAst.FindAll($varAssignPredicate, $true)
92+
[System.Management.Automation.Language.Ast[]]$assignments = $ScriptBlockAst.FindAll($varAssignPredicate, $false)
9393
# Now use that to search for var reads of the assigned variable
9494
foreach ($assignment in $assignments) {
9595
$variableName = $assignment.Left.VariablePath.UserPath

GoodEnoughRules/Public/Measure-InvokeWebRequestWithoutBasic.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ function Measure-InvokeWebRequestWithoutBasic {
4040
}
4141

4242
process {
43-
[System.Management.Automation.Language.Ast[]]$commands = $ScriptBlockAst.FindAll($predicate, $true)
43+
[System.Management.Automation.Language.Ast[]]$commands = $ScriptBlockAst.FindAll($predicate, $false)
4444
$commands | ForEach-Object {
4545
Write-Verbose "Analyzing command: $($_.GetCommandName())"
4646
$command = $_

build.ps1

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@ param(
88
switch ($Parameter) {
99
'Task' {
1010
if ([string]::IsNullOrEmpty($WordToComplete)) {
11-
Get-PSakeScriptTasks -buildFile $psakeFile | Select-Object -ExpandProperty Name
11+
Get-PSakeScriptTasks -BuildFile $psakeFile | Select-Object -ExpandProperty Name
1212
} else {
13-
Get-PSakeScriptTasks -buildFile $psakeFile |
13+
Get-PSakeScriptTasks -BuildFile $psakeFile |
1414
Where-Object { $_.Name -match $WordToComplete } |
1515
Select-Object -ExpandProperty Name
1616
}
1717
}
18-
Default {
18+
default {
1919
}
2020
}
2121
})]
@@ -55,10 +55,11 @@ if ($Bootstrap.IsPresent) {
5555
# Execute psake task(s)
5656
$psakeFile = './psakeFile.ps1'
5757
if ($PSCmdlet.ParameterSetName -eq 'Help') {
58-
Get-PSakeScriptTasks -buildFile $psakeFile |
58+
Get-PSakeScriptTasks -BuildFile $psakeFile |
5959
Format-Table -Property Name, Description, Alias, DependsOn
6060
} else {
61+
Invoke-PSDepend -Path './requirements.psd1' -Import -Force -WarningAction SilentlyContinue
6162
Set-BuildEnvironment -Force
62-
Invoke-psake -buildFile $psakeFile -taskList $Task -NoLogo -properties $Properties -parameters $Parameters
63+
Invoke-psake -buildFile $psakeFile -taskList $Task -nologo -properties $Properties -parameters $Parameters
6364
exit ([int](-not $psake.build_success))
6465
}

tests/Measure-BasicWebRequestProperty.tests.ps1

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
Describe 'Measure-TODOComment' {
1+
Describe 'Measure-BasicWebRequestProperty' {
22
BeforeAll {
33
if ( -not $env:BHPSModuleManifest ) {
4-
.\build.ps1 -Task Build -Verbose
4+
Set-BuildEnvironment -Path "$PSScriptRoot\.." -Force
55
}
66
$manifest = Import-PowerShellDataFile -Path $env:BHPSModuleManifest
77
$outputDir = Join-Path -Path $env:BHProjectPath -ChildPath 'Output'
88
$outputModDir = Join-Path -Path $outputDir -ChildPath $env:BHProjectName
99
$outputModVerDir = Join-Path -Path $outputModDir -ChildPath $manifest.ModuleVersion
10+
$script:outputModVerModule = Join-Path -Path $outputModVerDir -ChildPath "$($env:BHProjectName).psm1"
1011
$outputModVerManifest = Join-Path -Path $outputModVerDir -ChildPath "$($env:BHProjectName).psd1"
1112

1213
# Get module commands
@@ -24,6 +25,19 @@ Describe 'Measure-TODOComment' {
2425
$result[0].Message | Should -Be "Invoke-WebRequest cannot use the 'Forms' parameter when 'UseBasicParsing' is specified."
2526
}
2627

28+
It 'Detects another bad method usage' {
29+
$file = "$PSScriptRoot\fixtures\ExampleFunction.ps1"
30+
$invokeScriptAnalyzerSplat = @{
31+
Path = $file
32+
IncludeRule = 'Measure-BasicWebRequestProperty'
33+
CustomRulePath = $script:outputModVerModule
34+
}
35+
$result = Invoke-ScriptAnalyzer @invokeScriptAnalyzerSplat
36+
$result.Count | Should -BeExactly 2
37+
$result[0].Message | Should -Be "Invoke-WebRequest cannot use the 'Forms' parameter when 'UseBasicParsing' is specified."
38+
$result[1].Message | Should -Be "Invoke-WebRequest cannot use the 'AllElements' parameter when 'UseBasicParsing' is specified."
39+
}
40+
2741
It 'does not detect correct usage of Images property' {
2842
$fakeScript = '$bar = (iwr -Uri https://example.com -UseBasicParsing).Images'
2943
$ast = [System.Management.Automation.Language.Parser]::ParseInput($fakeScript, [ref]$null, [ref]$null)

tests/Measure-InvokeWebRequestWithoutBasic.tests.ps1

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
Describe 'Measure-InvokeWebRequestWithoutBasic' {
22
BeforeAll {
33
if ( -not $env:BHPSModuleManifest ) {
4-
.\build.ps1 -Task Build -Verbose
4+
Set-BuildEnvironment -Path "$PSScriptRoot\.." -Force
55
}
66
$manifest = Import-PowerShellDataFile -Path $env:BHPSModuleManifest
77
$outputDir = Join-Path -Path $env:BHProjectPath -ChildPath 'Output'
88
$outputModDir = Join-Path -Path $outputDir -ChildPath $env:BHProjectName
99
$outputModVerDir = Join-Path -Path $outputModDir -ChildPath $manifest.ModuleVersion
10+
$script:outputModVerModule = Join-Path -Path $outputModVerDir -ChildPath "$($env:BHProjectName).psm1"
1011
$outputModVerManifest = Join-Path -Path $outputModVerDir -ChildPath "$($env:BHProjectName).psd1"
1112

1213
# Get module commands
1314
# Remove all versions of the module from the session. Pester can't handle multiple versions.
1415
Get-Module $env:BHProjectName | Remove-Module -Force -ErrorAction Ignore
1516
Import-Module -Name $outputModVerManifest -Verbose:$false -ErrorAction Stop
17+
Import-Module -Name 'PSScriptAnalyzer' -Verbose:$false -ErrorAction Inquire
1618
}
1719

1820
Context 'When Invoke-WebRequest is used without UseBasicParsing' {
@@ -27,6 +29,23 @@ Invoke-WebRequest -Uri 'https://example.com'
2729
$result[0].Severity | Should -Be 'Error'
2830
}
2931

32+
It 'detects Invoke-WebRequest without UseBasicParsing in different formatting' {
33+
$file = "$PSScriptRoot\fixtures\ExampleFunction.ps1"
34+
$invokeScriptAnalyzerSplat = @{
35+
Path = $file
36+
IncludeRule = 'Measure-InvokeWebRequestWithoutBasic'
37+
CustomRulePath = $script:outputModVerModule
38+
}
39+
40+
$result = Invoke-ScriptAnalyzer @invokeScriptAnalyzerSplat
41+
$result.Count | Should -BeExactly 2
42+
$result[0].Message | Should -Be 'Invoke-WebRequest should be used with the UseBasicParsing parameter.'
43+
$result[0].Severity | Should -Be 'Error'
44+
$result[0].Line | Should -Be 6
45+
$result[1].Message | Should -Be 'Invoke-WebRequest should be used with the UseBasicParsing parameter.'
46+
$result[1].Severity | Should -Be 'Error'
47+
}
48+
3049
It 'Detects iwr alias without UseBasicParsing' {
3150
$fakeScript = @"
3251
iwr -Uri 'https://example.com'

tests/Measure-SecureStringWithKey.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Describe 'Measure-TODOComment' {
22
BeforeAll {
33
if ( -not $env:BHPSModuleManifest ) {
4-
.\build.ps1 -Task Build -Verbose
4+
Set-BuildEnvironment -Path "$PSScriptRoot\.." -Force
55
}
66
$manifest = Import-PowerShellDataFile -Path $env:BHPSModuleManifest
77
$outputDir = Join-Path -Path $env:BHProjectPath -ChildPath 'Output'

tests/Measure-TODOComment.tests.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Describe 'Measure-TODOComment' {
22
BeforeAll {
33
if ( -not $env:BHPSModuleManifest ) {
4-
.\build.ps1 -Task Build -Verbose
4+
Set-BuildEnvironment -Path "$PSScriptRoot\.." -Force
55
}
66
$manifest = Import-PowerShellDataFile -Path $env:BHPSModuleManifest
77
$outputDir = Join-Path -Path $env:BHProjectPath -ChildPath 'Output'

tests/fixtures/ExampleFunction.ps1

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
function Get-Example {
2+
[CmdletBinding()]
3+
param ()
4+
5+
begin {
6+
$beginIWR = Invoke-WebRequest -Uri "https://example.com"
7+
}
8+
9+
process {
10+
$processIWR = Invoke-WebRequest -Uri "https://example.org"
11+
$processIWR
12+
$badParam = Invoke-WebRequest -Uri "https://example.net" -UseBasicParsing
13+
$badParam.Forms
14+
}
15+
16+
end {
17+
$beginIWR.Forms
18+
$badParam.AllElements
19+
}
20+
}

0 commit comments

Comments
 (0)