import { Node , walk } from 'estree-walker' ; import MagicString from 'magic-string' ; import { attributeValueIsString , isMember , isObjectKey , isObjectValueShortHand , isObjectValue , getSlotName } from '../../utils/svelteAst' ; import TemplateScope from './TemplateScope' ; import { SvelteIdentifier , WithName } from '../../interfaces' ; import { getTypeForComponent } from '../../htmlxtojsx/utils/node-utils' ; import { Directive } from 'svelte/types/compiler/interfaces' ; import ts from 'typescript' ; import { isInterfaceOrTypeDeclaration } from '../utils/tsAst' ; function attributeStrValueAsJsExpression ( attr ) { if ( attr . value . length == 0 ) { return "''" ; } if ( attr . value . length == 1 ) { const attrVal = attr . value [ 0 ] ; if ( attrVal . type == 'Text' ) { return '"' + attrVal . raw + '"' ; } } return '"__svelte_ts_string"' ; } export function is$$SlotsDeclaration ( node ) { return isInterfaceOrTypeDeclaration ( node ) && node . name . text === '$$Slots' ; } export class SlotHandler { constructor ( private readonly htmlx ) { } slots = new Map < string , Map < string , string > > ( ) ; resolved = new Map < WithName , string > ( ) ; resolvedExpression = new Map < Node , string > ( ) ; resolve ( identifierDef , initExpression , scope ) { let resolved = this . resolved . get ( identifierDef ) ; if ( resolved ) { return resolved ; } resolved = this . getResolveExpressionStr ( identifierDef , scope , initExpression ) ; if ( resolved ) { this . resolved . set ( identifierDef , resolved ) ; } return resolved ; } /**
     * Returns a string which expresses the given identifier unpacked to
     * the top level in order to express the slot types correctly later on.
     *
     * Example: {#each items as item} ---> __sveltets_1_unwrapArr(items)
     */ private getResolveExpressionStr ( identifierDef , scope , initExpression ) { const { name } = identifierDef ; const owner = scope . getOwner ( name ) ; if ( owner ?. type === 'CatchBlock' ) { return '__sveltets_1_any({})' ; } else if ( owner ?. type === 'ThenBlock' ) { const resolvedExpression = this . resolveExpression ( initExpression , scope . parent ) ; return `__sveltets_1_unwrapPromiseLike(${ resolvedExpression })` ; } else if ( owner ?. type === 'EachBlock' ) { const resolvedExpression = this . resolveExpression ( initExpression , scope . parent ) ; return `__sveltets_1_unwrapArr(${ resolvedExpression })` ; } return null ; } resolveDestructuringAssignment ( destructuringNode , identifiers , initExpression , scope ) { const destructuring = this . htmlx . slice ( destructuringNode . start , destructuringNode . end ) ; identifiers . forEach ( ( identifier ) => { const resolved = this . getResolveExpressionStr ( identifier , scope , initExpression ) ; if ( resolved ) { this . resolved . set ( identifier , `((${ destructuring }) => ${ identifier . name })(${ resolved })` ) ; } } ) ; } resolveDestructuringAssignmentForLet ( destructuringNode , identifiers , letNode , component , slotName ) { const destructuring = this . htmlx . slice ( destructuringNode . start , destructuringNode . end ) ; identifiers . forEach ( ( identifier ) => { const resolved = this . getResolveExpressionStrForLet ( letNode , component , slotName ) ; this . resolved . set ( identifier , `((${ destructuring }) => ${ identifier . name })(${ resolved })` ) ; } ) ; } private getResolveExpressionStrForLet ( letNode , component , slotName ) { return `${ getSingleSlotDef ( component , slotName ) }.${ letNode . name }` ; } resolveLet ( letNode , identifierDef , component , slotName ) { let resolved = this . resolved . get ( identifierDef ) ; if ( resolved ) { return resolved ; } resolved = this . getResolveExpressionStrForLet ( letNode , component , slotName ) ; this . resolved . set ( identifierDef , resolved ) ; return resolved ; } getSlotConsumerOfComponent ( component ) { let result = this . getLetNodes ( component , 'default' ) ?? [ ] ; for ( const child of component . children ) { const slotName = getSlotName ( child ) ; if ( slotName ) { const letNodes = this . getLetNodes ( child , slotName ) ; if ( letNodes ?. length ) { result = result . concat ( letNodes ) ; } } } return result ; } private getLetNodes ( child , slotName ) { const letNodes = ( ( child ?. attributes as Node [ ] ) ?? [ ] ) . filter ( ( attr ) => attr . type === 'Let' ) as Directive [ ] ; return letNodes ?. map ( ( letNode ) => ( { letNode , slotName } ) ) ; } /**
     * Resolves the slot expression to a string that can be used
     * in the props-object in the return type of the render function
     */ private resolveExpression ( expression , scope ) { let resolved = this . resolvedExpression . get ( expression ) ; if ( resolved ) { return resolved ; } const strForExpression = new MagicString ( this . htmlx ) ; const identifiers = [ ] ; const objectShortHands = [ ] ; walk ( expression , { enter ( node , parent , prop ) { if ( node . type === 'Identifier' ) { if ( parent ) { if ( isMember ( parent , prop ) ) { return ; } if ( isObjectKey ( parent , prop ) ) { return ; } if ( isObjectValue ( parent , prop ) ) { if ( isObjectValueShortHand ( parent ) ) { this . skip ( ) ; objectShortHands . push ( node ) ; return ; } } } this . skip ( ) ; identifiers . push ( node ) ; } } } ) ; const getOverwrite = ( name ) => { const init = scope . getInit ( name ) ; return init ? this . resolved . get ( init ) : name ; } ; for ( const identifier of objectShortHands ) { const { end , name } = identifier ; const value = getOverwrite ( name ) ; strForExpression . appendLeft ( end , `:${ value }` ) ; } for ( const identifier of identifiers ) { const { start , end , name } = identifier ; const value = getOverwrite ( name ) ; strForExpression . overwrite ( start , end , value ) ; } resolved = strForExpression . slice ( expression . start , expression . end ) ; this . resolvedExpression . set ( expression , resolved ) ; return resolved ; } handleSlot ( node , scope ) { const nameAttr = node . attributes . find ( ( a ) => a . name == 'name' ) ; const slotName = nameAttr ? nameAttr . value [ 0 ] . raw : 'default' ; const attributes = new Map < string , string > ( ) ; for ( const attr of node . attributes ) { if ( attr . name == 'name' ) { continue ; } if ( attr . type === 'Spread' ) { const rawName = attr . expression . name ; const init = scope . getInit ( rawName ) ; const name = init ? this . resolved . get ( init ) : rawName ; attributes . set ( `__spread__${ name }` , name ) ; } if ( ! attr . value ?. length ) { continue ; } if ( attributeValueIsString ( attr ) ) { attributes . set ( attr . name , attributeStrValueAsJsExpression ( attr ) ) ; continue ; } attributes . set ( attr . name , this . resolveAttr ( attr , scope ) ) ; } this . slots . set ( slotName , attributes ) ; } getSlotDef ( ) { return this . slots ; } resolveAttr ( attr , scope ) { const attrVal = attr . value [ 0 ] ; if ( ! attrVal ) { return null ; } if ( attrVal . type == 'AttributeShorthand' ) { const { name } = attrVal . expression ; const init = scope . getInit ( name ) ; const resolved = this . resolved . get ( init ) ; return resolved ?? name ; } if ( attrVal . type == 'MustacheTag' ) { return this . resolveExpression ( attrVal . expression , scope ) ; } throw Error ( 'Unknown attribute value type:' + attrVal . type ) ; } } function getSingleSlotDef ( componentNode , slotName ) { const componentType = getTypeForComponent ( componentNode ) ; return `__sveltets_1_instanceOf(${ componentType }).$$slot_def['${ slotName }']` ; }