Modules/businessdev.ALbuild.Apps/Private/Get-BcAlObjectMap.ps1
|
function Get-BcAlObjectMap { <# .SYNOPSIS Maps AL object identity (type + id) to its source file by scanning a workspace's .al files. .DESCRIPTION Reads the first object declaration in each .al file (e.g. `codeunit 50100 "My Codeunit"`) and returns a hashtable keyed "<Type>:<Id>" -> { Type; Id; Name; File }. Used to attach BC code coverage rows (which carry only object type + id) to source files. Skips symbol/output/tooling folders (relative to the root), like Get-BcProjectBuildOrder. Objects belonging to a TEST app are skipped. The coverage gate exists to show how much PRODUCT code is covered, and test code is covered by construction - it runs when the test runs. Measuring it mixes a near-100% block into the number and rewards writing more test code over testing more deeply. In one real repository this was 42% of the denominator and 8.7 percentage points of the reported figure - enough to decide a 80% gate on its own. The app an object belongs to is resolved from the nearest app.json (Resolve-BcAlAppContext), so this works for every repository without any pipeline change. #> [CmdletBinding()] [OutputType([hashtable])] param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $WorkspaceRoot ) $root = (Resolve-Path -LiteralPath $WorkspaceRoot).Path $map = @{} # First object declaration: <type> <id> <name> ; name may be quoted or bare. $rx = [regex]::new( '(?im)^\s*(codeunit|table|page|report|xmlport|query|enum|controladdin|pageextension|tableextension|reportextension|enumextension|permissionset|permissionsetextension|profile|interface|entitlement|dotnet)\s+(\d+)\s+("(?<q>[^"]+)"|(?<b>[A-Za-z0-9_]+))') foreach ($file in Get-ChildItem -LiteralPath $root -Filter '*.al' -File -Recurse -ErrorAction SilentlyContinue | Where-Object { $_.FullName.Substring($root.Length) -notmatch '[\\/](\.alpackages|\.altemplates|\.snapshots|\.output|output|\.git|\.claude|node_modules)[\\/]' }) { # Test apps never enter the measurement (see the description). Checked before reading the file: # the app context is cached per app folder, so this costs one lookup per app, not per file. if ((Resolve-BcAlAppContext -Path $file.FullName).IsTestApp) { continue } $text = Get-Content -LiteralPath $file.FullName -Raw -ErrorAction SilentlyContinue if (-not $text) { continue } $m = $rx.Match($text) if (-not $m.Success) { continue } $type = (Get-Culture).TextInfo.ToTitleCase($m.Groups[1].Value.ToLowerInvariant()) $id = [int]$m.Groups[2].Value $name = if ($m.Groups['q'].Success) { $m.Groups['q'].Value } else { $m.Groups['b'].Value } $map["$type`:$id"] = [PSCustomObject]@{ Type = $type; Id = $id; Name = $name; File = $file.FullName } } return $map } |