import { ESTree } from 'meriyah' ; import { ReactNode } from 'react' ; import { JSXComponent , JSXElement , JSXFragment , JSXNode , JSXProperties , JSXText } from '../types/node' ; import { evalArrayPattern , evalBindingPattern , evalObjectPattern , evalRestElement , setBinding } from './bind' ; import { evalClassDeclaration , evalClassExpression } from './class' ; import { JSXContext } from './context' ; import { evalMethodDefinition } from './definition' ; import { JSXEvaluateError , wrapJSXError } from './error' ; import { bindFunction , evalFunction } from './function' ; export const evalExpression = ( exp , context ) : any => { try { switch ( exp . type ) { case 'ArrayExpression' : return evalArrayExpression ( exp , context ) ; case 'ArrayPattern' : return evalArrayPattern ( exp , context ) ; case 'ArrowFunctionExpression' : return evalArrowFunctionExpression ( exp , context ) ; case 'AssignmentExpression' : return evalAssignmentExpression ( exp , context ) ; case 'AwaitExpression' : return evalAwaitExpression ( exp , context ) ; case 'BinaryExpression' : return evalBinaryExpression ( exp , context ) ; case 'CallExpression' : return evalCallExpression ( exp , context ) ; case 'ChainExpression' : return evalChainExpression ( exp , context ) ; case 'ClassDeclaration' : return evalClassDeclaration ( exp , context ) ; case 'ClassExpression' : return evalClassExpression ( exp , context ) ; case 'ConditionalExpression' : return evalConditionalExpression ( exp , context ) ; case 'FunctionExpression' : return evalFunctionExpression ( exp , context ) ; case 'Identifier' : return evalIdentifier ( exp , context ) ; case 'Import' : return evalImport ( exp , context ) ; case 'ImportExpression' : return evalImportExpression ( exp , context ) ; case 'JSXClosingElement' : return evalJSXClosingElement ( exp , context ) ; case 'JSXClosingFragment' : return evalJSXClosingFragment ( exp , context ) ; case 'JSXElement' : return evalJSXElement ( exp , context ) ; case 'JSXExpressionContainer' : return evalJSXExpressionContainer ( exp , context ) ; case 'JSXFragment' : return evalJSXFragment ( exp , context ) ; case 'JSXOpeningElement' : return evalJSXOpeningElement ( exp , context ) ; case 'JSXOpeningFragment' : return evalJSXOpeningFragment ( exp , context ) ; case 'JSXSpreadChild' : return evalJSXSpreadChild ( exp , context ) ; case 'Literal' : return evalLiteral ( exp , context ) ; case 'LogicalExpression' : return evalLogicalExpression ( exp , context ) ; case 'MemberExpression' : return evalMemberExpression ( exp , context ) ; case 'MetaProperty' : return evalMetaProperty ( exp , context ) ; case 'NewExpression' : return evalNewExpression ( exp , context ) ; case 'ObjectExpression' : return evalObjectExpression ( exp , context ) ; case 'ObjectPattern' : return evalObjectPattern ( exp , context ) ; case 'RestElement' : return evalRestElement ( exp , context ) ; case 'SequenceExpression' : return evalSequenceExpression ( exp , context ) ; case 'SpreadElement' : return evalSpreadElement ( exp , context ) ; case 'Super' : return evalSuper ( exp , context ) ; case 'TaggedTemplateExpression' : return evalTaggedTemplateExpression ( exp , context ) ; case 'TemplateLiteral' : return evalTemplateLiteral ( exp , context ) ; case 'ThisExpression' : return evalThisExpression ( exp , context ) ; case 'UnaryExpression' : return evalUnaryExpression ( exp , context ) ; case 'UpdateExpression' : return evalUpdateExpression ( exp , context ) ; case 'YieldExpression' : return evalYieldExpression ( exp , context ) ; default : throw new JSXEvaluateError ( 'Not implemented expression' , exp , context ) ; } } catch ( e ) { throw wrapJSXError ( e , exp , context ) ; } } ; export const evalArrayExpression = ( exp , context ) : Array < any > => { return exp . elements . map ( ( element ) => ( element ? evalExpression ( element , context ) : null ) ) ; } ; export const evalArrowFunctionExpression = ( exp , context ) => { const self = context . resolveThis ( ) ; const func = bindFunction ( evalFunction ( exp , context ) [ 1 ] , self , context ) ; if ( context . options . allowUserDefinedFunction && context . hasAllowedFunctions ) { context . allowedFunctions . push ( func ) ; } return func ; } ; export const evalAssignmentExpression = ( exp , context ) => { const binding = evalBindingPattern ( exp . left , context ) ; const { operator } = exp ; if ( operator === '=' ) { const val = evalExpression ( exp . right , context ) ; setBinding ( binding , val , context ) ; return val ; } else { const val = evalBinaryExpression ( { type : 'BinaryExpression' , operator : operator . slice ( 0 , operator . length - 1 ) , left : exp . left , right : exp . right , } , context ) ; setBinding ( binding , val , context ) ; return val ; } } ; export const evalAwaitExpression = ( exp , context ) => { throw new JSXEvaluateError ( 'await is not supported' , exp , context ) ; } ; export const evalBinaryExpression = ( exp , context ) => { const left = ( ) => evalExpression ( exp . left , context ) ; const right = ( ) => evalExpression ( exp . right , context ) ; switch ( exp . operator ) { case '+' : return left ( ) + right ( ) ; case '-' : return left ( ) - right ( ) ; case '/' : return left ( ) / right ( ) ; case '*' : return left ( ) * right ( ) ; case '%' : return left ( ) % right ( ) ; case '**' : return left ( ) ** right ( ) ; case 'in' : return left ( ) in right ( ) ; case 'instanceof' : return left ( ) instanceof right ( ) ; case '<' : return left ( ) < right ( ) ; case '>' : return left ( ) > right ( ) ; case '<=' : return left ( ) <= right ( ) ; case '>=' : return left ( ) >= right ( ) ; case '==' : return left ( ) == right ( ) ; case '!=' : return left ( ) != right ( ) ; case '===' : return left ( ) === right ( ) ; case '!==' : return left ( ) !== right ( ) ; case '<<' : return left ( ) << right ( ) ; case '>>' : return left ( ) >> right ( ) ; case '>>>' : return left ( ) >>> right ( ) ; case '&' : return left ( ) & right ( ) ; case '|' : return left ( ) | right ( ) ; case '^' : return left ( ) ^ right ( ) ; default : throw new JSXEvaluateError ( `Unknown binary operator: ${ exp . operator }` , exp , context ) ; } } ; export const evalCallExpression = ( exp , context ) => { if ( context . options . disableCall ) return undefined ; try { const callee = exp . callee as ESTree . Expression ; const receiver = callee . type === 'MemberExpression' ? evalExpression ( callee . object , context ) : context . resolveThis ( ) ; const getName = ( callee ) => { return callee . type === 'Identifier' ? callee . name : callee . type === 'MemberExpression' ? getName ( callee . property ) : null ; } ; if ( exp . optional && receiver === undefined ) return undefined ; const method = evalExpression ( callee , context ) as ( ... args : any [ ] ) => any ; if ( typeof method !== 'function' ) { throw new JSXEvaluateError ( `${ getName ( callee ) || 'f' } is not a function` , exp , context ) ; } if ( ! context . isAllowedFunction ( method ) ) { throw new JSXEvaluateError ( `${ getName ( callee ) || 'f' } is not allowed function` , exp , context ) ; } const args = exp . arguments . map ( ( arg ) => evalExpression ( arg , context ) ) ; context . pushStack ( receiver ) ; const retval = method . call ( receiver , ... args ) ; context . popStack ( ) ; return retval ; } catch ( e ) { throw wrapJSXError ( e , exp , context ) ; } } ; export const evalChainExpression = ( exp , context ) => { return evalExpression ( exp . expression , context ) ; } ; export const evalConditionalExpression = ( exp , context ) => { return evalExpression ( exp . test , context ) ? evalExpression ( exp . consequent , context ) : evalExpression ( exp . alternate , context ) ; } ; export const evalFunctionExpression = ( exp , context ) => { const func = evalFunction ( exp , context ) [ 1 ] ; if ( context . options . allowUserDefinedFunction && context . hasAllowedFunctions ) { context . allowedFunctions . push ( func ) ; } return func ; } ; export const evalIdentifier = ( exp , context ) => { const variable = context . resolveIdentifier ( exp . name ) ; if ( ! variable ) { if ( context . options . raiseReferenceError ) { throw new JSXEvaluateError ( `${ exp . name } is not defined` , exp , context ) ; } else { return undefined ; } } return variable . value ; } ; export const evalImport = ( exp , context ) => { throw new JSXEvaluateError ( 'import is not supported' , exp , context ) ; } ; export const evalImportExpression = ( exp , context ) => { throw new JSXEvaluateError ( 'import is not supported' , exp , context ) ; } ; export const evalLiteral = ( exp , _context ) : ESTree . Literal [ 'value' ] => { return exp . value ; } ; export const evalLogicalExpression = ( exp , context ) => { const left = ( ) => evalExpression ( exp . left , context ) ; const right = ( ) => evalExpression ( exp . right , context ) ; switch ( exp . operator ) { case '&&' : return left ( ) && right ( ) ; case '||' : return left ( ) || right ( ) ; case '??' : return left ( ) ?? right ( ) ; default : throw new JSXEvaluateError ( `Unknown logical operator: ${ exp . operator }` , exp , context ) ; } } ; export const evalMemberExpression = ( exp , context ) => { try { const { object , property } = exp ; const receiver = evalExpression ( object , context ) ; const key = property . type === 'Identifier' ? property . name : property . type === 'PrivateIdentifier' ? property . name : evalExpression ( property , context ) ; if ( exp . optional && receiver === undefined ) return undefined ; context . pushStack ( receiver ) ; const retval = receiver [ key ] ; context . popStack ( ) ; return retval ; } catch ( e ) { throw wrapJSXError ( e , exp , context ) ; } } ; export const evalMetaProperty = ( exp , context ) => { throw new JSXEvaluateError ( 'meta property is not supported' , exp , context ) ; } ; export const evalNewExpression = ( exp , context ) => { try { if ( context . options . disableCall || context . options . disableNew ) return undefined ; const callee = evalExpression ( exp . callee , context ) ; const arugments = exp . arguments . map ( ( arg ) => evalExpression ( arg , context ) ) ; return new callee ( ... arugments ) ; } catch ( e ) { throw wrapJSXError ( e , exp , context ) ; } } ; export const evalObjectExpression = ( exp , context ) => { const object = { } ; exp . properties . forEach ( ( property ) => { evalObjectLiteralElementLike ( object , property , context ) ; } ) ; return object ; } ; export const evalSequenceExpression = ( exp , context ) => { return exp . expressions . reduce ( ( _ , e ) => evalExpression ( e , context ) , undefined ) ; } ; export const evalSpreadElement = ( exp , context ) => { return evalExpression ( exp . argument , context ) ; } ; export const evalSuper = ( _ , context ) => { const ctor = context . resolveThis ( ) . constructor ; return ctor . super ; } ; export const evalTaggedTemplateExpression = ( exp , context ) => { const { quasi } = exp ; const tag = evalExpression ( exp . tag , context ) ; const quasis = quasi . quasis . map ( ( q ) => q . value . cooked ) ; const expressions = quasi . expressions . map ( ( e ) => evalExpression ( e , context ) ) ; return tag ( quasis , ... expressions ) ; } ; const getLocStart = ( node ) => { if ( node . loc ) return node . loc . start ; return { line : 0 , column : 0 } ; } ; export const evalTemplateLiteral = ( exp , context ) => { return [ ... exp . expressions , ... exp . quasis ] . sort ( ( a , b ) => { const aLoc = getLocStart ( a ) ; const bLoc = getLocStart ( b ) ; if ( aLoc . line === bLoc . line ) return aLoc . column - bLoc . column ; return aLoc . line - bLoc . line ; } ) . map ( ( e ) => { switch ( e . type ) { case 'TemplateElement' : return e . value . cooked ; default : return evalExpression ( e , context ) ; } } ) . join ( '' ) ; } ; export const evalThisExpression = ( _ , context ) => { return context . resolveThis ( ) ; } ; export const evalUnaryExpression = ( exp , context ) => { switch ( exp . operator ) { case '+' : return + evalExpression ( exp . argument , context ) ; case '-' : return - evalExpression ( exp . argument , context ) ; case '~' : return ~ evalExpression ( exp . argument , context ) ; case '!' : return ! evalExpression ( exp . argument , context ) ; case 'void' : return void evalExpression ( exp . argument , context ) ; case 'typeof' : return typeof evalExpression ( exp . argument , context ) ; default : throw new JSXEvaluateError ( `Unknown unary operator: ${ exp . operator }` , exp , context ) ; } } ; export const evalUpdateExpression = ( exp , context ) => { const binding = evalBindingPattern ( exp . argument , context ) ; const current = evalExpression ( exp . argument , context ) ; switch ( exp . operator ) { case '++' : return setBinding ( binding , current + 1 , context ) ; case '--' : return setBinding ( binding , current - 1 , context ) ; default : throw new JSXEvaluateError ( `Unknown update operator: ${ exp . operator }` , exp , context ) ; } } ; export const evalYieldExpression = ( exp , context ) => { throw new JSXEvaluateError ( 'yield is not supported' , exp , context ) ; } ; const evalObjectLiteralElementLike = ( object , exp , context ) => { switch ( exp . type ) { case 'MethodDefinition' : evalMethodDefinition ( exp , context ) ; break ; case 'Property' : { evalProperty ( object , exp , context ) ; break ; } case 'SpreadElement' : { Object . assign ( object , evalSpreadElement ( exp , context ) ) ; break ; } } } ; export const evalProperty = ( object , exp , context ) => { let key ; if ( exp . computed ) { key = evalExpression ( exp . key , context ) ; } else { switch ( exp . key . type ) { case 'Literal' : key = evalLiteral ( exp . key , context ) ; break ; case 'Identifier' : key = exp . key . name ; break ; } } const value = ( ( exp ) => { switch ( exp . type ) { case 'AssignmentPattern' : case 'ArrayPattern' : case 'ObjectPattern' : return undefined ; default : return evalExpression ( exp , context ) ; } } ) ( exp . value ) ; switch ( exp . kind ) { case 'init' : object [ key ] = value ; break ; case 'get' : Object . defineProperty ( object , key , { get : bindFunction ( value , object , context ) } ) ; break ; case 'set' : Object . defineProperty ( object , key , { set : bindFunction ( value , object , context ) } ) ; break ; } } ; export const evalJSXChild = ( jsx , context ) : JSXNode => { switch ( jsx . type ) { case 'JSXEmptyExpression' : return evalJSXEmptyExpression ( jsx , context ) ; case 'JSXText' : return evalJSXText ( jsx , context ) ; default : return evalExpression ( jsx , context ) ; } } ; export const evalJSXElement = ( jsx , context ) : JSXElement | ReactNode => { const { openingElement } = jsx ; const [ component , properties ] = evalExpression ( openingElement , context ) ; const children = jsx . children . map ( ( child ) => evalJSXChild ( child , context ) ) ; jsx . closingElement && evalExpression ( jsx . closingElement , context ) ; const { start : loc } = Object . assign ( { } , { start : undefined } , jsx . loc ) ; return { type : 'element' , component , props : properties , children , loc , } ; } ; export const evalJSXEmptyExpression = ( _jsx , _context ) : JSXNode => { return undefined ; } ; export const evalJSXSpreadChild = ( jsx , context ) : JSXFragment | ReactNode | undefined => { const { expression } = jsx ; const fragment = evalJSXFragment ( { type : 'JSXFragment' , openingFragment : { type : 'JSXOpeningFragment' , } , closingFragment : { type : 'JSXClosingFragment' , } , children : [ ] , } , context ) ; fragment . children = Array . from ( evalJSXExpressionContainer ( { type : 'JSXExpressionContainer' , expression } , context ) ) ; return fragment ; } ; export const evalJSXExpressionContainer = ( jsx , context ) : any => { const { expression } = jsx ; switch ( expression . type ) { case 'JSXEmptyExpression' : return evalJSXEmptyExpression ( expression , context ) ; default : return evalExpression ( expression , context ) ; } } ; export const evalJSXFragment = ( jsx , context ) : JSXFragment => { const { openingFragment } = jsx ; const [ , properties ] = evalExpression ( openingFragment , context ) ; const children = jsx . children . map ( ( child ) => evalJSXChild ( child , context ) ) ; evalExpression ( jsx . closingFragment , context ) ; const { start : loc } = Object . assign ( { } , { start : undefined } , jsx . loc ) ; return { type : 'fragment' , props : properties , children , loc , } ; } ; export const evalJSXText = ( jsx , _context ) : JSXText => { return jsx . value ; } ; export const evalJSXClosingElement = ( _jsx , context ) => { context . keyGenerator . closingElement ( ) ; return undefined ; } ; export const evalJSXClosingFragment = ( _jsx , context ) => { context . keyGenerator . closingElement ( ) ; return undefined ; } ; export const evalJSXOpeningElement = ( jsx , context ) : [ JSXComponent , JSXProperties ] => { const { attributes } = jsx ; const name = evalJSXTagNameExpression ( jsx . name , context ) ; const component = context . resolveComponent ( name ) ; const properties = { } ; attributes . forEach ( ( attribute ) => { switch ( attribute . type ) { case 'JSXAttribute' : { const [ key , value ] = evalJSXAttribute ( attribute , context ) ; properties [ key ] = value ; break ; } case 'JSXSpreadAttribute' : { Object . assign ( properties , evalJSXSpreadAttribute ( attribute , context ) ) ; break ; } } } ) ; if ( ! context . options . disableKeyGeneration && properties [ 'key' ] === undefined ) { const key = context . keyGenerator . generate ( ) ; properties [ 'key' ] = key ; } context . keyGenerator . openingElement ( ) ; if ( jsx . selfClosing ) context . keyGenerator . closingElement ( ) ; return [ component , properties ] ; } ; export const evalJSXOpeningFragment = ( _jsx , context ) : [ undefined , JSXProperties ] => { const properties = { } ; if ( ! context . options . disableKeyGeneration && properties [ 'key' ] === undefined ) { properties [ 'key' ] = context . keyGenerator . generate ( ) ; } context . keyGenerator . openingElement ( ) ; return [ undefined , properties ] ; } ; export const evalJSXTagNameExpression = ( jsx , context ) : string => { switch ( jsx . type ) { case 'JSXIdentifier' : return evalJSXIdentifier ( jsx , context ) ; case 'JSXMemberExpression' : return evalJSXMemberExpression ( jsx , context ) ; case 'JSXNamespacedName' : return evalJSXNamespacedName ( jsx , context ) ; } } ; export const evalJSXIdentifier = ( jsx , _context ) : string => { const { name } = jsx ; return name ; } ; export const evalJSXMemberExpression = ( jsx , context ) : string => { const { object , property } = jsx ; return `${ evalJSXTagNameExpression ( object , context ) }.${ evalJSXIdentifier ( property , context ) }` ; } ; export const evalJSXNamespacedName = ( jsx , context ) : string => { const { namespace , name } = jsx ; return `${ evalJSXTagNameExpression ( namespace , context ) }:${ evalJSXIdentifier ( name , context ) }` ; } ; export const evalJSXAttribute = ( jsx , context ) : [ string , any ] => { const name = evalJSXTagNameExpression ( jsx . name , context ) ; const value = evalJSXAttributeValue ( jsx . value , context ) ; return [ name , value ] ; } ; export const evalJSXSpreadAttribute = ( jsx , context ) => { return evalExpression ( jsx . argument , context ) ; } ; export const evalJSXAttributeValue = ( jsx , context ) => { if ( ! jsx ) return true ; switch ( jsx . type ) { case 'JSXIdentifier' : return evalJSXIdentifier ( jsx , context ) ; case 'Literal' : return evalLiteral ( jsx , context ) ; case 'JSXElement' : return evalJSXElement ( jsx , context ) ; case 'JSXFragment' : return evalJSXFragment ( jsx , context ) ; case 'JSXExpressionContainer' : return evalJSXExpressionContainer ( jsx , context ) ; case 'JSXSpreadChild' : return evalJSXSpreadChild ( jsx , context ) ; } } ;