< Summary - Kestrun — Combined Coverage

Information
Class: Private.Variable.Get-KrAssignedVariables
Assembly: Kestrun.PowerShell.Private
File(s): /home/runner/work/Kestrun/Kestrun/src/PowerShell/Kestrun/Private/Variable/Get-KrAssignedVariables.ps1
Tag: Kestrun/Kestrun@9d3a582b2d63930269564a7591aa77ef297cadeb
Line coverage
67%
Covered lines: 58
Uncovered lines: 28
Coverable lines: 86
Total lines: 185
Line coverage: 67.4%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

File(s)

/home/runner/work/Kestrun/Kestrun/src/PowerShell/Kestrun/Private/Variable/Get-KrAssignedVariables.ps1

#LineLine coverage
 1<#
 2    .SYNOPSIS
 3      Find variables that are *defined/assigned* in a scriptblock and (optionally) fetch their values.
 4    .DESCRIPTION
 5      Scans the AST for AssignmentStatementAst where the LHS is a VariableExpressionAst,
 6      and optionally Set-Variable/New-Variable calls. Can resolve current values from
 7      the appropriate scope (local/script/global) in the *current* runspace.
 8    .PARAMETER ScriptBlock
 9      ScriptBlock to scan. If omitted, use -Current to inspect the currently running block.
 10    .PARAMETER FromParent
 11      Inspect the *currently executing* scriptblock (via $MyInvocation.MyCommand.ScriptBlock).
 12    .PARAMETER IncludeSetVariable
 13      Also detect variables created via Set-Variable/New-Variable (-Name … [-Scope …]).
 14    .PARAMETER ResolveValues
 15      Resolve current values from variable:<scope>:<name> and include Type/Value.
 16    .PARAMETER DefaultScope
 17      Scope to assume when the LHS had no explicit scope (default: Local).
 18  #>
 19function Get-KrAssignedVariable {
 20    [CmdletBinding(DefaultParameterSetName = 'Given')]
 21    param(
 22        [Parameter(ParameterSetName = 'Given', Position = 0)]
 23        [scriptblock]$ScriptBlock,
 24
 25        # NEW: use the caller's scriptblock (parent frame)
 26        [Parameter(ParameterSetName = 'FromParent', Mandatory)]
 27        [switch]$FromParent,
 28
 29        # How many frames up to climb (1 = immediate caller)
 30        [Parameter(ParameterSetName = 'FromParent')]
 31        [int]$Up = 1,
 32
 33        # Optional: skip frames from these modules when searching
 34        [Parameter(ParameterSetName = 'FromParent')]
 135        [string[]]$ExcludeModules = @('Kestrun'),
 36
 37        [switch]$IncludeSetVariable,
 38        [switch]$ResolveValues,
 39        [ValidateSet('Local', 'Script', 'Global')]
 40        [string]$DefaultScope = 'Script'
 41    )
 42
 43    # ---------- resolve $ScriptBlock source ----------
 144    if ($FromParent.IsPresent) {
 145        $allFrames = Get-PSCallStack
 46        # 0 = this function, 1 = immediate caller, 2+ = higher parents
 247        $frames = $allFrames | Select-Object -Skip 1
 48
 149        if ($ExcludeModules.Count) {
 250            $frames = $frames | Where-Object {
 151                $mn = $_.InvocationInfo.MyCommand.ModuleName
 352                -not ($mn -and ($mn -in $ExcludeModules))
 53            }
 54        }
 55
 56        # pick the desired parent frame
 357        $frame = $frames | Select-Object -Skip ($Up - 1) -First 1
 158        if (-not $frame) { throw "No parent frame found (Up=$Up)." }
 59
 60        # Figure out how far “up” that is compared to the original call stack
 261        $scopeUp = ($allFrames.IndexOf($frame)) + 1
 162        if ($scopeUp -lt 1) { throw "Parent frame not found." }
 63
 64        # prefer its live ScriptBlock; if null, rebuild from file
 165        $ScriptBlock = $frame.InvocationInfo.MyCommand.ScriptBlock
 166        if (-not $ScriptBlock -and $frame.ScriptName) {
 067            $ScriptBlock = [scriptblock]::Create((Get-Content -Raw -LiteralPath $frame.ScriptName))
 68        }
 169        if (-not $ScriptBlock) { throw "Parent frame has no scriptblock or script file to parse." }
 70    }
 71
 172    if (-not $ScriptBlock) {
 073        throw "No scriptblock provided. Use -FromParent or pass a ScriptBlock."
 74    }
 275    $ast = ($ScriptBlock.Ast).ToString()
 76
 177    $endstring = $ast.IndexOf("Enable-KrConfiguration", [StringComparison]::OrdinalIgnoreCase)
 178    if ($endstring -lt 0) {
 079        throw "The provided scriptblock does not appear to contain 'Enable-KrConfiguration' call."
 80    }
 181    $ast = $ast.Substring(0, $endstring).Trim()
 182    if ($ast.StartsWith('{')) {
 183        $ast += "`n}"
 84    }
 185    $ScriptBlock = [scriptblock]::Create($ast)
 86
 87
 88    <#
 89   .SYNOPSIS
 90       Checks if a given AST node is inside a function.
 91   .DESCRIPTION
 92       This function traverses the parent nodes of the given AST node to determine if it is
 93       located within a function definition.
 94    .PARAMETER node
 95       The AST node to check.
 96    .OUTPUTS
 97       [bool] Returns true if the node is inside a function, false otherwise.
 98   #>
 99    function _IsInFunction([System.Management.Automation.Language.Ast] $node) {
 1100        $p = $node.Parent
 1101        while ($p) {
 1102            if ($p -is [System.Management.Automation.Language.FunctionDefinitionAst]) { return $true }
 1103            $p = $p.Parent
 104        }
 1105        return $false
 106    }
 107
 1108    $assignAsts = $ScriptBlock.Ast.FindAll(
 1109        { param($n) $n -is [System.Management.Automation.Language.AssignmentStatementAst] }, $true)
 110
 1111    foreach ($a in $assignAsts) {
 1112        $varAst = $a.Left.Find(
 1113            { param($n) $n -is [System.Management.Automation.Language.VariableExpressionAst] }, $true
 1114        ) | Select-Object -First 1
 1115        if (-not $varAst) { continue }
 116
 1117        $vp = $varAst.VariablePath
 1118        $name = $vp.UnqualifiedPath
 119
 2120        if (-not $name) { $name = $vp.UserPath }            # ← fallback
 1121        if (-not $name) { continue }
 1122        $name = $name -replace '^[^:]*:', ''
 1123        if ($name.Contains('.') -or $name.Contains('[')) { continue }
 1124        if ($name.StartsWith('{') -and $name.EndsWith('}')) {
 0125            $name = $name.Substring(1, $name.Length - 2)        # ← ${foo} → foo
 126        }
 1127        $val = Get-Variable -Name $name -Scope $scopeUp -ErrorAction SilentlyContinue
 128
 1129        [pscustomobject]@{
 1130            Name = $name
 1131            ScopeHint = $scope
 1132            ProviderPath = $provider
 1133            Source = 'Assignment'
 1134            Operator = $a.Operator.ToString()
 1135            Type = $type
 1136            Value = $val
 137        }
 138    }
 139
 1140    if ($IncludeSetVariable) {
 1141        $cmdAsts = $ScriptBlock.Ast.FindAll(
 2142            { param($n) $n -is [System.Management.Automation.Language.CommandAst] -and -not (_IsInFunction $n) }, $true)
 143
 1144        foreach ($c in $cmdAsts) {
 1145            $cmd = $c.GetCommandName()
 1146            if ($cmd -notin 'Set-Variable', 'New-Variable') { continue }
 0147            $named = @{}
 0148            foreach ($e in $c.CommandElements) {
 0149                if ($e -is [System.Management.Automation.Language.CommandParameterAst] -and $e.ParameterName -in 'Name',
 0150                    $arg = $e.Argument
 0151                    if ($arg -is [System.Management.Automation.Language.StringConstantExpressionAst]) {
 0152                        $named[$e.ParameterName] = $arg.Value
 153                    }
 154                }
 155            }
 0156            if ($named.ContainsKey('Name')) {
 0157                $name = $named['Name']
 0158                $scope = $named['Scope'] ?? $DefaultScope
 0159                $provider = "variable:$($scope):$name"
 0160                $val = $null; $type = $null
 0161                if ($ResolveValues) {
 162                    try {
 0163                        $val = (Get-Item -EA SilentlyContinue $provider).Value
 0164                        if ($null -ne $val) { $type = $val.GetType().FullName }
 165                    } catch {
 0166                        Write-Warning "Failed to resolve variable '$name' in scope '$scope': $_"
 167                    }
 168                }
 0169                [pscustomobject]@{
 0170                    Name = $name
 0171                    ScopeHint = $scope
 0172                    ProviderPath = $provider
 0173                    Source = $cmd
 0174                    Operator = $null
 0175                    Type = $type
 0176                    Value = $val
 0177                } | ForEach-Object { [void]$rows.Add($_) }
 178            }
 179        }
 180    }
 181
 182    # keep last occurrence per (ScopeHint, Name)
 3183    $rows | Group-Object ScopeHint, Name | ForEach-Object { $_.Group[-1] }
 184}
 185