Found while closing #14742 (PR #15486), which added a regex-literal recogniser to scripts/check-dispatcher-error-vocabulary.mjs. Out of scope there: that card's scope is the scanner's blind spot and its ledgers, and consolidating two modules is a separate change with its own risk.
What is duplicated
Two modules now answer "does a / here open a RegularExpressionLiteral?", each with its own copy:
The 14-word keyword set is byte-identical in both. That set is not decorative: js-comment-mask.mjs's own header records that dropping return from it passes all 23 of its self-test cases and is caught only by the corpus sweep, on scripts/check-test-source-alias.mjs. So the copy that drifts drifts silently in exactly the direction the header warns about.
What is deliberately NOT duplicated, and why a naive extraction is wrong
The two position rules differ on purpose, and the difference is a failure-direction argument rather than an oversight:
| preceding token |
js-comment-mask.mjs |
check-dispatcher-error-vocabulary.mjs |
) , ] |
division |
division |
} |
regex |
division |
bare < / > |
regex |
division |
++ / -- |
regex |
division |
A masker fails safe by over-masking (it under-reports loudly). A scanner that skips a span it invented desynchronises silently — which is the defect #14742 closed. ⇒ any extraction has to keep the position rule parameterised, not pick one of the two.
Mitigation already in place
#14742 added a --self-test case that runs both recognisers over the same fixtures and asserts they agree everywhere except that documented divergence, and a second case that asserts the divergence itself still exists. So a silent drift is caught today; what is left is the duplication, not an unguarded one.
What a fix would look like
Export the keyword set and a position-rule-parameterised recogniser from one place (most likely js-comment-mask.mjs, which already owns "which bytes are literal"), and have the gate consume it with its conservative rule passed in. ⚠️ js-comment-mask.mjs has a hand-written .d.mts (see #15398) and a corpus sweep in CI — both are part of the cost.
Unassigned and unlabeled, for triage.
Generated by Claude Code
Found while closing #14742 (PR #15486), which added a regex-literal recogniser to
scripts/check-dispatcher-error-vocabulary.mjs. Out of scope there: that card's scope is the scanner's blind spot and its ledgers, and consolidating two modules is a separate change with its own risk.What is duplicated
Two modules now answer "does a
/here open a RegularExpressionLiteral?", each with its own copy:scripts/js-comment-mask.mjs—REGEX_AFTER_KEYWORD,IDENT_CHAR, and the position rule insidescanSource;scripts/check-dispatcher-error-vocabulary.mjs—REGEX_AFTER_KEYWORD,IDENT_CHAR,regexMayBeginAt,regexLiteralAt(added by The shared textual scanners open a string on a quote inside a REGEX literal - 46 code: positions in 68 files stay unplaceable after #14626 #14742).The 14-word keyword set is byte-identical in both. That set is not decorative:
js-comment-mask.mjs's own header records that droppingreturnfrom it passes all 23 of its self-test cases and is caught only by the corpus sweep, onscripts/check-test-source-alias.mjs. So the copy that drifts drifts silently in exactly the direction the header warns about.What is deliberately NOT duplicated, and why a naive extraction is wrong
The two position rules differ on purpose, and the difference is a failure-direction argument rather than an oversight:
js-comment-mask.mjscheck-dispatcher-error-vocabulary.mjs),]}</>++/--A masker fails safe by over-masking (it under-reports loudly). A scanner that skips a span it invented desynchronises silently — which is the defect #14742 closed. ⇒ any extraction has to keep the position rule parameterised, not pick one of the two.
Mitigation already in place
#14742 added a
--self-testcase that runs both recognisers over the same fixtures and asserts they agree everywhere except that documented divergence, and a second case that asserts the divergence itself still exists. So a silent drift is caught today; what is left is the duplication, not an unguarded one.What a fix would look like
Export the keyword set and a position-rule-parameterised recogniser from one place (most likely⚠️
js-comment-mask.mjs, which already owns "which bytes are literal"), and have the gate consume it with its conservative rule passed in.js-comment-mask.mjshas a hand-written.d.mts(see #15398) and a corpus sweep in CI — both are part of the cost.Unassigned and unlabeled, for triage.
Generated by Claude Code