Skip to content

fix(ingestion): parse tsconfig.json as JSONC, not JSON - #2156

Closed
nickbrus wants to merge 1 commit into
repowise-dev:mainfrom
nickbrus:fix/tsconfig-jsonc-comments
Closed

fix(ingestion): parse tsconfig.json as JSONC, not JSON#2156
nickbrus wants to merge 1 commit into
repowise-dev:mainfrom
nickbrus:fix/tsconfig-jsonc-comments

Conversation

@nickbrus

@nickbrus nickbrus commented Sep 7, 2026

Copy link
Copy Markdown

Summary

  • tsconfig.json is JSONC — tsc accepts // and /* */, and real configs carry them. TsconfigResolver._parse_json_lenient tolerated trailing commas only, so a commented root config raised, the error was swallowed at log.debug, and the resolver came up with zero path aliases.
  • Downstream that is silent and expensive: every aliased import (@/components/Button, ~/lib/x) is minted as an external: node instead of an edge to the real file, the real file's in-degree stays 0, and the dead-code analyzer reports it unreachable.
  • The stripper is a hand-written scanner, not a regex — a regex cannot tell a // inside a string literal ("url": "https://example.com") from a real comment, and tsconfig files carry both.

What it looked like in the field

One TypeScript monorepo, one // comment at line 76 of the root tsconfig.json:

dead-code findings
stock 0.48.0 / 0.49.0 1,288
with this fix 618

670 false rows from one comment. Eleven aliases were being silently lost — ~/*, @/*, @assets/*, @styles/*, @ui/*, two package roots and their wildcards, and two macro paths. Nothing surfaced: the only trace was one log.debug("tsconfig_parse_failed", …).

tsconfig_resolver.py is byte-identical in 0.48.0, 0.49.0 and main, so this is live on main today.

Related Issues

Same symptom as #648, reached through the config loader rather than the rebuild wiring. Not a duplicate — #648's path is fixed; this one is not.

Notes on the implementation

  • Candidates are tried cheapest-first (text → trailing-comma strip → comment strip → both), so a plain JSON file still costs one json.loads and no scanning.
  • A file that parses as neither JSON nor JSONC now logs a clear reason instead of a JSONDecodeError byte offset.
  • No behaviour change for any config that parsed before — the first two candidates are exactly the old code path.

Test Plan

TestJsoncComments in tests/unit/ingestion/test_tsconfig_resolver.py — 10 cases:

  • the four shapes tsc accepts: plain JSON, trailing comma, //, /* */, and comment + trailing comma together;
  • the two traps a regex-based stripper falls into: "https://example.com" and an escaped quote "a\"//b";
  • two negatives: not-JSON-at-all and a non-dict top level both return None;
  • one end-to-end: resolve("@/components/Button", …) through a config with comments in it.

Four of the ten fail on main before the change. Full file: 39 passed.

  • Tests pass (pytest tests/unit/ingestion tests/unit/dead_code) — failure set identical to main on the same machine, and +10 passing
  • Lint passes (ruff check, ruff format --check on both touched files)
  • Web build — no frontend changes

Checklist

  • My code follows the project's code style
  • I have added tests for new functionality
  • All existing tests still pass
  • I have updated documentation if needed (none needed — no user-facing surface changes)

tsconfig.json is JSONC: tsc accepts // and /* */ and real configs carry
them. _parse_json_lenient tolerated trailing commas only, so a commented
root config raised, the error was swallowed at debug level, and the
resolver came up with zero path aliases.

Downstream that is silent and expensive. Every aliased import
("@/components/Button", "~/lib/x") is minted as an external: node instead
of an edge to the real file; the real file's in-degree stays 0; and the
dead-code analyzer, which credits a symbol or file only on an inbound
edge, reports it unreachable. On one TS monorepo with a single // comment
at line 76 of the root tsconfig, dead-code findings went from 618 to
1,288 -- 670 false rows, and 11 aliases (~/*, @/*, @assets/*, @styles/*,
@ui/* and friends) silently lost. Same symptom as #648, reached through
the config loader rather than the rebuild wiring.

The stripper is a hand-written scanner, not a regex: a regex cannot tell
a // inside a string literal ("url": "https://example.com") from a real
comment, and tsconfig files carry both. Candidates are tried
cheapest-first, so a plain JSON file still costs one json.loads and no
scanning, and an unparseable file now logs a clear reason instead of a
JSONDecodeError offset.

Tests: 10 cases in TestJsoncComments -- the four shapes tsc accepts, the
two traps a regex-based stripper falls into, two negatives, and one
end-to-end resolve through a commented config. Four of them fail on main.
@nickbrus nickbrus closed this Sep 7, 2026
@nickbrus
nickbrus deleted the fix/tsconfig-jsonc-comments branch September 7, 2026 15:58
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