AST.psm1

[CmdletBinding()]
param()
$baseName = [System.IO.Path]::GetFileNameWithoutExtension($PSCommandPath)
$script:PSModuleInfo = Test-ModuleManifest -Path "$PSScriptRoot\$baseName.psd1"
$script:PSModuleInfo | Format-List | Out-String -Stream | ForEach-Object { Write-Debug $_ }
$scriptName = $script:PSModuleInfo.Name
Write-Debug "[$scriptName] - Importing module"
#region [functions] - [public]
Write-Debug "[$scriptName] - [functions] - [public] - Processing folder"
#region [functions] - [public] - [Core]
Write-Debug "[$scriptName] - [functions] - [public] - [Core] - Processing folder"
#region [functions] - [public] - [Core] - [Get-FunctionAST]
Write-Debug "[$scriptName] - [functions] - [public] - [Core] - [Get-FunctionAST] - Importing"
function Get-FunctionAST {
    <#
        .SYNOPSIS
        Retrieves the abstract syntax tree (AST) of function definitions from a PowerShell script.

        .DESCRIPTION
        Parses a specified PowerShell script file and extracts function definitions matching the given name.
        By default, it returns all function definitions if no specific name is provided.

        .EXAMPLE
        Get-FunctionAST -Path "C:\Scripts\MyScript.ps1"

        Retrieves all function definitions from "MyScript.ps1".

        .EXAMPLE
        Get-FunctionAST -Name "Get-Data" -Path "C:\Scripts\MyScript.ps1"

        Retrieves only the function definition named "Get-Data" from "MyScript.ps1".
    #>

    [CmdletBinding()]
    param (
        # The name of the function to search for. Defaults to all functions ('*').
        [Parameter()]
        [string] $Name = '*',

        # The path to the PowerShell script file to be parsed.
        [Parameter(Mandatory)]
        [ValidateScript({ Test-Path -Path $_ -PathType Leaf })]
        [string] $Path
    )

    begin {}

    process {
        # Parse the script file into an AST
        $ast = Get-ScriptAST -Path $Path

        # Extract function definitions
        $ast.FindAll({ $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] }, $true) | Where-Object { $_.Name -like $Name }
    }

    end {}
}
Write-Debug "[$scriptName] - [functions] - [public] - [Core] - [Get-FunctionAST] - Done"
#endregion [functions] - [public] - [Core] - [Get-FunctionAST]
#region [functions] - [public] - [Core] - [Get-ScriptAST]
Write-Debug "[$scriptName] - [functions] - [public] - [Core] - [Get-ScriptAST] - Importing"
function Get-ScriptAST {
    <#
        .SYNOPSIS
        Parses a PowerShell script or script file and returns its Abstract Syntax Tree (AST).

        .DESCRIPTION
        This function parses a PowerShell script from a file or a string input and returns its AST.
        It can optionally provide token and error information.

        .EXAMPLE
        Get-ScriptAST -Path "C:\Scripts\MyScript.ps1"

        Parses the PowerShell script at "MyScript.ps1" and returns its AST.

        .EXAMPLE
        Get-ScriptAST -Script "Get-Process | Select-Object Name"

        Parses the provided PowerShell script string and returns its AST.

        .EXAMPLE
        Get-ScriptAST -Path "C:\Scripts\MyScript.ps1" -Tokens ([ref]$tokens) -Errors ([ref]$errors)

        Parses the script file while also capturing tokens and errors.
    #>

    [outputType([System.Management.Automation.Language.ScriptBlockAst])]
    [CmdletBinding()]
    param (
        # The path to the PowerShell script file to be parsed.
        [Parameter(
            Mandatory,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName,
            ParameterSetName = 'Path'
        )]
        # Validate using Test-Path
        [ValidateScript({ Test-Path -Path $_ -PathType Leaf })]
        [string] $Path,

        # The PowerShell script to be parsed.
        [Parameter(
            Mandatory,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName,
            ParameterSetName = 'Script'
        )]
        [string] $Script,

        # Reference variable to store parsed tokens.
        [Parameter()]
        [ref] $Tokens,

        # Reference variable to store parsing errors.
        [Parameter()]
        [ref] $Errors
    )

    begin {}

    process {
        switch ($PSCmdlet.ParameterSetName) {
            'Path' {
                [System.Management.Automation.Language.Parser]::ParseFile($Path, [ref]$Tokens, [ref]$Errors)
            }
            'Script' {
                [System.Management.Automation.Language.Parser]::ParseInput($Script, [ref]$Tokens, [ref]$Errors)
            }
        }
    }

    end {}
}
Write-Debug "[$scriptName] - [functions] - [public] - [Core] - [Get-ScriptAST] - Done"
#endregion [functions] - [public] - [Core] - [Get-ScriptAST]
Write-Debug "[$scriptName] - [functions] - [public] - [Core] - Done"
#endregion [functions] - [public] - [Core]
#region [functions] - [public] - [Functions]
Write-Debug "[$scriptName] - [functions] - [public] - [Functions] - Processing folder"
#region [functions] - [public] - [Functions] - [Get-FunctionAlias]
Write-Debug "[$scriptName] - [functions] - [public] - [Functions] - [Get-FunctionAlias] - Importing"
function Get-FunctionAlias {
    <#
        .SYNOPSIS
        Retrieves function aliases from a PowerShell script file.

        .DESCRIPTION
        Parses a specified PowerShell script file to identify function definitions and extract their associated aliases.
        Returns a custom object containing function names and their corresponding aliases.

        .EXAMPLE
        Get-FunctionAlias -Path "C:\Scripts\MyScript.ps1"

        Retrieves all function aliases defined in the specified script file.

        .EXAMPLE
        Get-FunctionAlias -Name "Get-Data" -Path "C:\Scripts\MyScript.ps1"

        Retrieves the alias information for the function named "Get-Data" from the specified script file.
    #>

    [CmdletBinding()]
    param (
        # The name of the function to search for. Defaults to all functions ('*').
        [Parameter()]
        [string] $Name = '*',

        # The path to the PowerShell script file to be parsed.
        [Parameter(Mandatory)]
        [ValidateScript({ Test-Path -Path $_ -PathType Leaf })]
        [string] $Path
    )

    # Extract function definitions
    $functions = Get-FunctionAST -Path $Path

    # Process each function and extract aliases
    $functions | ForEach-Object {
        $funcName = $_.Name
        $attributes = $_.FindAll({ $args[0] -is [System.Management.Automation.Language.AttributeAst] }, $true)
        $aliasAttr = $attributes | Where-Object { $_ -is [System.Management.Automation.Language.AttributeAst] -and $_.TypeName.Name -eq 'Alias' }

        if ($aliasAttr) {
            $aliases = $aliasAttr.PositionalArguments | ForEach-Object { $_.ToString().Trim('"', "'") }
            [PSCustomObject]@{
                Name  = $funcName
                Alias = $aliases
            }
        }
    } | Where-Object { $_.Name -like $Name }
}
Write-Debug "[$scriptName] - [functions] - [public] - [Functions] - [Get-FunctionAlias] - Done"
#endregion [functions] - [public] - [Functions] - [Get-FunctionAlias]
#region [functions] - [public] - [Functions] - [Get-FunctionName]
Write-Debug "[$scriptName] - [functions] - [public] - [Functions] - [Get-FunctionName] - Importing"
function Get-FunctionName {
    <#
        .SYNOPSIS
        Extracts function names from a specified PowerShell script.

        .DESCRIPTION
        Parses the given PowerShell script file and retrieves all function names
        defined within it. This function utilizes the PowerShell Abstract Syntax Tree (AST)
        to analyze the script and extract function definitions.

        .EXAMPLE
        Get-FunctionName -Path "C:\Scripts\MyScript.ps1"

        Retrieves all function names defined in the specified script file.

        .NOTES
        Uses PowerShell's AST to analyze script structure.
    #>


    [CmdletBinding()]
    param (
        # The path to the script file to parse
        [Parameter(Mandatory)]
        [ValidateScript({ Test-Path -Path $_ -PathType Leaf })]
        [string] $Path
    )

    # Extract function definitions
    $functions = Get-FunctionAST -Path $Path

    # Process each function and extract the name
    $functions | ForEach-Object {
        $_.Name
    }
}
Write-Debug "[$scriptName] - [functions] - [public] - [Functions] - [Get-FunctionName] - Done"
#endregion [functions] - [public] - [Functions] - [Get-FunctionName]
#region [functions] - [public] - [Functions] - [Get-FunctionType]
Write-Debug "[$scriptName] - [functions] - [public] - [Functions] - [Get-FunctionType] - Importing"
function Get-FunctionType {
    <#
        .SYNOPSIS
        Extracts function types from a specified PowerShell script.

        .DESCRIPTION
        Parses the given PowerShell script file and retrieves all function types
        defined within it. This function utilizes the PowerShell Abstract Syntax Tree (AST)
        to analyze the script and extract function definitions.

        .EXAMPLE
        Get-FunctionType -Path "C:\Scripts\MyScript.ps1"

        Retrieves all function types defined in the specified script file.
    #>

    [OutputType([pscustomobject])]
    [CmdletBinding()]
    param(
        # The path to the PowerShell script file to be parsed.
        [Parameter(
            Mandatory,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName,
            ParameterSetName = 'Path'
        )]
        [ValidateScript({ Test-Path -Path $_ -PathType Leaf })]
        [string] $Path
    )

    begin {}

    process {
        $functionAST = Get-FunctionAST -Path $Path

        $functionAST | ForEach-Object {
            $type = $_.IsFilter ? 'Filter' : $_.IsWorkflow ? 'Workflow' : $_.IsConfiguration ? 'Configuration' : 'Function'
            [pscustomobject]@{
                Name = $_.Name
                Type = $type
            }
        }
    }

    end {}
}
Write-Debug "[$scriptName] - [functions] - [public] - [Functions] - [Get-FunctionType] - Done"
#endregion [functions] - [public] - [Functions] - [Get-FunctionType]
Write-Debug "[$scriptName] - [functions] - [public] - [Functions] - Done"
#endregion [functions] - [public] - [Functions]
#region [functions] - [public] - [Scripts]
Write-Debug "[$scriptName] - [functions] - [public] - [Scripts] - Processing folder"
#region [functions] - [public] - [Scripts] - [Get-ScriptCommand]
Write-Debug "[$scriptName] - [functions] - [public] - [Scripts] - [Get-ScriptCommand] - Importing"
function Get-ScriptCommand {
    <#
        .SYNOPSIS
        Retrieves the commands used within a specified PowerShell script.

        .DESCRIPTION
        Analyzes a given PowerShell script and extracts all command invocations.
        Optionally includes call operators (& and .) in the results.
        Returns details such as command name, position, and file reference.

        .EXAMPLE
        Get-ScriptCommand -Path "C:\Scripts\example.ps1"

        Extracts and lists all commands found in the specified PowerShell script.

        .EXAMPLE
        Get-ScriptCommand -Path "C:\Scripts\example.ps1" -IncludeCallOperators

        Extracts all commands, including those executed with call operators (& and .).
    #>

    [Alias('Get-ScriptCommands')]
    [CmdletBinding()]
    param (
        # The path to the PowerShell script file to be parsed.
        [Parameter(Mandatory)]
        [ValidateScript({ Test-Path -Path $_ -PathType Leaf })]
        [string] $Path,

        # Include call operators in the results, i.e. & and .
        [Parameter()]
        [switch] $IncludeCallOperators
    )

    # Extract function definitions
    $ast = Get-ScriptAST -Path $Path

    # Gather CommandAsts
    $commandAST = $ast.FindAll({ $args[0] -is [System.Management.Automation.Language.CommandAst] }, $true)

    if (-not $IncludeCallOperators) {
        $commandAST = $commandAST | Where-Object { $_.InvocationOperator -notin 'Ampersand', 'Dot' }
    }

    $commandAST | ForEach-Object {
        $invocationOperator = switch ($_.InvocationOperator) {
            'Ampersand' { '&' }
            'Dot' { '.' }
        }
        $_.CommandElements[0].Extent | ForEach-Object {
            [pscustomobject]@{
                Name              = [string]::IsNullOrEmpty($invocationOperator) ? $_.Text : $invocationOperator
                StartLineNumber   = $_.StartLineNumber
                StartColumnNumber = $_.StartColumnNumber
                EndLineNumber     = $_.EndLineNumber
                EndColumnNumber   = $_.EndColumnNumber
                File              = $_.File
            }
        }
    }
}
Write-Debug "[$scriptName] - [functions] - [public] - [Scripts] - [Get-ScriptCommand] - Done"
#endregion [functions] - [public] - [Scripts] - [Get-ScriptCommand]
Write-Debug "[$scriptName] - [functions] - [public] - [Scripts] - Done"
#endregion [functions] - [public] - [Scripts]
Write-Debug "[$scriptName] - [functions] - [public] - Done"
#endregion [functions] - [public]

#region Member exporter
$exports = @{
    Alias    = '*'
    Cmdlet   = ''
    Function = @(
        'Get-FunctionAST'
        'Get-ScriptAST'
        'Get-FunctionAlias'
        'Get-FunctionName'
        'Get-FunctionType'
        'Get-ScriptCommand'
    )
}
Export-ModuleMember @exports
#endregion Member exporter