import type { NodePath } from '@babel/traverse' ; import traverse from '@babel/traverse' ; import * as t from '@babel/types' ; import type { Metadata } from '../../types' ; import { getPathOfNode , wrapNodeInIIFE } from '../ast' ; import { isCompiledKeyframesCallExpression } from '../is-compiled' ; import { resolveBinding } from '../resolve-binding' ; import { createResultPair } from './common' ; import { traverseMemberAccessPath } from './traverse-access-path' ; param expression param meta { Metadata } const traverseIdentifier = ( expression , meta ) => { let value = undefined ; let updatedMeta = meta ; const resolvedBinding = resolveBinding ( expression . name , updatedMeta ) ; if ( resolvedBinding && resolvedBinding . constant && resolvedBinding . node ) { ( { value , meta : updatedMeta } = evaluateExpression ( resolvedBinding . node as t . Expression , resolvedBinding . meta ) ) ; } return createResultPair ( value as t . Expression , updatedMeta ) ; } ; param expression const getMemberExpressionMeta = ( expression ) : { accessPath : t . Identifier [ ] ; bindingIdentifier : t . Identifier | null ; originalBindingType : t . Expression [ 'type' ] ; } => { const accessPath = [ ] ; let bindingIdentifier = null ; let originalBindingType = 'Identifier' ; traverse ( t . expressionStatement ( expression ) , { noScope : true , MemberExpression ( path ) { if ( path . listKey === 'arguments' ) { return ; } if ( t . isIdentifier ( path . node . object ) ) { bindingIdentifier = path . node . object ; originalBindingType = bindingIdentifier . type ; } else if ( t . isCallExpression ( path . node . object ) ) { if ( t . isIdentifier ( path . node . object . callee ) ) { bindingIdentifier = path . node . object . callee ; originalBindingType = path . node . object . type ; } } if ( t . isIdentifier ( path . node . property ) ) { accessPath . push ( path . node . property ) ; } else if ( t . isCallExpression ( path . node . property ) && t . isIdentifier ( path . node . property . callee ) ) { accessPath . push ( path . node . property . callee ) ; } } , } ) ; return { accessPath : accessPath . reverse ( ) , bindingIdentifier , originalBindingType , } ; } ; param expression param meta { Metadata } const traverseMemberExpression = ( expression , meta ) : ReturnType < typeof createResultPair > => { const { accessPath , bindingIdentifier } = getMemberExpressionMeta ( expression ) ; if ( bindingIdentifier ) { return traverseMemberAccessPath ( t . identifier ( bindingIdentifier . name ) , meta , bindingIdentifier . name , accessPath , expression , { callExpression : traverseCallExpression , memberExpression : traverseMemberExpression } ) ; } return createResultPair ( expression , meta ) ; } ; param expression param meta { Metadata } const traverseFunction = ( expression , meta ) => { let value = undefined ; let updatedMeta = meta ; if ( t . isBlockStatement ( expression . body ) ) { traverse ( expression . body , { noScope : true , ReturnStatement ( path ) { const { argument } = path . node ; if ( argument ) { ( { value , meta : updatedMeta } = evaluateExpression ( argument , meta ) ) ; } path . stop ( ) ; } , } ) ; } else { ( { value , meta : updatedMeta } = evaluateExpression ( expression . body , meta ) ) ; } return createResultPair ( value as t . Expression , updatedMeta ) ; } ; param expression param meta { Metadata } const traverseCallExpression = ( expression , meta ) => { const callee = expression . callee ; let value = undefined ; let updatedMeta = { ... meta } ; if ( t . isExpression ( callee ) ) { let functionNode ; if ( t . isFunction ( callee ) ) { functionNode = callee ; } else { if ( t . isIdentifier ( callee ) ) { const resolvedBinding = resolveBinding ( callee . name , updatedMeta ) ; if ( resolvedBinding && resolvedBinding . constant ) { functionNode = resolvedBinding . node ; } } else if ( t . isMemberExpression ( callee ) && t . isIdentifier ( callee . property ) ) { callee . property = t . callExpression ( callee . property , expression . arguments ) ; return traverseMemberExpression ( callee , updatedMeta ) ; } } if ( functionNode && t . isFunction ( functionNode ) ) { const { params } = functionNode ; const evaluatedArguments = expression . arguments . map ( ( argument ) => evaluateExpression ( argument as t . Expression , updatedMeta ) . value ) ; const expressionPath = getPathOfNode ( expression , updatedMeta . parentPath ) ; const [ wrappingNodePath ] = expressionPath . replaceWith ( wrapNodeInIIFE ( expression ) ) ; const arrowFunctionExpressionPath = getPathOfNode ( wrappingNodePath . node . callee , wrappingNodePath as any ) ; params . filter ( ( param ) => t . isIdentifier ( param ) || t . isObjectPattern ( param ) ) . forEach ( ( param , index ) => { const evaluatedArgument = evaluatedArguments [ index ] ; arrowFunctionExpressionPath . scope . push ( { id : param , init : evaluatedArgument , kind : 'const' , } ) ; } ) ; updatedMeta . ownPath = arrowFunctionExpressionPath ; } ( { value , meta : updatedMeta } = evaluateExpression ( callee , updatedMeta ) ) ; } return createResultPair ( value as t . Expression , updatedMeta ) ; } ; param path const isIdentifierReferencesMutated = ( path ) : boolean => { const binding = path . scope . getBinding ( path . node . name ) ; if ( ! binding ) { return false ; } if ( ! t . isVariableDeclarator ( binding . path . node ) || ! binding . constant ) { return true ; } for ( let i = 0 ; i < binding . referencePaths . length ; i ++ ) { const refPath = binding . referencePaths [ i ] ; const innerBinding = refPath . scope . getBinding ( path . node . name ) ; if ( ! innerBinding ) { continue ; } if ( ! t . isVariableDeclarator ( innerBinding . path . node ) || ! innerBinding . constant ) { return true ; } } return false ; } ; param path const isPathReferencingAnyMutatedIdentifiers = ( path ) : boolean => { if ( path . isIdentifier ( ) ) { return isIdentifierReferencesMutated ( path ) ; } let mutated = false ; path . traverse ( { Identifier ( innerPath ) { const result = isIdentifierReferencesMutated ( path ) ; if ( result ) { mutated = true ; innerPath . stop ( ) ; } } , } ) ; return mutated ; } ; param node param meta { Metadata } param fallbackNode const babelEvaluateExpression = ( node , meta , fallbackNode = node ) : t . Expression => { const path = getPathOfNode ( node , meta . parentPath ) ; if ( isPathReferencingAnyMutatedIdentifiers ( path ) ) { return fallbackNode ; } const result = path . evaluate ( ) ; if ( result . value ) { switch ( typeof result . value ) { case 'string' : return t . stringLiteral ( result . value ) ; case 'number' : return t . numericLiteral ( result . value ) ; } } return fallbackNode ; } ; param expression param meta { Metadata } export const evaluateExpression = ( expression , meta ) : ReturnType < typeof createResultPair > => { let value = undefined ; let updatedMeta = meta ; if ( t . isIdentifier ( expression ) ) { ( { value , meta : updatedMeta } = traverseIdentifier ( expression , updatedMeta ) ) ; } else if ( t . isMemberExpression ( expression ) ) { ( { value , meta : updatedMeta } = traverseMemberExpression ( expression , updatedMeta ) ) ; } else if ( t . isFunction ( expression ) ) { ( { value , meta : updatedMeta } = traverseFunction ( expression , updatedMeta ) ) ; } else if ( t . isCallExpression ( expression ) ) { ( { value , meta : updatedMeta } = traverseCallExpression ( expression , updatedMeta ) ) ; } if ( t . isStringLiteral ( value ) || t . isNumericLiteral ( value ) || t . isObjectExpression ( value ) || t . isTaggedTemplateExpression ( value ) || ( value && isCompiledKeyframesCallExpression ( value , updatedMeta . state ) ) ) { return createResultPair ( value , updatedMeta ) ; } if ( value ) { const babelEvaluatedNode = babelEvaluateExpression ( value , updatedMeta , expression ) ; return createResultPair ( babelEvaluatedNode , updatedMeta ) ; } const babelEvaluatedNode = babelEvaluateExpression ( expression , updatedMeta ) ; return createResultPair ( babelEvaluatedNode , updatedMeta ) ; } ;