Skip to content

Commit 1697831

Browse files
claude[bot]claude
andauthored
refactor(devx): js-comment-mask publishes maskCommentsAndLiterals, and its two callers import it (#15594) (#15774)
* refactor(devx): js-comment-mask publishes the comments+literals projection `js-comment-mask.mjs` published `stripComments` and `maskComments` but no comments+literals projection -- the mask a gate needs when its signal is a bare CODE position and a spelling inside prose *or* inside a string must not satisfy it. Two gates on main re-derived it under two names. Export `maskCommentsAndLiterals(source)`, documented beside `maskComments` in the module's own terms (the `stripComments` docblock already argues the module owns the projections rather than the callers), declared in the `.d.mts`, and driven by nine new self-test rows under a literal roster and floor of their own -- the shape the recogniser section established, so a deleted or renamed row names itself in the refusal. The rows live outside the `cases` table on purpose: that table asserts the OPPOSITE property for quoted text (`REAL` code inside a string MUST survive `maskComments`), and one fixture cannot state both directions. Self-test: 44 -> 53 cases (23 mask/strip corpus, 12 interpolation view, 9 shared recogniser, 9 comments+literals projection). Part of #15594 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk * refactor(devx): the two named callers import the projection instead of re-deriving it `check-adr-0087-registration.mjs` spelled it `maskCommentsAndLiterals` (`comment.map((c, i) => c || literal[i])`); `check-registry-log-declared.mjs` spelled it `maskCode` (`comment[i] | literal[i]` over a Uint8Array). Same projection, two names, no shared pin. Both are now the module's export, and each file's now-unused `blank`/`scanSource` imports go with the local copy. `maskCode`'s docblock said it stays "local only because js-comment-mask.mjs publishes no comments+literals projection yet, and hoisting one waits on a follow-up card". That card is this one, so the docblock moves with the conversion rather than becoming a stale assertion: what survives is the fact that belongs to THIS gate -- that it reads two projections of one source and relies on them agreeing offset-for-offset. Behaviour byte-identical, proven per gate by diffing plain and `--self-test` output before and after (four diffs, exit 0 each): check-adr-0087-registration ✓ ... no declared-breaking changeset ✓ ... 304 assertions over real temp git repos check-registry-log-declared OK: 72 vitest-running package(s) walked, 8 ... self-test OK: 16 cases + level-vocabulary read ... Part of #15594 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 5315098 commit 1697831

4 files changed

Lines changed: 181 additions & 35 deletions

File tree

scripts/check-adr-0087-registration.mjs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@ import { tmpdir } from 'node:os';
354354
import { dirname, join, resolve } from 'node:path';
355355
import { fileURLToPath } from 'node:url';
356356
import { isEntrypoint } from './invoked-as.mjs';
357-
import { blank, maskComments, scanSource } from './js-comment-mask.mjs';
357+
import { maskComments, maskCommentsAndLiterals } from './js-comment-mask.mjs';
358358

359359
// ── The self-test's own battery roster and floor (#13489) ──────────────────
360360
//
@@ -2410,13 +2410,6 @@ export function isErasedType(text) {
24102410
return /^(?:any|unknown)$/.test(s);
24112411
}
24122412

2413-
/** Comment AND string spans blanked -- offsets preserved. The house scanner, not a private one. */
2414-
function maskCommentsAndLiterals(source) {
2415-
const { comment, literal } = scanSource(source);
2416-
const flags = comment.map((c, i) => c || literal[i]);
2417-
return blank(source, flags);
2418-
}
2419-
24202413
/**
24212414
* The type text of a `<symbol>(...)` RETURN ANNOTATION, or a stated absence.
24222415
*

scripts/check-registry-log-declared.mjs

Lines changed: 16 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ import { existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync,
127127
import { dirname, join, resolve, sep } from 'node:path';
128128
import { tmpdir } from 'node:os';
129129
import { fileURLToPath } from 'node:url';
130-
import { blank, maskComments, scanSource } from './js-comment-mask.mjs';
130+
import { maskComments, maskCommentsAndLiterals } from './js-comment-mask.mjs';
131131
import { isEntrypoint } from './invoked-as.mjs';
132132
import { workspacePackageDirs } from './check-console-intercept-disarm.mjs';
133133

@@ -178,28 +178,19 @@ const REMEDY = ` // #13517: quiet the registry's per-item registration chatte
178178
// default. Enforced by scripts/check-registry-log-declared.mjs.
179179
env: { OS_REGISTRY_LOG: 'warn' },`;
180180

181-
/**
182-
* Comments AND string/template/regex content blanked, offsets kept. Used where
183-
* the signal is a bare CODE position (`new SchemaRegistry(`, a property key), so
184-
* a spelling inside prose or a template literal can never satisfy it.
185-
*
186-
* It COMPOSES the shared scanner — `scanSource`'s `comment` and `literal` flags
187-
* OR-ed through `blank` — and carries no scanning logic of its own; it stays
188-
* local only because `js-comment-mask.mjs` publishes no comments+literals
189-
* projection yet, and hoisting one waits on a follow-up card.
190-
*
191-
* The imported `maskComments` (comments blanked, string/template/regex content
192-
* INTACT — S2/S3 read import specifiers out of it) and this mask both preserve
193-
* offsets, so a range brace-matched on the code mask indexes the comment mask
194-
* identically — which is how the level VALUE (a string, blanked by this mask)
195-
* is read out of a block located with it.
196-
*/
197-
function maskCode(source) {
198-
const { comment, literal } = scanSource(source);
199-
const flags = new Uint8Array(comment.length);
200-
for (let i = 0; i < flags.length; i++) flags[i] = comment[i] | literal[i];
201-
return blank(source, flags);
202-
}
181+
// This gate reads TWO projections of the same source and relies on them
182+
// agreeing offset-for-offset: `maskCommentsAndLiterals` (the signal is a bare
183+
// CODE position — `new SchemaRegistry(`, a property key — so a spelling inside
184+
// prose or a template must never satisfy it) and `maskComments` (S2/S3 read
185+
// import specifiers, which ARE quoted text, out of it). Both preserve offsets,
186+
// so a range brace-matched on the code mask indexes the comment mask
187+
// identically — which is how the level VALUE (a string, blanked by the code
188+
// mask) is read out of a block located with it.
189+
//
190+
// The comments+literals projection used to be spelled here as a local
191+
// `maskCode`, "local only because js-comment-mask.mjs publishes no
192+
// comments+literals projection yet". It publishes one now (#15594), so this
193+
// gate composes it like every other projection it reads.
203194

204195
/**
205196
* The level vocabulary, read from the engine's own declaration rather than
@@ -269,7 +260,7 @@ export function bootSignals(dir) {
269260
const comments = maskComments(raw);
270261
if (!s2 && S2_DEFINE_RE.test(comments)) s2 = true;
271262
if (!isTest(file)) continue;
272-
if (!s1 && S1_REGISTRY_RE.test(maskCode(raw))) s1 = true;
263+
if (!s1 && S1_REGISTRY_RE.test(maskCommentsAndLiterals(raw))) s1 = true;
273264
if (!s2 && S2_IMPORT_RE.test(comments)) s2 = true;
274265
if (!s3 && S3_EXAMPLE_RE.test(comments)) s3 = true;
275266
}
@@ -411,7 +402,7 @@ export function scan(root, levels = readRegistryLogLevels(root)) {
411402
continue;
412403
}
413404
const raw = readFileSync(join(dir, configName), 'utf8');
414-
const code = maskCode(raw);
405+
const code = maskCommentsAndLiterals(raw);
415406
const comments = maskComments(raw);
416407
const where = `${name}/${configName}`;
417408

scripts/js-comment-mask.d.mts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,18 @@ export function stripComments(source: string): string;
7676
*/
7777
export function maskComments(source: string): string;
7878

79+
/**
80+
* `source` with its COMMENT spans AND its LITERAL content both blanked.
81+
* Offsets and line numbers both survive, exactly as under `maskComments`.
82+
*
83+
* Pick this when the signal is a bare CODE position (`new SchemaRegistry(`, a
84+
* property key): under `maskComments` the same spelling inside a string or a
85+
* template still satisfies it, so the gate reports a finding made of quoted
86+
* text. Literal DELIMITERS are not literal content, so the quotes survive and
87+
* a caller can still pair them.
88+
*/
89+
export function maskCommentsAndLiterals(source: string): string;
90+
7991
/**
8092
* The outcome of walking the body of the regex literal opening at `at`.
8193
*

scripts/js-comment-mask.mjs

Lines changed: 152 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,39 @@ export function maskComments(source) {
541541
return blank(source, scanSource(source).comment);
542542
}
543543

544+
/**
545+
* The source with its COMMENT spans AND its LITERAL content both blanked --
546+
* offsets and line numbers both survive, exactly as under `maskComments`.
547+
*
548+
* The THIRD projection, and the one to reach for when the signal is a bare
549+
* CODE position: `new SchemaRegistry(`, a property key, a call to a named
550+
* member. `maskComments` deliberately leaves strings, templates and regex
551+
* literals intact, so under it a spelling inside quoted text still satisfies
552+
* such a signal and the gate FABRICATES a finding out of a string. Only the
553+
* position the language would EXECUTE survives this one.
554+
*
555+
* ## Picking between the two, which is the same question `stripComments` asks
556+
*
557+
* Ask what the signal IS, not what the source contains. The signal is itself
558+
* quoted text -- an import specifier, an error code, a level name -- ->
559+
* `maskComments`, which MUST leave literals intact or it erases the thing
560+
* being looked for. The signal is a code position -> this one.
561+
*
562+
* A caller that needs both at once gets them for free: both preserve offsets,
563+
* so a range brace-matched on this mask indexes `maskComments`'s output
564+
* identically (`check-registry-log-declared.mjs` reads a level VALUE -- a
565+
* string, blanked here -- out of a block it located with this mask).
566+
*
567+
* Literal DELIMITERS are not literal content (see `scanSource`), so the quotes
568+
* themselves survive and a caller can still pair them.
569+
*/
570+
export function maskCommentsAndLiterals(source) {
571+
const { comment, literal } = scanSource(source);
572+
const flags = new Uint8Array(comment.length);
573+
for (let k = 0; k < flags.length; k++) flags[k] = comment[k] | literal[k];
574+
return blank(source, flags);
575+
}
576+
544577
// ---------------------------------------------------------------------------
545578
// Self-test -- the shapes, not the corpus
546579
// ---------------------------------------------------------------------------
@@ -654,6 +687,32 @@ const SELF_TEST_RECOGNISER_BATTERIES = Object.freeze({
654687
});
655688
const SELF_TEST_RECOGNISER_FLOOR = 9;
656689

690+
// -- The COMMENTS+LITERALS PROJECTION's own roster and floor (#15594) -------
691+
//
692+
// Declared as a LITERAL for the same reason as the two rosters above: a
693+
// deleted or renamed row must name ITSELF in the refusal rather than quietly
694+
// lowering a count it also supplies.
695+
//
696+
// This section exists instead of rows in the `cases` table because the table
697+
// asserts the OPPOSITE property for quoted text. There `REAL` code inside a
698+
// string MUST survive -- `maskComments` and `stripComments` leave literals
699+
// intact by design, and a row that removed one would be reporting a bug. Under
700+
// `maskCommentsAndLiterals` that same spelling must NOT survive. One table
701+
// cannot state both directions about the same fixture, so the projection is
702+
// driven here, against a fixture whose signal is a bare CODE position.
703+
const SELF_TEST_PROJECTION_BATTERIES = Object.freeze({
704+
'the fixture spells one bare-code signal four times': 1,
705+
'a code signal inside a COMMENT does not survive the projection': 1,
706+
'...nor one inside a STRING': 1,
707+
'...nor one inside a TEMPLATE': 1,
708+
'...while the REAL code position DOES survive': 1,
709+
'the control: under maskComments the string and template spellings both survive': 1,
710+
'the projection IS blank(source, comment OR literal), recomputed independently here': 1,
711+
'...and that equality holds on every row of the corpus table too': 1,
712+
'offsets and line count survive, so a caller can index the original text': 1,
713+
});
714+
const SELF_TEST_PROJECTION_FLOOR = 9;
715+
657716
export function selfTest() {
658717
const BT = String.fromCharCode(96); // backtick, kept out of the literal below
659718
const cases = [
@@ -901,7 +960,70 @@ export function selfTest() {
901960
console.log(` ${ok ? '\u2713' : '\u2717'} ${name}${ok ? '' : ' -- ' + JSON.stringify(detail)}`);
902961
}
903962

904-
const total = cases.length + extra.length + recog.length;
963+
// -- the COMMENTS+LITERALS projection (#15594) ----------------------------
964+
//
965+
// The fixture spells ONE bare-code signal four times -- in prose, in a
966+
// string, in a template, and once for real -- and every row is asserted at
967+
// the signal's own OFFSET rather than by counting occurrences, so a mask that
968+
// moved bytes could not be read as one that removed the right ones.
969+
const proj = [];
970+
const xp = (name, ok, detail) => proj.push([name, Boolean(ok), detail]);
971+
972+
const SIG = 'new SchemaRegistry(';
973+
const projSrc = [
974+
'// ' + SIG + 'ghostInProse);',
975+
"const HINT = '" + SIG + "ghostInString)';",
976+
'const TPL = ' + BT + SIG + 'ghostInTemplate)' + BT + ';',
977+
'const registry = ' + SIG + 'realCode);',
978+
].join('\n');
979+
const sigOffsets = [];
980+
for (let k = projSrc.indexOf(SIG); k !== -1; k = projSrc.indexOf(SIG, k + 1)) sigOffsets.push(k);
981+
const projMasked = maskCommentsAndLiterals(projSrc);
982+
const survives = sigOffsets.map((k) => projMasked.startsWith(SIG, k));
983+
984+
xp('the fixture spells one bare-code signal four times', sigOffsets.length === 4, sigOffsets);
985+
xp('a code signal inside a COMMENT does not survive the projection', survives[0] === false, projMasked);
986+
xp('...nor one inside a STRING', survives[1] === false, projMasked);
987+
xp('...nor one inside a TEMPLATE', survives[2] === false, projMasked);
988+
xp('...while the REAL code position DOES survive', survives[3] === true, projMasked);
989+
990+
// The CONTROL, and the reason this fixture can fail: under the
991+
// comments-only projection the string and template spellings DO survive.
992+
// Without it every row above would also pass on a mask that blanked the
993+
// whole file, and the corpus table's own rows would not notice -- none of
994+
// them puts a bare-code signal inside a literal.
995+
const projComments = maskComments(projSrc);
996+
const survivesComments = sigOffsets.map((k) => projComments.startsWith(SIG, k));
997+
xp('the control: under maskComments the string and template spellings both survive',
998+
survivesComments[0] === false && survivesComments[1] === true
999+
&& survivesComments[2] === true && survivesComments[3] === true,
1000+
survivesComments);
1001+
1002+
// The projection RESTATED, computed here from the two flag arrays with the
1003+
// other spelling (`||` over a plain array, which is how one of the two
1004+
// converted callers wrote it). An oracle re-derived on purpose: pinning the
1005+
// export against itself would pin nothing.
1006+
const orView = (src) => {
1007+
const { comment, literal } = scanSource(src);
1008+
return blank(src, comment.map((c, i) => c || literal[i]));
1009+
};
1010+
xp('the projection IS blank(source, comment OR literal), recomputed independently here',
1011+
maskCommentsAndLiterals(projSrc) === orView(projSrc), [projMasked, orView(projSrc)]);
1012+
const corpusDisagreement = cases.find(([, src]) => maskCommentsAndLiterals(src) !== orView(src));
1013+
xp('...and that equality holds on every row of the corpus table too',
1014+
corpusDisagreement === undefined, corpusDisagreement && corpusDisagreement[0]);
1015+
1016+
xp('offsets and line count survive, so a caller can index the original text',
1017+
projMasked.length === projSrc.length
1018+
&& projMasked.split('\n').length === projSrc.split('\n').length,
1019+
[projSrc.length, projMasked.length]);
1020+
1021+
for (const [name, ok, detail] of proj) {
1022+
if (!ok) failed++;
1023+
console.log(` ${ok ? '\u2713' : '\u2717'} ${name}${ok ? '' : ' -- ' + JSON.stringify(detail)}`);
1024+
}
1025+
1026+
const total = cases.length + extra.length + recog.length + proj.length;
9051027

9061028
// ── The floor: every declared row RAN, and ran its case (#13489) ───────
9071029
//
@@ -978,6 +1100,33 @@ export function selfTest() {
9781100
);
9791101
}
9801102

1103+
// The same treatment for the comments+literals projection section (#15594).
1104+
const declaredProjection = Object.keys(SELF_TEST_PROJECTION_BATTERIES);
1105+
if (declaredProjection.length < SELF_TEST_PROJECTION_FLOOR) {
1106+
floorBreached = true;
1107+
floorFailure(
1108+
`SELF_TEST_PROJECTION_BATTERIES declares ${declaredProjection.length} assertions, below the pinned ` +
1109+
`${SELF_TEST_PROJECTION_FLOOR} — an assertion deleted from the roster takes its own floor with it.`,
1110+
);
1111+
}
1112+
const projectionRan = proj.map(([name]) => name);
1113+
for (const name of projectionRan) {
1114+
if (declaredProjection.includes(name)) continue;
1115+
floorBreached = true;
1116+
floorFailure(
1117+
`comments+literals assertion "${name}" ran but is not declared in ` +
1118+
'SELF_TEST_PROJECTION_BATTERIES — an assertion attributed to no declared row is one nothing floors.',
1119+
);
1120+
}
1121+
for (const name of declaredProjection) {
1122+
if (projectionRan.filter((n) => n === name).length >= SELF_TEST_PROJECTION_BATTERIES[name]) continue;
1123+
floorBreached = true;
1124+
floorFailure(
1125+
`comments+literals assertion "${name}" DID NOT RUN — the verdict below would have claimed that a ` +
1126+
'code signal inside prose and inside a string are both masked when one of them is not.',
1127+
);
1128+
}
1129+
9811130
if (floorBreached) {
9821131
floorFailure(
9831132
'A battery at or below its floor means cases STOPPED RUNNING — the battery is the bug, not the ' +
@@ -992,7 +1141,8 @@ export function selfTest() {
9921141
}
9931142
console.log(
9941143
`\u2713 js-comment-mask self-test: ${total} cases pass (${cases.length} mask/strip corpus, `
995-
+ `${extra.length} interpolation view, ${recog.length} shared recogniser).`,
1144+
+ `${extra.length} interpolation view, ${recog.length} shared recogniser, `
1145+
+ `${proj.length} comments+literals projection).`,
9961146
);
9971147

9981148
return SELF_TEST_VERDICT;

0 commit comments

Comments
 (0)