'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var shared = require('@vue/shared'); var sourceMap = require('source-map'); var parser = require('@babel/parser'); var estreeWalker = require('estree-walker'); function defaultOnError(error) { throw error; } function createCompilerError(code, loc, messages, additionalMessage) { const msg = (messages || errorMessages)[code] + (additionalMessage || ``) ; const error = new SyntaxError(String(msg)); error.code = code; error.loc = loc; return error; } const errorMessages = { // parse errors [0 /* ABRUPT_CLOSING_OF_EMPTY_COMMENT */]: 'Illegal comment.', [1 /* CDATA_IN_HTML_CONTENT */]: 'CDATA section is allowed only in XML context.', [2 /* DUPLICATE_ATTRIBUTE */]: 'Duplicate attribute.', [3 /* END_TAG_WITH_ATTRIBUTES */]: 'End tag cannot have attributes.', [4 /* END_TAG_WITH_TRAILING_SOLIDUS */]: "Illegal '/' in tags.", [5 /* EOF_BEFORE_TAG_NAME */]: 'Unexpected EOF in tag.', [6 /* EOF_IN_CDATA */]: 'Unexpected EOF in CDATA section.', [7 /* EOF_IN_COMMENT */]: 'Unexpected EOF in comment.', [8 /* EOF_IN_SCRIPT_HTML_COMMENT_LIKE_TEXT */]: 'Unexpected EOF in script.', [9 /* EOF_IN_TAG */]: 'Unexpected EOF in tag.', [10 /* INCORRECTLY_CLOSED_COMMENT */]: 'Incorrectly closed comment.', [11 /* INCORRECTLY_OPENED_COMMENT */]: 'Incorrectly opened comment.', [12 /* INVALID_FIRST_CHARACTER_OF_TAG_NAME */]: "Illegal tag name. Use '<' to print '<'.", [13 /* MISSING_ATTRIBUTE_VALUE */]: 'Attribute value was expected.', [14 /* MISSING_END_TAG_NAME */]: 'End tag name was expected.', [15 /* MISSING_WHITESPACE_BETWEEN_ATTRIBUTES */]: 'Whitespace was expected.', [16 /* NESTED_COMMENT */]: "Unexpected ' isRef(x) ? x.value = y : x = y const rVal = parent.right; const rExp = rawExp.slice(rVal.start - 1, rVal.end - 1); const rExpString = stringifyExpression(processExpression(createSimpleExpression(rExp, false), context)); return `${context.helperString(IS_REF)}(${raw})${context.isTS ? ` //@ts-ignore\n` : ``} ? ${raw}.value = ${rExpString} : ${raw}`; } else if (isUpdateArg) { // make id replace parent in the code range so the raw update operator // is removed id.start = parent.start; id.end = parent.end; const { prefix: isPrefix, operator } = parent; const prefix = isPrefix ? operator : ``; const postfix = isPrefix ? `` : operator; // let binding. // x++ --> isRef(a) ? a.value++ : a++ return `${context.helperString(IS_REF)}(${raw})${context.isTS ? ` //@ts-ignore\n` : ``} ? ${prefix}${raw}.value${postfix} : ${prefix}${raw}${postfix}`; } else if (isDestructureAssignment) { // TODO // let binding in a destructure assignment - it's very tricky to // handle both possible cases here without altering the original // structure of the code, so we just assume it's not a ref here // for now return raw; } else { return `${context.helperString(UNREF)}(${raw})`; } } else if (type === "props" /* PROPS */) { // use __props which is generated by compileScript so in ts mode // it gets correct type return `__props.${raw}`; } } else { if (type && type.startsWith('setup')) { // setup bindings in non-inline mode return `$setup.${raw}`; } else if (type) { return `$${type}.${raw}`; } } // fallback to ctx return `_ctx.${raw}`; }; // fast path if expression is a simple identifier. const rawExp = node.content; // bail constant on parens (function invocation) and dot (member access) const bailConstant = rawExp.indexOf(`(`) > -1 || rawExp.indexOf('.') > 0; if (isSimpleIdentifier(rawExp)) { const isScopeVarReference = context.identifiers[rawExp]; const isAllowedGlobal = shared.isGloballyWhitelisted(rawExp); const isLiteral = isLiteralWhitelisted(rawExp); if (!asParams && !isScopeVarReference && !isAllowedGlobal && !isLiteral) { // const bindings exposed from setup can be skipped for patching but // cannot be hoisted to module scope if (bindingMetadata[node.content] === "setup-const" /* SETUP_CONST */) { node.constType = 1 /* CAN_SKIP_PATCH */; } node.content = rewriteIdentifier(rawExp); } else if (!isScopeVarReference) { if (isLiteral) { node.constType = 3 /* CAN_STRINGIFY */; } else { node.constType = 2 /* CAN_HOIST */; } } return node; } let ast; // exp needs to be parsed differently: // 1. Multiple inline statements (v-on, with presence of `;`): parse as raw // exp, but make sure to pad with spaces for consistent ranges // 2. Expressions: wrap with parens (for e.g. object expressions) // 3. Function arguments (v-for, v-slot): place in a function argument position const source = asRawStatements ? ` ${rawExp} ` : `(${rawExp})${asParams ? `=>{}` : ``}`; try { ast = parser.parse(source, { plugins: [...context.expressionPlugins, ...shared.babelParserDefaultPlugins] }).program; } catch (e) { context.onError(createCompilerError(43 /* X_INVALID_EXPRESSION */, node.loc, undefined, e.message)); return node; } const ids = []; const knownIds = Object.create(context.identifiers); const isDuplicate = (node) => ids.some(id => id.start === node.start); const parentStack = []; estreeWalker.walk(ast, { enter(node, parent) { parent && parentStack.push(parent); if (node.type === 'Identifier') { if (!isDuplicate(node)) { const needPrefix = shouldPrefix(node, parent, parentStack); if (!knownIds[node.name] && needPrefix) { if (isStaticProperty(parent) && parent.shorthand) { // property shorthand like { foo }, we need to add the key since // we rewrite the value node.prefix = `${node.name}: `; } node.name = rewriteIdentifier(node.name, parent, node); ids.push(node); } else if (!isStaticPropertyKey(node, parent)) { // The identifier is considered constant unless it's pointing to a // scope variable (a v-for alias, or a v-slot prop) if (!(needPrefix && knownIds[node.name]) && !bailConstant) { node.isConstant = true; } // also generate sub-expressions for other identifiers for better // source map support. (except for property keys which are static) ids.push(node); } } } else if (isFunction(node)) { // walk function expressions and add its arguments to known identifiers // so that we don't prefix them node.params.forEach(p => estreeWalker.walk(p, { enter(child, parent) { if (child.type === 'Identifier' && // do not record as scope variable if is a destructured key !isStaticPropertyKey(child, parent) && // do not record if this is a default value // assignment of a destructured variable !(parent && parent.type === 'AssignmentPattern' && parent.right === child)) { const { name } = child; if (node.scopeIds && node.scopeIds.has(name)) { return; } if (name in knownIds) { knownIds[name]++; } else { knownIds[name] = 1; } (node.scopeIds || (node.scopeIds = new Set())).add(name); } } })); } }, leave(node, parent) { parent && parentStack.pop(); if (node !== ast.body[0].expression && node.scopeIds) { node.scopeIds.forEach((id) => { knownIds[id]--; if (knownIds[id] === 0) { delete knownIds[id]; } }); } } }); // We break up the compound expression into an array of strings and sub // expressions (for identifiers that have been prefixed). In codegen, if // an ExpressionNode has the `.children` property, it will be used instead of // `.content`. const children = []; ids.sort((a, b) => a.start - b.start); ids.forEach((id, i) => { // range is offset by -1 due to the wrapping parens when parsed const start = id.start - 1; const end = id.end - 1; const last = ids[i - 1]; const leadingText = rawExp.slice(last ? last.end - 1 : 0, start); if (leadingText.length || id.prefix) { children.push(leadingText + (id.prefix || ``)); } const source = rawExp.slice(start, end); children.push(createSimpleExpression(id.name, false, { source, start: advancePositionWithClone(node.loc.start, source, start), end: advancePositionWithClone(node.loc.start, source, end) }, id.isConstant ? 3 /* CAN_STRINGIFY */ : 0 /* NOT_CONSTANT */)); if (i === ids.length - 1 && end < rawExp.length) { children.push(rawExp.slice(end)); } }); let ret; if (children.length) { ret = createCompoundExpression(children, node.loc); } else { ret = node; ret.constType = bailConstant ? 0 /* NOT_CONSTANT */ : 3 /* CAN_STRINGIFY */; } ret.identifiers = Object.keys(knownIds); return ret; } const isFunction = (node) => { return /Function(?:Expression|Declaration)$|Method$/.test(node.type); }; const isStaticProperty = (node) => node && (node.type === 'ObjectProperty' || node.type === 'ObjectMethod') && !node.computed; const isStaticPropertyKey = (node, parent) => isStaticProperty(parent) && parent.key === node; function shouldPrefix(id, parent, parentStack) { // declaration id if ((parent.type === 'VariableDeclarator' || parent.type === 'ClassDeclaration') && parent.id === id) { return false; } if (isFunction(parent)) { // function decalration/expression id if (parent.id === id) { return false; } // params list if (parent.params.includes(id)) { return false; } } // property key // this also covers object destructure pattern if (isStaticPropertyKey(id, parent)) { return false; } // non-assignment array destructure pattern if (parent.type === 'ArrayPattern' && !isInDestructureAssignment(parent, parentStack)) { return false; } // member expression property if ((parent.type === 'MemberExpression' || parent.type === 'OptionalMemberExpression') && parent.property === id && !parent.computed) { return false; } // is a special keyword but parsed as identifier if (id.name === 'arguments') { return false; } // skip whitelisted globals if (shared.isGloballyWhitelisted(id.name)) { return false; } // special case for webpack compilation if (id.name === 'require') { return false; } return true; } function isInDestructureAssignment(parent, parentStack) { if (parent && (parent.type === 'ObjectProperty' || parent.type === 'ArrayPattern')) { let i = parentStack.length; while (i--) { const p = parentStack[i]; if (p.type === 'AssignmentExpression') { return true; } else if (p.type !== 'ObjectProperty' && !p.type.endsWith('Pattern')) { break; } } } return false; } function stringifyExpression(exp) { if (shared.isString(exp)) { return exp; } else if (exp.type === 4 /* SIMPLE_EXPRESSION */) { return exp.content; } else { return exp.children .map(stringifyExpression) .join(''); } } const transformIf = createStructuralDirectiveTransform(/^(if|else|else-if)$/, (node, dir, context) => { return processIf(node, dir, context, (ifNode, branch, isRoot) => { // #1587: We need to dynamically increment the key based on the current // node's sibling nodes, since chained v-if/else branches are // rendered at the same depth const siblings = context.parent.children; let i = siblings.indexOf(ifNode); let key = 0; while (i-- >= 0) { const sibling = siblings[i]; if (sibling && sibling.type === 9 /* IF */) { key += sibling.branches.length; } } // Exit callback. Complete the codegenNode when all children have been // transformed. return () => { if (isRoot) { ifNode.codegenNode = createCodegenNodeForBranch(branch, key, context); } else { // attach this branch's codegen node to the v-if root. const parentCondition = getParentCondition(ifNode.codegenNode); parentCondition.alternate = createCodegenNodeForBranch(branch, key + ifNode.branches.length - 1, context); } }; }); }); // target-agnostic transform used for both Client and SSR function processIf(node, dir, context, processCodegen) { if (dir.name !== 'else' && (!dir.exp || !dir.exp.content.trim())) { const loc = dir.exp ? dir.exp.loc : node.loc; context.onError(createCompilerError(27 /* X_V_IF_NO_EXPRESSION */, dir.loc)); dir.exp = createSimpleExpression(`true`, false, loc); } if (context.prefixIdentifiers && dir.exp) { // dir.exp can only be simple expression because vIf transform is applied // before expression transform. dir.exp = processExpression(dir.exp, context); } if (dir.name === 'if') { const branch = createIfBranch(node, dir); const ifNode = { type: 9 /* IF */, loc: node.loc, branches: [branch] }; context.replaceNode(ifNode); if (processCodegen) { return processCodegen(ifNode, branch, true); } } else { // locate the adjacent v-if const siblings = context.parent.children; const comments = []; let i = siblings.indexOf(node); while (i-- >= -1) { const sibling = siblings[i]; if (sibling && sibling.type === 3 /* COMMENT */) { context.removeNode(sibling); comments.unshift(sibling); continue; } if (sibling && sibling.type === 2 /* TEXT */ && !sibling.content.trim().length) { context.removeNode(sibling); continue; } if (sibling && sibling.type === 9 /* IF */) { // move the node to the if node's branches context.removeNode(); const branch = createIfBranch(node, dir); if (comments.length) { branch.children = [...comments, ...branch.children]; } // check if user is forcing same key on different branches { const key = branch.userKey; if (key) { sibling.branches.forEach(({ userKey }) => { if (isSameKey(userKey, key)) { context.onError(createCompilerError(28 /* X_V_IF_SAME_KEY */, branch.userKey.loc)); } }); } } sibling.branches.push(branch); const onExit = processCodegen && processCodegen(sibling, branch, false); // since the branch was removed, it will not be traversed. // make sure to traverse here. traverseNode(branch, context); // call on exit if (onExit) onExit(); // make sure to reset currentNode after traversal to indicate this // node has been removed. context.currentNode = null; } else { context.onError(createCompilerError(29 /* X_V_ELSE_NO_ADJACENT_IF */, node.loc)); } break; } } } function createIfBranch(node, dir) { return { type: 10 /* IF_BRANCH */, loc: node.loc, condition: dir.name === 'else' ? undefined : dir.exp, children: node.tagType === 3 /* TEMPLATE */ && !findDir(node, 'for') ? node.children : [node], userKey: findProp(node, `key`) }; } function createCodegenNodeForBranch(branch, keyIndex, context) { if (branch.condition) { return createConditionalExpression(branch.condition, createChildrenCodegenNode(branch, keyIndex, context), // make sure to pass in asBlock: true so that the comment node call // closes the current block. createCallExpression(context.helper(CREATE_COMMENT), [ '"v-if"' , 'true' ])); } else { return createChildrenCodegenNode(branch, keyIndex, context); } } function createChildrenCodegenNode(branch, keyIndex, context) { const { helper } = context; const keyProperty = createObjectProperty(`key`, createSimpleExpression(`${keyIndex}`, false, locStub, 2 /* CAN_HOIST */)); const { children } = branch; const firstChild = children[0]; const needFragmentWrapper = children.length !== 1 || firstChild.type !== 1 /* ELEMENT */; if (needFragmentWrapper) { if (children.length === 1 && firstChild.type === 11 /* FOR */) { // optimize away nested fragments when child is a ForNode const vnodeCall = firstChild.codegenNode; injectProp(vnodeCall, keyProperty, context); return vnodeCall; } else { return createVNodeCall(context, helper(FRAGMENT), createObjectExpression([keyProperty]), children, 64 /* STABLE_FRAGMENT */ + (` /* ${shared.PatchFlagNames[64 /* STABLE_FRAGMENT */]} */` ), undefined, undefined, true, false, branch.loc); } } else { const vnodeCall = firstChild .codegenNode; // Change createVNode to createBlock. if (vnodeCall.type === 13 /* VNODE_CALL */) { vnodeCall.isBlock = true; helper(OPEN_BLOCK); helper(CREATE_BLOCK); } // inject branch key injectProp(vnodeCall, keyProperty, context); return vnodeCall; } } function isSameKey(a, b) { if (!a || a.type !== b.type) { return false; } if (a.type === 6 /* ATTRIBUTE */) { if (a.value.content !== b.value.content) { return false; } } else { // directive const exp = a.exp; const branchExp = b.exp; if (exp.type !== branchExp.type) { return false; } if (exp.type !== 4 /* SIMPLE_EXPRESSION */ || (exp.isStatic !== branchExp.isStatic || exp.content !== branchExp.content)) { return false; } } return true; } function getParentCondition(node) { while (true) { if (node.type === 19 /* JS_CONDITIONAL_EXPRESSION */) { if (node.alternate.type === 19 /* JS_CONDITIONAL_EXPRESSION */) { node = node.alternate; } else { return node; } } else if (node.type === 20 /* JS_CACHE_EXPRESSION */) { node = node.value; } } } const transformFor = createStructuralDirectiveTransform('for', (node, dir, context) => { const { helper } = context; return processFor(node, dir, context, forNode => { // create the loop render function expression now, and add the // iterator on exit after all children have been traversed const renderExp = createCallExpression(helper(RENDER_LIST), [ forNode.source ]); const keyProp = findProp(node, `key`); const keyProperty = keyProp ? createObjectProperty(`key`, keyProp.type === 6 /* ATTRIBUTE */ ? createSimpleExpression(keyProp.value.content, true) : keyProp.exp) : null; if (context.prefixIdentifiers && keyProperty) { // #2085 process :key expression needs to be processed in order for it // to behave consistently for