What was measured
scripts/js-comment-mask.mjs is this tree's one code/prose separator. Its scanSource
returns three per-character flag arrays — its own JSDoc says so, and so does the code:
scripts/js-comment-mask.mjs
* @returns {{ comment: Uint8Array, literal: Uint8Array, interpolation: Uint8Array }}
return { comment, literal, interpolation };
The hand-written declaration beside it, scripts/js-comment-mask.d.mts, declares two:
export interface SourceFlags {
comment: Uint8Array;
literal: Uint8Array;
}
export function scanSource(source: string): SourceFlags;
interpolation is not a vestigial field. It is already load-bearing for a real consumer:
scripts/pm/dispatch-gates.mjs:3005 * interpolations, which are code — `scan.interpolation` exists for exactly
scripts/pm/dispatch-gates.mjs:3027 if (scan.literal[i] && !scan.interpolation[i]) continue;
Why this is worth a card
That consumer is a .mjs file, so no tsc program reads it and the gap costs nothing today.
It is the NEXT consumer that pays, and the shape of the payment is the specific failure the
whole module exists to prevent.
js-comment-mask.d.mts's own header states why it exists: a gate under
packages/spec/scripts/ imports the scanner, that directory is inside a tsc program, and an
untyped .mjs import is TS7016 — the scanner silently becomes any. So the declaration is
the surface every TYPE-CHECKED consumer sees. A TypeScript gate that needs to tell an
interpolation's code bytes from its template's literal bytes reads .interpolation, gets
TS2339 against SourceFlags, and reasonably concludes the shared scanner cannot answer its
question.
The next step after that conclusion is a private scanner — which is exactly what
check:comment-mask-adoption exists to red, and exactly the drift whose measured cost is
recorded in the module's own header (16 files disagreeing with a real parser, 15 of them in
the fabricating direction).
The same header says the declaration is maintained by hand: "Keep this file in step
with the module by hand, and keep it small enough that doing so stays trivial." This is one
step out of step. Nothing mechanically holds the two in agreement, which is presumably how
it drifted and is the reason it can drift again.
Not proposed here, left to triage
Whether the fix is (a) adding the field to SourceFlags, (b) adding it and a check that
holds the declaration equal to the module's returned shape, or (c) declaring the omission
deliberate in the header (an intentionally narrowed public surface, with the .mjs
consumer named as the exception) is a real choice with different costs, and this card takes
no position. ⚠️ Option (c) is not obviously wrong: the header does say the two flag arrays
are "the load-bearing part of the surface, so they are typed precisely", which can be read
as a deliberate narrowing rather than an oversight. If it was deliberate, the header should
say so where the next reader will look, because today the JSDoc and the declaration simply
disagree.
Context — surfaced while implementing #13874, which imports scanSource from this
module for its literal flag. That card needed only literal and is unaffected; nothing in
it is blocked by this. ⛔ Deliberately not fixed there: it is a different file, a different
question, and #13874's ruling is strictly additive.
What was measured
scripts/js-comment-mask.mjsis this tree's one code/prose separator. ItsscanSourcereturns three per-character flag arrays — its own JSDoc says so, and so does the code:
The hand-written declaration beside it,
scripts/js-comment-mask.d.mts, declares two:interpolationis not a vestigial field. It is already load-bearing for a real consumer:Why this is worth a card
That consumer is a
.mjsfile, so no tsc program reads it and the gap costs nothing today.It is the NEXT consumer that pays, and the shape of the payment is the specific failure the
whole module exists to prevent.
js-comment-mask.d.mts's own header states why it exists: a gate underpackages/spec/scripts/imports the scanner, that directory is inside a tsc program, and anuntyped
.mjsimport is TS7016 — the scanner silently becomesany. So the declaration isthe surface every TYPE-CHECKED consumer sees. A TypeScript gate that needs to tell an
interpolation's code bytes from its template's literal bytes reads
.interpolation, getsTS2339 against
SourceFlags, and reasonably concludes the shared scanner cannot answer itsquestion.
The next step after that conclusion is a private scanner — which is exactly what
check:comment-mask-adoptionexists to red, and exactly the drift whose measured cost isrecorded in the module's own header (16 files disagreeing with a real parser, 15 of them in
the fabricating direction).
The same header says the declaration is maintained by hand: "Keep this file in step
with the module by hand, and keep it small enough that doing so stays trivial." This is one
step out of step. Nothing mechanically holds the two in agreement, which is presumably how
it drifted and is the reason it can drift again.
Not proposed here, left to triage
Whether the fix is (a) adding the field to⚠️ Option (c) is not obviously wrong: the header does say the two flag arrays
SourceFlags, (b) adding it and a check thatholds the declaration equal to the module's returned shape, or (c) declaring the omission
deliberate in the header (an intentionally narrowed public surface, with the
.mjsconsumer named as the exception) is a real choice with different costs, and this card takes
no position.
are "the load-bearing part of the surface, so they are typed precisely", which can be read
as a deliberate narrowing rather than an oversight. If it was deliberate, the header should
say so where the next reader will look, because today the JSDoc and the declaration simply
disagree.
Context — surfaced while implementing #13874, which imports
scanSourcefrom thismodule for its
literalflag. That card needed onlyliteraland is unaffected; nothing init is blocked by this. ⛔ Deliberately not fixed there: it is a different file, a different
question, and #13874's ruling is strictly additive.