107107 * masked before the statement is located, through the repo's one comment
108108 * scanner (`scripts/js-comment-mask.mjs`).
109109 *
110+ * ## The SECOND way a declaration is invisible: the VALUE, not the spelling
111+ *
112+ * Everything above is about the SPELLING -- a declaration computed instead of
113+ * written out. A declaration can be a perfect literal array and STILL
114+ * contribute nothing, because `scripts/pm/dispatch-gates.mjs#extractWatchHints`
115+ * admits a literal only when it STARTS with a word character, a dot or an `@`.
116+ * A literal opening with a glob never reaches the resolve step, never becomes a
117+ * hint, and places its gate on NO card:
118+ *
119+ * ['scripts/**'] -> ["scripts/**"]
120+ * ['**\/package.json'] -> [] <- literal, and dropped anyway
121+ *
122+ * Four instruments passed that drop, because each asked its own question and
123+ * none asked whether the literals were ADMISSIBLE: this gate saw a literal
124+ * array, the gate's own self-test saw its own constant,
125+ * `check:declared-population-live` was satisfied by nothing (a family
126+ * declaring zero literals "declares nothing", a legitimate state), and it scored
127+ * `undetermined` or `silent` for every card in the tree. A declaring-zero gate
128+ * and a declared-but-eaten gate are the same colour on all of them.
129+ *
130+ * So the sweep asks the second question too, and it asks it OF THE EXTRACTOR
131+ * rather than of a copy of its rule -- a second copy of an admission regex is
132+ * a thing that drifts, and the drift would be silent in the same direction.
133+ * Each declared literal is put through `extractWatchHints` on its own, and a
134+ * literal that yields NO hint is the finding.
135+ *
136+ * ⛔ Admission is NOT widened to accept a leading glob, and this gate exists
137+ * BECAUSE it is not: that refusal is measured (the admission comment inside
138+ * `extractWatchHints` prices bare-top-level-word admission at +139084
139+ * fabricated pairs, and records a re-measured refusal of the resolved-form
140+ * widening), and relaxing it is a change to every gate in the farm rather than
141+ * a repair to one declaration. The remedy this gate prints is therefore the
142+ * enumerable root-prefixed spelling, never a request to change the rule.
143+ *
144+ * ⭐ What this buys is NOT cheaper enumeration -- the author of a file-kind
145+ * population still has to name every root that holds the kind, and still has to
146+ * pin that enumeration against the gate's own scan. It solves the other half:
147+ * it does not solve the trouble of enumerating, it solves not knowing that you
148+ * need to enumerate.
149+ *
150+ * ## Why ONE literal at a time, and never the module's whole extraction
151+ *
152+ * The tempting spelling is to extract the whole module and diff the declared
153+ * literals against the result. Measured on this tree, that spelling is wrong in
154+ * both directions. It FALSELY accuses: `scripts/pm/dispatch-gates.mjs` spells a
155+ * rostered declaration inside its own self-test as a fixture string, which
156+ * `extractWatchHints` blanks along with the rest of the self-test, so a
157+ * perfectly admissible literal is absent from that module's extraction. And it
158+ * would falsely accuse again on any declaration written module-relative, whose
159+ * extracted hint is the RESOLVED path and not the literal as spelled. Probing
160+ * one literal at a time asks exactly the question the finding is about -- does
161+ * this literal produce a hint at all -- and neither residue reaches it.
162+ *
110163 * ## What is asserted, and what is deliberately NOT
111164 *
112165 * Asserted: the right-hand side of the declaration is an ARRAY OF QUOTED STRING
113- * LITERALS and nothing else. That is stronger than "each declared hint appears
114- * quoted inside the statement", and it needs no runtime value, so this gate
115- * never imports the files it judges -- a gate that imported 14 modules to read
116- * one constant would run their module bodies to do it.
166+ * LITERALS and nothing else, and every literal in it is one the hint extractor
167+ * admits. The first is stronger than "each declared hint appears quoted inside
168+ * the statement"; neither needs a runtime value, so this gate never imports the
169+ * files it judges -- a gate that imported 14 modules to read one constant would
170+ * run their module bodies to do it. The extractor itself IS imported, and that
171+ * is the opposite case: it is the authority being consulted, not a subject
172+ * being read, and it is a pure string function with no module-scope work.
117173 *
118174 * NOT asserted: that a declaration is CORRECT -- that it names the roots the
119175 * gate really walks, and only those. That claim is local to each gate and each
@@ -147,6 +203,7 @@ import { fileURLToPath } from 'node:url';
147203
148204import { isEntrypoint } from './invoked-as.mjs' ;
149205import { maskComments } from './js-comment-mask.mjs' ;
206+ import { extractWatchHints } from './pm/dispatch-gates.mjs' ;
150207
151208// ── The self-test's own battery roster and floor (#13489) ──────────────────
152209//
@@ -181,7 +238,8 @@ const SELF_TEST_BATTERIES = Object.freeze({
181238 'the per-name floor' : 5 ,
182239 'discovery of an UNROSTERED spelling of the idiom' : 5 ,
183240 'the empty population is refused, not passed' : 1 ,
184- 'the live tree' : 14 ,
241+ "literals the extractor's admission DROPS" : 14 ,
242+ 'the live tree' : 15 ,
185243} ) ;
186244
187245// DELETING an entry silences that battery's floor exactly as effectively as
@@ -396,6 +454,57 @@ export function literalHints(rhs) {
396454 return rest . trim ( ) === '' ? hints : null ;
397455}
398456
457+ /**
458+ * The hints `extractWatchHints` builds from ONE declared literal, standing
459+ * alone under `name`.
460+ *
461+ * The probe source is a declaration and not a bare string on purpose: the
462+ * extractor's exclusion channel keys on the DECLARATION NAME, so a future
463+ * rostered name that happened to read as an exclusion would drop its literals
464+ * here and be reported LOUDLY rather than judged under a rule that does not
465+ * apply to it.
466+ *
467+ * A literal carrying both quote characters cannot be delimited, and the
468+ * extractor cannot read such a span out of the real source either -- so it
469+ * yields nothing here, which is the same verdict for the same reason.
470+ */
471+ function admittedHints ( name , literal ) {
472+ const quote = literal . includes ( "'" ) ? ( literal . includes ( '"' ) ? null : '"' ) : "'" ;
473+ if ( quote === null ) return [ ] ;
474+ return extractWatchHints ( `const ${ name } = [${ quote } ${ literal } ${ quote } ];\n` ) ;
475+ }
476+
477+ /**
478+ * The declared literals that produce NO hint at all -- the value-side drop.
479+ *
480+ * Pure over the list, and split out of `auditSourceName` for the same reason
481+ * `missingNames` is split out of `main`: the self-test drives it directly, and
482+ * the shape it guards against is one a healthy live tree cannot exhibit.
483+ */
484+ export function droppedByAdmission ( name , hints ) {
485+ return hints . filter ( ( h ) => admittedHints ( name , h ) . length === 0 ) ;
486+ }
487+
488+ /**
489+ * What a dropped literal's author is told: what happened, what to write
490+ * instead, and why the rule that dropped it is not the thing being changed.
491+ */
492+ function admissionRemedy ( name , dropped ) {
493+ return `the ${ name } declaration is a literal array, and ${ dropped . length } of its literal(s) build NO `
494+ + `hint at all: ${ dropped . map ( ( h ) => JSON . stringify ( h ) ) . join ( ', ' ) } . The hint extractor admits a `
495+ + 'literal only when it STARTS with a word character, a dot or an @, so one opening with a glob '
496+ + 'never reaches the resolve step, never becomes a hint, and places this gate on NO card -- the '
497+ + 'same silent drop this gate exists for, arrived at through the VALUE instead of the spelling. '
498+ + 'Remedy: spell the population as one enumerable root-prefixed entry per root that holds the '
499+ + 'kind, and pin that enumeration against the walk this gate really performs so it cannot go '
500+ + 'stale quietly. Admission is deliberately NOT widened to accept a leading glob: see the '
501+ + 'admission comment inside extractWatchHints, which prices bare-top-level-word admission at '
502+ + '+139084 fabricated pairs and records a re-measured refusal of the resolved-form widening -- '
503+ + 'relaxing it is a change to every gate in the farm, not a repair to one declaration. This '
504+ + 'finding does not solve the trouble of enumerating; it solves not knowing that you need to '
505+ + 'enumerate.' ;
506+ }
507+
399508/**
400509 * One file's verdict for ONE rostered name. `null` means the file does not
401510 * declare that name at all -- it mentions it in prose, or reads someone else's.
@@ -426,6 +535,13 @@ export function auditSourceName(rel, source, name) {
426535 if ( hints . length === 0 ) {
427536 return { rel, name, ok : false , why : `the ${ name } declaration is EMPTY -- it names no subtree at all` } ;
428537 }
538+ // The value-side question, asked only once the spelling-side one has passed:
539+ // an empty or computed declaration has no literals to judge, and naming the
540+ // same declaration under two remedies would leave its author choosing.
541+ const dropped = droppedByAdmission ( name , hints ) ;
542+ if ( dropped . length > 0 ) {
543+ return { rel, name, ok : false , why : admissionRemedy ( name , dropped ) } ;
544+ }
429545 return { rel, name, ok : true , hints } ;
430546}
431547
@@ -530,18 +646,21 @@ function main() {
530646 if ( bad . length ) {
531647 console . error (
532648 `✗ check-watch-hint-literal: ${ bad . length } of ${ rows . length } declaration(s) are not readable `
533- + 'as literals. Spell the hints inside the declaration statement.' ,
649+ + 'by the hint extractor. Spell the hints inside the declaration statement, and spell each one '
650+ + 'so the extractor admits it.' ,
534651 ) ;
535652 return 1 ;
536653 }
537654
538655 const perName = DECL_NAMES
539656 . map ( ( n ) => `${ n } ${ rows . filter ( ( r ) => r . name === n ) . length } ` )
540657 . join ( ', ' ) ;
658+ const literals = rows . reduce ( ( n , r ) => n + r . hints . length , 0 ) ;
541659 console . log (
542660 `✓ check-watch-hint-literal: ${ rows . length } declaration(s) across ${ DECL_NAMES . length } rostered `
543- + `name(s) -- ${ perName } -- every one an array of quoted literals inside its own statement, `
544- + 'every rostered name non-empty, and no unrostered spelling of the idiom in the tree.' ,
661+ + `name(s) -- ${ perName } -- every one an array of quoted literals inside its own statement, all `
662+ + `${ literals } of those literals admitted by the hint extractor, every rostered name non-empty, `
663+ + 'and no unrostered spelling of the idiom in the tree.' ,
545664 ) ;
546665 return 0 ;
547666}
@@ -686,6 +805,60 @@ export function selfTest() {
686805 t ( 'an empty file list produces no rows, which the floor above refuses' ,
687806 audit ( [ ] ) . rows . length === 0 && missingNames ( audit ( [ ] ) . rows ) . length === DECL_NAMES . length ) ;
688807
808+ // -- literals the extractor's admission DROPS ------------------------------
809+ // ⭐ The value-side drop. Every case here declares a perfect literal array,
810+ // so every one of them passes the spelling-side question above; what
811+ // separates them is whether the hint extractor can make a hint out of what
812+ // is inside it.
813+ battery ( "literals the extractor's admission DROPS" ) ;
814+ const KIND_ONE_LINER = "['**/package.json']" ;
815+ t ( 'a declaration whose only literal opens with a glob is rejected' ,
816+ rejected ( decl ( KIND_ONE_LINER ) ) ) ;
817+ t ( '...and it is rejected on the VALUE, not on the spelling -- the declaration IS a literal array' ,
818+ literalHints ( KIND_ONE_LINER ) !== null && literalHints ( KIND_ONE_LINER ) . length === 1 ) ;
819+ const globWhy = verdict ( decl ( KIND_ONE_LINER ) ) ?. why ?? '' ;
820+ t ( 'the remedy names the literal that was dropped' , globWhy . includes ( '**/package.json' ) ) ;
821+ t ( 'the remedy states the admission rule that dropped it' ,
822+ globWhy . includes ( 'STARTS with a word character, a dot or an @' ) ) ;
823+ t ( 'the remedy prescribes the enumerable root-prefixed spelling rather than only refusing' ,
824+ globWhy . includes ( 'enumerable root-prefixed entry per root that holds the kind' ) ) ;
825+ t ( 'the remedy carries the measurement behind the refusal, so widening admission is not the obvious read' ,
826+ globWhy . includes ( '+139084' ) && globWhy . includes ( 'every gate in the farm' ) ) ;
827+ t ( 'the remedy says what the finding is actually for' ,
828+ globWhy . includes ( 'it solves not knowing that you need to enumerate' ) ) ;
829+
830+ // The negatives PR #16446 shipped: the same population, enumerated.
831+ const ADMISSIBLE_KIND = [
832+ 'packages/**/package.json' ,
833+ 'apps/**/package.json' ,
834+ 'examples/**/package.json' ,
835+ 'package.json/**' ,
836+ ] ;
837+ t ( 'the enumerated spelling of the same population is accepted' ,
838+ accepted ( decl ( `[${ ADMISSIBLE_KIND . map ( ( h ) => `'${ h } '` ) . join ( ', ' ) } ]` ) ) ,
839+ ADMISSIBLE_KIND . join ( ' · ' ) ) ;
840+ t ( '...and every one of those literals is admitted individually' ,
841+ droppedByAdmission ( DIR_NAME , ADMISSIBLE_KIND ) . length === 0 ) ;
842+ t ( 'one dropped literal among admissible ones still fails, and only it is named' ,
843+ rejected ( decl ( `['scripts/**', '**/package.json']` ) )
844+ && droppedByAdmission ( DIR_NAME , [ 'scripts/**' , '**/package.json' ] ) . join ( ) === '**/package.json' ) ;
845+
846+ // ⭐ The false positive a whole-MODULE diff would produce, which is why the
847+ // probe is per literal: `scripts/pm/dispatch-gates.mjs` spells a rostered
848+ // declaration inside its own self-test, and the extractor blanks self-tests --
849+ // so that literal is absent from that module's extraction while being
850+ // perfectly admissible.
851+ t ( 'a literal is judged on its own admissibility, not on whether its module extraction carries it' ,
852+ droppedByAdmission ( DIR_NAME , [ 'packages/drivers/**' ] ) . length === 0 ) ;
853+ t ( '...and a module-relative literal, whose extracted hint is the RESOLVED path, is not accused either' ,
854+ droppedByAdmission ( DIR_NAME , [ '../../packages/spec/**' ] ) . length === 0 ) ;
855+
856+ // The two states that must keep their OWN remedy rather than acquiring this one.
857+ t ( 'an EMPTY declaration keeps the empty-population remedy, not the admission one' ,
858+ ( verdict ( decl ( '[]' ) ) ?. why ?? '' ) . includes ( 'names no subtree at all' ) ) ;
859+ t ( 'a COMPUTED declaration keeps the computed remedy, not the admission one' ,
860+ ( verdict ( decl ( '[`${SCAN_ROOT}/**`]' ) ) ?. why ?? '' ) . includes ( 'is COMPUTED, not a literal array' ) ) ;
861+
689862 // -- the live tree ---------------------------------------------------------
690863 battery ( 'the live tree' ) ;
691864 const { rows : live , strays } = audit ( walk ( REPO_ROOT ) ) ;
@@ -694,6 +867,10 @@ export function selfTest() {
694867 `${ live . length } declaration(s)` ) ;
695868 t ( 'every live declaration is a literal' , live . every ( ( r ) => r . ok ) ,
696869 live . filter ( ( r ) => ! r . ok ) . map ( ( r ) => `${ r . rel } :${ r . name } ` ) . join ( ' · ' ) ) ;
870+ t ( 'and every literal in every live declaration is one the hint extractor admits' ,
871+ live . every ( ( r ) => ! r . ok || droppedByAdmission ( r . name , r . hints ) . length === 0 ) ,
872+ live . flatMap ( ( r ) => ( r . ok ? droppedByAdmission ( r . name , r . hints )
873+ . map ( ( h ) => `${ r . rel } :${ r . name } :${ h } ` ) : [ ] ) ) . join ( ' · ' ) ) ;
697874 t ( 'EVERY rostered name has a live declarer -- the floor is armed, not merely coded' ,
698875 missingNames ( live ) . length === 0 , `missing: ${ missingNames ( live ) . join ( ', ' ) || 'none' } ` ) ;
699876 for ( const name of DECL_NAMES ) {
@@ -737,7 +914,8 @@ export function selfTest() {
737914 + 'spellings rejected, the statement-scoped search proved against runtime and comment copies of '
738915 + `the literal beside it, all ${ DECL_NAMES . length } rostered names judged for both spellings, the `
739916 + 'per-name floor proved against a population that is healthy on every name but one, unrostered '
740- + 'spellings of the idiom discovered, and the live repo-wide population judged.' ,
917+ + 'spellings of the idiom discovered, the value-side drop proved on a literal array the extractor '
918+ + 'admits nothing out of, and the live repo-wide population judged on both questions.' ,
741919 ) ;
742920 selfTestReachedVerdict = true ;
743921 return 0 ;
0 commit comments