diff --git a/src/functions/private/Read-AstDirectory.ps1 b/src/functions/private/Read-AstDirectory.ps1 new file mode 100644 index 0000000..0bf39dd --- /dev/null +++ b/src/functions/private/Read-AstDirectory.ps1 @@ -0,0 +1,47 @@ +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( + # 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 + + 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..eae9068 --- /dev/null +++ b/src/functions/private/Read-AstScriptContent.ps1 @@ -0,0 +1,49 @@ +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( + # The PowerShell script content to parse. + [Parameter(Mandatory)] + [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..cabf586 --- /dev/null +++ b/src/functions/private/Read-AstScriptFile.ps1 @@ -0,0 +1,51 @@ +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( + # The path to the PowerShell script file to parse. + [Parameter(Mandatory)] + [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 ab19b53..1d1d592 100644 --- a/src/functions/public/Core/Get-AstScript.ps1 +++ b/src/functions/public/Core/Get-AstScript.ps1 @@ -32,6 +32,31 @@ 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. + + .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 @@ -45,46 +70,83 @@ https://psmodule.io/Ast/Functions/Core/Get-AstScript/ #> [outputType([System.Management.Automation.Language.ScriptBlockAst])] - [CmdletBinding()] + [CmdletBinding(DefaultParameterSetName = 'PipelineInput')] 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, - # The PowerShell script to be parsed. + # Process directories recursively + [Parameter(ParameterSetName = 'Path')] + [Parameter(ParameterSetName = 'PipelineInput')] + [switch] $Recurse, + + # 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, + ValueFromPipeline, + ParameterSetName = 'PipelineInput' + )] + [string[]] $InputObject ) begin {} 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) { + Read-AstDirectory -DirPath $p -RecurseDir $Recurse + } else { + # Path is a file + Read-AstScriptFile -FilePath $p + } + } } 'Script' { - $ast = [System.Management.Automation.Language.Parser]::ParseInput($Script, [ref]$tokens, [ref]$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 + } + } + } } - } - [pscustomobject]@{ - Ast = $ast - Tokens = $tokens - Errors = $errors } } diff --git a/tests/AST.Tests.ps1 b/tests/AST.Tests.ps1 deleted file mode 100644 index 6c1b09a..0000000 --- a/tests/AST.Tests.ps1 +++ /dev/null @@ -1,131 +0,0 @@ -#Requires -Modules @{ ModuleName = 'Pester'; RequiredVersion = '5.7.1' } - -Describe 'Core' { - 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 | Should -Not -BeNullOrEmpty - $script.Ast | Should -BeOfType [System.Management.Automation.Language.ScriptBlockAst] - } - } - 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 | Should -Not -BeNullOrEmpty - $function.Ast | Should -BeOfType [System.Management.Automation.Language.FunctionDefinitionAst] - } - } - 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 | Should -Not -BeNullOrEmpty - $command.Ast | Should -BeOfType [System.Management.Automation.Language.CommandAst] - } - } -} - -Describe 'Functions' { - 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.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' - } - } - 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' - } - } -} - -Describe 'Lines' { - Context 'Function: Get-ASTLineComment' { - It 'Get-ASTLineComment gets the line comment' { - $line = '# This is a comment' - $line = Get-ASTLineComment -Line $line - $line | Should -Be '# This is a comment' - } - It 'Get-ASTLineComment gets the line comment without leading whitespace' { - $line = ' # This is a comment' - $line = Get-ASTLineComment -Line $line - $line | Should -Be '# This is a comment' - } - It 'Get-ASTLineComment gets the line comment but not the command' { - $line = ' Get-Command # This is a comment ' - $line = Get-ASTLineComment -Line $line - $line | Should -Be '# This is a comment ' - } - It 'Get-ASTLineComment returns nothing when no comment is present' { - $line = 'Get-Command' - $line | Get-ASTLineComment | Should -BeNullOrEmpty - } - } -} - -Describe 'Scripts' { - 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 | Should -Not -BeNullOrEmpty - $commands | Should -BeOfType [pscustomobject] - $commands.Name | Should -Not -Contain 'ForEach-Object' - $commands.Name | Should -Not -Contain 'Get-Process' - $commands.Name | Should -Not -Contain 'ipmo' - $commands.Name | Should -Contain 'Register-ArgumentCompleter' - $commands.Name | Should -Not -Contain '.' - $commands.Name | Should -Not -Contain '&' - } - It 'Get-ASTScriptCommands gets the script commands (recursive)' { - $path = Join-Path $PSScriptRoot 'src\Test-Function.ps1' - $commands = Get-ASTScriptCommand -Path $path -Recurse - $commands | Should -Not -BeNullOrEmpty - $commands | Should -BeOfType [pscustomobject] - $commands.Name | Should -Contain 'ForEach-Object' - $commands.Name | Should -Contain 'Get-Process' - $commands.Name | Should -Contain 'ipmo' - $commands.Name | Should -Contain 'Register-ArgumentCompleter' - $commands.Name | Should -Not -Contain '.' - $commands.Name | Should -Not -Contain '&' - } - 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 | Should -Not -BeNullOrEmpty - $commands | Should -BeOfType [pscustomobject] - $commands.Name | Should -Not -Contain 'ForEach-Object' - $commands.Name | Should -Not -Contain 'Get-Process' - $commands.Name | Should -Not -Contain 'ipmo' - $commands.Name | Should -Contain 'Register-ArgumentCompleter' - $commands.Name | Should -Not -Contain '.' - $commands.Name | Should -Not -Contain '&' - } - 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 | Should -Not -BeNullOrEmpty - $commands | Should -BeOfType [pscustomobject] - $commands.Name | Should -Contain 'ForEach-Object' - $commands.Name | Should -Contain 'Get-Process' - $commands.Name | Should -Contain 'ipmo' - $commands.Name | Should -Contain 'Register-ArgumentCompleter' - $commands.Name | Should -Contain '.' - $commands.Name | Should -Contain '&' - } - } -} diff --git a/tests/Ast.Tests.ps1 b/tests/Ast.Tests.ps1 new file mode 100644 index 0000000..5056e2e --- /dev/null +++ b/tests/Ast.Tests.ps1 @@ -0,0 +1,229 @@ +#Requires -Modules @{ ModuleName = 'Pester'; RequiredVersion = '5.7.1' } + +Describe 'Core' { + 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 | Should -Not -BeNullOrEmpty + $script.Ast | Should -BeOfType [System.Management.Automation.Language.ScriptBlockAst] + } + + 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 '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 '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 '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 '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 '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' { + $path = Join-Path $PSScriptRoot 'src\Test-Function.ps1' + $function = Get-AstFunction -Path $path + $function | Should -Not -BeNullOrEmpty + $function.Ast | Should -BeOfType [System.Management.Automation.Language.FunctionDefinitionAst] + } + } + 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 | Should -Not -BeNullOrEmpty + $command.Ast | Should -BeOfType [System.Management.Automation.Language.CommandAst] + } + } +} + +Describe 'Functions' { + 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.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' + } + } + 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' + } + } +} + +Describe 'Lines' { + Context 'Function: Get-AstLineComment' { + It 'Get-AstLineComment gets the line comment' { + $line = '# This is a comment' + $line = Get-AstLineComment -Line $line + $line | Should -Be '# This is a comment' + } + It 'Get-AstLineComment gets the line comment without leading whitespace' { + $line = ' # This is a comment' + $line = Get-AstLineComment -Line $line + $line | Should -Be '# This is a comment' + } + It 'Get-AstLineComment gets the line comment but not the command' { + $line = ' Get-Command # This is a comment ' + $line = Get-AstLineComment -Line $line + $line | Should -Be '# This is a comment ' + } + It 'Get-AstLineComment returns nothing when no comment is present' { + $line = 'Get-Command' + $line | Get-AstLineComment | Should -BeNullOrEmpty + } + } +} + +Describe 'Scripts' { + 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 | Should -Not -BeNullOrEmpty + $commands | Should -BeOfType [pscustomobject] + $commands.Name | Should -Not -Contain 'ForEach-Object' + $commands.Name | Should -Not -Contain 'Get-Process' + $commands.Name | Should -Not -Contain 'ipmo' + $commands.Name | Should -Contain 'Register-ArgumentCompleter' + $commands.Name | Should -Not -Contain '.' + $commands.Name | Should -Not -Contain '&' + } + It 'Get-AstScriptCommands gets the script commands (recursive)' { + $path = Join-Path $PSScriptRoot 'src\Test-Function.ps1' + $commands = Get-AstScriptCommand -Path $path -Recurse + $commands | Should -Not -BeNullOrEmpty + $commands | Should -BeOfType [pscustomobject] + $commands.Name | Should -Contain 'ForEach-Object' + $commands.Name | Should -Contain 'Get-Process' + $commands.Name | Should -Contain 'ipmo' + $commands.Name | Should -Contain 'Register-ArgumentCompleter' + $commands.Name | Should -Not -Contain '.' + $commands.Name | Should -Not -Contain '&' + } + 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 | Should -Not -BeNullOrEmpty + $commands | Should -BeOfType [pscustomobject] + $commands.Name | Should -Not -Contain 'ForEach-Object' + $commands.Name | Should -Not -Contain 'Get-Process' + $commands.Name | Should -Not -Contain 'ipmo' + $commands.Name | Should -Contain 'Register-ArgumentCompleter' + $commands.Name | Should -Not -Contain '.' + $commands.Name | Should -Not -Contain '&' + } + 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 | Should -Not -BeNullOrEmpty + $commands | Should -BeOfType [pscustomobject] + $commands.Name | Should -Contain 'ForEach-Object' + $commands.Name | Should -Contain 'Get-Process' + $commands.Name | Should -Contain 'ipmo' + $commands.Name | Should -Contain 'Register-ArgumentCompleter' + $commands.Name | Should -Contain '.' + $commands.Name | Should -Contain '&' + } + } +} diff --git a/tests/src/Functions/Test-ComplexFunction.ps1 b/tests/src/Functions/Test-ComplexFunction.ps1 new file mode 100644 index 0000000..5ab5cf9 --- /dev/null +++ b/tests/src/Functions/Test-ComplexFunction.ps1 @@ -0,0 +1,58 @@ +<# +.SYNOPSIS + A complex test function with nested commands and structures. +.DESCRIPTION + 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]$DebugTest + ) + + begin { + # Comments should be parsed correctly + $results = @() + if ($DebugTest) { + 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..602bcf2 --- /dev/null +++ b/tests/src/Functions/Test-FilterFunction.ps1 @@ -0,0 +1,51 @@ +<# +.SYNOPSIS + A test filter function for AST parsing. +.DESCRIPTION + This filter demonstrates how filter functions work and how they can be parsed. +#> +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 { + <# + .SYNOPSIS + Short description + + .DESCRIPTION + Long description + #> + param( + [Parameter(ValueFromPipeline)] + $InputObject + ) + + process { + $InputObject | Test-FilterFunction + } +} diff --git a/tests/src/Test-MultipleAliases.ps1 b/tests/src/Test-MultipleAliases.ps1 new file mode 100644 index 0000000..19993ae --- /dev/null +++ b/tests/src/Test-MultipleAliases.ps1 @@ -0,0 +1,25 @@ +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 ( + [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) + $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 new file mode 100644 index 0000000..a22a63b --- /dev/null +++ b/tests/src/Test-ParameterTypes.ps1 @@ -0,0 +1,66 @@ +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 ( + [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() + 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 { + # 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 + } +}