2222 *
2323 * The population is the card's own enumeration command,
2424 * `git grep -n 'lookupArtifactItem(' -- packages/metadata-protocol/src`
25- * (tests excluded), which is one file: every match is a call or the
26- * definition. Each is keyed by the class member it sits in and its argument
27- * text, which survive line moves and fail on any change of scope. A new call,
28- * a removed one or a re-scoped one turns this red until its row is recorded
29- * here with its disposition (and the card's table in the PR that adds it).
25+ * (tests excluded), which is two files: every match is a call or a
26+ * definition. Each is keyed by the file, the class member or module function
27+ * it sits in, and its argument text, which survive line moves and fail on any
28+ * change of scope. A new call, a removed one or a re-scoped one turns this red
29+ * until its row is recorded here with its disposition (and the card's table in
30+ * the PR that adds it).
31+ *
32+ * [#25467 cut 15] The second file is `protocol-artifact-lookup.ts`. The
33+ * lookup's body moved there with `shippedArtifactsOf`'s, so it holds the
34+ * function the class member delegates to and that member's two calls; the
35+ * class member keeps its signature in `protocol.ts`, and `shippedArtifactsOf`'s
36+ * stub there hands the moved body the lookup as a closure, one more site.
3037 */
3138import { readFileSync } from 'node:fs' ;
3239import { fileURLToPath } from 'node:url' ;
3340import { describe , expect , it } from 'vitest' ;
3441
3542const SOURCE = readFileSync ( fileURLToPath ( new URL ( './protocol.ts' , import . meta. url ) ) , 'utf8' ) ;
43+ /** [#25467 cut 15] The seam module the lookup's body moved to; scanned with its own header shape. */
44+ const SEAM_FILE = 'protocol-artifact-lookup.ts' ;
45+ const SEAM = readFileSync ( fileURLToPath ( new URL ( `./${ SEAM_FILE } ` , import . meta. url ) ) , 'utf8' ) ;
3646const CALLEE = 'lookupArtifactItem(' ;
3747
3848/** A class member's header in `protocol.ts`: four spaces, optional modifiers, its name, its parameter list. */
3949const MEMBER_HEADER = / ^ { 4 } (?: (?: p r i v a t e | p u b l i c | p r o t e c t e d | s t a t i c | a s y n c | o v e r r i d e | r e a d o n l y ) \s + ) * ( [ A - Z a - z _ $ ] [ \w $ ] * ) \s * (?: < [ ^ > ] * > ) ? \( / ;
50+ /** A module function's header in the seam module: `export`, optional `async`, `function`, its name, its parameter list. */
51+ const FUNCTION_HEADER = / ^ e x p o r t (?: a s y n c ) ? f u n c t i o n ( [ A - Z a - z _ $ ] [ \w $ ] * ) \s * (?: < [ ^ > ] * > ) ? \( / ;
52+
53+ interface Site { readonly file ?: string ; readonly member : string ; readonly args : string }
4054
41- interface Site { readonly member : string ; readonly args : string }
55+ /** Every `lookupArtifactItem(` in `protocol.ts` and the seam module: the file, the member it sits in and its argument text, whitespace-normalised. */
56+ function sitesIn ( ) : Site [ ] {
57+ return [
58+ ...sitesOf ( SOURCE , MEMBER_HEADER ) ,
59+ ...sitesOf ( SEAM , FUNCTION_HEADER ) . map ( ( site ) => ( { ...site , file : SEAM_FILE } ) ) ,
60+ ] ;
61+ }
4262
43- /** Every `lookupArtifactItem(` in the source: the member it sits in and its argument text, whitespace-normalised. */
44- function sitesOf ( source : string ) : Site [ ] {
63+ /** Every `lookupArtifactItem(` in one source: the member it sits in (by `header`) and its argument text, whitespace-normalised. */
64+ function sitesOf ( source : string , header : RegExp ) : Site [ ] {
4565 const lines = source . split ( '\n' ) ;
4666 const sites : Site [ ] = [ ] ;
4767 for ( let at = source . indexOf ( CALLEE ) ; at !== - 1 ; at = source . indexOf ( CALLEE , at + 1 ) ) {
@@ -53,8 +73,8 @@ function sitesOf(source: string): Site[] {
5373 const args = source . slice ( at + CALLEE . length , end - 1 ) . replace ( / \s + / g, ' ' ) . trim ( ) . replace ( / , $ / , '' ) ;
5474 let member = '(no member)' ;
5575 for ( let line = source . slice ( 0 , at ) . split ( '\n' ) . length - 1 ; line >= 0 ; line -- ) {
56- const header = MEMBER_HEADER . exec ( lines [ line ] ) ;
57- if ( header ) { member = header [ 1 ] ; break ; }
76+ const found = header . exec ( lines [ line ] ) ;
77+ if ( found ) { member = found [ 1 ] ; break ; }
5878 }
5979 sites . push ( { member, args } ) ;
6080 }
@@ -70,7 +90,9 @@ type Disposition = 'definition' | 'served-package envelope' | 'no';
7090 */
7191const SITES : ReadonlyArray < Site & { readonly noPackage : boolean ; readonly disposition : Disposition ; readonly why : string } > = [
7292 { member : 'lookupArtifactItem' , args : 'type: string, name: string, currentPackageId?: string, member?: string' , noPackage : false , disposition : 'definition' ,
73- why : 'the definition itself' } ,
93+ why : 'the definition itself: the class member\'s declared signature (its body delegates to the seam function below)' } ,
94+ { file : SEAM_FILE , member : 'lookupArtifactItem' , args : 'this: Pick<ArtifactLookupHost, \'engine\'>, type: string, name: string, currentPackageId?: string, member?: string' , noPackage : false , disposition : 'definition' ,
95+ why : '[#25467 cut 15] the definition\'s body, the function the class member delegates to' } ,
7496 { member : 'readMetaItem' , args : 'request.type, request.name, envelopePackageId(request.packageId, item), bundleMemberOf(request.type, item)' , noPackage : true , disposition : 'served-package envelope' ,
7597 why : 'the by-name read\'s envelope: naming no package, the package of the item it serves (the list\'s rule); [#23074] at the served item\'s own bundle member' } ,
7698 { member : 'readFlattenedMetaItems' , args : 'request.type, itemName, itemPackageId, bundleMemberOf(request.type, it)' , noPackage : true , disposition : 'served-package envelope' ,
@@ -91,10 +113,12 @@ const SITES: ReadonlyArray<Site & { readonly noPackage: boolean; readonly dispos
91113 why : 'a boolean containment test over the one owner of an object' } ,
92114 { member : 'packagedArtifactOwner' , args : 'folded.type, folded.name' , noPackage : true , disposition : 'no' ,
93115 why : 'a shipped-package check by name: it returns a package id, never an envelope on a served body' } ,
94- { member : 'shippedArtifactsOf' , args : 'type, name' , noPackage : true , disposition : 'no' ,
116+ { file : SEAM_FILE , member : 'shippedArtifactsOf' , args : 'type, name' , noPackage : true , disposition : 'no' ,
95117 why : 'the lock\'s artifact layer (the one item-lock resolution), never an envelope on a served body' } ,
96- { member : 'shippedArtifactsOf' , args : 'type, name, packageId' , noPackage : false , disposition : 'no' ,
118+ { file : SEAM_FILE , member : 'shippedArtifactsOf' , args : 'type, name, packageId' , noPackage : false , disposition : 'no' ,
97119 why : 'scoped to a named package, and kept only when it is that package\'s own' } ,
120+ { member : 'shippedArtifactsOf' , args : '...a' , noPackage : true , disposition : 'no' ,
121+ why : '[#25467 cut 15] the class member\'s host closure: it forwards the moved body\'s two calls (the rows above) unchanged, so its disposition is theirs' } ,
98122 { member : 'hydrateOverlayIntoRegistry' , args : 'type, (data as any).name, options.packageId ?? undefined, bundleMemberOf(type, data)' , noPackage : true , disposition : 'no' ,
99123 why : 'scoped to the row\'s own package; undefined only for a package-less row, whose body names no package; [#23074] at the row\'s own bundle member' } ,
100124 { member : 'expandRuntimeViewContainer' , args : 'type, String(item.name), ownPackageId' , noPackage : false , disposition : 'no' ,
@@ -109,10 +133,10 @@ const SITES: ReadonlyArray<Site & { readonly noPackage: boolean; readonly dispos
109133 why : 'the save door\'s collision check: the package id names a shipper in a refusal, never an envelope on a served body' } ,
110134] ;
111135
112- const keyOf = ( site : Site ) => `${ site . member } : ${ CALLEE } ${ site . args } )` ;
136+ const keyOf = ( site : Site ) => `${ site . file ?? 'protocol.ts' } ${ site . member } : ${ CALLEE } ${ site . args } )` ;
113137
114138describe ( '[#22024] every lookupArtifactItem( in protocol.ts has a recorded disposition' , ( ) => {
115- const found = sitesOf ( SOURCE ) ;
139+ const found = sitesIn ( ) ;
116140
117141 it ( 'the sites in the source are exactly the recorded ones' , ( ) => {
118142 const recorded = SITES . map ( keyOf ) . sort ( ) ;
@@ -128,11 +152,21 @@ describe('[#22024] every lookupArtifactItem( in protocol.ts has a recorded dispo
128152
129153 it ( 'the population is the card\'s enumeration: one definition and every call, each in a member' , ( ) => {
130154 // Non-vacuity: a scan that found nothing, or lost the members, cannot pass the test above by accident.
131- expect ( found . filter ( ( site ) => site . member === 'lookupArtifactItem' && site . args . includes ( ': string' ) ) ) . toHaveLength ( 1 ) ;
155+ // [#25467 cut 15] One definition per file: the class member's signature and the seam function it delegates to.
156+ expect ( found . filter ( ( site ) => site . member === 'lookupArtifactItem' && site . args . includes ( ': string' ) ) . map ( ( site ) => site . file ?? 'protocol.ts' ) )
157+ . toEqual ( [ 'protocol.ts' , SEAM_FILE ] ) ;
132158 expect ( found . filter ( ( site ) => site . member === '(no member)' ) ) . toEqual ( [ ] ) ;
133159 expect ( found . length ) . toBe ( SITES . length ) ;
134160 } ) ;
135161
162+ it ( '[#25467 cut 15] the seam module is scanned: the definition\'s body and both of shippedArtifactsOf\'s calls are found there' , ( ) => {
163+ // Non-vacuity for the second file: a scan that read the wrong file, or none, finds no site there.
164+ expect ( found . filter ( ( site ) => site . file === SEAM_FILE ) . map ( keyOf ) . sort ( ) ) . toEqual (
165+ SITES . filter ( ( site ) => site . file === SEAM_FILE ) . map ( keyOf ) . sort ( ) ,
166+ ) ;
167+ expect ( found . filter ( ( site ) => site . file === SEAM_FILE ) ) . toHaveLength ( 3 ) ;
168+ } ) ;
169+
136170 it ( 'a site passing fewer than three arguments is recorded as passing no package' , ( ) => {
137171 const topLevelArgs = ( args : string ) => {
138172 let depth = 0 ;
0 commit comments