diff --git a/CHANGELOG.md b/CHANGELOG.md index 40dc24a..df2233d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [0.3.1] 2025-12-14 + +### Fixed + +- `Measure-BasicWebRequestProperty`: AST search modified to fix duplicate errors + due to recursive search. +- `Measure-InvokeWebRequestWithoutBasic`: AST search modified to fix duplicate + errors due to recursive search. + ## [0.3.0] 2025-12-13 ### Added diff --git a/GoodEnoughRules/GoodEnoughRules.psd1 b/GoodEnoughRules/GoodEnoughRules.psd1 index 5a9c7b2..a8d1243 100644 --- a/GoodEnoughRules/GoodEnoughRules.psd1 +++ b/GoodEnoughRules/GoodEnoughRules.psd1 @@ -12,7 +12,7 @@ RootModule = 'GoodEnoughRules.psm1' # Version number of this module. - ModuleVersion = '0.3.0' + ModuleVersion = '0.3.1' # Supported PSEditions # CompatiblePSEditions = @() diff --git a/GoodEnoughRules/Public/Measure-BasicWebRequestProperty.ps1 b/GoodEnoughRules/Public/Measure-BasicWebRequestProperty.ps1 index 920aa21..0627aec 100644 --- a/GoodEnoughRules/Public/Measure-BasicWebRequestProperty.ps1 +++ b/GoodEnoughRules/Public/Measure-BasicWebRequestProperty.ps1 @@ -77,7 +77,7 @@ function Measure-BasicWebRequestProperty { process { # Find all member expression ASTs that match our criteria - [System.Management.Automation.Language.Ast[]]$memberReads = $ScriptBlockAst.FindAll($iwrMemberRead, $true) + [System.Management.Automation.Language.Ast[]]$memberReads = $ScriptBlockAst.FindAll($iwrMemberRead, $false) foreach ($memberRead in $memberReads) { # ParenExpression would be the whole line: (iwr -Uri ... -UseBasicParsing).Foo $parenExpression = $memberRead.Parent.Parent @@ -89,7 +89,7 @@ function Measure-BasicWebRequestProperty { } } # Find all assignment ASTs that match our criteria - [System.Management.Automation.Language.Ast[]]$assignments = $ScriptBlockAst.FindAll($varAssignPredicate, $true) + [System.Management.Automation.Language.Ast[]]$assignments = $ScriptBlockAst.FindAll($varAssignPredicate, $false) # Now use that to search for var reads of the assigned variable foreach ($assignment in $assignments) { $variableName = $assignment.Left.VariablePath.UserPath diff --git a/GoodEnoughRules/Public/Measure-InvokeWebRequestWithoutBasic.ps1 b/GoodEnoughRules/Public/Measure-InvokeWebRequestWithoutBasic.ps1 index 412ed75..c302853 100644 --- a/GoodEnoughRules/Public/Measure-InvokeWebRequestWithoutBasic.ps1 +++ b/GoodEnoughRules/Public/Measure-InvokeWebRequestWithoutBasic.ps1 @@ -40,7 +40,7 @@ function Measure-InvokeWebRequestWithoutBasic { } process { - [System.Management.Automation.Language.Ast[]]$commands = $ScriptBlockAst.FindAll($predicate, $true) + [System.Management.Automation.Language.Ast[]]$commands = $ScriptBlockAst.FindAll($predicate, $false) $commands | ForEach-Object { Write-Verbose "Analyzing command: $($_.GetCommandName())" $command = $_ diff --git a/build.ps1 b/build.ps1 index 3c46c75..0ad1044 100644 --- a/build.ps1 +++ b/build.ps1 @@ -8,14 +8,14 @@ param( switch ($Parameter) { 'Task' { if ([string]::IsNullOrEmpty($WordToComplete)) { - Get-PSakeScriptTasks -buildFile $psakeFile | Select-Object -ExpandProperty Name + Get-PSakeScriptTasks -BuildFile $psakeFile | Select-Object -ExpandProperty Name } else { - Get-PSakeScriptTasks -buildFile $psakeFile | + Get-PSakeScriptTasks -BuildFile $psakeFile | Where-Object { $_.Name -match $WordToComplete } | Select-Object -ExpandProperty Name } } - Default { + default { } } })] @@ -55,10 +55,11 @@ if ($Bootstrap.IsPresent) { # Execute psake task(s) $psakeFile = './psakeFile.ps1' if ($PSCmdlet.ParameterSetName -eq 'Help') { - Get-PSakeScriptTasks -buildFile $psakeFile | + Get-PSakeScriptTasks -BuildFile $psakeFile | Format-Table -Property Name, Description, Alias, DependsOn } else { + Invoke-PSDepend -Path './requirements.psd1' -Import -Force -WarningAction SilentlyContinue Set-BuildEnvironment -Force - Invoke-psake -buildFile $psakeFile -taskList $Task -NoLogo -properties $Properties -parameters $Parameters + Invoke-psake -buildFile $psakeFile -taskList $Task -nologo -properties $Properties -parameters $Parameters exit ([int](-not $psake.build_success)) } diff --git a/tests/Measure-BasicWebRequestProperty.tests.ps1 b/tests/Measure-BasicWebRequestProperty.tests.ps1 index 3cb6efc..79031f0 100644 --- a/tests/Measure-BasicWebRequestProperty.tests.ps1 +++ b/tests/Measure-BasicWebRequestProperty.tests.ps1 @@ -1,12 +1,13 @@ -Describe 'Measure-TODOComment' { +Describe 'Measure-BasicWebRequestProperty' { BeforeAll { if ( -not $env:BHPSModuleManifest ) { - .\build.ps1 -Task Build -Verbose + Set-BuildEnvironment -Path "$PSScriptRoot\.." -Force } $manifest = Import-PowerShellDataFile -Path $env:BHPSModuleManifest $outputDir = Join-Path -Path $env:BHProjectPath -ChildPath 'Output' $outputModDir = Join-Path -Path $outputDir -ChildPath $env:BHProjectName $outputModVerDir = Join-Path -Path $outputModDir -ChildPath $manifest.ModuleVersion + $script:outputModVerModule = Join-Path -Path $outputModVerDir -ChildPath "$($env:BHProjectName).psm1" $outputModVerManifest = Join-Path -Path $outputModVerDir -ChildPath "$($env:BHProjectName).psd1" # Get module commands @@ -24,6 +25,19 @@ Describe 'Measure-TODOComment' { $result[0].Message | Should -Be "Invoke-WebRequest cannot use the 'Forms' parameter when 'UseBasicParsing' is specified." } + It 'Detects another bad method usage' { + $file = "$PSScriptRoot\fixtures\ExampleFunction.ps1" + $invokeScriptAnalyzerSplat = @{ + Path = $file + IncludeRule = 'Measure-BasicWebRequestProperty' + CustomRulePath = $script:outputModVerModule + } + $result = Invoke-ScriptAnalyzer @invokeScriptAnalyzerSplat + $result.Count | Should -BeExactly 2 + $result[0].Message | Should -Be "Invoke-WebRequest cannot use the 'Forms' parameter when 'UseBasicParsing' is specified." + $result[1].Message | Should -Be "Invoke-WebRequest cannot use the 'AllElements' parameter when 'UseBasicParsing' is specified." + } + It 'does not detect correct usage of Images property' { $fakeScript = '$bar = (iwr -Uri https://example.com -UseBasicParsing).Images' $ast = [System.Management.Automation.Language.Parser]::ParseInput($fakeScript, [ref]$null, [ref]$null) diff --git a/tests/Measure-InvokeWebRequestWithoutBasic.tests.ps1 b/tests/Measure-InvokeWebRequestWithoutBasic.tests.ps1 index fe94a98..04b28cf 100644 --- a/tests/Measure-InvokeWebRequestWithoutBasic.tests.ps1 +++ b/tests/Measure-InvokeWebRequestWithoutBasic.tests.ps1 @@ -1,18 +1,20 @@ Describe 'Measure-InvokeWebRequestWithoutBasic' { BeforeAll { if ( -not $env:BHPSModuleManifest ) { - .\build.ps1 -Task Build -Verbose + Set-BuildEnvironment -Path "$PSScriptRoot\.." -Force } $manifest = Import-PowerShellDataFile -Path $env:BHPSModuleManifest $outputDir = Join-Path -Path $env:BHProjectPath -ChildPath 'Output' $outputModDir = Join-Path -Path $outputDir -ChildPath $env:BHProjectName $outputModVerDir = Join-Path -Path $outputModDir -ChildPath $manifest.ModuleVersion + $script:outputModVerModule = Join-Path -Path $outputModVerDir -ChildPath "$($env:BHProjectName).psm1" $outputModVerManifest = Join-Path -Path $outputModVerDir -ChildPath "$($env:BHProjectName).psd1" # Get module commands # Remove all versions of the module from the session. Pester can't handle multiple versions. Get-Module $env:BHProjectName | Remove-Module -Force -ErrorAction Ignore Import-Module -Name $outputModVerManifest -Verbose:$false -ErrorAction Stop + Import-Module -Name 'PSScriptAnalyzer' -Verbose:$false -ErrorAction Inquire } Context 'When Invoke-WebRequest is used without UseBasicParsing' { @@ -27,6 +29,23 @@ Invoke-WebRequest -Uri 'https://example.com' $result[0].Severity | Should -Be 'Error' } + It 'detects Invoke-WebRequest without UseBasicParsing in different formatting' { + $file = "$PSScriptRoot\fixtures\ExampleFunction.ps1" + $invokeScriptAnalyzerSplat = @{ + Path = $file + IncludeRule = 'Measure-InvokeWebRequestWithoutBasic' + CustomRulePath = $script:outputModVerModule + } + + $result = Invoke-ScriptAnalyzer @invokeScriptAnalyzerSplat + $result.Count | Should -BeExactly 2 + $result[0].Message | Should -Be 'Invoke-WebRequest should be used with the UseBasicParsing parameter.' + $result[0].Severity | Should -Be 'Error' + $result[0].Line | Should -Be 6 + $result[1].Message | Should -Be 'Invoke-WebRequest should be used with the UseBasicParsing parameter.' + $result[1].Severity | Should -Be 'Error' + } + It 'Detects iwr alias without UseBasicParsing' { $fakeScript = @" iwr -Uri 'https://example.com' diff --git a/tests/Measure-SecureStringWithKey.ps1 b/tests/Measure-SecureStringWithKey.ps1 index 4d01da2..b4a7148 100644 --- a/tests/Measure-SecureStringWithKey.ps1 +++ b/tests/Measure-SecureStringWithKey.ps1 @@ -1,7 +1,7 @@ Describe 'Measure-TODOComment' { BeforeAll { if ( -not $env:BHPSModuleManifest ) { - .\build.ps1 -Task Build -Verbose + Set-BuildEnvironment -Path "$PSScriptRoot\.." -Force } $manifest = Import-PowerShellDataFile -Path $env:BHPSModuleManifest $outputDir = Join-Path -Path $env:BHProjectPath -ChildPath 'Output' diff --git a/tests/Measure-TODOComment.tests.ps1 b/tests/Measure-TODOComment.tests.ps1 index 65d1207..8d44675 100644 --- a/tests/Measure-TODOComment.tests.ps1 +++ b/tests/Measure-TODOComment.tests.ps1 @@ -1,7 +1,7 @@ Describe 'Measure-TODOComment' { BeforeAll { if ( -not $env:BHPSModuleManifest ) { - .\build.ps1 -Task Build -Verbose + Set-BuildEnvironment -Path "$PSScriptRoot\.." -Force } $manifest = Import-PowerShellDataFile -Path $env:BHPSModuleManifest $outputDir = Join-Path -Path $env:BHProjectPath -ChildPath 'Output' diff --git a/tests/fixtures/ExampleFunction.ps1 b/tests/fixtures/ExampleFunction.ps1 new file mode 100644 index 0000000..cbb673e --- /dev/null +++ b/tests/fixtures/ExampleFunction.ps1 @@ -0,0 +1,20 @@ +function Get-Example { + [CmdletBinding()] + param () + + begin { + $beginIWR = Invoke-WebRequest -Uri "https://example.com" + } + + process { + $processIWR = Invoke-WebRequest -Uri "https://example.org" + $processIWR + $badParam = Invoke-WebRequest -Uri "https://example.net" -UseBasicParsing + $badParam.Forms + } + + end { + $beginIWR.Forms + $badParam.AllElements + } +}