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) } |