Skip to content

fix(reusable-lint): parse JSONC-by-specification files with a JSONC parser - #57

Merged
bryanfawcett merged 1 commit into
mainfrom
ci/jsonc-validity
Sep 11, 2026
Merged

fix(reusable-lint): parse JSONC-by-specification files with a JSONC parser#57
bryanfawcett merged 1 commit into
mainfrom
ci/jsonc-validity

Conversation

@bryanfawcett

Copy link
Copy Markdown
Contributor

lint / JSON validity parsed every *.json with a strict json.load(). But ".json" is two different formats.

The bug, found in production

openNTL/ntl's mcp/ntl-postgres-mcp-server/tsconfig.json fails the gate. It contains a four-line comment explaining why exactOptionalPropertyTypes is deliberately off — that the MCP SDK's own Transport and CallToolResult types are not written for it. TypeScript documents and supports comments in tsconfig.json. Nothing about that file is broken; the checker was. The only way to satisfy a strict parser there is to delete a correct explanation.

The fix

Two parsers, chosen by filename. Ordinary .json stays strict. The by-specification JSONC names get a JSONC parse: tsconfig*.json, jsconfig*.json, .vscode/*.json, devcontainer.json, .eslintrc.json, *.jsonc.

This tightens the gate. A genuine syntax error in a tsconfig still fails; *.jsonc files are now validated at all, where the old -name '*.json' find never saw them; nothing is exempted. The -type f behaviour from #54 is preserved.

Comments are stripped before trailing commas, in two passes, because a trailing comma can be separated from its brace by a comment — { "a": true, /* note */ }. My first attempt got this wrong and the test caught it. Newlines inside block comments are preserved so reported line numbers still point at the real line.

Proven both ways

Passes (correctly):

case result
legal // and /* */ comments in tsconfig.json pass
trailing comma behind a comment in .vscode/settings.json pass
a string containing "// not a comment and , } inside" pass
a directory named server-card.json (the #54 case) skipped
openNTL/ntl in full, 7 files pass
this repo, 5 files pass

Still fails (correctly):

case result
missing : in a commented tsconfig.json line=4 invalid JSONC: Expecting ':' delimiter
unterminated string in a commented tsconfig.json line=3 invalid JSONC: Invalid control character
// comment in package.json — not JSONC by spec line=2 invalid JSON: Expecting property name

Unblocks openNTL/ntl#30, which has auto-merge armed.

Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com

…arser

`lint / JSON validity` parsed every *.json with a strict json.load(). But
".json" is two different formats. TypeScript documents and supports `//`
comments in tsconfig.json, as do VS Code's settings files, devcontainer
and .eslintrc. Pointing a strict parser at those is simply the wrong
parser, and the only way to satisfy it is to delete the comments.

It has already cost real information once. openNTL/ntl's
mcp/ntl-postgres-mcp-server/tsconfig.json carries a four-line comment
explaining why exactOptionalPropertyTypes is deliberately off - that the
MCP SDK's own Transport and CallToolResult types are not written for it.
Nothing about that file is broken; the checker was.

So: two parsers, chosen by filename. Ordinary .json stays strict. The
by-specification JSONC names - tsconfig*.json, jsconfig*.json,
.vscode/*.json, devcontainer.json, .eslintrc.json, *.jsonc - get a JSONC
parse that strips comments and trailing commas without touching string
contents. This TIGHTENS the gate rather than weakening it: a genuine
syntax error in a tsconfig still fails, *.jsonc files are now validated
where previously the `-name '*.json'` find never saw them at all, and
nothing is exempted.

Comments are stripped before trailing commas, in two passes, because a
trailing comma can be separated from its brace by a comment as in
`{ "a": true, /* note */ }`. Newlines inside block comments are preserved
so reported line numbers still point at the real line. The `-type f`
behaviour from #54 is kept - os.walk lists a DIRECTORY named *.json under
dirs rather than files, and an isfile() guard covers symlinks to one.

Proven both ways before landing. PASS: legal comments in tsconfig.json
and .vscode/settings.json; a string containing "// not a comment and , }";
a directory named server-card.json; openNTL/ntl in full; this repo.
FAIL as required: a missing ':' in a commented tsconfig (line 4), an
unterminated string in a commented tsconfig (line 3), and a `//` comment
in package.json, which is NOT JSONC by specification (line 2).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@bryanfawcett
bryanfawcett enabled auto-merge (rebase) September 11, 2026 18:48
@bryanfawcett
bryanfawcett merged commit 7a21169 into main Sep 11, 2026
6 checks passed
@bryanfawcett
bryanfawcett deleted the ci/jsonc-validity branch September 11, 2026 18:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant