Skip to content

fix(tools): parse UI source with the TypeScript compiler in the font gate (RIG-3855) - #1274

Open
rigel-mintaka wants to merge 1 commit into
mainfrom
compass-ux/3855-font-gate-parser
Open

rigel-mintaka wants to merge 1 commit into
mainfrom
compass-ux/3855-font-gate-parser

Conversation

@rigel-mintaka

Copy link
Copy Markdown
Contributor

The font-coverage gate located rendered characters with a hand-rolled
comment/string lexer. It had no JSX or regex-literal awareness, so an
apostrophe in JSX text opened a string state that never closed on that
line and swallowed everything after it until the next matching quote.

Measured on apps/ui/src: three files and 42 lines sat inside such a
fake quote state, invisible to the scan. RightSidebar.tsx shows both
directions of the bug — the apostrophe in {handle}'s workspace hid the
following lines, and when the state closed on a later quote the em-dash
inside a doc comment was reported as a rendered character. The sibling
gap had the same cause: a // inside a regex literal blanked its line.

The gate is WARN-only today, so the false positive is harmless. The
suppression is not: RIG-3742 makes the gate fail-closed, at which point
an uncovered glyph written into any of those lines passes green, which
is the failure the gate exists to prevent. The file's own header claimed
these gaps did not fire on the current tree; that claim was wrong and is
removed.

scanSource now parses with the TypeScript compiler and reads only leaf
tokens, so comment bodies drop out as trivia and JSX text, regex
literals, and template spans are located as the parser tokenizes them.
JSDoc subtrees are skipped explicitly: they are the one comment form
surfaced as real nodes, and without the skip every doc-comment em-dash
becomes a finding. typescript was already a declared devDependency of
this tool, so no dependency is added. stripComments is deleted; it had
no remaining references.

Verification. Old and new scanners were compared over 163 files keyed
path:line:codepoint: 94 findings before, 93 after, the single dropped
one being the doc-comment false positive, and nothing newly missed. A
span oracle marking every byte reached at a leaf or inside a skipped
JSDoc subtree, against comment trivia from getLeadingCommentRanges,
found no non-ASCII byte in neither set — no fail-open path. Every
finding's reported line and column round-trips back to its own
character. Malformed input (a hard parse error, an unterminated string,
JSX in a .ts file) still yields findings rather than silently
returning none.

Three tests pin the defect classes and genuinely fail against the old
implementation. Three more pin positions on tokens with leading trivia:
mutating the offset base to getFullStart previously survived the whole
suite, so a class of location bug was unpinned on a gate whose entire
output is path:line:column. Finding.column is now documented as a
UTF-16 code-unit offset and pinned by a test, since the old loop counted
codepoints.

One assumption is recorded rather than fixed: a .ts file containing
JSX would be parsed as non-JSX and its JSX text unscanned. It cannot
fire while JSX lives in .tsx, and parsing everything as TSX would
change how type-assertion casts parse.

RIG-3855

Co-authored-by: Matt Wilkinson matt@rigel.build

…gate (RIG-3855)

The font-coverage gate located rendered characters with a hand-rolled
comment/string lexer. It had no JSX or regex-literal awareness, so an
apostrophe in JSX text opened a string state that never closed on that
line and swallowed everything after it until the next matching quote.

Measured on `apps/ui/src`: three files and 42 lines sat inside such a
fake quote state, invisible to the scan. `RightSidebar.tsx` shows both
directions of the bug — the apostrophe in `{handle}'s workspace` hid the
following lines, and when the state closed on a later quote the em-dash
inside a doc comment was reported as a rendered character. The sibling
gap had the same cause: a `//` inside a regex literal blanked its line.

The gate is WARN-only today, so the false positive is harmless. The
suppression is not: RIG-3742 makes the gate fail-closed, at which point
an uncovered glyph written into any of those lines passes green, which
is the failure the gate exists to prevent. The file's own header claimed
these gaps did not fire on the current tree; that claim was wrong and is
removed.

`scanSource` now parses with the TypeScript compiler and reads only leaf
tokens, so comment bodies drop out as trivia and JSX text, regex
literals, and template spans are located as the parser tokenizes them.
JSDoc subtrees are skipped explicitly: they are the one comment form
surfaced as real nodes, and without the skip every doc-comment em-dash
becomes a finding. `typescript` was already a declared devDependency of
this tool, so no dependency is added. `stripComments` is deleted; it had
no remaining references.

Verification. Old and new scanners were compared over 163 files keyed
`path:line:codepoint`: 94 findings before, 93 after, the single dropped
one being the doc-comment false positive, and nothing newly missed. A
span oracle marking every byte reached at a leaf or inside a skipped
JSDoc subtree, against comment trivia from `getLeadingCommentRanges`,
found no non-ASCII byte in neither set — no fail-open path. Every
finding's reported line and column round-trips back to its own
character. Malformed input (a hard parse error, an unterminated string,
JSX in a `.ts` file) still yields findings rather than silently
returning none.

Three tests pin the defect classes and genuinely fail against the old
implementation. Three more pin positions on tokens with leading trivia:
mutating the offset base to `getFullStart` previously survived the whole
suite, so a class of location bug was unpinned on a gate whose entire
output is `path:line:column`. `Finding.column` is now documented as a
UTF-16 code-unit offset and pinned by a test, since the old loop counted
codepoints.

One assumption is recorded rather than fixed: a `.ts` file containing
JSX would be parsed as non-JSX and its JSX text unscanned. It cannot
fire while JSX lives in `.tsx`, and parsing everything as TSX would
change how type-assertion casts parse.

RIG-3855

Co-authored-by: Matt Wilkinson <matt@rigel.build>
@trunk-io

trunk-io Bot commented Sep 17, 2026

Copy link
Copy Markdown

Merging to main in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here

@rigel-mintaka
rigel-mintaka marked this pull request as ready for review September 17, 2026 03:00
@linear-code

linear-code Bot commented Sep 17, 2026

Copy link
Copy Markdown

RIG-3855

@github-actions

Copy link
Copy Markdown

Compass engineering docs preview: https://compass-ux-3855-font-gate-pa.compass-eng-docs.pages.dev

Deployed from compass-ux/3855-font-gate-parser at 89e2b97.

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