From 744f1e4b60c7d86337dab5831c43594493bb7f5c Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 9 Mar 2025 21:49:28 +0100 Subject: [PATCH 1/9] =?UTF-8?q?=F0=9F=9A=80=20[Feature]:=20Add=20unit=20te?= =?UTF-8?q?sts=20for=20AST-related=20functions=20in=20AT.Tests.ps1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/{AST.Tests.ps1 => AT.Tests.ps1} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/{AST.Tests.ps1 => AT.Tests.ps1} (100%) diff --git a/tests/AST.Tests.ps1 b/tests/AT.Tests.ps1 similarity index 100% rename from tests/AST.Tests.ps1 rename to tests/AT.Tests.ps1 From 302797b6be5d37e51c1292373638c138410a03a4 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 9 Mar 2025 21:50:31 +0100 Subject: [PATCH 2/9] =?UTF-8?q?=F0=9F=9A=80=20[Feature]:=20Add=20unit=20te?= =?UTF-8?q?sts=20for=20AST-related=20functions=20in=20Ast.Tests.ps1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/{AT.Tests.ps1 => Ast.Tests.ps1} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/{AT.Tests.ps1 => Ast.Tests.ps1} (100%) diff --git a/tests/AT.Tests.ps1 b/tests/Ast.Tests.ps1 similarity index 100% rename from tests/AT.Tests.ps1 rename to tests/Ast.Tests.ps1 From 400b2225aa05baae466ef03aec219ba4d7fb191e Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 9 Mar 2025 21:56:38 +0100 Subject: [PATCH 3/9] =?UTF-8?q?=F0=9F=9A=80=20[Feature]:=20Add=20sample=20?= =?UTF-8?q?test=20scripts=20and=20enhance=20Get-AstScript=20to=20support?= =?UTF-8?q?=20multiple=20file=20paths=20and=20recursive=20directory=20pars?= =?UTF-8?q?ing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/functions/public/Core/Get-AstScript.ps1 | 64 +- tests/Ast.Tests.ps1 | 610 +++++++++++++++++- .../Subfolder/TestScript3.ps1 | 11 + .../src/Get-AstScript-Samples/TestScript1.ps1 | 7 + .../src/Get-AstScript-Samples/TestScript2.ps1 | 7 + 5 files changed, 682 insertions(+), 17 deletions(-) create mode 100644 tests/src/Get-AstScript-Samples/Subfolder/TestScript3.ps1 create mode 100644 tests/src/Get-AstScript-Samples/TestScript1.ps1 create mode 100644 tests/src/Get-AstScript-Samples/TestScript2.ps1 diff --git a/src/functions/public/Core/Get-AstScript.ps1 b/src/functions/public/Core/Get-AstScript.ps1 index ab19b53..44c6388 100644 --- a/src/functions/public/Core/Get-AstScript.ps1 +++ b/src/functions/public/Core/Get-AstScript.ps1 @@ -32,6 +32,16 @@ Parses the provided PowerShell script string and returns its Ast, tokens, and any parsing errors. + .EXAMPLE + Get-AstScript -Path "C:\\Scripts" -Recurse + + Parses all PowerShell script files in the "C:\\Scripts" directory and its subdirectories. + + .EXAMPLE + Get-AstScript -Path @("C:\\Scripts\\example.ps1", "C:\\Scripts\\example2.ps1") + + Parses multiple PowerShell script files and returns their Asts. + .OUTPUTS PSCustomObject @@ -47,16 +57,24 @@ [outputType([System.Management.Automation.Language.ScriptBlockAst])] [CmdletBinding()] param ( - # The path to the PowerShell script file to be parsed. - # Validate using Test-Path + # The path(s) to PowerShell script file(s) or folder(s) to be parsed. [Parameter( Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, ParameterSetName = 'Path' )] - [ValidateScript({ Test-Path -Path $_ })] - [string] $Path, + [ValidateScript({ + foreach ($p in $_) { + if (-not (Test-Path -Path $p)) { return $false } + } + return $true + })] + [string[]] $Path, + + # Process directories recursively + [Parameter(ParameterSetName = 'Path')] + [switch] $Recurse, # The PowerShell script to be parsed. [Parameter( @@ -73,19 +91,45 @@ process { $tokens = $null $errors = $null + switch ($PSCmdlet.ParameterSetName) { 'Path' { - $ast = [System.Management.Automation.Language.Parser]::ParseFile($Path, [ref]$tokens, [ref]$errors) + foreach ($p in $Path) { + # Check if the path is a directory + if (Test-Path -Path $p -PathType Container) { + # Get all .ps1 files in the directory + $files = Get-ChildItem -Path $p -Filter '*.ps1' -File -Recurse:$Recurse + + foreach ($file in $files) { + $ast = [System.Management.Automation.Language.Parser]::ParseFile($file.FullName, [ref]$tokens, [ref]$errors) + [pscustomobject]@{ + Path = $file.FullName + Ast = $ast + Tokens = $tokens + Errors = $errors + } + } + } else { + # Path is a file + $ast = [System.Management.Automation.Language.Parser]::ParseFile($p, [ref]$tokens, [ref]$errors) + [pscustomobject]@{ + Path = $p + Ast = $ast + Tokens = $tokens + Errors = $errors + } + } + } } 'Script' { $ast = [System.Management.Automation.Language.Parser]::ParseInput($Script, [ref]$tokens, [ref]$errors) + [pscustomobject]@{ + Ast = $ast + Tokens = $tokens + Errors = $errors + } } } - [pscustomobject]@{ - Ast = $ast - Tokens = $tokens - Errors = $errors - } } end {} diff --git a/tests/Ast.Tests.ps1 b/tests/Ast.Tests.ps1 index 6c1b09a..27f7142 100644 --- a/tests/Ast.Tests.ps1 +++ b/tests/Ast.Tests.ps1 @@ -1,5 +1,207 @@ #Requires -Modules @{ ModuleName = 'Pester'; RequiredVersion = '5.7.1' } +BeforeAll { + # Set paths for test files + $testSamplesDir = Join-Path -Path $PSScriptRoot -ChildPath 'src\Get-AstScript-Samples' + $testScript1 = Join-Path -Path $testSamplesDir -ChildPath 'TestScript1.ps1' + $testScript2 = Join-Path -Path $testSamplesDir -ChildPath 'TestScript2.ps1' + $subfolderScript = Join-Path -Path $testSamplesDir -ChildPath 'Subfolder\TestScript3.ps1' + + # Create a temp folder for additional tests + $tempFolder = Join-Path -Path $TestDrive -ChildPath 'TempScripts' + New-Item -Path $tempFolder -ItemType Directory -Force | Out-Null + + # Create some test scripts in the temp folder + @' +function Get-Something { + param($param1) + Write-Output $param1 +} +'@ | Out-File -FilePath (Join-Path -Path $tempFolder -ChildPath 'Script1.ps1') + + @' +function Set-Something { + param($value) + $global:testValue = $value +} +'@ | Out-File -FilePath (Join-Path -Path $tempFolder -ChildPath 'Script2.ps1') + + # Create a subfolder with a script + $subFolder = Join-Path -Path $tempFolder -ChildPath 'SubFolder' + New-Item -Path $subFolder -ItemType Directory -Force | Out-Null + + @' +function Test-Something { + return $true +} +'@ | Out-File -FilePath (Join-Path -Path $subFolder -ChildPath 'Script3.ps1') + + # Also create a non-PS1 file that should be ignored + "This is not a PowerShell script" | Out-File -FilePath (Join-Path -Path $tempFolder -ChildPath 'NotAScript.txt') + + # Create a new test script with multiple commands + $multiCommandScriptPath = Join-Path -Path $TestDrive -ChildPath 'MultiCommandScript.ps1' + @' +function Test-MultiCommand { + param($value) + + Get-Process | Where-Object { $_.WorkingSet -gt 100MB } + Write-Host "Testing commands" + Invoke-Command -ScriptBlock { Get-Service } +} +'@ | Out-File -FilePath $multiCommandScriptPath + + # Create a file with multiple functions for testing + $multiFunctionScriptPath = Join-Path -Path $TestDrive -ChildPath 'MultiFunctionScript.ps1' + @' +function Get-Something { + param($param1) + Write-Output $param1 +} + +function Set-Something { + param($value) + $global:testValue = $value +} + +function Private-Helper { + # Hidden helper function + return $true +} +'@ | Out-File -FilePath $multiFunctionScriptPath + + # Create test scripts with different function types + $functionTypesPath = Join-Path -Path $TestDrive -ChildPath 'FunctionTypes.ps1' + @' +function Regular-Function { + param($param1) + Write-Output $param1 +} + +filter Filter-Function { + $_ | Where-Object { $_ -gt 5 } +} + +workflow Test-Workflow { + param($items) + foreach -parallel ($item in $items) { + Write-Output $item + } +} + +configuration Test-Configuration { + param($ComputerName) + + Node $ComputerName { + File TestFile { + DestinationPath = "C:\test.txt" + Contents = "Test content" + } + } +} +'@ | Out-File -FilePath $functionTypesPath + + # Create another file for testing multiple files with function types + $secondTypesFilePath = Join-Path -Path $TestDrive -ChildPath 'SecondFunctionTypes.ps1' + @' +function Another-Function { + # Regular function +} + +filter Another-Filter { + # Filter function +} +'@ | Out-File -FilePath $secondTypesFilePath + + # Create a folder structure for testing recursion with function types + $typesFolder = Join-Path -Path $TestDrive -ChildPath 'FunctionTypesFolder' + New-Item -Path $typesFolder -ItemType Directory -Force | Out-Null + Copy-Item -Path $functionTypesPath -Destination $typesFolder + + $typesSubfolder = Join-Path -Path $typesFolder -ChildPath 'SubFolder' + New-Item -Path $typesSubfolder -ItemType Directory -Force | Out-Null + + $typesSubfolderFilePath = Join-Path -Path $typesSubfolder -ChildPath 'SubFile.ps1' + @' +function Nested-Function { + # Nested function +} + +filter Nested-Filter { + # Nested filter +} +'@ | Out-File -FilePath $typesSubfolderFilePath + + # Create test scripts with function aliases + $functionAliasPath = Join-Path -Path $TestDrive -ChildPath 'FunctionAlias.ps1' + @' +function Get-Something { + [Alias("gs", "getSomething")] + param($param1) + Write-Output $param1 +} + +function Set-Something { + [Alias("ss")] + param($value) + $global:testValue = $value +} + +function Test-NoAlias { + param($test) + # This function has no alias +} +'@ | Out-File -FilePath $functionAliasPath + + # Create another file for testing multiple files with aliases + $secondAliasFilePath = Join-Path -Path $TestDrive -ChildPath 'SecondAliasFile.ps1' + @' +function Get-AnotherThing { + [Alias("gat", "getAnother")] + param($item) + return $item +} +'@ | Out-File -FilePath $secondAliasFilePath + + # Create a folder structure for testing recursion with aliases + $aliasFolder = Join-Path -Path $TestDrive -ChildPath 'AliasFolder' + New-Item -Path $aliasFolder -ItemType Directory -Force | Out-Null + Copy-Item -Path $functionAliasPath -Destination $aliasFolder + + $aliasSubfolder = Join-Path -Path $aliasFolder -ChildPath 'SubFolder' + New-Item -Path $aliasSubfolder -ItemType Directory -Force | Out-Null + + $aliasSubfolderFilePath = Join-Path -Path $aliasSubfolder -ChildPath 'NestedAlias.ps1' + @' +function Get-NestedItem { + [Alias("gni", "getNestedItem")] + param($item) + return $item +} +'@ | Out-File -FilePath $aliasSubfolderFilePath + + # Create a folder with test scripts for command testing + $testCommandFolder = Join-Path -Path $TestDrive -ChildPath 'CommandTests' + New-Item -Path $testCommandFolder -ItemType Directory -Force | Out-Null + + # Create scripts in the command test folder + @' +Get-ChildItem | Sort-Object Name +'@ | Out-File -FilePath (Join-Path -Path $testCommandFolder -ChildPath 'Script1.ps1') + + @' +Get-Process | Select-Object Name, Id +'@ | Out-File -FilePath (Join-Path -Path $testCommandFolder -ChildPath 'Script2.ps1') + + # Create a subfolder with script for command testing + $commandSubFolder = Join-Path -Path $testCommandFolder -ChildPath 'SubFolder' + New-Item -Path $commandSubFolder -ItemType Directory -Force | Out-Null + + @' +Get-Service | Where-Object { $_.Status -eq 'Running' } +'@ | Out-File -FilePath (Join-Path -Path $commandSubFolder -ChildPath 'Script3.ps1') +} + Describe 'Core' { Context "Function: 'Get-ASTScript'" { It 'Get-ASTScript gets the script AST' { @@ -8,7 +210,105 @@ Describe 'Core' { $script | Should -Not -BeNullOrEmpty $script.Ast | Should -BeOfType [System.Management.Automation.Language.ScriptBlockAst] } + + It 'Should process an array of file paths' { + $results = Get-AstScript -Path @($testScript1, $testScript2) + + # Verify we got results for both files + $results | Should -HaveCount 2 + + # Verify paths + $paths = $results | ForEach-Object { $_.Path } + $paths | Should -Contain $testScript1 + $paths | Should -Contain $testScript2 + + # Verify ASTs + $results | ForEach-Object { + $_.Ast | Should -BeOfType [System.Management.Automation.Language.ScriptBlockAst] + } + } + + It 'Should process all PS1 files in a folder' { + $results = Get-AstScript -Path $testSamplesDir + + # Should find the two scripts in the root folder but not the one in subfolder + $results | Should -HaveCount 2 + + # Check paths + $paths = $results | ForEach-Object { $_.Path } + $paths | Should -Contain $testScript1 + $paths | Should -Contain $testScript2 + $paths | Should -Not -Contain $subfolderScript + } + + It 'Should process all PS1 files in a folder recursively when -Recurse is specified' { + $results = Get-AstScript -Path $testSamplesDir -Recurse + + # Should find all three scripts (including subfolder) + $results | Should -HaveCount 3 + + # Check paths + $paths = $results | ForEach-Object { $_.Path } + $paths | Should -Contain $testScript1 + $paths | Should -Contain $testScript2 + $paths | Should -Contain $subfolderScript + } + + It 'Should validate all paths in an array exist' { + # Testing the ValidateScript for array paths + { Get-AstScript -Path @($testScript1, 'NonExistentFile.ps1') } | + Should -Throw + } + + It 'Should handle mixed paths (files and folders)' { + $script1Path = Join-Path -Path $tempFolder -ChildPath 'Script1.ps1' + $results = Get-AstScript -Path @($script1Path, $tempFolder) + + # Should get results for Script1.ps1 (explicitly) + Script1.ps1 and Script2.ps1 (from folder) + $results | Should -HaveCount 3 + + $paths = $results | ForEach-Object { $_.Path } + $script1Path | Should -BeIn $paths + (Join-Path -Path $tempFolder -ChildPath 'Script2.ps1') | Should -BeIn $paths + } + + It 'Should not process files that are not PS1' { + $results = Get-AstScript -Path $tempFolder + + # Should only find the PS1 files (2), not the txt file + $results | Should -HaveCount 2 + + $paths = $results | ForEach-Object { $_.Path } + (Join-Path -Path $tempFolder -ChildPath 'NotAScript.txt') | Should -Not -BeIn $paths + } + + It 'Should properly handle the -Recurse switch' { + # Without recurse + $withoutRecurse = Get-AstScript -Path $tempFolder + $withoutRecurse | Should -HaveCount 2 + + # With recurse + $withRecurse = Get-AstScript -Path $tempFolder -Recurse + $withRecurse | Should -HaveCount 3 + + $paths = $withRecurse | ForEach-Object { $_.Path } + (Join-Path -Path $tempFolder -ChildPath 'SubFolder\Script3.ps1') | Should -BeIn $paths + } + + It 'Should return Path property for file inputs' { + $results = Get-AstScript -Path (Join-Path -Path $tempFolder -ChildPath 'Script1.ps1') + $results.Path | Should -Not -BeNullOrEmpty + } + + It 'Should include correct AST information' { + $results = Get-AstScript -Path (Join-Path -Path $tempFolder -ChildPath 'Script1.ps1') + + # Check for function definition in the AST + $functionDef = $results.Ast.FindAll({ $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] }, $true) + $functionDef.Name | Should -Be 'Get-Something' + } } + Context "Function: 'Get-ASTFunction'" { It 'Get-ASTFunction gets the function AST' { $path = Join-Path $PSScriptRoot 'src\Test-Function.ps1' @@ -16,7 +316,66 @@ Describe 'Core' { $function | Should -Not -BeNullOrEmpty $function.Ast | Should -BeOfType [System.Management.Automation.Language.FunctionDefinitionAst] } + + It 'Should process an array of file paths' { + $results = Get-AstFunction -Path @($testScript1, $testScript2) + + # We should have two ASTs (one for each function) + $results.Ast | Should -HaveCount 2 + + # Verify function names + $functionNames = $results.Ast | ForEach-Object { $_.Name } + $functionNames | Should -Contain 'Test-Function1' + $functionNames | Should -Contain 'Test-Function2' + } + + It 'Should process all PS1 files in a folder' { + $results = Get-AstFunction -Path $testSamplesDir + + # Should find function definitions from the two scripts in the root folder + $results.Ast | Should -HaveCount 2 + + # Verify function names + $functionNames = $results.Ast | ForEach-Object { $_.Name } + $functionNames | Should -Contain 'Test-Function1' + $functionNames | Should -Contain 'Test-Function2' + $functionNames | Should -Not -Contain 'Test-Function3' + } + + It 'Should process all PS1 files in a folder recursively when -Recurse is specified' { + $results = Get-AstFunction -Path $testSamplesDir -Recurse + + # Should find functions from all three scripts (including subfolder) + $results.Ast | Should -HaveCount 3 + + # Verify function names + $functionNames = $results.Ast | ForEach-Object { $_.Name } + $functionNames | Should -Contain 'Test-Function1' + $functionNames | Should -Contain 'Test-Function2' + $functionNames | Should -Contain 'Test-Function3' + } + + It 'Should filter function names based on the -Name parameter' { + $results = Get-AstFunction -Path $testSamplesDir -Recurse -Name 'Test-Function1' + + # Should only find the one specified function + $results.Ast | Should -HaveCount 1 + $results.Ast[0].Name | Should -Be 'Test-Function1' + } + + It 'Should support wildcards in -Name parameter' { + $results = Get-AstFunction -Path $testSamplesDir -Recurse -Name 'Test-Function*' + + # Should find all three functions + $results.Ast | Should -HaveCount 3 + + $functionNames = $results.Ast | ForEach-Object { $_.Name } + $functionNames | Should -Contain 'Test-Function1' + $functionNames | Should -Contain 'Test-Function2' + $functionNames | Should -Contain 'Test-Function3' + } } + Context "Function: 'Get-ASTCommand'" { It 'Get-ASTCommand gets the command AST' { $path = Join-Path $PSScriptRoot 'src\Test-Function.ps1' @@ -24,6 +383,74 @@ Describe 'Core' { $command | Should -Not -BeNullOrEmpty $command.Ast | Should -BeOfType [System.Management.Automation.Language.CommandAst] } + + It 'Should process an array of file paths' { + $results = Get-AstCommand -Path @($multiCommandScriptPath, $testScript1) + + # Should find commands from both files + $results.Ast | Should -Not -BeNullOrEmpty + + # The multi-command script has 5 commands: Get-Process, Where-Object, Write-Host, Invoke-Command, Get-Service + # The test script has 1 command: Write-Host + # So we expect at least 6 commands in total + $results.Ast.Count | Should -BeGreaterOrEqual 6 + + # Verify some command names are found + $commandNames = $results.Ast | ForEach-Object { $_.CommandElements[0].Value } + $commandNames | Should -Contain 'Get-Process' + $commandNames | Should -Contain 'Write-Host' + } + + It 'Should process all PS1 files in a folder' { + # Test without recursion + $resultsNoRecurse = Get-AstCommand -Path $testCommandFolder + + # Should find commands from the two scripts in the root folder + $commandsNoRecurse = $resultsNoRecurse.Ast | ForEach-Object { + $_.CommandElements[0].Value + } | Where-Object { $_ } + + $commandsNoRecurse | Should -Contain 'Get-ChildItem' + $commandsNoRecurse | Should -Contain 'Sort-Object' + $commandsNoRecurse | Should -Contain 'Get-Process' + $commandsNoRecurse | Should -Contain 'Select-Object' + $commandsNoRecurse | Should -Not -Contain 'Get-Service' + } + + It 'Should process all PS1 files in a folder recursively when -Recurse is specified' { + # Test with recursion + $resultsRecurse = Get-AstCommand -Path $testCommandFolder -Recurse + + $commandsRecurse = $resultsRecurse.Ast | ForEach-Object { + $_.CommandElements[0].Value + } | Where-Object { $_ } + + $commandsRecurse | Should -Contain 'Get-ChildItem' + $commandsRecurse | Should -Contain 'Sort-Object' + $commandsRecurse | Should -Contain 'Get-Process' + $commandsRecurse | Should -Contain 'Select-Object' + $commandsRecurse | Should -Contain 'Get-Service' + $commandsRecurse | Should -Contain 'Where-Object' + } + + It 'Should filter command names based on the -Name parameter' { + $results = Get-AstCommand -Path $multiCommandScriptPath -Name 'Get-Process' + + # Should only find the specified command + $results.Ast | Should -HaveCount 1 + $results.Ast[0].CommandElements[0].Value | Should -Be 'Get-Process' + } + + It 'Should support wildcards in -Name parameter' { + $results = Get-AstCommand -Path $multiCommandScriptPath -Name 'Get-*' + + # Should find Get-Process and Get-Service + $results.Ast | Should -HaveCount 2 + + $commandNames = $results.Ast | ForEach-Object { $_.CommandElements[0].Value } + $commandNames | Should -Contain 'Get-Process' + $commandNames | Should -Contain 'Get-Service' + } } } @@ -34,21 +461,190 @@ Describe 'Functions' { $functionType = Get-ASTFunctionType -Path $path $functionType.Type | Should -Be 'Function' } + + It 'Should process an array of file paths' { + $results = Get-AstFunctionType -Path @($functionTypesPath, $secondTypesFilePath) + + # Should find function types from both files + $results | Should -HaveCount 6 + + # Check regular functions + $results | Where-Object { $_.Name -eq 'Regular-Function' } | ForEach-Object { $_.Type | Should -Be 'Function' } + $results | Where-Object { $_.Name -eq 'Another-Function' } | ForEach-Object { $_.Type | Should -Be 'Function' } + + # Check filter functions + $results | Where-Object { $_.Name -eq 'Filter-Function' } | ForEach-Object { $_.Type | Should -Be 'Filter' } + $results | Where-Object { $_.Name -eq 'Another-Filter' } | ForEach-Object { $_.Type | Should -Be 'Filter' } + + # Check workflow and configuration + $results | Where-Object { $_.Name -eq 'Test-Workflow' } | ForEach-Object { $_.Type | Should -Be 'Workflow' } + $results | Where-Object { $_.Name -eq 'Test-Configuration' } | ForEach-Object { $_.Type | Should -Be 'Configuration' } + } + + It 'Should process all PS1 files in a folder' { + $results = Get-AstFunctionType -Path $typesFolder + + # Should find functions from the main script in the folder (4 functions) + $results | Should -HaveCount 4 + $functionNames = $results | ForEach-Object { $_.Name } + $functionNames | Should -Contain 'Regular-Function' + $functionNames | Should -Contain 'Filter-Function' + $functionNames | Should -Contain 'Test-Workflow' + $functionNames | Should -Contain 'Test-Configuration' + $functionNames | Should -Not -Contain 'Nested-Function' + } + + It 'Should process all PS1 files in a folder recursively when -Recurse is specified' { + $results = Get-AstFunctionType -Path $typesFolder -Recurse + + # Should find functions from all scripts (6 functions total) + $results | Should -HaveCount 6 + $functionNames = $results | ForEach-Object { $_.Name } + $functionNames | Should -Contain 'Regular-Function' + $functionNames | Should -Contain 'Filter-Function' + $functionNames | Should -Contain 'Test-Workflow' + $functionNames | Should -Contain 'Test-Configuration' + $functionNames | Should -Contain 'Nested-Function' + $functionNames | Should -Contain 'Nested-Filter' + + # Check the types + $results | Where-Object { $_.Name -eq 'Nested-Function' } | ForEach-Object { $_.Type | Should -Be 'Function' } + $results | Where-Object { $_.Name -eq 'Nested-Filter' } | ForEach-Object { $_.Type | Should -Be 'Filter' } + } + + It 'Should filter function names based on the -Name parameter' { + $results = Get-AstFunctionType -Path $functionTypesPath -Name '*-Function' + + # Should only find functions matching the pattern + $results | Should -HaveCount 1 + $results[0].Name | Should -Be 'Regular-Function' + $results[0].Type | Should -Be 'Function' + } } + Context "Function: 'Get-ASTFunctionName'" { It 'Get-ASTFunctionName gets the function name' { $path = Join-Path $PSScriptRoot 'src\Test-Function.ps1' $functionName = Get-ASTFunctionName -Path $path $functionName | Should -Be 'Test-Function' } + + It 'Should process an array of file paths' { + $results = Get-AstFunctionName -Path @($testScript1, $testScript2) + + # Should return function names from both files + $results | Should -HaveCount 2 + $results | Should -Contain 'Test-Function1' + $results | Should -Contain 'Test-Function2' + } + + It 'Should process all PS1 files in a folder' { + $results = Get-AstFunctionName -Path $testSamplesDir + + # Should find functions from the two scripts in the root folder + $results | Should -HaveCount 2 + $results | Should -Contain 'Test-Function1' + $results | Should -Contain 'Test-Function2' + $results | Should -Not -Contain 'Test-Function3' + } + + It 'Should process all PS1 files in a folder recursively when -Recurse is specified' { + $results = Get-AstFunctionName -Path $testSamplesDir -Recurse + + # Should find functions from all three scripts (including subfolder) + $results | Should -HaveCount 3 + $results | Should -Contain 'Test-Function1' + $results | Should -Contain 'Test-Function2' + $results | Should -Contain 'Test-Function3' + } + + It 'Should filter function names based on the -Name parameter' { + $results = Get-AstFunctionName -Path $multiFunctionScriptPath -Name 'Get-*' + + # Should only find functions matching the pattern + $results | Should -HaveCount 1 + $results | Should -Contain 'Get-Something' + $results | Should -Not -Contain 'Set-Something' + $results | Should -Not -Contain 'Private-Helper' + } + + It 'Should support exact matches in -Name parameter' { + $results = Get-AstFunctionName -Path $multiFunctionScriptPath -Name 'Set-Something' + + # Should only find the exact match + $results | Should -HaveCount 1 + $results | Should -Contain 'Set-Something' + } + + It 'Should return all function names from a file with multiple functions' { + $results = Get-AstFunctionName -Path $multiFunctionScriptPath + + # Should find all three functions + $results | Should -HaveCount 3 + $results | Should -Contain 'Get-Something' + $results | Should -Contain 'Set-Something' + $results | Should -Contain 'Private-Helper' + } } - Context "Function: 'Get-ASTFunctionAlias'" { - It 'Get-ASTFunctionAlias gets the function alias' { - $path = Join-Path $PSScriptRoot 'src\Test-Function.ps1' - $functionAlias = Get-ASTFunctionAlias -Path $path - $functionAlias.Alias | Should -Contain 'Test' - $functionAlias.Alias | Should -Contain 'TestFunc' - $functionAlias.Alias | Should -Contain 'Test-Func' + + Context "Function: 'Get-AstFunctionAlias'" { + It 'Should process an array of file paths' { + $results = Get-AstFunctionAlias -Path @($functionAliasPath, $secondAliasFilePath) + + # Should find aliases from both files (3 functions with aliases) + $results | Should -HaveCount 3 + + # Check function names and their aliases + $getSomething = $results | Where-Object { $_.Name -eq 'Get-Something' } + $getSomething.Alias | Should -Contain 'gs' + $getSomething.Alias | Should -Contain 'getSomething' + + $setSomething = $results | Where-Object { $_.Name -eq 'Set-Something' } + $setSomething.Alias | Should -Contain 'ss' + + $getAnotherThing = $results | Where-Object { $_.Name -eq 'Get-AnotherThing' } + $getAnotherThing.Alias | Should -Contain 'gat' + $getAnotherThing.Alias | Should -Contain 'getAnother' + + # Function without alias should not be returned + $results | Where-Object { $_.Name -eq 'Test-NoAlias' } | Should -BeNullOrEmpty + } + + It 'Should process all PS1 files in a folder' { + $results = Get-AstFunctionAlias -Path $aliasFolder + + # Should find functions with aliases from the main script in the folder + $results | Should -HaveCount 2 + $functionNames = $results | ForEach-Object { $_.Name } + $functionNames | Should -Contain 'Get-Something' + $functionNames | Should -Contain 'Set-Something' + $functionNames | Should -Not -Contain 'Get-NestedItem' + } + + It 'Should process all PS1 files in a folder recursively when -Recurse is specified' { + $results = Get-AstFunctionAlias -Path $aliasFolder -Recurse + + # Should find functions with aliases from all scripts + $results | Should -HaveCount 3 + $functionNames = $results | ForEach-Object { $_.Name } + $functionNames | Should -Contain 'Get-Something' + $functionNames | Should -Contain 'Set-Something' + $functionNames | Should -Contain 'Get-NestedItem' + + # Check the nested function aliases + $nestedFunc = $results | Where-Object { $_.Name -eq 'Get-NestedItem' } + $nestedFunc.Alias | Should -Contain 'gni' + $nestedFunc.Alias | Should -Contain 'getNestedItem' + } + + It 'Should filter function names based on the -Name parameter' { + $results = Get-AstFunctionAlias -Path $functionAliasPath -Name 'Get-*' + + # Should only find functions matching the pattern + $results | Should -HaveCount 1 + $results[0].Name | Should -Be 'Get-Something' + $results[0].Alias | Should -Contain 'gs' + $results[0].Alias | Should -Contain 'getSomething' } } } diff --git a/tests/src/Get-AstScript-Samples/Subfolder/TestScript3.ps1 b/tests/src/Get-AstScript-Samples/Subfolder/TestScript3.ps1 new file mode 100644 index 0000000..83db9ac --- /dev/null +++ b/tests/src/Get-AstScript-Samples/Subfolder/TestScript3.ps1 @@ -0,0 +1,11 @@ +function Test-Function3 { + param( + [bool]$Flag + ) + + if ($Flag) { + Write-Host 'Flag is true' + } else { + Write-Host 'Flag is false' + } +} diff --git a/tests/src/Get-AstScript-Samples/TestScript1.ps1 b/tests/src/Get-AstScript-Samples/TestScript1.ps1 new file mode 100644 index 0000000..453fd9d --- /dev/null +++ b/tests/src/Get-AstScript-Samples/TestScript1.ps1 @@ -0,0 +1,7 @@ +function Test-Function1 { + param( + [string]$Name + ) + + Write-Host "Hello, $Name!" +} diff --git a/tests/src/Get-AstScript-Samples/TestScript2.ps1 b/tests/src/Get-AstScript-Samples/TestScript2.ps1 new file mode 100644 index 0000000..54d68ba --- /dev/null +++ b/tests/src/Get-AstScript-Samples/TestScript2.ps1 @@ -0,0 +1,7 @@ +function Test-Function2 { + param( + [int]$Number + ) + + return $Number * 2 +} From ba7ad08d8d154d1d17dac057d4b4d8bafa369c2e Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 9 Mar 2025 22:34:29 +0100 Subject: [PATCH 4/9] =?UTF-8?q?=F0=9F=9A=80=20[Feature]:=20Implement=20fun?= =?UTF-8?q?ctions=20to=20read=20and=20parse=20PowerShell=20scripts=20and?= =?UTF-8?q?=20directories,=20enhancing=20Get-AstScript=20capabilities?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/functions/private/Read-AstDirectory.ps1 | 12 +++ .../private/Read-AstScriptContent.ps1 | 15 ++++ src/functions/private/Read-AstScriptFile.ps1 | 16 ++++ src/functions/public/Core/Get-AstScript.ps1 | 86 +++++++++++-------- 4 files changed, 95 insertions(+), 34 deletions(-) create mode 100644 src/functions/private/Read-AstDirectory.ps1 create mode 100644 src/functions/private/Read-AstScriptContent.ps1 create mode 100644 src/functions/private/Read-AstScriptFile.ps1 diff --git a/src/functions/private/Read-AstDirectory.ps1 b/src/functions/private/Read-AstDirectory.ps1 new file mode 100644 index 0000000..bb8611a --- /dev/null +++ b/src/functions/private/Read-AstDirectory.ps1 @@ -0,0 +1,12 @@ +function Read-AstDirectory { + param( + [string]$DirPath, + [bool]$RecurseDir + ) + + $files = Get-ChildItem -Path $DirPath -Filter '*.ps1' -File -Recurse:$RecurseDir + + foreach ($file in $files) { + Read-AstScriptFile -FilePath $file.FullName + } +} diff --git a/src/functions/private/Read-AstScriptContent.ps1 b/src/functions/private/Read-AstScriptContent.ps1 new file mode 100644 index 0000000..57814b2 --- /dev/null +++ b/src/functions/private/Read-AstScriptContent.ps1 @@ -0,0 +1,15 @@ +function Read-AstScriptContent { + param( + [string]$ScriptContent + ) + + $tokens = $null + $errors = $null + $ast = [System.Management.Automation.Language.Parser]::ParseInput($ScriptContent, [ref]$tokens, [ref]$errors) + + [pscustomobject]@{ + Ast = $ast + Tokens = $tokens + Errors = $errors + } +} diff --git a/src/functions/private/Read-AstScriptFile.ps1 b/src/functions/private/Read-AstScriptFile.ps1 new file mode 100644 index 0000000..15fc89d --- /dev/null +++ b/src/functions/private/Read-AstScriptFile.ps1 @@ -0,0 +1,16 @@ +function Read-AstScriptFile { + param( + [string]$FilePath + ) + + $tokens = $null + $errors = $null + $ast = [System.Management.Automation.Language.Parser]::ParseFile($FilePath, [ref]$tokens, [ref]$errors) + + [pscustomobject]@{ + Path = $FilePath + Ast = $ast + Tokens = $tokens + Errors = $errors + } +} diff --git a/src/functions/public/Core/Get-AstScript.ps1 b/src/functions/public/Core/Get-AstScript.ps1 index 44c6388..ac88fab 100644 --- a/src/functions/public/Core/Get-AstScript.ps1 +++ b/src/functions/public/Core/Get-AstScript.ps1 @@ -42,6 +42,21 @@ Parses multiple PowerShell script files and returns their Asts. + .EXAMPLE + Get-AstScript -Script @("Write-Host 'Hello'", "Write-Host 'World'") + + Parses multiple PowerShell script strings and returns their Asts. + + .EXAMPLE + "Write-Host 'Hello'", "Write-Host 'World'" | Get-AstScript + + Parses multiple PowerShell script strings from the pipeline and returns their Asts. + + .EXAMPLE + Get-ChildItem -Path "C:\\Scripts" -Filter "*.ps1" | Get-AstScript + + Parses all PowerShell script files returned by Get-ChildItem. + .OUTPUTS PSCustomObject @@ -55,13 +70,11 @@ https://psmodule.io/Ast/Functions/Core/Get-AstScript/ #> [outputType([System.Management.Automation.Language.ScriptBlockAst])] - [CmdletBinding()] + [CmdletBinding(DefaultParameterSetName = 'PipelineInput')] param ( # The path(s) to PowerShell script file(s) or folder(s) to be parsed. [Parameter( Mandatory, - ValueFromPipeline, - ValueFromPipelineByPropertyName, ParameterSetName = 'Path' )] [ValidateScript({ @@ -74,59 +87,64 @@ # Process directories recursively [Parameter(ParameterSetName = 'Path')] + [Parameter(ParameterSetName = 'PipelineInput')] [switch] $Recurse, - # The PowerShell script to be parsed. + # The PowerShell script(s) to be parsed. [Parameter( Mandatory, - ValueFromPipeline, - ValueFromPipelineByPropertyName, ParameterSetName = 'Script' )] - [string] $Script + [string[]] $Script, + + # Input from pipeline that will be automatically detected as path or script + [Parameter( + Mandatory = $false, + ValueFromPipeline, + ParameterSetName = 'PipelineInput' + )] + [string[]] $InputObject ) begin {} process { - $tokens = $null - $errors = $null - switch ($PSCmdlet.ParameterSetName) { 'Path' { foreach ($p in $Path) { # Check if the path is a directory if (Test-Path -Path $p -PathType Container) { - # Get all .ps1 files in the directory - $files = Get-ChildItem -Path $p -Filter '*.ps1' -File -Recurse:$Recurse - - foreach ($file in $files) { - $ast = [System.Management.Automation.Language.Parser]::ParseFile($file.FullName, [ref]$tokens, [ref]$errors) - [pscustomobject]@{ - Path = $file.FullName - Ast = $ast - Tokens = $tokens - Errors = $errors - } - } + Read-AstDirectory -DirPath $p -RecurseDir $Recurse } else { # Path is a file - $ast = [System.Management.Automation.Language.Parser]::ParseFile($p, [ref]$tokens, [ref]$errors) - [pscustomobject]@{ - Path = $p - Ast = $ast - Tokens = $tokens - Errors = $errors - } + Read-AstScriptFile -FilePath $p } } } 'Script' { - $ast = [System.Management.Automation.Language.Parser]::ParseInput($Script, [ref]$tokens, [ref]$errors) - [pscustomobject]@{ - Ast = $ast - Tokens = $tokens - Errors = $errors + foreach ($scriptContent in $Script) { + Read-AstScriptContent -ScriptContent $scriptContent + } + } + 'PipelineInput' { + # Default parameter set for handling pipeline input + if ($null -ne $InputObject) { + foreach ($item in $InputObject) { + # Check if input is a file path or directory + if (Test-Path -Path $item -ErrorAction SilentlyContinue) { + if (Test-Path -Path $item -PathType Container) { + Read-AstDirectory -DirPath $item -RecurseDir $Recurse + } else { + Read-AstScriptFile -FilePath $item + } + } elseif ($PSBoundParameters.ContainsKey('InputObject') -and + $InputObject.PSObject.Properties.Name -contains 'FullName' -and + (Test-Path -Path $InputObject.FullName -ErrorAction SilentlyContinue)) { + Read-AstScriptFile -FilePath $InputObject.FullName + } else { + Read-AstScriptContent -ScriptContent $item + } + } } } } From e81b71e17a393769d7f451f737452644bdcd0e7a Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 9 Mar 2025 22:50:18 +0100 Subject: [PATCH 5/9] =?UTF-8?q?=F0=9F=A9=B9=20[Cleanup]:=20Remove=20obsole?= =?UTF-8?q?te=20test=20scripts=20and=20add=20new=20test=20functions=20for?= =?UTF-8?q?=20AST=20parsing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/Ast.Tests.ps1 | 748 +++--------------- tests/src/Functions/Test-ComplexFunction.ps1 | 57 ++ tests/src/Functions/Test-FilterFunction.ps1 | 45 ++ .../Subfolder/TestScript3.ps1 | 11 - .../src/Get-AstScript-Samples/TestScript1.ps1 | 7 - .../src/Get-AstScript-Samples/TestScript2.ps1 | 7 - tests/src/Test-MultipleAliases.ps1 | 24 + tests/src/Test-ParameterTypes.ps1 | 60 ++ 8 files changed, 311 insertions(+), 648 deletions(-) create mode 100644 tests/src/Functions/Test-ComplexFunction.ps1 create mode 100644 tests/src/Functions/Test-FilterFunction.ps1 delete mode 100644 tests/src/Get-AstScript-Samples/Subfolder/TestScript3.ps1 delete mode 100644 tests/src/Get-AstScript-Samples/TestScript1.ps1 delete mode 100644 tests/src/Get-AstScript-Samples/TestScript2.ps1 create mode 100644 tests/src/Test-MultipleAliases.ps1 create mode 100644 tests/src/Test-ParameterTypes.ps1 diff --git a/tests/Ast.Tests.ps1 b/tests/Ast.Tests.ps1 index 27f7142..5056e2e 100644 --- a/tests/Ast.Tests.ps1 +++ b/tests/Ast.Tests.ps1 @@ -1,683 +1,185 @@ #Requires -Modules @{ ModuleName = 'Pester'; RequiredVersion = '5.7.1' } -BeforeAll { - # Set paths for test files - $testSamplesDir = Join-Path -Path $PSScriptRoot -ChildPath 'src\Get-AstScript-Samples' - $testScript1 = Join-Path -Path $testSamplesDir -ChildPath 'TestScript1.ps1' - $testScript2 = Join-Path -Path $testSamplesDir -ChildPath 'TestScript2.ps1' - $subfolderScript = Join-Path -Path $testSamplesDir -ChildPath 'Subfolder\TestScript3.ps1' - - # Create a temp folder for additional tests - $tempFolder = Join-Path -Path $TestDrive -ChildPath 'TempScripts' - New-Item -Path $tempFolder -ItemType Directory -Force | Out-Null - - # Create some test scripts in the temp folder - @' -function Get-Something { - param($param1) - Write-Output $param1 -} -'@ | Out-File -FilePath (Join-Path -Path $tempFolder -ChildPath 'Script1.ps1') - - @' -function Set-Something { - param($value) - $global:testValue = $value -} -'@ | Out-File -FilePath (Join-Path -Path $tempFolder -ChildPath 'Script2.ps1') - - # Create a subfolder with a script - $subFolder = Join-Path -Path $tempFolder -ChildPath 'SubFolder' - New-Item -Path $subFolder -ItemType Directory -Force | Out-Null - - @' -function Test-Something { - return $true -} -'@ | Out-File -FilePath (Join-Path -Path $subFolder -ChildPath 'Script3.ps1') - - # Also create a non-PS1 file that should be ignored - "This is not a PowerShell script" | Out-File -FilePath (Join-Path -Path $tempFolder -ChildPath 'NotAScript.txt') - - # Create a new test script with multiple commands - $multiCommandScriptPath = Join-Path -Path $TestDrive -ChildPath 'MultiCommandScript.ps1' - @' -function Test-MultiCommand { - param($value) - - Get-Process | Where-Object { $_.WorkingSet -gt 100MB } - Write-Host "Testing commands" - Invoke-Command -ScriptBlock { Get-Service } -} -'@ | Out-File -FilePath $multiCommandScriptPath - - # Create a file with multiple functions for testing - $multiFunctionScriptPath = Join-Path -Path $TestDrive -ChildPath 'MultiFunctionScript.ps1' - @' -function Get-Something { - param($param1) - Write-Output $param1 -} - -function Set-Something { - param($value) - $global:testValue = $value -} - -function Private-Helper { - # Hidden helper function - return $true -} -'@ | Out-File -FilePath $multiFunctionScriptPath - - # Create test scripts with different function types - $functionTypesPath = Join-Path -Path $TestDrive -ChildPath 'FunctionTypes.ps1' - @' -function Regular-Function { - param($param1) - Write-Output $param1 -} - -filter Filter-Function { - $_ | Where-Object { $_ -gt 5 } -} - -workflow Test-Workflow { - param($items) - foreach -parallel ($item in $items) { - Write-Output $item - } -} - -configuration Test-Configuration { - param($ComputerName) - - Node $ComputerName { - File TestFile { - DestinationPath = "C:\test.txt" - Contents = "Test content" - } - } -} -'@ | Out-File -FilePath $functionTypesPath - - # Create another file for testing multiple files with function types - $secondTypesFilePath = Join-Path -Path $TestDrive -ChildPath 'SecondFunctionTypes.ps1' - @' -function Another-Function { - # Regular function -} - -filter Another-Filter { - # Filter function -} -'@ | Out-File -FilePath $secondTypesFilePath - - # Create a folder structure for testing recursion with function types - $typesFolder = Join-Path -Path $TestDrive -ChildPath 'FunctionTypesFolder' - New-Item -Path $typesFolder -ItemType Directory -Force | Out-Null - Copy-Item -Path $functionTypesPath -Destination $typesFolder - - $typesSubfolder = Join-Path -Path $typesFolder -ChildPath 'SubFolder' - New-Item -Path $typesSubfolder -ItemType Directory -Force | Out-Null - - $typesSubfolderFilePath = Join-Path -Path $typesSubfolder -ChildPath 'SubFile.ps1' - @' -function Nested-Function { - # Nested function -} - -filter Nested-Filter { - # Nested filter -} -'@ | Out-File -FilePath $typesSubfolderFilePath - - # Create test scripts with function aliases - $functionAliasPath = Join-Path -Path $TestDrive -ChildPath 'FunctionAlias.ps1' - @' -function Get-Something { - [Alias("gs", "getSomething")] - param($param1) - Write-Output $param1 -} - -function Set-Something { - [Alias("ss")] - param($value) - $global:testValue = $value -} - -function Test-NoAlias { - param($test) - # This function has no alias -} -'@ | Out-File -FilePath $functionAliasPath - - # Create another file for testing multiple files with aliases - $secondAliasFilePath = Join-Path -Path $TestDrive -ChildPath 'SecondAliasFile.ps1' - @' -function Get-AnotherThing { - [Alias("gat", "getAnother")] - param($item) - return $item -} -'@ | Out-File -FilePath $secondAliasFilePath - - # Create a folder structure for testing recursion with aliases - $aliasFolder = Join-Path -Path $TestDrive -ChildPath 'AliasFolder' - New-Item -Path $aliasFolder -ItemType Directory -Force | Out-Null - Copy-Item -Path $functionAliasPath -Destination $aliasFolder - - $aliasSubfolder = Join-Path -Path $aliasFolder -ChildPath 'SubFolder' - New-Item -Path $aliasSubfolder -ItemType Directory -Force | Out-Null - - $aliasSubfolderFilePath = Join-Path -Path $aliasSubfolder -ChildPath 'NestedAlias.ps1' - @' -function Get-NestedItem { - [Alias("gni", "getNestedItem")] - param($item) - return $item -} -'@ | Out-File -FilePath $aliasSubfolderFilePath - - # Create a folder with test scripts for command testing - $testCommandFolder = Join-Path -Path $TestDrive -ChildPath 'CommandTests' - New-Item -Path $testCommandFolder -ItemType Directory -Force | Out-Null - - # Create scripts in the command test folder - @' -Get-ChildItem | Sort-Object Name -'@ | Out-File -FilePath (Join-Path -Path $testCommandFolder -ChildPath 'Script1.ps1') - - @' -Get-Process | Select-Object Name, Id -'@ | Out-File -FilePath (Join-Path -Path $testCommandFolder -ChildPath 'Script2.ps1') - - # Create a subfolder with script for command testing - $commandSubFolder = Join-Path -Path $testCommandFolder -ChildPath 'SubFolder' - New-Item -Path $commandSubFolder -ItemType Directory -Force | Out-Null - - @' -Get-Service | Where-Object { $_.Status -eq 'Running' } -'@ | Out-File -FilePath (Join-Path -Path $commandSubFolder -ChildPath 'Script3.ps1') -} - Describe 'Core' { - Context "Function: 'Get-ASTScript'" { - It 'Get-ASTScript gets the script AST' { + Context "Function: 'Get-AstScript'" { + It 'Get-AstScript gets the script AST' { $path = Join-Path $PSScriptRoot 'src\Test-Function.ps1' - $script = Get-ASTScript -Path $path + $script = Get-AstScript -Path $path $script | Should -Not -BeNullOrEmpty $script.Ast | Should -BeOfType [System.Management.Automation.Language.ScriptBlockAst] } - It 'Should process an array of file paths' { - $results = Get-AstScript -Path @($testScript1, $testScript2) - - # Verify we got results for both files - $results | Should -HaveCount 2 - - # Verify paths - $paths = $results | ForEach-Object { $_.Path } - $paths | Should -Contain $testScript1 - $paths | Should -Contain $testScript2 - - # Verify ASTs - $results | ForEach-Object { - $_.Ast | Should -BeOfType [System.Management.Automation.Language.ScriptBlockAst] - } - } - - It 'Should process all PS1 files in a folder' { - $results = Get-AstScript -Path $testSamplesDir - - # Should find the two scripts in the root folder but not the one in subfolder - $results | Should -HaveCount 2 - - # Check paths - $paths = $results | ForEach-Object { $_.Path } - $paths | Should -Contain $testScript1 - $paths | Should -Contain $testScript2 - $paths | Should -Not -Contain $subfolderScript - } - - It 'Should process all PS1 files in a folder recursively when -Recurse is specified' { - $results = Get-AstScript -Path $testSamplesDir -Recurse - - # Should find all three scripts (including subfolder) - $results | Should -HaveCount 3 - - # Check paths - $paths = $results | ForEach-Object { $_.Path } - $paths | Should -Contain $testScript1 - $paths | Should -Contain $testScript2 - $paths | Should -Contain $subfolderScript - } - - It 'Should validate all paths in an array exist' { - # Testing the ValidateScript for array paths - { Get-AstScript -Path @($testScript1, 'NonExistentFile.ps1') } | - Should -Throw + It 'Get-AstScript gets the script AST - with pipeline input' { + $path = Join-Path $PSScriptRoot 'src\Test-Function.ps1' + $script = $path | Get-AstScript + $script | Should -Not -BeNullOrEmpty + $script.Ast | Should -BeOfType [System.Management.Automation.Language.ScriptBlockAst] } - It 'Should handle mixed paths (files and folders)' { - $script1Path = Join-Path -Path $tempFolder -ChildPath 'Script1.ps1' - $results = Get-AstScript -Path @($script1Path, $tempFolder) - - # Should get results for Script1.ps1 (explicitly) + Script1.ps1 and Script2.ps1 (from folder) - $results | Should -HaveCount 3 - - $paths = $results | ForEach-Object { $_.Path } - $script1Path | Should -BeIn $paths - (Join-Path -Path $tempFolder -ChildPath 'Script2.ps1') | Should -BeIn $paths + It 'Get-AstScript gets the script AST - with script content parameter' { + $scriptContent = 'function Test-Script { param($Param1) Write-Output $Param1 }' + $script = Get-AstScript -Script $scriptContent + $script | Should -Not -BeNullOrEmpty + $script.Ast | Should -BeOfType [System.Management.Automation.Language.ScriptBlockAst] } - It 'Should not process files that are not PS1' { - $results = Get-AstScript -Path $tempFolder - - # Should only find the PS1 files (2), not the txt file - $results | Should -HaveCount 2 - - $paths = $results | ForEach-Object { $_.Path } - (Join-Path -Path $tempFolder -ChildPath 'NotAScript.txt') | Should -Not -BeIn $paths + It 'Get-AstScript gets the script AST - with script content pipeline input' { + $scriptContent = 'function Test-Script { param($Param1) Write-Output $Param1 }' + $script = $scriptContent | Get-AstScript + $script | Should -Not -BeNullOrEmpty + $script.Ast | Should -BeOfType [System.Management.Automation.Language.ScriptBlockAst] } - It 'Should properly handle the -Recurse switch' { - # Without recurse - $withoutRecurse = Get-AstScript -Path $tempFolder - $withoutRecurse | Should -HaveCount 2 - - # With recurse - $withRecurse = Get-AstScript -Path $tempFolder -Recurse - $withRecurse | Should -HaveCount 3 - - $paths = $withRecurse | ForEach-Object { $_.Path } - (Join-Path -Path $tempFolder -ChildPath 'SubFolder\Script3.ps1') | Should -BeIn $paths + It 'Get-AstScript processes an array of paths - parameter input' { + $paths = @( + (Join-Path $PSScriptRoot 'src\Test-Function.ps1'), + (Join-Path $PSScriptRoot 'src\Test-MultipleAliases.ps1') + ) + $scripts = Get-AstScript -Path $paths + $scripts | Should -HaveCount 2 + $scripts[0].Ast | Should -BeOfType [System.Management.Automation.Language.ScriptBlockAst] + $scripts[1].Ast | Should -BeOfType [System.Management.Automation.Language.ScriptBlockAst] + } + + It 'Get-AstScript processes an array of paths - pipeline input' { + $paths = @( + (Join-Path $PSScriptRoot 'src\Test-Function.ps1'), + (Join-Path $PSScriptRoot 'src\Test-MultipleAliases.ps1') + ) + $scripts = $paths | Get-AstScript + $scripts | Should -HaveCount 2 + $scripts[0].Ast | Should -BeOfType [System.Management.Automation.Language.ScriptBlockAst] + $scripts[1].Ast | Should -BeOfType [System.Management.Automation.Language.ScriptBlockAst] + } + + It 'Get-AstScript processes an array of script content - parameter input' { + $scriptContents = @( + 'function Test-Script1 { Write-Output "Test1" }', + 'function Test-Script2 { Write-Output "Test2" }' + ) + $scripts = Get-AstScript -Script $scriptContents + $scripts | Should -HaveCount 2 + $scripts[0].Ast | Should -BeOfType [System.Management.Automation.Language.ScriptBlockAst] + $scripts[1].Ast | Should -BeOfType [System.Management.Automation.Language.ScriptBlockAst] + } + + It 'Get-AstScript processes an array of script content - pipeline input' { + $scriptContents = @( + 'function Test-Script1 { Write-Output "Test1" }', + 'function Test-Script2 { Write-Output "Test2" }' + ) + $scripts = $scriptContents | Get-AstScript + $scripts | Should -HaveCount 2 + $scripts[0].Ast | Should -BeOfType [System.Management.Automation.Language.ScriptBlockAst] + $scripts[1].Ast | Should -BeOfType [System.Management.Automation.Language.ScriptBlockAst] + } + + It 'Get-AstScript processes a folder and returns script ASTs for all PS1 files - parameter input' { + $srcFolder = Join-Path $PSScriptRoot 'src' + $scripts = Get-AstScript -Path $srcFolder + $scripts | Should -Not -BeNullOrEmpty + $scripts.Count | Should -BeGreaterThan 1 + $scripts | ForEach-Object { + $_.Ast | Should -BeOfType [System.Management.Automation.Language.ScriptBlockAst] + } } - It 'Should return Path property for file inputs' { - $results = Get-AstScript -Path (Join-Path -Path $tempFolder -ChildPath 'Script1.ps1') - $results.Path | Should -Not -BeNullOrEmpty + It 'Get-AstScript processes a folder and returns script ASTs for all PS1 files - pipeline input' { + $srcFolder = Join-Path $PSScriptRoot 'src' + $scripts = $srcFolder | Get-AstScript + $scripts | Should -Not -BeNullOrEmpty + $scripts.Count | Should -BeGreaterThan 1 + $scripts | ForEach-Object { + $_.Ast | Should -BeOfType [System.Management.Automation.Language.ScriptBlockAst] + } } - It 'Should include correct AST information' { - $results = Get-AstScript -Path (Join-Path -Path $tempFolder -ChildPath 'Script1.ps1') - - # Check for function definition in the AST - $functionDef = $results.Ast.FindAll({ $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] }, $true) - $functionDef.Name | Should -Be 'Get-Something' + It 'Get-AstScript processes mixed input of files and folders - parameter input' { + $paths = @( + (Join-Path $PSScriptRoot 'src\Test-Function.ps1'), + (Join-Path $PSScriptRoot 'src\Functions') + ) + $scripts = Get-AstScript -Path $paths + $scripts | Should -Not -BeNullOrEmpty + $scripts.Count | Should -BeGreaterThan 2 + $scripts | ForEach-Object { + $_.Ast | Should -BeOfType [System.Management.Automation.Language.ScriptBlockAst] + } } } - - Context "Function: 'Get-ASTFunction'" { - It 'Get-ASTFunction gets the function AST' { + Context "Function: 'Get-AstFunction'" { + It 'Get-AstFunction gets the function AST' { $path = Join-Path $PSScriptRoot 'src\Test-Function.ps1' - $function = Get-ASTFunction -Path $path + $function = Get-AstFunction -Path $path $function | Should -Not -BeNullOrEmpty $function.Ast | Should -BeOfType [System.Management.Automation.Language.FunctionDefinitionAst] } - - It 'Should process an array of file paths' { - $results = Get-AstFunction -Path @($testScript1, $testScript2) - - # We should have two ASTs (one for each function) - $results.Ast | Should -HaveCount 2 - - # Verify function names - $functionNames = $results.Ast | ForEach-Object { $_.Name } - $functionNames | Should -Contain 'Test-Function1' - $functionNames | Should -Contain 'Test-Function2' - } - - It 'Should process all PS1 files in a folder' { - $results = Get-AstFunction -Path $testSamplesDir - - # Should find function definitions from the two scripts in the root folder - $results.Ast | Should -HaveCount 2 - - # Verify function names - $functionNames = $results.Ast | ForEach-Object { $_.Name } - $functionNames | Should -Contain 'Test-Function1' - $functionNames | Should -Contain 'Test-Function2' - $functionNames | Should -Not -Contain 'Test-Function3' - } - - It 'Should process all PS1 files in a folder recursively when -Recurse is specified' { - $results = Get-AstFunction -Path $testSamplesDir -Recurse - - # Should find functions from all three scripts (including subfolder) - $results.Ast | Should -HaveCount 3 - - # Verify function names - $functionNames = $results.Ast | ForEach-Object { $_.Name } - $functionNames | Should -Contain 'Test-Function1' - $functionNames | Should -Contain 'Test-Function2' - $functionNames | Should -Contain 'Test-Function3' - } - - It 'Should filter function names based on the -Name parameter' { - $results = Get-AstFunction -Path $testSamplesDir -Recurse -Name 'Test-Function1' - - # Should only find the one specified function - $results.Ast | Should -HaveCount 1 - $results.Ast[0].Name | Should -Be 'Test-Function1' - } - - It 'Should support wildcards in -Name parameter' { - $results = Get-AstFunction -Path $testSamplesDir -Recurse -Name 'Test-Function*' - - # Should find all three functions - $results.Ast | Should -HaveCount 3 - - $functionNames = $results.Ast | ForEach-Object { $_.Name } - $functionNames | Should -Contain 'Test-Function1' - $functionNames | Should -Contain 'Test-Function2' - $functionNames | Should -Contain 'Test-Function3' - } } - - Context "Function: 'Get-ASTCommand'" { - It 'Get-ASTCommand gets the command AST' { + Context "Function: 'Get-AstCommand'" { + It 'Get-AstCommand gets the command AST' { $path = Join-Path $PSScriptRoot 'src\Test-Function.ps1' - $command = Get-ASTCommand -Path $path + $command = Get-AstCommand -Path $path $command | Should -Not -BeNullOrEmpty $command.Ast | Should -BeOfType [System.Management.Automation.Language.CommandAst] } - - It 'Should process an array of file paths' { - $results = Get-AstCommand -Path @($multiCommandScriptPath, $testScript1) - - # Should find commands from both files - $results.Ast | Should -Not -BeNullOrEmpty - - # The multi-command script has 5 commands: Get-Process, Where-Object, Write-Host, Invoke-Command, Get-Service - # The test script has 1 command: Write-Host - # So we expect at least 6 commands in total - $results.Ast.Count | Should -BeGreaterOrEqual 6 - - # Verify some command names are found - $commandNames = $results.Ast | ForEach-Object { $_.CommandElements[0].Value } - $commandNames | Should -Contain 'Get-Process' - $commandNames | Should -Contain 'Write-Host' - } - - It 'Should process all PS1 files in a folder' { - # Test without recursion - $resultsNoRecurse = Get-AstCommand -Path $testCommandFolder - - # Should find commands from the two scripts in the root folder - $commandsNoRecurse = $resultsNoRecurse.Ast | ForEach-Object { - $_.CommandElements[0].Value - } | Where-Object { $_ } - - $commandsNoRecurse | Should -Contain 'Get-ChildItem' - $commandsNoRecurse | Should -Contain 'Sort-Object' - $commandsNoRecurse | Should -Contain 'Get-Process' - $commandsNoRecurse | Should -Contain 'Select-Object' - $commandsNoRecurse | Should -Not -Contain 'Get-Service' - } - - It 'Should process all PS1 files in a folder recursively when -Recurse is specified' { - # Test with recursion - $resultsRecurse = Get-AstCommand -Path $testCommandFolder -Recurse - - $commandsRecurse = $resultsRecurse.Ast | ForEach-Object { - $_.CommandElements[0].Value - } | Where-Object { $_ } - - $commandsRecurse | Should -Contain 'Get-ChildItem' - $commandsRecurse | Should -Contain 'Sort-Object' - $commandsRecurse | Should -Contain 'Get-Process' - $commandsRecurse | Should -Contain 'Select-Object' - $commandsRecurse | Should -Contain 'Get-Service' - $commandsRecurse | Should -Contain 'Where-Object' - } - - It 'Should filter command names based on the -Name parameter' { - $results = Get-AstCommand -Path $multiCommandScriptPath -Name 'Get-Process' - - # Should only find the specified command - $results.Ast | Should -HaveCount 1 - $results.Ast[0].CommandElements[0].Value | Should -Be 'Get-Process' - } - - It 'Should support wildcards in -Name parameter' { - $results = Get-AstCommand -Path $multiCommandScriptPath -Name 'Get-*' - - # Should find Get-Process and Get-Service - $results.Ast | Should -HaveCount 2 - - $commandNames = $results.Ast | ForEach-Object { $_.CommandElements[0].Value } - $commandNames | Should -Contain 'Get-Process' - $commandNames | Should -Contain 'Get-Service' - } } } Describe 'Functions' { - Context "Function: 'Get-ASTFunctionType'" { - It 'Get-ASTFunctionType gets the function type' { + Context "Function: 'Get-AstFunctionType'" { + It 'Get-AstFunctionType gets the function type' { $path = Join-Path $PSScriptRoot 'src\Test-Function.ps1' - $functionType = Get-ASTFunctionType -Path $path + $functionType = Get-AstFunctionType -Path $path $functionType.Type | Should -Be 'Function' } - - It 'Should process an array of file paths' { - $results = Get-AstFunctionType -Path @($functionTypesPath, $secondTypesFilePath) - - # Should find function types from both files - $results | Should -HaveCount 6 - - # Check regular functions - $results | Where-Object { $_.Name -eq 'Regular-Function' } | ForEach-Object { $_.Type | Should -Be 'Function' } - $results | Where-Object { $_.Name -eq 'Another-Function' } | ForEach-Object { $_.Type | Should -Be 'Function' } - - # Check filter functions - $results | Where-Object { $_.Name -eq 'Filter-Function' } | ForEach-Object { $_.Type | Should -Be 'Filter' } - $results | Where-Object { $_.Name -eq 'Another-Filter' } | ForEach-Object { $_.Type | Should -Be 'Filter' } - - # Check workflow and configuration - $results | Where-Object { $_.Name -eq 'Test-Workflow' } | ForEach-Object { $_.Type | Should -Be 'Workflow' } - $results | Where-Object { $_.Name -eq 'Test-Configuration' } | ForEach-Object { $_.Type | Should -Be 'Configuration' } - } - - It 'Should process all PS1 files in a folder' { - $results = Get-AstFunctionType -Path $typesFolder - - # Should find functions from the main script in the folder (4 functions) - $results | Should -HaveCount 4 - $functionNames = $results | ForEach-Object { $_.Name } - $functionNames | Should -Contain 'Regular-Function' - $functionNames | Should -Contain 'Filter-Function' - $functionNames | Should -Contain 'Test-Workflow' - $functionNames | Should -Contain 'Test-Configuration' - $functionNames | Should -Not -Contain 'Nested-Function' - } - - It 'Should process all PS1 files in a folder recursively when -Recurse is specified' { - $results = Get-AstFunctionType -Path $typesFolder -Recurse - - # Should find functions from all scripts (6 functions total) - $results | Should -HaveCount 6 - $functionNames = $results | ForEach-Object { $_.Name } - $functionNames | Should -Contain 'Regular-Function' - $functionNames | Should -Contain 'Filter-Function' - $functionNames | Should -Contain 'Test-Workflow' - $functionNames | Should -Contain 'Test-Configuration' - $functionNames | Should -Contain 'Nested-Function' - $functionNames | Should -Contain 'Nested-Filter' - - # Check the types - $results | Where-Object { $_.Name -eq 'Nested-Function' } | ForEach-Object { $_.Type | Should -Be 'Function' } - $results | Where-Object { $_.Name -eq 'Nested-Filter' } | ForEach-Object { $_.Type | Should -Be 'Filter' } - } - - It 'Should filter function names based on the -Name parameter' { - $results = Get-AstFunctionType -Path $functionTypesPath -Name '*-Function' - - # Should only find functions matching the pattern - $results | Should -HaveCount 1 - $results[0].Name | Should -Be 'Regular-Function' - $results[0].Type | Should -Be 'Function' - } } - - Context "Function: 'Get-ASTFunctionName'" { - It 'Get-ASTFunctionName gets the function name' { + Context "Function: 'Get-AstFunctionName'" { + It 'Get-AstFunctionName gets the function name' { $path = Join-Path $PSScriptRoot 'src\Test-Function.ps1' - $functionName = Get-ASTFunctionName -Path $path + $functionName = Get-AstFunctionName -Path $path $functionName | Should -Be 'Test-Function' } - - It 'Should process an array of file paths' { - $results = Get-AstFunctionName -Path @($testScript1, $testScript2) - - # Should return function names from both files - $results | Should -HaveCount 2 - $results | Should -Contain 'Test-Function1' - $results | Should -Contain 'Test-Function2' - } - - It 'Should process all PS1 files in a folder' { - $results = Get-AstFunctionName -Path $testSamplesDir - - # Should find functions from the two scripts in the root folder - $results | Should -HaveCount 2 - $results | Should -Contain 'Test-Function1' - $results | Should -Contain 'Test-Function2' - $results | Should -Not -Contain 'Test-Function3' - } - - It 'Should process all PS1 files in a folder recursively when -Recurse is specified' { - $results = Get-AstFunctionName -Path $testSamplesDir -Recurse - - # Should find functions from all three scripts (including subfolder) - $results | Should -HaveCount 3 - $results | Should -Contain 'Test-Function1' - $results | Should -Contain 'Test-Function2' - $results | Should -Contain 'Test-Function3' - } - - It 'Should filter function names based on the -Name parameter' { - $results = Get-AstFunctionName -Path $multiFunctionScriptPath -Name 'Get-*' - - # Should only find functions matching the pattern - $results | Should -HaveCount 1 - $results | Should -Contain 'Get-Something' - $results | Should -Not -Contain 'Set-Something' - $results | Should -Not -Contain 'Private-Helper' - } - - It 'Should support exact matches in -Name parameter' { - $results = Get-AstFunctionName -Path $multiFunctionScriptPath -Name 'Set-Something' - - # Should only find the exact match - $results | Should -HaveCount 1 - $results | Should -Contain 'Set-Something' - } - - It 'Should return all function names from a file with multiple functions' { - $results = Get-AstFunctionName -Path $multiFunctionScriptPath - - # Should find all three functions - $results | Should -HaveCount 3 - $results | Should -Contain 'Get-Something' - $results | Should -Contain 'Set-Something' - $results | Should -Contain 'Private-Helper' - } } - Context "Function: 'Get-AstFunctionAlias'" { - It 'Should process an array of file paths' { - $results = Get-AstFunctionAlias -Path @($functionAliasPath, $secondAliasFilePath) - - # Should find aliases from both files (3 functions with aliases) - $results | Should -HaveCount 3 - - # Check function names and their aliases - $getSomething = $results | Where-Object { $_.Name -eq 'Get-Something' } - $getSomething.Alias | Should -Contain 'gs' - $getSomething.Alias | Should -Contain 'getSomething' - - $setSomething = $results | Where-Object { $_.Name -eq 'Set-Something' } - $setSomething.Alias | Should -Contain 'ss' - - $getAnotherThing = $results | Where-Object { $_.Name -eq 'Get-AnotherThing' } - $getAnotherThing.Alias | Should -Contain 'gat' - $getAnotherThing.Alias | Should -Contain 'getAnother' - - # Function without alias should not be returned - $results | Where-Object { $_.Name -eq 'Test-NoAlias' } | Should -BeNullOrEmpty - } - - It 'Should process all PS1 files in a folder' { - $results = Get-AstFunctionAlias -Path $aliasFolder - - # Should find functions with aliases from the main script in the folder - $results | Should -HaveCount 2 - $functionNames = $results | ForEach-Object { $_.Name } - $functionNames | Should -Contain 'Get-Something' - $functionNames | Should -Contain 'Set-Something' - $functionNames | Should -Not -Contain 'Get-NestedItem' - } - - It 'Should process all PS1 files in a folder recursively when -Recurse is specified' { - $results = Get-AstFunctionAlias -Path $aliasFolder -Recurse - - # Should find functions with aliases from all scripts - $results | Should -HaveCount 3 - $functionNames = $results | ForEach-Object { $_.Name } - $functionNames | Should -Contain 'Get-Something' - $functionNames | Should -Contain 'Set-Something' - $functionNames | Should -Contain 'Get-NestedItem' - - # Check the nested function aliases - $nestedFunc = $results | Where-Object { $_.Name -eq 'Get-NestedItem' } - $nestedFunc.Alias | Should -Contain 'gni' - $nestedFunc.Alias | Should -Contain 'getNestedItem' - } - - It 'Should filter function names based on the -Name parameter' { - $results = Get-AstFunctionAlias -Path $functionAliasPath -Name 'Get-*' - - # Should only find functions matching the pattern - $results | Should -HaveCount 1 - $results[0].Name | Should -Be 'Get-Something' - $results[0].Alias | Should -Contain 'gs' - $results[0].Alias | Should -Contain 'getSomething' + It 'Get-AstFunctionAlias gets the function alias' { + $path = Join-Path $PSScriptRoot 'src\Test-Function.ps1' + $functionAlias = Get-AstFunctionAlias -Path $path + $functionAlias.Alias | Should -Contain 'Test' + $functionAlias.Alias | Should -Contain 'TestFunc' + $functionAlias.Alias | Should -Contain 'Test-Func' } } } Describe 'Lines' { - Context 'Function: Get-ASTLineComment' { - It 'Get-ASTLineComment gets the line comment' { + Context 'Function: Get-AstLineComment' { + It 'Get-AstLineComment gets the line comment' { $line = '# This is a comment' - $line = Get-ASTLineComment -Line $line + $line = Get-AstLineComment -Line $line $line | Should -Be '# This is a comment' } - It 'Get-ASTLineComment gets the line comment without leading whitespace' { + It 'Get-AstLineComment gets the line comment without leading whitespace' { $line = ' # This is a comment' - $line = Get-ASTLineComment -Line $line + $line = Get-AstLineComment -Line $line $line | Should -Be '# This is a comment' } - It 'Get-ASTLineComment gets the line comment but not the command' { + It 'Get-AstLineComment gets the line comment but not the command' { $line = ' Get-Command # This is a comment ' - $line = Get-ASTLineComment -Line $line + $line = Get-AstLineComment -Line $line $line | Should -Be '# This is a comment ' } - It 'Get-ASTLineComment returns nothing when no comment is present' { + It 'Get-AstLineComment returns nothing when no comment is present' { $line = 'Get-Command' - $line | Get-ASTLineComment | Should -BeNullOrEmpty + $line | Get-AstLineComment | Should -BeNullOrEmpty } } } Describe 'Scripts' { - Context "Function: 'Get-ASTScriptCommands'" { - It 'Get-ASTScriptCommands gets the script commands' { + Context "Function: 'Get-AstScriptCommands'" { + It 'Get-AstScriptCommands gets the script commands' { $path = Join-Path $PSScriptRoot 'src\Test-Function.ps1' - $commands = Get-ASTScriptCommand -Path $path + $commands = Get-AstScriptCommand -Path $path $commands | Should -Not -BeNullOrEmpty $commands | Should -BeOfType [pscustomobject] $commands.Name | Should -Not -Contain 'ForEach-Object' @@ -687,9 +189,9 @@ Describe 'Scripts' { $commands.Name | Should -Not -Contain '.' $commands.Name | Should -Not -Contain '&' } - It 'Get-ASTScriptCommands gets the script commands (recursive)' { + It 'Get-AstScriptCommands gets the script commands (recursive)' { $path = Join-Path $PSScriptRoot 'src\Test-Function.ps1' - $commands = Get-ASTScriptCommand -Path $path -Recurse + $commands = Get-AstScriptCommand -Path $path -Recurse $commands | Should -Not -BeNullOrEmpty $commands | Should -BeOfType [pscustomobject] $commands.Name | Should -Contain 'ForEach-Object' @@ -699,9 +201,9 @@ Describe 'Scripts' { $commands.Name | Should -Not -Contain '.' $commands.Name | Should -Not -Contain '&' } - It 'Get-ASTScriptCommands gets the script commands with call operators' { + It 'Get-AstScriptCommands gets the script commands with call operators' { $path = Join-Path $PSScriptRoot 'src\Test-Function.ps1' - $commands = Get-ASTScriptCommand -Path $path -IncludeCallOperators + $commands = Get-AstScriptCommand -Path $path -IncludeCallOperators $commands | Should -Not -BeNullOrEmpty $commands | Should -BeOfType [pscustomobject] $commands.Name | Should -Not -Contain 'ForEach-Object' @@ -711,9 +213,9 @@ Describe 'Scripts' { $commands.Name | Should -Not -Contain '.' $commands.Name | Should -Not -Contain '&' } - It 'Get-ASTScriptCommands gets the script commands with call operators (recursive)' { + It 'Get-AstScriptCommands gets the script commands with call operators (recursive)' { $path = Join-Path $PSScriptRoot 'src\Test-Function.ps1' - $commands = Get-ASTScriptCommand -Path $path -Recurse -IncludeCallOperators + $commands = Get-AstScriptCommand -Path $path -Recurse -IncludeCallOperators $commands | Should -Not -BeNullOrEmpty $commands | Should -BeOfType [pscustomobject] $commands.Name | Should -Contain 'ForEach-Object' diff --git a/tests/src/Functions/Test-ComplexFunction.ps1 b/tests/src/Functions/Test-ComplexFunction.ps1 new file mode 100644 index 0000000..e9e002d --- /dev/null +++ b/tests/src/Functions/Test-ComplexFunction.ps1 @@ -0,0 +1,57 @@ +<# +.SYNOPSIS + A complex test function with nested commands and structures. +.DESCRIPTION + This function demonstrates complex PowerShell structures for AST testing. +#> +function Test-ComplexFunction { + [CmdletBinding()] + [Alias('ComplexTest', 'Test-Complex')] + param ( + [Parameter(Mandatory)] + [string]$InputValue, + + [switch]$Debug + ) + + begin { + # Comments should be parsed correctly + $results = @() + if ($Debug) { + Write-Verbose 'Debug mode enabled' + } + } + + process { + try { + # This is a nested command structure + $items = $InputValue -split ',' | ForEach-Object { + $item = $_.Trim() + if ($item -match '\d+') { + [int]$item + } else { + $item.ToUpper() + } + } + + # This uses the call operator + & { + param($data) + foreach ($d in $data) { + [PSCustomObject]@{ + Value = $d + Type = $d.GetType().Name + } + } + } -data $items | ForEach-Object { + $results += $_ + } + } catch { + Write-Error "Error processing input: $_" + } + } + + end { + return $results + } +} diff --git a/tests/src/Functions/Test-FilterFunction.ps1 b/tests/src/Functions/Test-FilterFunction.ps1 new file mode 100644 index 0000000..3fce432 --- /dev/null +++ b/tests/src/Functions/Test-FilterFunction.ps1 @@ -0,0 +1,45 @@ +<# +.SYNOPSIS + A test filter function for AST parsing. +.DESCRIPTION + This filter demonstrates how filter functions work and how they can be parsed. +#> +[Alias('tf', 'testf')] +filter Test-FilterFunction { + $_ | ForEach-Object { + # Process each item + if ($_ -is [string]) { + [PSCustomObject]@{ + Type = 'String' + Value = $_ + Length = $_.Length + IsEmpty = [string]::IsNullOrEmpty($_) + } + } elseif ($_ -is [int] -or $_ -is [double]) { + [PSCustomObject]@{ + Type = $_.GetType().Name + Value = $_ + IsPositive = $_ -gt 0 + IsZero = $_ -eq 0 + } + } else { + [PSCustomObject]@{ + Type = $_.GetType().Name + Value = $_ + ToString = $_.ToString() + } + } + } +} + +# Add a helper function in the same file +function Convert-InputObject { + param( + [Parameter(ValueFromPipeline)] + $InputObject + ) + + process { + $InputObject | Test-FilterFunction + } +} diff --git a/tests/src/Get-AstScript-Samples/Subfolder/TestScript3.ps1 b/tests/src/Get-AstScript-Samples/Subfolder/TestScript3.ps1 deleted file mode 100644 index 83db9ac..0000000 --- a/tests/src/Get-AstScript-Samples/Subfolder/TestScript3.ps1 +++ /dev/null @@ -1,11 +0,0 @@ -function Test-Function3 { - param( - [bool]$Flag - ) - - if ($Flag) { - Write-Host 'Flag is true' - } else { - Write-Host 'Flag is false' - } -} diff --git a/tests/src/Get-AstScript-Samples/TestScript1.ps1 b/tests/src/Get-AstScript-Samples/TestScript1.ps1 deleted file mode 100644 index 453fd9d..0000000 --- a/tests/src/Get-AstScript-Samples/TestScript1.ps1 +++ /dev/null @@ -1,7 +0,0 @@ -function Test-Function1 { - param( - [string]$Name - ) - - Write-Host "Hello, $Name!" -} diff --git a/tests/src/Get-AstScript-Samples/TestScript2.ps1 b/tests/src/Get-AstScript-Samples/TestScript2.ps1 deleted file mode 100644 index 54d68ba..0000000 --- a/tests/src/Get-AstScript-Samples/TestScript2.ps1 +++ /dev/null @@ -1,7 +0,0 @@ -function Test-Function2 { - param( - [int]$Number - ) - - return $Number * 2 -} diff --git a/tests/src/Test-MultipleAliases.ps1 b/tests/src/Test-MultipleAliases.ps1 new file mode 100644 index 0000000..2791e68 --- /dev/null +++ b/tests/src/Test-MultipleAliases.ps1 @@ -0,0 +1,24 @@ +<# +.SYNOPSIS + A test function with multiple aliases. +.DESCRIPTION + This function demonstrates how multiple aliases can be defined and extracted. +#> +function Test-MultipleAliases { + [CmdletBinding()] + [Alias('tma', 'testalias', 'malias', 'Test-MA')] + param ( + [Parameter(ValueFromPipeline)] + [string]$Name = 'Default' + ) + + process { + Write-Output "Processing: $Name" + } +} + +# Register custom completer +Register-ArgumentCompleter -CommandName Test-MultipleAliases -ParameterName Name -ScriptBlock { + param($commandName, $parameterName, $wordToComplete) + @('Option1', 'Option2', 'Option3') | Where-Object { $_ -like "$wordToComplete*" } +} diff --git a/tests/src/Test-ParameterTypes.ps1 b/tests/src/Test-ParameterTypes.ps1 new file mode 100644 index 0000000..411f7b4 --- /dev/null +++ b/tests/src/Test-ParameterTypes.ps1 @@ -0,0 +1,60 @@ +<# +.SYNOPSIS + Tests various parameter types for AST parsing. +.DESCRIPTION + This function contains a variety of parameter types to test AST parsing capabilities. +#> +function Test-ParameterTypes { + [CmdletBinding(DefaultParameterSetName = 'Standard')] + [Alias('ParamTest')] + param ( + [Parameter(Mandatory, ParameterSetName = 'Standard')] + [string]$StringParam, + + [Parameter(ParameterSetName = 'Standard')] + [int]$IntParam, + + [Parameter(ParameterSetName = 'Standard')] + [datetime]$DateParam, + + [Parameter(ParameterSetName = 'Advanced')] + [ValidateSet('Option1', 'Option2', 'Option3')] + [string]$ChoiceParam, + + [Parameter(ParameterSetName = 'Advanced')] + [ValidateScript({ Test-Path $_ })] + [string]$PathParam, + + [Parameter(ValueFromPipeline)] + [object[]]$InputObject, + + [Parameter()] + [hashtable]$Config = @{} + ) + + begin { + $results = [System.Collections.ArrayList]::new() + } + + process { + # Here we'll invoke some commands for AST testing + foreach ($item in $InputObject) { + $info = Get-Member -InputObject $item + $null = $results.Add(@{ + Object = $item + Type = $item.GetType().Name + Members = $info | Select-Object -ExpandProperty Name + }) + } + } + + end { + # Use the dot source operator to load helpers + . { + param($data) + foreach ($entry in $data) { + [PSCustomObject]$entry + } + } -data $results + } +} From 087b848c797706f19ace871b316a7899db662731 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 9 Mar 2025 22:57:04 +0100 Subject: [PATCH 6/9] =?UTF-8?q?=F0=9F=9A=80=20[Feature]:=20Enhance=20Read-?= =?UTF-8?q?Ast=20functions=20with=20detailed=20documentation=20and=20manda?= =?UTF-8?q?tory=20parameters=20for=20improved=20usability?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/functions/private/Read-AstDirectory.ps1 | 39 ++++++++++++++++++- .../private/Read-AstScriptContent.ps1 | 36 ++++++++++++++++- src/functions/private/Read-AstScriptFile.ps1 | 37 +++++++++++++++++- 3 files changed, 108 insertions(+), 4 deletions(-) diff --git a/src/functions/private/Read-AstDirectory.ps1 b/src/functions/private/Read-AstDirectory.ps1 index bb8611a..0bf39dd 100644 --- a/src/functions/private/Read-AstDirectory.ps1 +++ b/src/functions/private/Read-AstDirectory.ps1 @@ -1,7 +1,42 @@ function Read-AstDirectory { + <# + .SYNOPSIS + Reads all PowerShell script files in a directory and processes them with Read-AstScriptFile. + + .DESCRIPTION + This function retrieves all `.ps1` files in the specified directory and passes each file's path + to the `Read-AstScriptFile` function. It supports recursion to include subdirectories. + + .EXAMPLE + Read-AstDirectory -DirPath "C:\Scripts" -RecurseDir $true + + Output: + ```powershell + Processing: C:\Scripts\Script1.ps1 + Processing: C:\Scripts\Subfolder\Script2.ps1 + ``` + + Reads all `.ps1` files from `C:\Scripts` and its subdirectories and processes them. + + .OUTPUTS + PSCustomObject + + .NOTES + An object containing the AST, tokens, and errors from parsing the script. + + .LINK + https://psmodule.io/Ast/Functions/Read-AstDirectory + #> + [OutputType([pscustomobject])] + [CmdletBinding()] param( - [string]$DirPath, - [bool]$RecurseDir + # Specifies the directory path to search for `.ps1` script files. + [Parameter(Mandatory)] + [string] $DirPath, + + # Indicates whether to search subdirectories recursively. + [Parameter()] + [bool] $RecurseDir ) $files = Get-ChildItem -Path $DirPath -Filter '*.ps1' -File -Recurse:$RecurseDir diff --git a/src/functions/private/Read-AstScriptContent.ps1 b/src/functions/private/Read-AstScriptContent.ps1 index 57814b2..eae9068 100644 --- a/src/functions/private/Read-AstScriptContent.ps1 +++ b/src/functions/private/Read-AstScriptContent.ps1 @@ -1,6 +1,40 @@ function Read-AstScriptContent { + <# + .SYNOPSIS + Parses a PowerShell script and returns its abstract syntax tree (AST), tokens, and errors. + + .DESCRIPTION + This function takes a PowerShell script as a string and parses it using the PowerShell language parser. + It returns an object containing the AST representation, tokens, and any syntax errors. + + .EXAMPLE + $script = "Get-Process" + Read-AstScriptContent -ScriptContent $script + + Output: + ```powershell + Ast : [System.Management.Automation.Language.ScriptBlockAst] + Tokens : {Get-Process, EndOfInput} + Errors : {} + ``` + + Parses the provided script and returns the abstract syntax tree, tokens, and errors. + + .OUTPUTS + PSCustomObject + + .NOTES + An object containing the AST, tokens, and errors from parsing the script. + + .LINK + https://psmodule.io/Ast/Functions/Read-AstScriptContent/ + #> + [OutputType([pscustomobject])] + [CmdletBinding()] param( - [string]$ScriptContent + # The PowerShell script content to parse. + [Parameter(Mandatory)] + [string] $ScriptContent ) $tokens = $null diff --git a/src/functions/private/Read-AstScriptFile.ps1 b/src/functions/private/Read-AstScriptFile.ps1 index 15fc89d..cabf586 100644 --- a/src/functions/private/Read-AstScriptFile.ps1 +++ b/src/functions/private/Read-AstScriptFile.ps1 @@ -1,6 +1,41 @@ function Read-AstScriptFile { + <# + .SYNOPSIS + Parses a PowerShell script file and returns its AST, tokens, and errors. + + .DESCRIPTION + Reads a PowerShell script file and processes it using the PowerShell parser. + Returns a custom object containing the file path, abstract syntax tree (AST), + tokens, and any errors encountered during parsing. + + .EXAMPLE + Read-AstScriptFile -FilePath "C:\Scripts\example.ps1" + + Output: + ```powershell + Path : C:\Scripts\example.ps1 + Ast : [System.Management.Automation.Language.ScriptBlockAst] + Tokens : {Token1, Token2, Token3} + Errors : {} + ``` + + Parses the script file "example.ps1" and returns its AST, tokens, and any parsing errors. + + .OUTPUTS + PSCustomObject + + .NOTES + Contains the file path, AST, tokens, and errors. + + .LINK + https://psmodule.io/Ast/Functions/Read-AstScriptFile/ + #> + + [CmdletBinding()] param( - [string]$FilePath + # The path to the PowerShell script file to parse. + [Parameter(Mandatory)] + [string] $FilePath ) $tokens = $null From 2958cfc8ea7e3f33045594b184458f11cf88e29f Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 9 Mar 2025 23:05:09 +0100 Subject: [PATCH 7/9] =?UTF-8?q?=F0=9F=9A=80=20[Feature]:=20Refactor=20test?= =?UTF-8?q?=20functions=20to=20improve=20naming=20consistency=20and=20enha?= =?UTF-8?q?nce=20output=20verbosity=20for=20better=20debugging?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/src/Functions/Test-ComplexFunction.ps1 | 5 +++-- tests/src/Test-MultipleAliases.ps1 | 3 ++- tests/src/Test-ParameterTypes.ps1 | 8 +++++++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/tests/src/Functions/Test-ComplexFunction.ps1 b/tests/src/Functions/Test-ComplexFunction.ps1 index e9e002d..5ab5cf9 100644 --- a/tests/src/Functions/Test-ComplexFunction.ps1 +++ b/tests/src/Functions/Test-ComplexFunction.ps1 @@ -5,19 +5,20 @@ This function demonstrates complex PowerShell structures for AST testing. #> function Test-ComplexFunction { + [OutputType([System.Object[]])] [CmdletBinding()] [Alias('ComplexTest', 'Test-Complex')] param ( [Parameter(Mandatory)] [string]$InputValue, - [switch]$Debug + [switch]$DebugTest ) begin { # Comments should be parsed correctly $results = @() - if ($Debug) { + if ($DebugTest) { Write-Verbose 'Debug mode enabled' } } diff --git a/tests/src/Test-MultipleAliases.ps1 b/tests/src/Test-MultipleAliases.ps1 index 2791e68..acdf5a6 100644 --- a/tests/src/Test-MultipleAliases.ps1 +++ b/tests/src/Test-MultipleAliases.ps1 @@ -4,7 +4,7 @@ .DESCRIPTION This function demonstrates how multiple aliases can be defined and extracted. #> -function Test-MultipleAliases { +function Test-MultipleAlias { [CmdletBinding()] [Alias('tma', 'testalias', 'malias', 'Test-MA')] param ( @@ -20,5 +20,6 @@ function Test-MultipleAliases { # Register custom completer Register-ArgumentCompleter -CommandName Test-MultipleAliases -ParameterName Name -ScriptBlock { param($commandName, $parameterName, $wordToComplete) + $null = $commandName, $parameterName, $wordToComplete @('Option1', 'Option2', 'Option3') | Where-Object { $_ -like "$wordToComplete*" } } diff --git a/tests/src/Test-ParameterTypes.ps1 b/tests/src/Test-ParameterTypes.ps1 index 411f7b4..113673b 100644 --- a/tests/src/Test-ParameterTypes.ps1 +++ b/tests/src/Test-ParameterTypes.ps1 @@ -4,7 +4,7 @@ .DESCRIPTION This function contains a variety of parameter types to test AST parsing capabilities. #> -function Test-ParameterTypes { +function Test-ParameterType { [CmdletBinding(DefaultParameterSetName = 'Standard')] [Alias('ParamTest')] param ( @@ -34,6 +34,12 @@ function Test-ParameterTypes { begin { $results = [System.Collections.ArrayList]::new() + Write-Verbose "StringParam: $StringParam" + Write-Verbose "IntParam: $IntParam" + Write-Verbose "DateParam: $DateParam" + Write-Verbose "ChoiceParam: $ChoiceParam" + Write-Verbose "PathParam: $PathParam" + Write-Verbose "Config: $($Config | Out-String)" } process { From 1535211041d3f660cec84274b4d87e23116ecb53 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 9 Mar 2025 23:10:54 +0100 Subject: [PATCH 8/9] =?UTF-8?q?=F0=9F=9A=80=20[Feature]:=20Add=20detailed?= =?UTF-8?q?=20documentation=20for=20filter=20functions=20and=20enhance=20a?= =?UTF-8?q?lias=20definitions=20for=20improved=20clarity=20and=20usability?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/src/Functions/Test-FilterFunction.ps1 | 8 +++++++- tests/src/Test-MultipleAliases.ps1 | 14 +++++++------- tests/src/Test-ParameterTypes.ps1 | 14 +++++++------- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/tests/src/Functions/Test-FilterFunction.ps1 b/tests/src/Functions/Test-FilterFunction.ps1 index 3fce432..602bcf2 100644 --- a/tests/src/Functions/Test-FilterFunction.ps1 +++ b/tests/src/Functions/Test-FilterFunction.ps1 @@ -4,7 +4,6 @@ .DESCRIPTION This filter demonstrates how filter functions work and how they can be parsed. #> -[Alias('tf', 'testf')] filter Test-FilterFunction { $_ | ForEach-Object { # Process each item @@ -34,6 +33,13 @@ filter Test-FilterFunction { # Add a helper function in the same file function Convert-InputObject { + <# + .SYNOPSIS + Short description + + .DESCRIPTION + Long description + #> param( [Parameter(ValueFromPipeline)] $InputObject diff --git a/tests/src/Test-MultipleAliases.ps1 b/tests/src/Test-MultipleAliases.ps1 index acdf5a6..19993ae 100644 --- a/tests/src/Test-MultipleAliases.ps1 +++ b/tests/src/Test-MultipleAliases.ps1 @@ -1,10 +1,10 @@ -<# -.SYNOPSIS - A test function with multiple aliases. -.DESCRIPTION - This function demonstrates how multiple aliases can be defined and extracted. -#> -function Test-MultipleAlias { +function Test-MultipleAlias { + <# + .SYNOPSIS + A test function with multiple aliases. + .DESCRIPTION + This function demonstrates how multiple aliases can be defined and extracted. + #> [CmdletBinding()] [Alias('tma', 'testalias', 'malias', 'Test-MA')] param ( diff --git a/tests/src/Test-ParameterTypes.ps1 b/tests/src/Test-ParameterTypes.ps1 index 113673b..a22a63b 100644 --- a/tests/src/Test-ParameterTypes.ps1 +++ b/tests/src/Test-ParameterTypes.ps1 @@ -1,10 +1,10 @@ -<# -.SYNOPSIS - Tests various parameter types for AST parsing. -.DESCRIPTION - This function contains a variety of parameter types to test AST parsing capabilities. -#> -function Test-ParameterType { +function Test-ParameterType { + <# + .SYNOPSIS + Tests various parameter types for AST parsing. + .DESCRIPTION + This function contains a variety of parameter types to test AST parsing capabilities. + #> [CmdletBinding(DefaultParameterSetName = 'Standard')] [Alias('ParamTest')] param ( From 10d015616eed13e25f097205bbce0d3c4be84bce Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Mon, 10 Mar 2025 00:28:23 +0100 Subject: [PATCH 9/9] =?UTF-8?q?=F0=9F=9A=80=20[Feature]:=20Make=20pipeline?= =?UTF-8?q?=20input=20parameter=20mandatory=20in=20Get-AstScript=20functio?= =?UTF-8?q?n=20for=20improved=20input=20validation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/functions/public/Core/Get-AstScript.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/functions/public/Core/Get-AstScript.ps1 b/src/functions/public/Core/Get-AstScript.ps1 index ac88fab..1d1d592 100644 --- a/src/functions/public/Core/Get-AstScript.ps1 +++ b/src/functions/public/Core/Get-AstScript.ps1 @@ -99,7 +99,7 @@ # Input from pipeline that will be automatically detected as path or script [Parameter( - Mandatory = $false, + Mandatory, ValueFromPipeline, ParameterSetName = 'PipelineInput' )]