import { ESTree } from 'meriyah' ; import { Binding , ComponentsBinding } from '../types/binding' ; import { AnyFunction , EvaluateOptions } from './options' ; class KeyGenerator { private readonly prefix : string ; private readonly counter : number [ ] ; constructor ( prefix ? ) { this . prefix = prefix || '' ; this . counter = [ 0 ] ; } public increment ( ) { this . counter [ this . counter . length - 1 ] ++ ; } public openingElement ( ) { this . counter . push ( 0 ) ; } public closingElement ( ) { this . counter . pop ( ) ; } public generate ( ) { this . increment ( ) ; const key = this . counter . map ( ( counter ) => counter . toFixed ( 0 ) ) . join ( '-' ) ; return this . prefix ? `${ this . prefix }-${ key }` : key ; } } type VariableKind = ESTree . VariableDeclaration [ 'kind' ] ; class Variable { public readonly kind : VariableKind ; private init = false ; private stored : any = undefined ; constructor ( kind ) { this . kind = kind ; } get value ( ) : any { return this . stored ; } set value ( val ) { if ( this . init && this . kind === 'const' ) return ; this . stored = val ; this . init = true ; } } class Stack { public readonly parent : Stack | undefined ; public readonly self : any ; private variables : Map < string , Variable > ; constructor ( parent , self , init ) { this . parent = parent ; this . self = self ; this . variables = new Map ( ) ; for ( const [ key , value ] of Object . entries ( init ) ) { const variable = new Variable ( 'const' ) ; variable . value = value ; this . variables . set ( key , variable ) ; } } public get ( name ) { return this . variables . get ( name ) || ( this . parent ? this . parent . get ( name ) : undefined ) ; } public define ( kind , name ) { this . variables . set ( name , new Variable ( kind ) ) ; } public set ( name , value ) { const variable = this . variables . get ( name ) ; if ( ! variable ) return this . parent ? this . parent . set ( name , value ) : undefined ; variable . value = value ; } } const systemVariables = { undefined : undefined , null : null , true : true , false : false , } as const ; export class JSXContext { public readonly options : EvaluateOptions ; public readonly keyGenerator : KeyGenerator ; public readonly binding : Binding ; public readonly components : ComponentsBinding ; public readonly allowedFunctions : AnyFunction [ ] ; public readonly deniedFunctions : AnyFunction [ ] ; public readonly exports : Record < string , any > ; public stack : Stack ; constructor ( options ) { this . options = options ; this . keyGenerator = new KeyGenerator ( options . keyPrefix ) ; this . binding = options . binding || { } ; this . components = options . components || { } ; this . allowedFunctions = [ ... ( options . allowedFunctions || [ ] ) ] ; this . deniedFunctions = [ ... ( options . deniedFunctions || [ ] ) ] ; this . stack = new Stack ( new Stack ( undefined , undefined , systemVariables ) , undefined , this . binding ) ; this . exports = { } ; } public get stackSize ( ) : number { const getStackSize = ( stack , num ) : number => ( stack . parent ? getStackSize ( stack . parent , num ) + 1 : num ) ; return getStackSize ( this . stack , 1 ) ; } public pushStack ( self ) { this . stack = new Stack ( this . stack , self , { } ) ; } public popStack ( ) { this . stack = this . stack . parent as Stack ; } public defineVariable ( kind , name ) { this . stack . define ( kind , name ) ; } public setVariable ( name , value ) { this . stack . set ( name , value ) ; } public resolveThis ( ) { return this . stack ? this . stack . self : undefined ; } public resolveIdentifier ( name ) { return this . stack . get ( name ) ; } public resolveComponent ( name ) { const component = this . resolveIdentifier ( name ) ; if ( component ) return component . value ; const allComponents = Object . assign ( { } , this . options . disableSearchCompontsByBinding ? { } : this . binding , this . components ) ; return name . split ( '.' ) . reduce < any > ( ( components , part ) => { return components [ part ] || part ; } , allComponents ) ; } public export ( name , value ) { this . exports [ name ] = value ; } private _label : string | undefined ; public get label ( ) : string | undefined { const label = this . _label ; this . _label = undefined ; return label ; } public set label ( l ) { this . _label = l ; } public isAllowedFunction ( func ) { return ! this . isDeniedFunc ( func ) && this . isAllowedFunc ( func ) ; } private isAllowedFunc ( func ) { if ( ! this . hasAllowedFunctions ) return true ; const match = this . allowedFunctions . reduce ( ( match , f ) => match || f === func , false ) ; return match ; } private isDeniedFunc ( func ) { const match = this . deniedFunctions . reduce ( ( match , f ) => match || f === func , false ) ; return match ; } public get hasAllowedFunctions ( ) : boolean { return ! ! this . options . allowedFunctions ; } }