Modules/businessdev.ALbuild.Apps/Private/Resolve-BcAlAssertionReach.ps1
|
function Resolve-BcAlAssertionReach { <# .SYNOPSIS Answers whether an assertion is reachable from a set of statements through calls into the workspace. .DESCRIPTION A test that delegates its checks - `MatrixLibrary.RunCase('AA-DEB-P1-B1-A1-X11')` - asserts nothing in its own body, and a per-body scan therefore has to either report it or trust the callee's name. This walks the calls instead: every identifier followed by '(' is looked up in the procedure index, and the callee's body is examined for an assertion, then its callees, up to MaxDepth. Only procedures that exist in the scanned source are followed, so the walk terminates on AL built-ins and on calls into dependency apps. Each procedure is visited once, which also breaks recursion. .PARAMETER Statements The statement lines to start from (the test body). .PARAMETER Index Procedure index: lowercase procedure name -> list of statement arrays. Several procedures may share a name (one per object), so all candidates are examined; the walk asks whether an assertion is reachable, not which overload the compiler would pick. .PARAMETER AssertionRegex The assertion pattern applied to the callee bodies. .PARAMETER MaxDepth How many call levels to follow. 1 = only the procedures the test calls itself. .OUTPUTS PSCustomObject (via, depth) when an assertion is reachable, otherwise $null. #> [CmdletBinding()] [OutputType([PSCustomObject])] param( [Parameter(Mandatory)] [AllowEmptyCollection()] [string[]] $Statements, [Parameter(Mandatory)] [System.Collections.IDictionary] $Index, [Parameter(Mandatory)] [regex] $AssertionRegex, [int] $MaxDepth = 3 ) if ($MaxDepth -lt 1) { return $null } $callRx = [regex]::new('(?i)([A-Za-z0-9_]+)\s*\(') $visited = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase) $frontier = @($Statements) for ($depth = 1; $depth -le $MaxDepth; $depth++) { $names = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase) foreach ($statement in $frontier) { foreach ($call in $callRx.Matches($statement)) { [void]$names.Add($call.Groups[1].Value) } } $next = [System.Collections.Generic.List[string]]::new() foreach ($name in $names) { if (-not $visited.Add($name)) { continue } if (-not $Index.Contains($name)) { continue } foreach ($body in @($Index[$name])) { foreach ($statement in @($body)) { if ($AssertionRegex.IsMatch($statement)) { return [PSCustomObject]@{ via = $name; depth = $depth } } [void]$next.Add($statement) } } } if ($next.Count -eq 0) { break } $frontier = @($next) } return $null } |