Modules/businessdev.ALbuild.Apps/Private/ConvertTo-BcAlStackFrame.ps1
|
function ConvertTo-BcAlStackFrame { <# .SYNOPSIS Parses one line of a Business Central AL callstack and resolves it to a file and line. .DESCRIPTION Internal helper. A frame looks like one of these, measured on BC 28.4: Library Assert(CodeUnit 130002).AreEqual line 3 - Library Assert by Microsoft version 28.4... AL Probe Differential(CodeUnit 50190).Case001_x line 6 - AL Probe Differential by 365 busi... Test Runner - Isol. Codeunit(CodeUnit 130450).OnRun(Trigger) line 4 - Test Runner by Micro... Command Line Test Tool(Page 130455).RunNextTest - OnAction(Trigger) line 7 - Test Runner b... THE LINE NUMBER IS NOT A FILE LINE. It is counted from the procedure's own declaration line, so the file line is declaration + reported. Verified against a generated test: the failing assertion sat at file line 16, its procedure was declared at line 10, and the frame said line 6. Returns $null for a line that is not a frame (blank lines, wrapped text), so the caller can hand the whole stack over without pre-filtering. #> [CmdletBinding()] [OutputType([PSCustomObject])] param( [Parameter(Mandatory)] [AllowEmptyString()] [string] $Line, [Parameter(Mandatory)] [hashtable] $ObjectIndex ) $pattern = '^\s*(?<obj>.+?)\((?<kind>CodeUnit|Codeunit|Page|Table|Report|Query|XmlPort)\s+(?<id>\d+)\)\.' + '(?<proc>[^\s(]+)(?<trigger>\s*-\s*\w+)?(?:\(Trigger\))?\s+line\s+(?<line>\d+)\s*-\s*' + '(?<app>.+?)\s+by\s+(?<pub>.+?)(?:\s+version\s+(?<ver>[\d.]+))?\s*$' $m = [regex]::Match($Line, $pattern) if (-not $m.Success) { return $null } $objectName = $m.Groups['obj'].Value.Trim() $kind = $m.Groups['kind'].Value.ToLowerInvariant() $id = [int] $m.Groups['id'].Value $reported = [int] $m.Groups['line'].Value # A trigger frame writes the member as "RunNextTest - OnAction"; the declaration to look up is the # trigger, not the action that led to it. $procedure = $m.Groups['proc'].Value if ($m.Groups['trigger'].Success) { $procedure = ($m.Groups['trigger'].Value -replace '^\s*-\s*', '') } # Prefer the id: object names repeat across apps, ids do not. $entry = $ObjectIndex["${kind}:${id}"] if (-not $entry) { $entry = $ObjectIndex[$objectName.ToLowerInvariant()] } $file = $null $resolvedLine = $null if ($entry -and $entry.Procedures.ContainsKey($procedure.ToLowerInvariant())) { $file = $entry.File $resolvedLine = [int] $entry.Procedures[$procedure.ToLowerInvariant()] + $reported } return [PSCustomObject]@{ Object = $objectName Kind = $m.Groups['kind'].Value Id = $id Procedure = $procedure ReportedLine = $reported File = $file Line = $resolvedLine App = $m.Groups['app'].Value.Trim() Publisher = $m.Groups['pub'].Value.Trim() # False for anything outside the workspace (Library Assert, the test runner, Microsoft objects). # Stated rather than guessed: a wrong file reference costs more than a missing one. Resolved = ($null -ne $resolvedLine) } } |