aboutsummaryrefslogtreecommitdiff
path: root/node_modules/vue/src/compiler/parser/index.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/vue/src/compiler/parser/index.js')
-rw-r--r--node_modules/vue/src/compiler/parser/index.js675
1 files changed, 675 insertions, 0 deletions
diff --git a/node_modules/vue/src/compiler/parser/index.js b/node_modules/vue/src/compiler/parser/index.js
new file mode 100644
index 00000000..a603b36b
--- /dev/null
+++ b/node_modules/vue/src/compiler/parser/index.js
@@ -0,0 +1,675 @@
+/* @flow */
+
+import he from 'he'
+import { parseHTML } from './html-parser'
+import { parseText } from './text-parser'
+import { parseFilters } from './filter-parser'
+import { genAssignmentCode } from '../directives/model'
+import { extend, cached, no, camelize } from 'shared/util'
+import { isIE, isEdge, isServerRendering } from 'core/util/env'
+
+import {
+ addProp,
+ addAttr,
+ baseWarn,
+ addHandler,
+ addDirective,
+ getBindingAttr,
+ getAndRemoveAttr,
+ pluckModuleFunction
+} from '../helpers'
+
+export const onRE = /^@|^v-on:/
+export const dirRE = /^v-|^@|^:/
+export const forAliasRE = /([^]*?)\s+(?:in|of)\s+([^]*)/
+export const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/
+const stripParensRE = /^\(|\)$/g
+
+const argRE = /:(.*)$/
+export const bindRE = /^:|^v-bind:/
+const modifierRE = /\.[^.]+/g
+
+const decodeHTMLCached = cached(he.decode)
+
+// configurable state
+export let warn: any
+let delimiters
+let transforms
+let preTransforms
+let postTransforms
+let platformIsPreTag
+let platformMustUseProp
+let platformGetTagNamespace
+
+type Attr = { name: string; value: string };
+
+export function createASTElement (
+ tag: string,
+ attrs: Array<Attr>,
+ parent: ASTElement | void
+): ASTElement {
+ return {
+ type: 1,
+ tag,
+ attrsList: attrs,
+ attrsMap: makeAttrsMap(attrs),
+ parent,
+ children: []
+ }
+}
+
+/**
+ * Convert HTML string to AST.
+ */
+export function parse (
+ template: string,
+ options: CompilerOptions
+): ASTElement | void {
+ warn = options.warn || baseWarn
+
+ platformIsPreTag = options.isPreTag || no
+ platformMustUseProp = options.mustUseProp || no
+ platformGetTagNamespace = options.getTagNamespace || no
+
+ transforms = pluckModuleFunction(options.modules, 'transformNode')
+ preTransforms = pluckModuleFunction(options.modules, 'preTransformNode')
+ postTransforms = pluckModuleFunction(options.modules, 'postTransformNode')
+
+ delimiters = options.delimiters
+
+ const stack = []
+ const preserveWhitespace = options.preserveWhitespace !== false
+ let root
+ let currentParent
+ let inVPre = false
+ let inPre = false
+ let warned = false
+
+ function warnOnce (msg) {
+ if (!warned) {
+ warned = true
+ warn(msg)
+ }
+ }
+
+ function closeElement (element) {
+ // check pre state
+ if (element.pre) {
+ inVPre = false
+ }
+ if (platformIsPreTag(element.tag)) {
+ inPre = false
+ }
+ // apply post-transforms
+ for (let i = 0; i < postTransforms.length; i++) {
+ postTransforms[i](element, options)
+ }
+ }
+
+ parseHTML(template, {
+ warn,
+ expectHTML: options.expectHTML,
+ isUnaryTag: options.isUnaryTag,
+ canBeLeftOpenTag: options.canBeLeftOpenTag,
+ shouldDecodeNewlines: options.shouldDecodeNewlines,
+ shouldDecodeNewlinesForHref: options.shouldDecodeNewlinesForHref,
+ shouldKeepComment: options.comments,
+ start (tag, attrs, unary) {
+ // check namespace.
+ // inherit parent ns if there is one
+ const ns = (currentParent && currentParent.ns) || platformGetTagNamespace(tag)
+
+ // handle IE svg bug
+ /* istanbul ignore if */
+ if (isIE && ns === 'svg') {
+ attrs = guardIESVGBug(attrs)
+ }
+
+ let element: ASTElement = createASTElement(tag, attrs, currentParent)
+ if (ns) {
+ element.ns = ns
+ }
+
+ if (isForbiddenTag(element) && !isServerRendering()) {
+ element.forbidden = true
+ process.env.NODE_ENV !== 'production' && warn(
+ 'Templates should only be responsible for mapping the state to the ' +
+ 'UI. Avoid placing tags with side-effects in your templates, such as ' +
+ `<${tag}>` + ', as they will not be parsed.'
+ )
+ }
+
+ // apply pre-transforms
+ for (let i = 0; i < preTransforms.length; i++) {
+ element = preTransforms[i](element, options) || element
+ }
+
+ if (!inVPre) {
+ processPre(element)
+ if (element.pre) {
+ inVPre = true
+ }
+ }
+ if (platformIsPreTag(element.tag)) {
+ inPre = true
+ }
+ if (inVPre) {
+ processRawAttrs(element)
+ } else if (!element.processed) {
+ // structural directives
+ processFor(element)
+ processIf(element)
+ processOnce(element)
+ // element-scope stuff
+ processElement(element, options)
+ }
+
+ function checkRootConstraints (el) {
+ if (process.env.NODE_ENV !== 'production') {
+ if (el.tag === 'slot' || el.tag === 'template') {
+ warnOnce(
+ `Cannot use <${el.tag}> as component root element because it may ` +
+ 'contain multiple nodes.'
+ )
+ }
+ if (el.attrsMap.hasOwnProperty('v-for')) {
+ warnOnce(
+ 'Cannot use v-for on stateful component root element because ' +
+ 'it renders multiple elements.'
+ )
+ }
+ }
+ }
+
+ // tree management
+ if (!root) {
+ root = element
+ checkRootConstraints(root)
+ } else if (!stack.length) {
+ // allow root elements with v-if, v-else-if and v-else
+ if (root.if && (element.elseif || element.else)) {
+ checkRootConstraints(element)
+ addIfCondition(root, {
+ exp: element.elseif,
+ block: element
+ })
+ } else if (process.env.NODE_ENV !== 'production') {
+ warnOnce(
+ `Component template should contain exactly one root element. ` +
+ `If you are using v-if on multiple elements, ` +
+ `use v-else-if to chain them instead.`
+ )
+ }
+ }
+ if (currentParent && !element.forbidden) {
+ if (element.elseif || element.else) {
+ processIfConditions(element, currentParent)
+ } else if (element.slotScope) { // scoped slot
+ currentParent.plain = false
+ const name = element.slotTarget || '"default"'
+ ;(currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element
+ } else {
+ currentParent.children.push(element)
+ element.parent = currentParent
+ }
+ }
+ if (!unary) {
+ currentParent = element
+ stack.push(element)
+ } else {
+ closeElement(element)
+ }
+ },
+
+ end () {
+ // remove trailing whitespace
+ const element = stack[stack.length - 1]
+ const lastNode = element.children[element.children.length - 1]
+ if (lastNode && lastNode.type === 3 && lastNode.text === ' ' && !inPre) {
+ element.children.pop()
+ }
+ // pop stack
+ stack.length -= 1
+ currentParent = stack[stack.length - 1]
+ closeElement(element)
+ },
+
+ chars (text: string) {
+ if (!currentParent) {
+ if (process.env.NODE_ENV !== 'production') {
+ if (text === template) {
+ warnOnce(
+ 'Component template requires a root element, rather than just text.'
+ )
+ } else if ((text = text.trim())) {
+ warnOnce(
+ `text "${text}" outside root element will be ignored.`
+ )
+ }
+ }
+ return
+ }
+ // IE textarea placeholder bug
+ /* istanbul ignore if */
+ if (isIE &&
+ currentParent.tag === 'textarea' &&
+ currentParent.attrsMap.placeholder === text
+ ) {
+ return
+ }
+ const children = currentParent.children
+ text = inPre || text.trim()
+ ? isTextTag(currentParent) ? text : decodeHTMLCached(text)
+ // only preserve whitespace if its not right after a starting tag
+ : preserveWhitespace && children.length ? ' ' : ''
+ if (text) {
+ let res
+ if (!inVPre && text !== ' ' && (res = parseText(text, delimiters))) {
+ children.push({
+ type: 2,
+ expression: res.expression,
+ tokens: res.tokens,
+ text
+ })
+ } else if (text !== ' ' || !children.length || children[children.length - 1].text !== ' ') {
+ children.push({
+ type: 3,
+ text
+ })
+ }
+ }
+ },
+ comment (text: string) {
+ currentParent.children.push({
+ type: 3,
+ text,
+ isComment: true
+ })
+ }
+ })
+ return root
+}
+
+function processPre (el) {
+ if (getAndRemoveAttr(el, 'v-pre') != null) {
+ el.pre = true
+ }
+}
+
+function processRawAttrs (el) {
+ const l = el.attrsList.length
+ if (l) {
+ const attrs = el.attrs = new Array(l)
+ for (let i = 0; i < l; i++) {
+ attrs[i] = {
+ name: el.attrsList[i].name,
+ value: JSON.stringify(el.attrsList[i].value)
+ }
+ }
+ } else if (!el.pre) {
+ // non root node in pre blocks with no attributes
+ el.plain = true
+ }
+}
+
+export function processElement (element: ASTElement, options: CompilerOptions) {
+ processKey(element)
+
+ // determine whether this is a plain element after
+ // removing structural attributes
+ element.plain = !element.key && !element.attrsList.length
+
+ processRef(element)
+ processSlot(element)
+ processComponent(element)
+ for (let i = 0; i < transforms.length; i++) {
+ element = transforms[i](element, options) || element
+ }
+ processAttrs(element)
+}
+
+function processKey (el) {
+ const exp = getBindingAttr(el, 'key')
+ if (exp) {
+ if (process.env.NODE_ENV !== 'production' && el.tag === 'template') {
+ warn(`<template> cannot be keyed. Place the key on real elements instead.`)
+ }
+ el.key = exp
+ }
+}
+
+function processRef (el) {
+ const ref = getBindingAttr(el, 'ref')
+ if (ref) {
+ el.ref = ref
+ el.refInFor = checkInFor(el)
+ }
+}
+
+export function processFor (el: ASTElement) {
+ let exp
+ if ((exp = getAndRemoveAttr(el, 'v-for'))) {
+ const res = parseFor(exp)
+ if (res) {
+ extend(el, res)
+ } else if (process.env.NODE_ENV !== 'production') {
+ warn(
+ `Invalid v-for expression: ${exp}`
+ )
+ }
+ }
+}
+
+type ForParseResult = {
+ for: string;
+ alias: string;
+ iterator1?: string;
+ iterator2?: string;
+};
+
+export function parseFor (exp: string): ?ForParseResult {
+ const inMatch = exp.match(forAliasRE)
+ if (!inMatch) return
+ const res = {}
+ res.for = inMatch[2].trim()
+ const alias = inMatch[1].trim().replace(stripParensRE, '')
+ const iteratorMatch = alias.match(forIteratorRE)
+ if (iteratorMatch) {
+ res.alias = alias.replace(forIteratorRE, '')
+ res.iterator1 = iteratorMatch[1].trim()
+ if (iteratorMatch[2]) {
+ res.iterator2 = iteratorMatch[2].trim()
+ }
+ } else {
+ res.alias = alias
+ }
+ return res
+}
+
+function processIf (el) {
+ const exp = getAndRemoveAttr(el, 'v-if')
+ if (exp) {
+ el.if = exp
+ addIfCondition(el, {
+ exp: exp,
+ block: el
+ })
+ } else {
+ if (getAndRemoveAttr(el, 'v-else') != null) {
+ el.else = true
+ }
+ const elseif = getAndRemoveAttr(el, 'v-else-if')
+ if (elseif) {
+ el.elseif = elseif
+ }
+ }
+}
+
+function processIfConditions (el, parent) {
+ const prev = findPrevElement(parent.children)
+ if (prev && prev.if) {
+ addIfCondition(prev, {
+ exp: el.elseif,
+ block: el
+ })
+ } else if (process.env.NODE_ENV !== 'production') {
+ warn(
+ `v-${el.elseif ? ('else-if="' + el.elseif + '"') : 'else'} ` +
+ `used on element <${el.tag}> without corresponding v-if.`
+ )
+ }
+}
+
+function findPrevElement (children: Array<any>): ASTElement | void {
+ let i = children.length
+ while (i--) {
+ if (children[i].type === 1) {
+ return children[i]
+ } else {
+ if (process.env.NODE_ENV !== 'production' && children[i].text !== ' ') {
+ warn(
+ `text "${children[i].text.trim()}" between v-if and v-else(-if) ` +
+ `will be ignored.`
+ )
+ }
+ children.pop()
+ }
+ }
+}
+
+export function addIfCondition (el: ASTElement, condition: ASTIfCondition) {
+ if (!el.ifConditions) {
+ el.ifConditions = []
+ }
+ el.ifConditions.push(condition)
+}
+
+function processOnce (el) {
+ const once = getAndRemoveAttr(el, 'v-once')
+ if (once != null) {
+ el.once = true
+ }
+}
+
+function processSlot (el) {
+ if (el.tag === 'slot') {
+ el.slotName = getBindingAttr(el, 'name')
+ if (process.env.NODE_ENV !== 'production' && el.key) {
+ warn(
+ `\`key\` does not work on <slot> because slots are abstract outlets ` +
+ `and can possibly expand into multiple elements. ` +
+ `Use the key on a wrapping element instead.`
+ )
+ }
+ } else {
+ let slotScope
+ if (el.tag === 'template') {
+ slotScope = getAndRemoveAttr(el, 'scope')
+ /* istanbul ignore if */
+ if (process.env.NODE_ENV !== 'production' && slotScope) {
+ warn(
+ `the "scope" attribute for scoped slots have been deprecated and ` +
+ `replaced by "slot-scope" since 2.5. The new "slot-scope" attribute ` +
+ `can also be used on plain elements in addition to <template> to ` +
+ `denote scoped slots.`,
+ true
+ )
+ }
+ el.slotScope = slotScope || getAndRemoveAttr(el, 'slot-scope')
+ } else if ((slotScope = getAndRemoveAttr(el, 'slot-scope'))) {
+ /* istanbul ignore if */
+ if (process.env.NODE_ENV !== 'production' && el.attrsMap['v-for']) {
+ warn(
+ `Ambiguous combined usage of slot-scope and v-for on <${el.tag}> ` +
+ `(v-for takes higher priority). Use a wrapper <template> for the ` +
+ `scoped slot to make it clearer.`,
+ true
+ )
+ }
+ el.slotScope = slotScope
+ }
+ const slotTarget = getBindingAttr(el, 'slot')
+ if (slotTarget) {
+ el.slotTarget = slotTarget === '""' ? '"default"' : slotTarget
+ // preserve slot as an attribute for native shadow DOM compat
+ // only for non-scoped slots.
+ if (el.tag !== 'template' && !el.slotScope) {
+ addAttr(el, 'slot', slotTarget)
+ }
+ }
+ }
+}
+
+function processComponent (el) {
+ let binding
+ if ((binding = getBindingAttr(el, 'is'))) {
+ el.component = binding
+ }
+ if (getAndRemoveAttr(el, 'inline-template') != null) {
+ el.inlineTemplate = true
+ }
+}
+
+function processAttrs (el) {
+ const list = el.attrsList
+ let i, l, name, rawName, value, modifiers, isProp
+ for (i = 0, l = list.length; i < l; i++) {
+ name = rawName = list[i].name
+ value = list[i].value
+ if (dirRE.test(name)) {
+ // mark element as dynamic
+ el.hasBindings = true
+ // modifiers
+ modifiers = parseModifiers(name)
+ if (modifiers) {
+ name = name.replace(modifierRE, '')
+ }
+ if (bindRE.test(name)) { // v-bind
+ name = name.replace(bindRE, '')
+ value = parseFilters(value)
+ isProp = false
+ if (modifiers) {
+ if (modifiers.prop) {
+ isProp = true
+ name = camelize(name)
+ if (name === 'innerHtml') name = 'innerHTML'
+ }
+ if (modifiers.camel) {
+ name = camelize(name)
+ }
+ if (modifiers.sync) {
+ addHandler(
+ el,
+ `update:${camelize(name)}`,
+ genAssignmentCode(value, `$event`)
+ )
+ }
+ }
+ if (isProp || (
+ !el.component && platformMustUseProp(el.tag, el.attrsMap.type, name)
+ )) {
+ addProp(el, name, value)
+ } else {
+ addAttr(el, name, value)
+ }
+ } else if (onRE.test(name)) { // v-on
+ name = name.replace(onRE, '')
+ addHandler(el, name, value, modifiers, false, warn)
+ } else { // normal directives
+ name = name.replace(dirRE, '')
+ // parse arg
+ const argMatch = name.match(argRE)
+ const arg = argMatch && argMatch[1]
+ if (arg) {
+ name = name.slice(0, -(arg.length + 1))
+ }
+ addDirective(el, name, rawName, value, arg, modifiers)
+ if (process.env.NODE_ENV !== 'production' && name === 'model') {
+ checkForAliasModel(el, value)
+ }
+ }
+ } else {
+ // literal attribute
+ if (process.env.NODE_ENV !== 'production') {
+ const res = parseText(value, delimiters)
+ if (res) {
+ warn(
+ `${name}="${value}": ` +
+ 'Interpolation inside attributes has been removed. ' +
+ 'Use v-bind or the colon shorthand instead. For example, ' +
+ 'instead of <div id="{{ val }}">, use <div :id="val">.'
+ )
+ }
+ }
+ addAttr(el, name, JSON.stringify(value))
+ // #6887 firefox doesn't update muted state if set via attribute
+ // even immediately after element creation
+ if (!el.component &&
+ name === 'muted' &&
+ platformMustUseProp(el.tag, el.attrsMap.type, name)) {
+ addProp(el, name, 'true')
+ }
+ }
+ }
+}
+
+function checkInFor (el: ASTElement): boolean {
+ let parent = el
+ while (parent) {
+ if (parent.for !== undefined) {
+ return true
+ }
+ parent = parent.parent
+ }
+ return false
+}
+
+function parseModifiers (name: string): Object | void {
+ const match = name.match(modifierRE)
+ if (match) {
+ const ret = {}
+ match.forEach(m => { ret[m.slice(1)] = true })
+ return ret
+ }
+}
+
+function makeAttrsMap (attrs: Array<Object>): Object {
+ const map = {}
+ for (let i = 0, l = attrs.length; i < l; i++) {
+ if (
+ process.env.NODE_ENV !== 'production' &&
+ map[attrs[i].name] && !isIE && !isEdge
+ ) {
+ warn('duplicate attribute: ' + attrs[i].name)
+ }
+ map[attrs[i].name] = attrs[i].value
+ }
+ return map
+}
+
+// for script (e.g. type="x/template") or style, do not decode content
+function isTextTag (el): boolean {
+ return el.tag === 'script' || el.tag === 'style'
+}
+
+function isForbiddenTag (el): boolean {
+ return (
+ el.tag === 'style' ||
+ (el.tag === 'script' && (
+ !el.attrsMap.type ||
+ el.attrsMap.type === 'text/javascript'
+ ))
+ )
+}
+
+const ieNSBug = /^xmlns:NS\d+/
+const ieNSPrefix = /^NS\d+:/
+
+/* istanbul ignore next */
+function guardIESVGBug (attrs) {
+ const res = []
+ for (let i = 0; i < attrs.length; i++) {
+ const attr = attrs[i]
+ if (!ieNSBug.test(attr.name)) {
+ attr.name = attr.name.replace(ieNSPrefix, '')
+ res.push(attr)
+ }
+ }
+ return res
+}
+
+function checkForAliasModel (el, value) {
+ let _el = el
+ while (_el) {
+ if (_el.for && _el.alias === value) {
+ warn(
+ `<${el.tag} v-model="${value}">: ` +
+ `You are binding v-model directly to a v-for iteration alias. ` +
+ `This will not be able to modify the v-for source array because ` +
+ `writing to the alias is like modifying a function local variable. ` +
+ `Consider using an array of objects and use v-model on an object property instead.`
+ )
+ }
+ _el = _el.parent
+ }
+}