Modules/businessdev.ALbuild.Apps/Private/ConvertFrom-BcJsonc.ps1

function ConvertFrom-BcJsonc {
    <#
    .SYNOPSIS
        Parses JSON with comments and trailing commas (JSONC), as VS Code writes it.
 
    .DESCRIPTION
        '.vscode/settings.json' is read by VS Code, which accepts JSONC: '//' line comments, '/* */'
        block comments, and a comma after the last element. ConvertFrom-Json accepts none of that, and
        a repo whose file carries one of them looked exactly like a repo with no configuration at all -
        Banking's quality gate ran with no analyzers for weeks because of a single trailing comma.
 
        Comments and commas are removed with a scanner rather than a regular expression, because both
        appear legitimately INSIDE strings: '"al.ruleSetPath": "http://example/x"' must survive, and so
        must a value that ends in a comma character. The scanner tracks string state and escapes.
 
        Whatever is still unparsable afterwards throws, and the caller is expected to say so loudly.
 
    .PARAMETER Text
        The file's contents.
 
    .OUTPUTS
        The parsed object, as ConvertFrom-Json returns it.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)] [AllowEmptyString()] [string] $Text
    )

    $out = [System.Text.StringBuilder]::new($Text.Length)
    $inString = $false
    $escaped = $false
    $i = 0
    while ($i -lt $Text.Length) {
        $c = $Text[$i]

        if ($inString) {
            [void]$out.Append($c)
            if ($escaped) { $escaped = $false }
            elseif ($c -eq '\') { $escaped = $true }
            elseif ($c -eq '"') { $inString = $false }
            $i++
            continue
        }

        if ($c -eq '"') { $inString = $true; [void]$out.Append($c); $i++; continue }

        if ($c -eq '/' -and ($i + 1) -lt $Text.Length) {
            $next = $Text[$i + 1]
            if ($next -eq '/') {
                while ($i -lt $Text.Length -and $Text[$i] -ne "`n") { $i++ }
                continue
            }
            if ($next -eq '*') {
                $i += 2
                while (($i + 1) -lt $Text.Length -and -not ($Text[$i] -eq '*' -and $Text[$i + 1] -eq '/')) { $i++ }
                $i += 2
                continue
            }
        }

        if ($c -eq ',') {
            # A comma is trailing when the next thing that is not whitespace closes the container.
            $j = $i + 1
            while ($j -lt $Text.Length -and [char]::IsWhiteSpace($Text[$j])) { $j++ }
            if ($j -lt $Text.Length -and ($Text[$j] -eq '}' -or $Text[$j] -eq ']')) { $i++; continue }
        }

        [void]$out.Append($c)
        $i++
    }

    return ($out.ToString() | ConvertFrom-Json)
}