diff options
| author | ruki <waruqi@gmail.com> | 2018-11-08 00:38:48 +0800 |
|---|---|---|
| committer | ruki <waruqi@gmail.com> | 2018-11-07 21:53:09 +0800 |
| commit | 26105034da4fcce7ac883c899d781f016559310d (patch) | |
| tree | c459a5dc4e3aa0972d9919033ece511ce76dd129 /node_modules/vue/src/compiler/parser | |
| parent | 2c77f00f1a7ecb6c8192f9c16d3b2001b254a107 (diff) | |
| download | xmake-docs-26105034da4fcce7ac883c899d781f016559310d.tar.gz xmake-docs-26105034da4fcce7ac883c899d781f016559310d.zip | |
switch to vuepress
Diffstat (limited to 'node_modules/vue/src/compiler/parser')
| -rw-r--r-- | node_modules/vue/src/compiler/parser/entity-decoder.js | 11 | ||||
| -rw-r--r-- | node_modules/vue/src/compiler/parser/filter-parser.js | 97 | ||||
| -rw-r--r-- | node_modules/vue/src/compiler/parser/html-parser.js | 310 | ||||
| -rw-r--r-- | node_modules/vue/src/compiler/parser/index.js | 675 | ||||
| -rw-r--r-- | node_modules/vue/src/compiler/parser/text-parser.js | 53 |
5 files changed, 1146 insertions, 0 deletions
diff --git a/node_modules/vue/src/compiler/parser/entity-decoder.js b/node_modules/vue/src/compiler/parser/entity-decoder.js new file mode 100644 index 00000000..2836294f --- /dev/null +++ b/node_modules/vue/src/compiler/parser/entity-decoder.js @@ -0,0 +1,11 @@ +/* @flow */ + +let decoder + +export default { + decode (html: string): string { + decoder = decoder || document.createElement('div') + decoder.innerHTML = html + return decoder.textContent + } +} diff --git a/node_modules/vue/src/compiler/parser/filter-parser.js b/node_modules/vue/src/compiler/parser/filter-parser.js new file mode 100644 index 00000000..10939d3f --- /dev/null +++ b/node_modules/vue/src/compiler/parser/filter-parser.js @@ -0,0 +1,97 @@ +/* @flow */ + +const validDivisionCharRE = /[\w).+\-_$\]]/ + +export function parseFilters (exp: string): string { + let inSingle = false + let inDouble = false + let inTemplateString = false + let inRegex = false + let curly = 0 + let square = 0 + let paren = 0 + let lastFilterIndex = 0 + let c, prev, i, expression, filters + + for (i = 0; i < exp.length; i++) { + prev = c + c = exp.charCodeAt(i) + if (inSingle) { + if (c === 0x27 && prev !== 0x5C) inSingle = false + } else if (inDouble) { + if (c === 0x22 && prev !== 0x5C) inDouble = false + } else if (inTemplateString) { + if (c === 0x60 && prev !== 0x5C) inTemplateString = false + } else if (inRegex) { + if (c === 0x2f && prev !== 0x5C) inRegex = false + } else if ( + c === 0x7C && // pipe + exp.charCodeAt(i + 1) !== 0x7C && + exp.charCodeAt(i - 1) !== 0x7C && + !curly && !square && !paren + ) { + if (expression === undefined) { + // first filter, end of expression + lastFilterIndex = i + 1 + expression = exp.slice(0, i).trim() + } else { + pushFilter() + } + } else { + switch (c) { + case 0x22: inDouble = true; break // " + case 0x27: inSingle = true; break // ' + case 0x60: inTemplateString = true; break // ` + case 0x28: paren++; break // ( + case 0x29: paren--; break // ) + case 0x5B: square++; break // [ + case 0x5D: square--; break // ] + case 0x7B: curly++; break // { + case 0x7D: curly--; break // } + } + if (c === 0x2f) { // / + let j = i - 1 + let p + // find first non-whitespace prev char + for (; j >= 0; j--) { + p = exp.charAt(j) + if (p !== ' ') break + } + if (!p || !validDivisionCharRE.test(p)) { + inRegex = true + } + } + } + } + + if (expression === undefined) { + expression = exp.slice(0, i).trim() + } else if (lastFilterIndex !== 0) { + pushFilter() + } + + function pushFilter () { + (filters || (filters = [])).push(exp.slice(lastFilterIndex, i).trim()) + lastFilterIndex = i + 1 + } + + if (filters) { + for (i = 0; i < filters.length; i++) { + expression = wrapFilter(expression, filters[i]) + } + } + + return expression +} + +function wrapFilter (exp: string, filter: string): string { + const i = filter.indexOf('(') + if (i < 0) { + // _f: resolveFilter + return `_f("${filter}")(${exp})` + } else { + const name = filter.slice(0, i) + const args = filter.slice(i + 1) + return `_f("${name}")(${exp}${args !== ')' ? ',' + args : args}` + } +} diff --git a/node_modules/vue/src/compiler/parser/html-parser.js b/node_modules/vue/src/compiler/parser/html-parser.js new file mode 100644 index 00000000..e2203613 --- /dev/null +++ b/node_modules/vue/src/compiler/parser/html-parser.js @@ -0,0 +1,310 @@ +/** + * Not type-checking this file because it's mostly vendor code. + */ + +/*! + * HTML Parser By John Resig (ejohn.org) + * Modified by Juriy "kangax" Zaytsev + * Original code by Erik Arvidsson, Mozilla Public License + * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js + */ + +import { makeMap, no } from 'shared/util' +import { isNonPhrasingTag } from 'web/compiler/util' + +// Regular Expressions for parsing tags and attributes +const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/ +// could use https://www.w3.org/TR/1999/REC-xml-names-19990114/#NT-QName +// but for Vue templates we can enforce a simple charset +const ncname = '[a-zA-Z_][\\w\\-\\.]*' +const qnameCapture = `((?:${ncname}\\:)?${ncname})` +const startTagOpen = new RegExp(`^<${qnameCapture}`) +const startTagClose = /^\s*(\/?)>/ +const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`) +const doctype = /^<!DOCTYPE [^>]+>/i +// #7298: escape - to avoid being pased as HTML comment when inlined in page +const comment = /^<!\--/ +const conditionalComment = /^<!\[/ + +let IS_REGEX_CAPTURING_BROKEN = false +'x'.replace(/x(.)?/g, function (m, g) { + IS_REGEX_CAPTURING_BROKEN = g === '' +}) + +// Special Elements (can contain anything) +export const isPlainTextElement = makeMap('script,style,textarea', true) +const reCache = {} + +const decodingMap = { + '<': '<', + '>': '>', + '"': '"', + '&': '&', + ' ': '\n', + '	': '\t' +} +const encodedAttr = /&(?:lt|gt|quot|amp);/g +const encodedAttrWithNewLines = /&(?:lt|gt|quot|amp|#10|#9);/g + +// #5992 +const isIgnoreNewlineTag = makeMap('pre,textarea', true) +const shouldIgnoreFirstNewline = (tag, html) => tag && isIgnoreNewlineTag(tag) && html[0] === '\n' + +function decodeAttr (value, shouldDecodeNewlines) { + const re = shouldDecodeNewlines ? encodedAttrWithNewLines : encodedAttr + return value.replace(re, match => decodingMap[match]) +} + +export function parseHTML (html, options) { + const stack = [] + const expectHTML = options.expectHTML + const isUnaryTag = options.isUnaryTag || no + const canBeLeftOpenTag = options.canBeLeftOpenTag || no + let index = 0 + let last, lastTag + while (html) { + last = html + // Make sure we're not in a plaintext content element like script/style + if (!lastTag || !isPlainTextElement(lastTag)) { + let textEnd = html.indexOf('<') + if (textEnd === 0) { + // Comment: + if (comment.test(html)) { + const commentEnd = html.indexOf('-->') + + if (commentEnd >= 0) { + if (options.shouldKeepComment) { + options.comment(html.substring(4, commentEnd)) + } + advance(commentEnd + 3) + continue + } + } + + // http://en.wikipedia.org/wiki/Conditional_comment#Downlevel-revealed_conditional_comment + if (conditionalComment.test(html)) { + const conditionalEnd = html.indexOf(']>') + + if (conditionalEnd >= 0) { + advance(conditionalEnd + 2) + continue + } + } + + // Doctype: + const doctypeMatch = html.match(doctype) + if (doctypeMatch) { + advance(doctypeMatch[0].length) + continue + } + + // End tag: + const endTagMatch = html.match(endTag) + if (endTagMatch) { + const curIndex = index + advance(endTagMatch[0].length) + parseEndTag(endTagMatch[1], curIndex, index) + continue + } + + // Start tag: + const startTagMatch = parseStartTag() + if (startTagMatch) { + handleStartTag(startTagMatch) + if (shouldIgnoreFirstNewline(lastTag, html)) { + advance(1) + } + continue + } + } + + let text, rest, next + if (textEnd >= 0) { + rest = html.slice(textEnd) + while ( + !endTag.test(rest) && + !startTagOpen.test(rest) && + !comment.test(rest) && + !conditionalComment.test(rest) + ) { + // < in plain text, be forgiving and treat it as text + next = rest.indexOf('<', 1) + if (next < 0) break + textEnd += next + rest = html.slice(textEnd) + } + text = html.substring(0, textEnd) + advance(textEnd) + } + + if (textEnd < 0) { + text = html + html = '' + } + + if (options.chars && text) { + options.chars(text) + } + } else { + let endTagLength = 0 + const stackedTag = lastTag.toLowerCase() + const reStackedTag = reCache[stackedTag] || (reCache[stackedTag] = new RegExp('([\\s\\S]*?)(</' + stackedTag + '[^>]*>)', 'i')) + const rest = html.replace(reStackedTag, function (all, text, endTag) { + endTagLength = endTag.length + if (!isPlainTextElement(stackedTag) && stackedTag !== 'noscript') { + text = text + .replace(/<!\--([\s\S]*?)-->/g, '$1') // #7298 + .replace(/<!\[CDATA\[([\s\S]*?)]]>/g, '$1') + } + if (shouldIgnoreFirstNewline(stackedTag, text)) { + text = text.slice(1) + } + if (options.chars) { + options.chars(text) + } + return '' + }) + index += html.length - rest.length + html = rest + parseEndTag(stackedTag, index - endTagLength, index) + } + + if (html === last) { + options.chars && options.chars(html) + if (process.env.NODE_ENV !== 'production' && !stack.length && options.warn) { + options.warn(`Mal-formatted tag at end of template: "${html}"`) + } + break + } + } + + // Clean up any remaining tags + parseEndTag() + + function advance (n) { + index += n + html = html.substring(n) + } + + function parseStartTag () { + const start = html.match(startTagOpen) + if (start) { + const match = { + tagName: start[1], + attrs: [], + start: index + } + advance(start[0].length) + let end, attr + while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) { + advance(attr[0].length) + match.attrs.push(attr) + } + if (end) { + match.unarySlash = end[1] + advance(end[0].length) + match.end = index + return match + } + } + } + + function handleStartTag (match) { + const tagName = match.tagName + const unarySlash = match.unarySlash + + if (expectHTML) { + if (lastTag === 'p' && isNonPhrasingTag(tagName)) { + parseEndTag(lastTag) + } + if (canBeLeftOpenTag(tagName) && lastTag === tagName) { + parseEndTag(tagName) + } + } + + const unary = isUnaryTag(tagName) || !!unarySlash + + const l = match.attrs.length + const attrs = new Array(l) + for (let i = 0; i < l; i++) { + const args = match.attrs[i] + // hackish work around FF bug https://bugzilla.mozilla.org/show_bug.cgi?id=369778 + if (IS_REGEX_CAPTURING_BROKEN && args[0].indexOf('""') === -1) { + if (args[3] === '') { delete args[3] } + if (args[4] === '') { delete args[4] } + if (args[5] === '') { delete args[5] } + } + const value = args[3] || args[4] || args[5] || '' + const shouldDecodeNewlines = tagName === 'a' && args[1] === 'href' + ? options.shouldDecodeNewlinesForHref + : options.shouldDecodeNewlines + attrs[i] = { + name: args[1], + value: decodeAttr(value, shouldDecodeNewlines) + } + } + + if (!unary) { + stack.push({ tag: tagName, lowerCasedTag: tagName.toLowerCase(), attrs: attrs }) + lastTag = tagName + } + + if (options.start) { + options.start(tagName, attrs, unary, match.start, match.end) + } + } + + function parseEndTag (tagName, start, end) { + let pos, lowerCasedTagName + if (start == null) start = index + if (end == null) end = index + + if (tagName) { + lowerCasedTagName = tagName.toLowerCase() + } + + // Find the closest opened tag of the same type + if (tagName) { + for (pos = stack.length - 1; pos >= 0; pos--) { + if (stack[pos].lowerCasedTag === lowerCasedTagName) { + break + } + } + } else { + // If no tag name is provided, clean shop + pos = 0 + } + + if (pos >= 0) { + // Close all the open elements, up the stack + for (let i = stack.length - 1; i >= pos; i--) { + if (process.env.NODE_ENV !== 'production' && + (i > pos || !tagName) && + options.warn + ) { + options.warn( + `tag <${stack[i].tag}> has no matching end tag.` + ) + } + if (options.end) { + options.end(stack[i].tag, start, end) + } + } + + // Remove the open elements from the stack + stack.length = pos + lastTag = pos && stack[pos - 1].tag + } else if (lowerCasedTagName === 'br') { + if (options.start) { + options.start(tagName, [], true, start, end) + } + } else if (lowerCasedTagName === 'p') { + if (options.start) { + options.start(tagName, [], false, start, end) + } + if (options.end) { + options.end(tagName, start, end) + } + } + } +} 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 + } +} diff --git a/node_modules/vue/src/compiler/parser/text-parser.js b/node_modules/vue/src/compiler/parser/text-parser.js new file mode 100644 index 00000000..284fca33 --- /dev/null +++ b/node_modules/vue/src/compiler/parser/text-parser.js @@ -0,0 +1,53 @@ +/* @flow */ + +import { cached } from 'shared/util' +import { parseFilters } from './filter-parser' + +const defaultTagRE = /\{\{((?:.|\n)+?)\}\}/g +const regexEscapeRE = /[-.*+?^${}()|[\]\/\\]/g + +const buildRegex = cached(delimiters => { + const open = delimiters[0].replace(regexEscapeRE, '\\$&') + const close = delimiters[1].replace(regexEscapeRE, '\\$&') + return new RegExp(open + '((?:.|\\n)+?)' + close, 'g') +}) + +type TextParseResult = { + expression: string, + tokens: Array<string | { '@binding': string }> +} + +export function parseText ( + text: string, + delimiters?: [string, string] +): TextParseResult | void { + const tagRE = delimiters ? buildRegex(delimiters) : defaultTagRE + if (!tagRE.test(text)) { + return + } + const tokens = [] + const rawTokens = [] + let lastIndex = tagRE.lastIndex = 0 + let match, index, tokenValue + while ((match = tagRE.exec(text))) { + index = match.index + // push text token + if (index > lastIndex) { + rawTokens.push(tokenValue = text.slice(lastIndex, index)) + tokens.push(JSON.stringify(tokenValue)) + } + // tag token + const exp = parseFilters(match[1].trim()) + tokens.push(`_s(${exp})`) + rawTokens.push({ '@binding': exp }) + lastIndex = index + match[0].length + } + if (lastIndex < text.length) { + rawTokens.push(tokenValue = text.slice(lastIndex)) + tokens.push(JSON.stringify(tokenValue)) + } + return { + expression: tokens.join('+'), + tokens: rawTokens + } +} |
