| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502 |
- import {
- type BlockCodegenNode,
- type CallExpression,
- type DirectiveNode,
- type ElementNode,
- ElementTypes,
- type ExpressionNode,
- type IfBranchNode,
- type InterpolationNode,
- type JSChildNode,
- type MemoExpression,
- NodeTypes,
- type ObjectExpression,
- type Position,
- type Property,
- type RenderSlotCall,
- type RootNode,
- type SimpleExpressionNode,
- type SlotOutletNode,
- type TemplateChildNode,
- type TemplateNode,
- type TextNode,
- type VNodeCall,
- createCallExpression,
- createObjectExpression,
- } from './ast'
- import type { TransformContext } from './transform'
- import {
- BASE_TRANSITION,
- GUARD_REACTIVE_PROPS,
- KEEP_ALIVE,
- MERGE_PROPS,
- NORMALIZE_PROPS,
- SUSPENSE,
- TELEPORT,
- TO_HANDLERS,
- WITH_MEMO,
- } from './runtimeHelpers'
- import { NOOP, isObject, isString } from '@vue/shared'
- import type { PropsExpression } from './transforms/transformElement'
- import { parseExpression } from '@babel/parser'
- import type { Expression } from '@babel/types'
- import { unwrapTSNode } from './babelUtils'
- export const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>
- p.type === NodeTypes.SIMPLE_EXPRESSION && p.isStatic
- export function isCoreComponent(tag: string): symbol | void {
- switch (tag) {
- case 'Teleport':
- case 'teleport':
- return TELEPORT
- case 'Suspense':
- case 'suspense':
- return SUSPENSE
- case 'KeepAlive':
- case 'keep-alive':
- return KEEP_ALIVE
- case 'BaseTransition':
- case 'base-transition':
- return BASE_TRANSITION
- }
- }
- const nonIdentifierRE = /^\d|[^\$\w]/
- export const isSimpleIdentifier = (name: string): boolean =>
- !nonIdentifierRE.test(name)
- 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 isMemberExpressionBrowser = (path: string): boolean => {
- // remove whitespaces around . or [ first
- path = path.trim().replace(whitespaceRE, s => s.trim())
- let state = MemberExpLexState.inMemberExp
- let stateStack: MemberExpLexState[] = []
- let currentOpenBracketCount = 0
- let currentOpenParensCount = 0
- let currentStringType: "'" | '"' | '`' | null = 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 the exp ends as a call then it should not be considered valid
- 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 const isMemberExpressionNode = __BROWSER__
- ? (NOOP as any as (path: string, context: TransformContext) => boolean)
- : (path: string, context: TransformContext): boolean => {
- try {
- let ret: Expression = parseExpression(path, {
- plugins: context.expressionPlugins,
- })
- ret = unwrapTSNode(ret) as Expression
- return (
- ret.type === 'MemberExpression' ||
- ret.type === 'OptionalMemberExpression' ||
- (ret.type === 'Identifier' && ret.name !== 'undefined')
- )
- } catch (e) {
- return false
- }
- }
- export const isMemberExpression = __BROWSER__
- ? isMemberExpressionBrowser
- : isMemberExpressionNode
- export function advancePositionWithClone(
- pos: Position,
- source: string,
- numberOfCharacters: number = source.length,
- ): Position {
- return advancePositionWithMutation(
- {
- offset: pos.offset,
- line: pos.line,
- column: pos.column,
- },
- source,
- numberOfCharacters,
- )
- }
- // advance by mutation without cloning (for performance reasons), since this
- // gets called a lot in the parser
- export function advancePositionWithMutation(
- pos: Position,
- source: string,
- numberOfCharacters: number = source.length,
- ): Position {
- let linesCount = 0
- let lastNewLinePos = -1
- for (let i = 0; i < numberOfCharacters; i++) {
- if (source.charCodeAt(i) === 10 /* newline char code */) {
- linesCount++
- lastNewLinePos = i
- }
- }
- pos.offset += numberOfCharacters
- pos.line += linesCount
- pos.column =
- lastNewLinePos === -1
- ? pos.column + numberOfCharacters
- : numberOfCharacters - lastNewLinePos
- return pos
- }
- export function assert(condition: boolean, msg?: string) {
- /* istanbul ignore if */
- if (!condition) {
- throw new Error(msg || `unexpected compiler condition`)
- }
- }
- export function findDir(
- node: ElementNode,
- name: string | RegExp,
- allowEmpty: boolean = false,
- ): DirectiveNode | undefined {
- 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: ElementNode,
- name: string,
- dynamicOnly: boolean = false,
- allowEmpty: boolean = false,
- ): ElementNode['props'][0] | undefined {
- 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) &&
- isStaticArgOf(p.arg, name)
- ) {
- return p
- }
- }
- }
- export function isStaticArgOf(
- arg: DirectiveNode['arg'],
- name: string,
- ): boolean {
- return !!(arg && isStaticExp(arg) && arg.content === name)
- }
- export function hasDynamicKeyVBind(node: ElementNode): boolean {
- return node.props.some(
- p =>
- p.type === NodeTypes.DIRECTIVE &&
- p.name === 'bind' &&
- (!p.arg || // v-bind="obj"
- p.arg.type !== NodeTypes.SIMPLE_EXPRESSION || // v-bind:[_ctx.foo]
- !p.arg.isStatic), // v-bind:[foo]
- )
- }
- export function isText(
- node: TemplateChildNode,
- ): node is TextNode | InterpolationNode {
- return node.type === NodeTypes.INTERPOLATION || node.type === NodeTypes.TEXT
- }
- export function isVSlot(p: ElementNode['props'][0]): p is DirectiveNode {
- return p.type === NodeTypes.DIRECTIVE && p.name === 'slot'
- }
- export function isTemplateNode(
- node: RootNode | TemplateChildNode,
- ): node is TemplateNode {
- return (
- node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.TEMPLATE
- )
- }
- export function isSlotOutlet(
- node: RootNode | TemplateChildNode,
- ): node is SlotOutletNode {
- return node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.SLOT
- }
- const propsHelperSet = new Set([NORMALIZE_PROPS, GUARD_REACTIVE_PROPS])
- function getUnnormalizedProps(
- props: PropsExpression | '{}',
- callPath: CallExpression[] = [],
- ): [PropsExpression | '{}', CallExpression[]] {
- 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: VNodeCall | RenderSlotCall,
- prop: Property,
- context: TransformContext,
- ) {
- let propsWithInjection: ObjectExpression | CallExpression | undefined
- /**
- * 1. mergeProps(...)
- * 2. toHandlers(...)
- * 3. normalizeProps(...)
- * 4. normalizeProps(guardReactiveProps(...))
- *
- * we need to get the real props before normalization
- */
- let props =
- node.type === NodeTypes.VNODE_CALL ? node.props : node.arguments[2]
- let callPath: CallExpression[] = []
- let parentCall: CallExpression | undefined
- 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) {
- // merged props... add ours
- // only inject key to object literal if it's the first argument so that
- // if doesn't override user provided keys
- const first = props.arguments[0] as string | JSChildNode
- if (!isString(first) && first.type === NodeTypes.JS_OBJECT_EXPRESSION) {
- // #6631
- if (!hasProp(prop, first)) {
- first.properties.unshift(prop)
- }
- } else {
- if (props.callee === TO_HANDLERS) {
- // #2366
- 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) {
- if (!hasProp(prop, props)) {
- props.properties.unshift(prop)
- }
- propsWithInjection = props
- } else {
- // single v-bind with expression, return a merged replacement
- propsWithInjection = createCallExpression(context.helper(MERGE_PROPS), [
- createObjectExpression([prop]),
- props,
- ])
- // in the case of nested helper call, e.g. `normalizeProps(guardReactiveProps(props))`,
- // it will be rewritten as `normalizeProps(mergeProps({ key: 0 }, props))`,
- // the `guardReactiveProps` will no longer be needed
- 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
- }
- }
- }
- // check existing key to avoid overriding user provided keys
- function hasProp(prop: Property, props: ObjectExpression) {
- let result = false
- if (prop.key.type === NodeTypes.SIMPLE_EXPRESSION) {
- const propKeyName = prop.key.content
- result = props.properties.some(
- p =>
- p.key.type === NodeTypes.SIMPLE_EXPRESSION &&
- p.key.content === propKeyName,
- )
- }
- return result
- }
- export function toValidAssetId(
- name: string,
- type: 'component' | 'directive' | 'filter',
- ): string {
- // see issue#4422, we need adding identifier on validAssetId if variable `name` has specific character
- return `_${type}_${name.replace(/[^\w]/g, (searchValue, replaceValue) => {
- return searchValue === '-' ? '_' : name.charCodeAt(replaceValue).toString()
- })}`
- }
- // Check if a node contains expressions that reference current context scope ids
- export function hasScopeRef(
- node: TemplateChildNode | IfBranchNode | ExpressionNode | undefined,
- ids: TransformContext['identifiers'],
- ): boolean {
- 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: never = node
- exhaustiveCheck
- }
- return false
- }
- }
- export function getMemoedVNodeCall(node: BlockCodegenNode | MemoExpression) {
- if (node.type === NodeTypes.JS_CALL_EXPRESSION && node.callee === WITH_MEMO) {
- return node.arguments[1].returns as VNodeCall
- } else {
- return node
- }
- }
- export const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/
|