@@ -273,6 +273,44 @@ async function generate(
273273 }
274274 return
275275 }
276+ const memberValues = (
277+ file : string ,
278+ node : t . Node ,
279+ seen = new Set < string > ( )
280+ ) : { file : string ; node : t . Node } [ ] => {
281+ node = unwrap ( node )
282+ if ( seen . size >= 12 ) return [ ]
283+ if ( t . isIdentifier ( node ) ) {
284+ const ref = `${ file } #${ node . name } `
285+ const found = locate ( file , node . name )
286+ return found && ! seen . has ( ref )
287+ ? memberValues ( found . file , found . node , new Set ( seen ) . add ( ref ) )
288+ : [ ]
289+ }
290+ if ( t . isConditionalExpression ( node ) || t . isLogicalExpression ( node ) ) {
291+ const branches = t . isConditionalExpression ( node )
292+ ? [ node . consequent , node . alternate ]
293+ : [ node . left , node . right ]
294+ return branches . flatMap ( ( branch ) => memberValues ( file , branch , seen ) )
295+ }
296+ if ( t . isMemberExpression ( node ) ) {
297+ const selected =
298+ ! node . computed || t . isStringLiteral ( node . property ) || t . isNumericLiteral ( node . property )
299+ ? key ( node . property )
300+ : undefined
301+ return memberValues ( file , node . object , seen ) . flatMap ( ( object ) =>
302+ t . isObjectExpression ( object . node )
303+ ? object . node . properties . flatMap ( ( property ) =>
304+ t . isObjectProperty ( property ) &&
305+ ( selected === undefined || key ( property . key ) === selected )
306+ ? memberValues ( object . file , property . value , seen )
307+ : [ ]
308+ )
309+ : [ ]
310+ )
311+ }
312+ return [ { file, node } ]
313+ }
276314 const strings = ( file : string , node : t . Node , seen = new Set < string > ( ) ) : string [ ] => {
277315 node = unwrap ( node )
278316 if ( seen . size >= 12 ) {
@@ -304,16 +342,10 @@ async function generate(
304342 return node . properties . flatMap ( ( p ) =>
305343 t . isObjectProperty ( p ) ? strings ( file , p . value , seen ) : [ ]
306344 )
307- if ( t . isMemberExpression ( node ) && t . isIdentifier ( node . object ) ) {
308- const found = locate ( file , node . object . name )
309- if ( ! found && modules . get ( file ) ?. imports . has ( node . object . name ) )
310- note (
311- file ,
312- node . loc ?. start . line ?? 1 ,
313- `Unresolved imported styling: ${ node . object . name } .${ key ( node . property ) } `
314- )
315- if ( found )
316- return strings ( found . file , found . node , new Set ( seen ) . add ( `${ file } #${ node . object . name } ` ) )
345+ if ( t . isMemberExpression ( node ) ) {
346+ const values = memberValues ( file , node , seen )
347+ if ( ! values . length ) note ( file , node . loc ?. start . line ?? 1 , 'Unresolved styling member lookup' )
348+ return values . flatMap ( ( value ) => strings ( value . file , value . node , seen ) )
317349 }
318350 if ( t . isFunction ( node ) ) {
319351 const returns : t . Node [ ] = [ ]
@@ -645,19 +677,24 @@ async function generate(
645677 const usedRecipes = new Set < string > ( )
646678 const params = new Map < string , string > ( )
647679 const objects = new Set < string > ( )
648- const rests = new Set < string > ( )
680+ const rests = new Map < string , Set < string > > ( )
681+ const patternProps = ( pattern : t . ObjectPattern ) => {
682+ const consumed = new Set (
683+ pattern . properties . flatMap ( ( p ) => ( t . isObjectProperty ( p ) ? [ key ( p . key ) ] : [ ] ) )
684+ )
685+ for ( const field of pattern . properties ) {
686+ if ( t . isRestElement ( field ) && t . isIdentifier ( field . argument ) )
687+ rests . set ( field . argument . name , consumed )
688+ if ( t . isObjectProperty ( field ) ) {
689+ const value = t . isAssignmentPattern ( field . value ) ? field . value . left : field . value
690+ if ( t . isIdentifier ( value ) ) params . set ( value . name , key ( field . key ) )
691+ }
692+ }
693+ }
649694 if ( meta . fn ) {
650695 for ( const p of meta . fn . params ) {
651696 if ( t . isIdentifier ( p ) ) objects . add ( p . name )
652- if ( t . isObjectPattern ( p ) )
653- for ( const field of p . properties ) {
654- if ( t . isRestElement ( field ) && t . isIdentifier ( field . argument ) )
655- rests . add ( field . argument . name )
656- if ( t . isObjectProperty ( field ) ) {
657- const value = t . isAssignmentPattern ( field . value ) ? field . value . left : field . value
658- if ( t . isIdentifier ( value ) ) params . set ( value . name , key ( field . key ) )
659- }
660- }
697+ if ( t . isObjectPattern ( p ) ) patternProps ( p )
661698 }
662699 t . traverseFast ( meta . fn . body , ( n ) => {
663700 if (
@@ -666,14 +703,7 @@ async function generate(
666703 t . isIdentifier ( n . init ) &&
667704 objects . has ( n . init . name )
668705 )
669- for ( const field of n . id . properties ) {
670- if ( t . isRestElement ( field ) && t . isIdentifier ( field . argument ) )
671- rests . add ( field . argument . name )
672- if ( t . isObjectProperty ( field ) ) {
673- const value = t . isAssignmentPattern ( field . value ) ? field . value . left : field . value
674- if ( t . isIdentifier ( value ) ) params . set ( value . name , key ( field . key ) )
675- }
676- }
706+ patternProps ( n . id )
677707 } )
678708 const propOf = ( n : t . Node ) =>
679709 t . isIdentifier ( n )
@@ -727,22 +757,24 @@ async function generate(
727757 forwards : [ ] ,
728758 }
729759 }
730- const spread = n . attributes . some (
731- ( a ) =>
732- t . isJSXSpreadAttribute ( a ) &&
733- t . isIdentifier ( a . argument ) &&
734- ( rests . has ( a . argument . name ) || objects . has ( a . argument . name ) )
735- )
760+ const spreadInputs = new Set < string > ( )
761+ for ( const attribute of n . attributes ) {
762+ if ( ! t . isJSXSpreadAttribute ( attribute ) || ! t . isIdentifier ( attribute . argument ) ) continue
763+ const name = attribute . argument . name
764+ if ( ! rests . has ( name ) && ! objects . has ( name ) ) continue
765+ for ( const input of Object . keys ( entry . slots ) )
766+ if ( ! rests . get ( name ) ?. has ( input ) ) spreadInputs . add ( input )
767+ }
736768 for ( const attribute of n . attributes )
737769 if ( t . isJSXSpreadAttribute ( attribute ) && t . isIdentifier ( attribute . argument ) ) {
738770 const bundle = bundles . get ( attribute . argument . name )
739771 if ( bundle && ! native )
740772 for ( const [ forwarded , input ] of bundle )
741773 entry . slots [ input ] ?. forwards . push ( { target, slot : forwarded } )
742774 }
743- if ( spread && ! native )
744- for ( const [ name , slot ] of Object . entries ( entry . slots ) )
745- slot . forwards . push ( { target, slot : name } )
775+ if ( ! native )
776+ for ( const input of spreadInputs )
777+ entry . slots [ input ] . forwards . push ( { target, slot : input } )
746778 for ( const attr of n . attributes ) {
747779 if ( ! t . isJSXAttribute ( attr ) || ! attr . value ) continue
748780 const slotName = key ( attr . name )
@@ -769,7 +801,7 @@ async function generate(
769801 }
770802 }
771803 } )
772- if ( spread && entry . slots [ slotName ] ) inputs . add ( slotName )
804+ if ( spreadInputs . has ( slotName ) ) inputs . add ( slotName )
773805 const owned = protectedFor ( [
774806 ...strings ( meta . file , value ) ,
775807 ...[ ...recipes ] . flatMap ( ( r ) => out . recipes [ r ] . classes ) ,
@@ -781,7 +813,14 @@ async function generate(
781813 if ( category ( property ) ) owned . push ( family ( property ) )
782814 }
783815 } )
784- if ( slotName === 'className' && inputs . has ( 'className' ) && entry . slots . style )
816+ if (
817+ native &&
818+ slotName === 'className' &&
819+ inputs . has ( 'className' ) &&
820+ ( spreadInputs . has ( 'style' ) ||
821+ n . attributes . some ( ( a ) => t . isJSXAttribute ( a ) && key ( a . name ) === 'style' ) ) &&
822+ entry . slots . style
823+ )
785824 inputs . add ( 'style' )
786825 for ( const input of inputs ) {
787826 const slot = entry . slots [ input ]
@@ -871,8 +910,47 @@ async function generate(
871910 }
872911 }
873912 // Resolve slot forwarding to a fixed point. Unknown routes stay diagnostics, never permission.
874- const publicTarget = ( target : string ) =>
875- target . startsWith ( '@sim/emcn#' ) ? target . slice ( 10 ) : internalIds . get ( target )
913+ const targetCache = new Map < string , string | undefined > ( )
914+ const publicTarget = ( target : string ) : string | undefined => {
915+ if ( target . startsWith ( '@sim/emcn#' ) ) return target . slice ( 10 )
916+ if ( internalIds . has ( target ) ) return internalIds . get ( target )
917+ if ( targetCache . has ( target ) ) return targetCache . get ( target )
918+ const [ file , name ] = target . split ( '#' )
919+ const module = program . getSourceFile ( path . join ( virtualRoot , file ) )
920+ const moduleSymbol = module && checker . getSymbolAtLocation ( module )
921+ const [ root , ...members ] = ( name ?? '' ) . split ( '.' )
922+ let symbol =
923+ moduleSymbol && checker . getExportsOfModule ( moduleSymbol ) . find ( ( s ) => s . name === root )
924+ for ( const member of members ) {
925+ if ( ! symbol ) break
926+ if ( symbol . flags & ts . SymbolFlags . Alias ) symbol = checker . getAliasedSymbol ( symbol )
927+ const declaration = symbol . valueDeclaration ?? symbol . declarations ?. [ 0 ]
928+ symbol = declaration
929+ ? checker . getTypeOfSymbolAtLocation ( symbol , declaration ) . getProperty ( member )
930+ : undefined
931+ }
932+ if ( symbol ?. flags && symbol . flags & ts . SymbolFlags . Alias )
933+ symbol = checker . getAliasedSymbol ( symbol )
934+ let declaration = symbol ?. valueDeclaration ?? symbol ?. declarations ?. [ 0 ]
935+ if ( declaration && ts . isShorthandPropertyAssignment ( declaration ) ) {
936+ symbol = checker . getShorthandAssignmentValueSymbol ( declaration )
937+ declaration = symbol ?. valueDeclaration ?? symbol ?. declarations ?. [ 0 ]
938+ }
939+ const sourceName =
940+ declaration &&
941+ ts . isPropertyAssignment ( declaration ) &&
942+ ts . isIdentifier ( declaration . initializer )
943+ ? declaration . initializer . text
944+ : symbol ?. name
945+ const result =
946+ declaration && sourceName
947+ ? internalIds . get (
948+ `${ path . relative ( virtualRoot , declaration . getSourceFile ( ) . fileName ) } #${ sourceName } `
949+ )
950+ : undefined
951+ targetCache . set ( target , result )
952+ return result
953+ }
876954 for ( let pass = 0 ; pass < 12 ; pass ++ ) {
877955 let changed = false
878956 for ( const entry of Object . values ( out . exports ) )
0 commit comments