import { SourceLocation , Position , ElementNode , NodeTypes , CallExpression , createCallExpression , DirectiveNode , ElementTypes , TemplateChildNode , RootNode , ObjectExpression , Property , JSChildNode , createObjectExpression , SlotOutletNode , TemplateNode , RenderSlotCall , ExpressionNode , IfBranchNode , TextNode , InterpolationNode , VNodeCall , SimpleExpressionNode , BlockCodegenNode , MemoExpression } from './ast' ; import { TransformContext } from './transform' ; import { MERGE_PROPS , TELEPORT , SUSPENSE , KEEP_ALIVE , BASE_TRANSITION , TO_HANDLERS , NORMALIZE_PROPS , GUARD_REACTIVE_PROPS , CREATE_BLOCK , CREATE_ELEMENT_BLOCK , CREATE_VNODE , CREATE_ELEMENT_VNODE , WITH_MEMO , OPEN_BLOCK } from './runtimeHelpers' ; import { isString , isObject , hyphenate , extend } from '@vue/shared' ; import { PropsExpression } from './transforms/transformElement' ; export const isStaticExp = ( p ) : p is SimpleExpressionNode => p . type === NodeTypes . SIMPLE_EXPRESSION && p . isStatic ; export const isBuiltInType = ( tag , expected ) : boolean => tag === expected || tag === hyphenate ( expected ) ; export function isCoreComponent ( tag ) { if ( isBuiltInType ( tag , 'Teleport' ) ) { return TELEPORT ; } else if ( isBuiltInType ( tag , 'Suspense' ) ) { return SUSPENSE ; } else if ( isBuiltInType ( tag , 'KeepAlive' ) ) { return KEEP_ALIVE ; } else if ( isBuiltInType ( tag , 'BaseTransition' ) ) { return BASE_TRANSITION ; } } const nonIdentifierRE = /^\d|[^\$\w]/ ; export const isSimpleIdentifier = ( name ) : boolean => ! nonIdentifierRE . test ( name ) ; const enum MemberExpLexState { inMemberExp , inBrackets , inParens , inString } const validFirstIdentCharRE = /[A-Za-z_$\xA0-\uFFFF]/ ; const validIdentCharRE = /[\.\?\w$\xA0-\uFFFF]/ ; const whitespaceRE = /\s+[.[]\s*|\s*[.[]\s+/g ; /**
 * Simple lexer to check if an expression is a member expression. This is
 * lax and only checks validity at the root level (i.e. does not validate exps
 * inside square brackets), but it's ok since these are only used on template
 * expressions and false positives are invalid expressions in the first place.
 */ export const isMemberExpression = ( path ) : boolean => { path = path . trim ( ) . replace ( whitespaceRE , s => s . trim ( ) ) ; let state = MemberExpLexState . inMemberExp ; let stateStack = [ ] ; let currentOpenBracketCount = 0 ; let currentOpenParensCount = 0 ; let currentStringType = null ; for ( let i = 0 ; i < path . length ; i ++ ) { const char = path . charAt ( i ) ; switch ( state ) { case MemberExpLexState . inMemberExp : if ( char === '[' ) { stateStack . push ( state ) ; state = MemberExpLexState . inBrackets ; currentOpenBracketCount ++ ; } else if ( char === '(' ) { stateStack . push ( state ) ; state = MemberExpLexState . inParens ; currentOpenParensCount ++ ; } else if ( ! ( i === 0 ? validFirstIdentCharRE : validIdentCharRE ) . test ( char ) ) { return false ; } break ; case MemberExpLexState . inBrackets : if ( char === `'` || char === `"` || char === '`' ) { stateStack . push ( state ) ; state = MemberExpLexState . inString ; currentStringType = char ; } else if ( char === `[` ) { currentOpenBracketCount ++ ; } else if ( char === `]` ) { if ( ! -- currentOpenBracketCount ) { state = stateStack . pop ( ) ! ; } } break ; case MemberExpLexState . inParens : if ( char === `'` || char === `"` || char === '`' ) { stateStack . push ( state ) ; state = MemberExpLexState . inString ; currentStringType = char ; } else if ( char === `(` ) { currentOpenParensCount ++ ; } else if ( char === `)` ) { if ( i === path . length - 1 ) { return false ; } if ( ! -- currentOpenParensCount ) { state = stateStack . pop ( ) ! ; } } break ; case MemberExpLexState . inString : if ( char === currentStringType ) { state = stateStack . pop ( ) ! ; currentStringType = null ; } break ; } } return ! currentOpenBracketCount && ! currentOpenParensCount ; } ; export function getInnerRange ( loc , offset , length ? ) { __TEST__ && assert ( offset <= loc . source . length ) ; const source = loc . source . substr ( offset , length ) ; const newLoc = { source , start : advancePositionWithClone ( loc . start , loc . source , offset ) , end : loc . end } ; if ( length != null ) { __TEST__ && assert ( offset + length <= loc . source . length ) ; newLoc . end = advancePositionWithClone ( loc . start , loc . source , offset + length ) ; } return newLoc ; } export function advancePositionWithClone ( pos , source , numberOfCharacters = source . length ) { return advancePositionWithMutation ( extend ( { } , pos ) , source , numberOfCharacters ) ; } export function advancePositionWithMutation ( pos , source , numberOfCharacters = source . length ) { let linesCount = 0 ; let lastNewLinePos = - 1 ; for ( let i = 0 ; i < numberOfCharacters ; i ++ ) { if ( source . charCodeAt ( i ) === 10 ) { linesCount ++ ; lastNewLinePos = i ; } } pos . offset += numberOfCharacters ; pos . line += linesCount ; pos . column = lastNewLinePos === - 1 ? pos . column + numberOfCharacters : numberOfCharacters - lastNewLinePos ; return pos ; } export function assert ( condition , msg ? ) { if ( ! condition ) { throw new Error ( msg || `unexpected compiler condition` ) ; } } export function findDir ( node , name , allowEmpty = false ) { for ( let i = 0 ; i < node . props . length ; i ++ ) { const p = node . props [ i ] ; if ( p . type === NodeTypes . DIRECTIVE && ( allowEmpty || p . exp ) && ( isString ( name ) ? p . name === name : name . test ( p . name ) ) ) { return p ; } } } export function findProp ( node , name , dynamicOnly = false , allowEmpty = false ) { for ( let i = 0 ; i < node . props . length ; i ++ ) { const p = node . props [ i ] ; if ( p . type === NodeTypes . ATTRIBUTE ) { if ( dynamicOnly ) continue ; if ( p . name === name && ( p . value || allowEmpty ) ) { return p ; } } else if ( p . name === 'bind' && ( p . exp || allowEmpty ) && isBindKey ( p . arg , name ) ) { return p ; } } } export function isBindKey ( arg , name ) { return ! ! ( arg && isStaticExp ( arg ) && arg . content === name ) ; } export function hasDynamicKeyVBind ( node ) { return node . props . some ( p => p . type === NodeTypes . DIRECTIVE && p . name === 'bind' && ( ! p . arg || p . arg . type !== NodeTypes . SIMPLE_EXPRESSION || ! p . arg . isStatic ) ) ; } export function isText ( node ) { return node . type === NodeTypes . INTERPOLATION || node . type === NodeTypes . TEXT ; } export function isVSlot ( p ) { return p . type === NodeTypes . DIRECTIVE && p . name === 'slot' ; } export function isTemplateNode ( node ) { return ( node . type === NodeTypes . ELEMENT && node . tagType === ElementTypes . TEMPLATE ) ; } export function isSlotOutlet ( node ) { return node . type === NodeTypes . ELEMENT && node . tagType === ElementTypes . SLOT ; } export function getVNodeHelper ( ssr , isComponent ) { return ssr || isComponent ? CREATE_VNODE : CREATE_ELEMENT_VNODE ; } export function getVNodeBlockHelper ( ssr , isComponent ) { return ssr || isComponent ? CREATE_BLOCK : CREATE_ELEMENT_BLOCK ; } const propsHelperSet = new Set ( [ NORMALIZE_PROPS , GUARD_REACTIVE_PROPS ] ) ; function getUnnormalizedProps ( props , callPath = [ ] ) { if ( props && ! isString ( props ) && props . type === NodeTypes . JS_CALL_EXPRESSION ) { const callee = props . callee ; if ( ! isString ( callee ) && propsHelperSet . has ( callee ) ) { return getUnnormalizedProps ( props . arguments [ 0 ] as PropsExpression , callPath . concat ( props ) ) ; } } return [ props , callPath ] ; } export function injectProp ( node , prop , context ) { let propsWithInjection ; const originalProps = node . type === NodeTypes . VNODE_CALL ? node . props : node . arguments [ 2 ] ; /**
     * 1. mergeProps(...)
     * 2. toHandlers(...)
     * 3. normalizeProps(...)
     * 4. normalizeProps(guardReactiveProps(...))
     *
     * we need to get the real props before normalization
     */ let props = originalProps ; let callPath = [ ] ; let parentCall ; if ( props && ! isString ( props ) && props . type === NodeTypes . JS_CALL_EXPRESSION ) { const ret = getUnnormalizedProps ( props ) ; props = ret [ 0 ] ; callPath = ret [ 1 ] ; parentCall = callPath [ callPath . length - 1 ] ; } if ( props == null || isString ( props ) ) { propsWithInjection = createObjectExpression ( [ prop ] ) ; } else if ( props . type === NodeTypes . JS_CALL_EXPRESSION ) { const first = props . arguments [ 0 ] as string | JSChildNode ; if ( ! isString ( first ) && first . type === NodeTypes . JS_OBJECT_EXPRESSION ) { first . properties . unshift ( prop ) ; } else { if ( props . callee === TO_HANDLERS ) { propsWithInjection = createCallExpression ( context . helper ( MERGE_PROPS ) , [ createObjectExpression ( [ prop ] ) , props ] ) ; } else { props . arguments . unshift ( createObjectExpression ( [ prop ] ) ) ; } } ! propsWithInjection && ( propsWithInjection = props ) ; } else if ( props . type === NodeTypes . JS_OBJECT_EXPRESSION ) { let alreadyExists = false ; if ( prop . key . type === NodeTypes . SIMPLE_EXPRESSION ) { const propKeyName = prop . key . content ; alreadyExists = props . properties . some ( p => p . key . type === NodeTypes . SIMPLE_EXPRESSION && p . key . content === propKeyName ) ; } if ( ! alreadyExists ) { props . properties . unshift ( prop ) ; } propsWithInjection = props ; } else { propsWithInjection = createCallExpression ( context . helper ( MERGE_PROPS ) , [ createObjectExpression ( [ prop ] ) , props ] ) ; if ( parentCall && parentCall . callee === GUARD_REACTIVE_PROPS ) { parentCall = callPath [ callPath . length - 2 ] ; } } if ( node . type === NodeTypes . VNODE_CALL ) { if ( parentCall ) { parentCall . arguments [ 0 ] = propsWithInjection ; } else { node . props = propsWithInjection ; } } else { if ( parentCall ) { parentCall . arguments [ 0 ] = propsWithInjection ; } else { node . arguments [ 2 ] = propsWithInjection ; } } } export function toValidAssetId ( name , type ) { return `_${ type }_${ name . replace ( /[^\w]/g , '_' ) }` ; } export function hasScopeRef ( node , ids ) { if ( ! node || Object . keys ( ids ) . length === 0 ) { return false ; } switch ( node . type ) { case NodeTypes . ELEMENT : for ( let i = 0 ; i < node . props . length ; i ++ ) { const p = node . props [ i ] ; if ( p . type === NodeTypes . DIRECTIVE && ( hasScopeRef ( p . arg , ids ) || hasScopeRef ( p . exp , ids ) ) ) { return true ; } } return node . children . some ( c => hasScopeRef ( c , ids ) ) ; case NodeTypes . FOR : if ( hasScopeRef ( node . source , ids ) ) { return true ; } return node . children . some ( c => hasScopeRef ( c , ids ) ) ; case NodeTypes . IF : return node . branches . some ( b => hasScopeRef ( b , ids ) ) ; case NodeTypes . IF_BRANCH : if ( hasScopeRef ( node . condition , ids ) ) { return true ; } return node . children . some ( c => hasScopeRef ( c , ids ) ) ; case NodeTypes . SIMPLE_EXPRESSION : return ( ! node . isStatic && isSimpleIdentifier ( node . content ) && ! ! ids [ node . content ] ) ; case NodeTypes . COMPOUND_EXPRESSION : return node . children . some ( c => isObject ( c ) && hasScopeRef ( c , ids ) ) ; case NodeTypes . INTERPOLATION : case NodeTypes . TEXT_CALL : return hasScopeRef ( node . content , ids ) ; case NodeTypes . TEXT : case NodeTypes . COMMENT : return false ; default : if ( __DEV__ ) { const exhaustiveCheck = node ; exhaustiveCheck ; } return false ; } } export function getMemoedVNodeCall ( node ) { if ( node . type === NodeTypes . JS_CALL_EXPRESSION && node . callee === WITH_MEMO ) { return node . arguments [ 1 ] . returns as VNodeCall ; } else { return node ; } } export function makeBlock ( node , { helper , removeHelper , inSSR } ) { if ( ! node . isBlock ) { node . isBlock = true ; removeHelper ( getVNodeHelper ( inSSR , node . isComponent ) ) ; helper ( OPEN_BLOCK ) ; helper ( getVNodeBlockHelper ( inSSR , node . isComponent ) ) ; } }