tools/Fix-PesterSuite.ps1
|
[CmdletBinding()] param( [string]$TestsRoot = (Join-Path (Split-Path -Parent $PSScriptRoot) 'Tests') ) $ErrorActionPreference = 'Stop' # Phase 1: Migrate legacy `Should X` -> `Should -X` syntax across all .Tests.ps1 files. # Order matters: longest / most-specific patterns first to avoid partial-replace collisions. $replacements = [ordered]@{ 'Should Not BeNullOrEmpty' = 'Should -Not -BeNullOrEmpty' 'Should Not BeOfType ' = 'Should -Not -BeOfType ' 'Should Not BeGreaterThan ' = 'Should -Not -BeGreaterThan ' 'Should Not BeLessThan ' = 'Should -Not -BeLessThan ' 'Should Not BeExactly ' = 'Should -Not -BeExactly ' 'Should Not BeLike ' = 'Should -Not -BeLike ' 'Should Not HaveCount ' = 'Should -Not -HaveCount ' 'Should Not Match ' = 'Should -Not -Match ' 'Should Not Contain ' = 'Should -Not -Contain ' 'Should Not FileContentMatch ' = 'Should -Not -FileContentMatch ' 'Should Not Exist' = 'Should -Not -Exist' 'Should Not Throw' = 'Should -Not -Throw' 'Should Not Be ' = 'Should -Not -Be ' 'Should BeOfType ' = 'Should -BeOfType ' 'Should BeGreaterThan ' = 'Should -BeGreaterThan ' 'Should BeLessThan ' = 'Should -BeLessThan ' 'Should BeExactly ' = 'Should -BeExactly ' 'Should BeLike ' = 'Should -BeLike ' 'Should BeNullOrEmpty' = 'Should -BeNullOrEmpty' 'Should HaveCount ' = 'Should -HaveCount ' 'Should FileContentMatch ' = 'Should -FileContentMatch ' 'Should Match ' = 'Should -Match ' 'Should Contain ' = 'Should -Contain ' 'Should Exist' = 'Should -Exist' 'Should Throw' = 'Should -Throw' 'Should Be ' = 'Should -Be ' } $files = @( Get-ChildItem -LiteralPath $TestsRoot -Filter '*.Tests.ps1' -File Get-ChildItem -LiteralPath (Join-Path $TestsRoot 'Unit') -Filter '*.Tests.ps1' -File -ErrorAction SilentlyContinue ) Write-Host "[Phase 1] Migrating Should syntax across $($files.Count) files..." foreach ($file in $files) { $text = Get-Content -LiteralPath $file.FullName -Raw $orig = $text foreach ($pair in $replacements.GetEnumerator()) { # Use simple string replacement, not regex - patterns include spaces and no special chars. $text = $text.Replace($pair.Key, $pair.Value) } if ($text -ne $orig) { Set-Content -LiteralPath $file.FullName -Value $text -NoNewline -Encoding UTF8 Write-Host " syntax: $($file.Name)" } } # Phase 2: Wrap pre-Describe script-top region in BeforeAll { ... } so vars and helper # functions are visible inside Pester 5 BeforeAll/BeforeEach/It scopes. # Strategy: read each file's tokenized AST, find the first 'Describe' command at script-block # top level, and wrap everything before it (excluding comments-only headers) in BeforeAll. Write-Host "[Phase 2] Fixing script-top scoping..." foreach ($file in $files) { $text = Get-Content -LiteralPath $file.FullName -Raw if ($text -match '(?m)^\s*BeforeAll\s*\{') { # Already has a top-level BeforeAll wrapper - skip # (but verify it's BEFORE the first Describe) } $tokens = $null $errors = $null $ast = [System.Management.Automation.Language.Parser]::ParseInput($text, [ref]$tokens, [ref]$errors) if ($errors.Count -gt 0) { Write-Warning " parse errors in $($file.Name); skipping scoping fix" continue } # Find the first top-level CommandAst whose first element is 'Describe' $topStatements = $ast.EndBlock.Statements $firstDescribeOffset = $null foreach ($stmt in $topStatements) { $cmd = $stmt.PipelineElements | Where-Object { $_ -is [System.Management.Automation.Language.CommandAst] } | Select-Object -First 1 if ($cmd) { $firstElement = $cmd.CommandElements | Select-Object -First 1 if ($firstElement -and $firstElement.Value -eq 'Describe') { $firstDescribeOffset = $stmt.Extent.StartOffset break } } } if ($null -eq $firstDescribeOffset) { Write-Warning " no Describe block found in $($file.Name); skipping" continue } $head = $text.Substring(0, $firstDescribeOffset) $body = $text.Substring($firstDescribeOffset) # If $head has only comments/whitespace, nothing to wrap. $headStripped = ($head -replace '(?m)^\s*#.*$', '').Trim() if ([string]::IsNullOrWhiteSpace($headStripped)) { continue } # If $head already starts with BeforeAll { ... }, leave it alone. if ($head -match '(?ms)^\s*BeforeAll\s*\{.*\}\s*$') { continue } # Extract any pure leading comment block to keep it ABOVE BeforeAll for readability. $leadingComments = '' $rest = $head if ($head -match '\A((?:^\s*(?:#.*)?\r?\n)+)') { $leadingComments = $matches[1] $rest = $head.Substring($leadingComments.Length) } # Wrap $rest in BeforeAll WITHOUT re-indenting interior lines - here-strings forbid # whitespace before the terminating "@ / '@ marker, so leave content as-is. $wrapped = $leadingComments + 'BeforeAll {' + [Environment]::NewLine + $rest.TrimEnd() + [Environment]::NewLine + '}' + [Environment]::NewLine + [Environment]::NewLine $newText = $wrapped + $body if ($newText -ne $text) { Set-Content -LiteralPath $file.FullName -Value $newText -NoNewline -Encoding UTF8 Write-Host " scope: $($file.Name)" } } Write-Host 'Done.' |