UNPKG

27.9 kBJavaScriptView Raw
1/* @flow */
2
3import he from 'he'
4import { parseHTML } from './html-parser'
5import { parseText } from './text-parser'
6import { parseFilters } from './filter-parser'
7import { genAssignmentCode } from '../directives/model'
8import { extend, cached, no, camelize, hyphenate } from 'shared/util'
9import { isIE, isEdge, isServerRendering } from 'core/util/env'
10
11import {
12 addProp,
13 addAttr,
14 baseWarn,
15 addHandler,
16 addDirective,
17 getBindingAttr,
18 getAndRemoveAttr,
19 getRawBindingAttr,
20 pluckModuleFunction,
21 getAndRemoveAttrByRegex
22} from '../helpers'
23
24export const onRE = /^@|^v-on:/
25export const dirRE = process.env.VBIND_PROP_SHORTHAND
26 ? /^v-|^@|^:|^\./
27 : /^v-|^@|^:/
28export const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/
29export const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/
30const stripParensRE = /^\(|\)$/g
31const dynamicArgRE = /^\[.*\]$/
32
33const argRE = /:(.*)$/
34export const bindRE = /^:|^\.|^v-bind:/
35const propBindRE = /^\./
36const modifierRE = /\.[^.]+/g
37
38const slotRE = /^v-slot(:|$)|^#/
39
40const lineBreakRE = /[\r\n]/
41const whitespaceRE = /\s+/g
42
43const invalidAttributeRE = /[\s"'<>\/=]/
44
45const decodeHTMLCached = cached(he.decode)
46
47export const emptySlotScopeToken = `_empty_`
48
49// configurable state
50export let warn: any
51let delimiters
52let transforms
53let preTransforms
54let postTransforms
55let platformIsPreTag
56let platformMustUseProp
57let platformGetTagNamespace
58let maybeComponent
59
60export function createASTElement (
61 tag: string,
62 attrs: Array<ASTAttr>,
63 parent: ASTElement | void
64): ASTElement {
65 return {
66 type: 1,
67 tag,
68 attrsList: attrs,
69 attrsMap: makeAttrsMap(attrs),
70 rawAttrsMap: {},
71 parent,
72 children: []
73 }
74}
75
76/**
77 * Convert HTML string to AST.
78 */
79export function parse (
80 template: string,
81 options: CompilerOptions
82): ASTElement | void {
83 warn = options.warn || baseWarn
84
85 platformIsPreTag = options.isPreTag || no
86 platformMustUseProp = options.mustUseProp || no
87 platformGetTagNamespace = options.getTagNamespace || no
88 const isReservedTag = options.isReservedTag || no
89 maybeComponent = (el: ASTElement) => !!el.component || !isReservedTag(el.tag)
90
91 transforms = pluckModuleFunction(options.modules, 'transformNode')
92 preTransforms = pluckModuleFunction(options.modules, 'preTransformNode')
93 postTransforms = pluckModuleFunction(options.modules, 'postTransformNode')
94
95 delimiters = options.delimiters
96
97 const stack = []
98 const preserveWhitespace = options.preserveWhitespace !== false
99 const whitespaceOption = options.whitespace
100 let root
101 let currentParent
102 let inVPre = false
103 let inPre = false
104 let warned = false
105
106 function warnOnce (msg, range) {
107 if (!warned) {
108 warned = true
109 warn(msg, range)
110 }
111 }
112
113 function closeElement (element) {
114 trimEndingWhitespace(element)
115 if (!inVPre && !element.processed) {
116 element = processElement(element, options)
117 }
118 // tree management
119 if (!stack.length && element !== root) {
120 // allow root elements with v-if, v-else-if and v-else
121 if (root.if && (element.elseif || element.else)) {
122 if (process.env.NODE_ENV !== 'production') {
123 checkRootConstraints(element)
124 }
125 addIfCondition(root, {
126 exp: element.elseif,
127 block: element
128 })
129 } else if (process.env.NODE_ENV !== 'production') {
130 warnOnce(
131 `Component template should contain exactly one root element. ` +
132 `If you are using v-if on multiple elements, ` +
133 `use v-else-if to chain them instead.`,
134 { start: element.start }
135 )
136 }
137 }
138 if (currentParent && !element.forbidden) {
139 if (element.elseif || element.else) {
140 processIfConditions(element, currentParent)
141 } else {
142 if (element.slotScope) {
143 // scoped slot
144 // keep it in the children list so that v-else(-if) conditions can
145 // find it as the prev node.
146 const name = element.slotTarget || '"default"'
147 ;(currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element
148 }
149 currentParent.children.push(element)
150 element.parent = currentParent
151 }
152 }
153
154 // final children cleanup
155 // filter out scoped slots
156 element.children = element.children.filter(c => !(c: any).slotScope)
157 // remove trailing whitespace node again
158 trimEndingWhitespace(element)
159
160 // check pre state
161 if (element.pre) {
162 inVPre = false
163 }
164 if (platformIsPreTag(element.tag)) {
165 inPre = false
166 }
167 // apply post-transforms
168 for (let i = 0; i < postTransforms.length; i++) {
169 postTransforms[i](element, options)
170 }
171 }
172
173 function trimEndingWhitespace (el) {
174 // remove trailing whitespace node
175 if (!inPre) {
176 let lastNode
177 while (
178 (lastNode = el.children[el.children.length - 1]) &&
179 lastNode.type === 3 &&
180 lastNode.text === ' '
181 ) {
182 el.children.pop()
183 }
184 }
185 }
186
187 function checkRootConstraints (el) {
188 if (el.tag === 'slot' || el.tag === 'template') {
189 warnOnce(
190 `Cannot use <${el.tag}> as component root element because it may ` +
191 'contain multiple nodes.',
192 { start: el.start }
193 )
194 }
195 if (el.attrsMap.hasOwnProperty('v-for')) {
196 warnOnce(
197 'Cannot use v-for on stateful component root element because ' +
198 'it renders multiple elements.',
199 el.rawAttrsMap['v-for']
200 )
201 }
202 }
203
204 parseHTML(template, {
205 warn,
206 expectHTML: options.expectHTML,
207 isUnaryTag: options.isUnaryTag,
208 canBeLeftOpenTag: options.canBeLeftOpenTag,
209 shouldDecodeNewlines: options.shouldDecodeNewlines,
210 shouldDecodeNewlinesForHref: options.shouldDecodeNewlinesForHref,
211 shouldKeepComment: options.comments,
212 outputSourceRange: options.outputSourceRange,
213 start (tag, attrs, unary, start) {
214 // check namespace.
215 // inherit parent ns if there is one
216 const ns = (currentParent && currentParent.ns) || platformGetTagNamespace(tag)
217
218 // handle IE svg bug
219 /* istanbul ignore if */
220 if (isIE && ns === 'svg') {
221 attrs = guardIESVGBug(attrs)
222 }
223
224 let element: ASTElement = createASTElement(tag, attrs, currentParent)
225 if (ns) {
226 element.ns = ns
227 }
228
229 if (process.env.NODE_ENV !== 'production') {
230 if (options.outputSourceRange) {
231 element.start = start
232 element.rawAttrsMap = element.attrsList.reduce((cumulated, attr) => {
233 cumulated[attr.name] = attr
234 return cumulated
235 }, {})
236 }
237 attrs.forEach(attr => {
238 if (invalidAttributeRE.test(attr.name)) {
239 warn(
240 `Invalid dynamic argument expression: attribute names cannot contain ` +
241 `spaces, quotes, <, >, / or =.`,
242 {
243 start: attr.start + attr.name.indexOf(`[`),
244 end: attr.start + attr.name.length
245 }
246 )
247 }
248 })
249 }
250
251 if (isForbiddenTag(element) && !isServerRendering()) {
252 element.forbidden = true
253 process.env.NODE_ENV !== 'production' && warn(
254 'Templates should only be responsible for mapping the state to the ' +
255 'UI. Avoid placing tags with side-effects in your templates, such as ' +
256 `<${tag}>` + ', as they will not be parsed.',
257 { start: element.start }
258 )
259 }
260
261 // apply pre-transforms
262 for (let i = 0; i < preTransforms.length; i++) {
263 element = preTransforms[i](element, options) || element
264 }
265
266 if (!inVPre) {
267 processPre(element)
268 if (element.pre) {
269 inVPre = true
270 }
271 }
272 if (platformIsPreTag(element.tag)) {
273 inPre = true
274 }
275 if (inVPre) {
276 processRawAttrs(element)
277 } else if (!element.processed) {
278 // structural directives
279 processFor(element)
280 processIf(element)
281 processOnce(element)
282 }
283
284 if (!root) {
285 root = element
286 if (process.env.NODE_ENV !== 'production') {
287 checkRootConstraints(root)
288 }
289 }
290
291 if (!unary) {
292 currentParent = element
293 stack.push(element)
294 } else {
295 closeElement(element)
296 }
297 },
298
299 end (tag, start, end) {
300 const element = stack[stack.length - 1]
301 // pop stack
302 stack.length -= 1
303 currentParent = stack[stack.length - 1]
304 if (process.env.NODE_ENV !== 'production' && options.outputSourceRange) {
305 element.end = end
306 }
307 closeElement(element)
308 },
309
310 chars (text: string, start: number, end: number) {
311 if (!currentParent) {
312 if (process.env.NODE_ENV !== 'production') {
313 if (text === template) {
314 warnOnce(
315 'Component template requires a root element, rather than just text.',
316 { start }
317 )
318 } else if ((text = text.trim())) {
319 warnOnce(
320 `text "${text}" outside root element will be ignored.`,
321 { start }
322 )
323 }
324 }
325 return
326 }
327 // IE textarea placeholder bug
328 /* istanbul ignore if */
329 if (isIE &&
330 currentParent.tag === 'textarea' &&
331 currentParent.attrsMap.placeholder === text
332 ) {
333 return
334 }
335 const children = currentParent.children
336 if (inPre || text.trim()) {
337 text = isTextTag(currentParent) ? text : decodeHTMLCached(text)
338 } else if (!children.length) {
339 // remove the whitespace-only node right after an opening tag
340 text = ''
341 } else if (whitespaceOption) {
342 if (whitespaceOption === 'condense') {
343 // in condense mode, remove the whitespace node if it contains
344 // line break, otherwise condense to a single space
345 text = lineBreakRE.test(text) ? '' : ' '
346 } else {
347 text = ' '
348 }
349 } else {
350 text = preserveWhitespace ? ' ' : ''
351 }
352 if (text) {
353 if (whitespaceOption === 'condense') {
354 // condense consecutive whitespaces into single space
355 text = text.replace(whitespaceRE, ' ')
356 }
357 let res
358 let child: ?ASTNode
359 if (!inVPre && text !== ' ' && (res = parseText(text, delimiters))) {
360 child = {
361 type: 2,
362 expression: res.expression,
363 tokens: res.tokens,
364 text
365 }
366 } else if (text !== ' ' || !children.length || children[children.length - 1].text !== ' ') {
367 child = {
368 type: 3,
369 text
370 }
371 }
372 if (child) {
373 if (process.env.NODE_ENV !== 'production' && options.outputSourceRange) {
374 child.start = start
375 child.end = end
376 }
377 children.push(child)
378 }
379 }
380 },
381 comment (text: string, start, end) {
382 // adding anyting as a sibling to the root node is forbidden
383 // comments should still be allowed, but ignored
384 if (currentParent) {
385 const child: ASTText = {
386 type: 3,
387 text,
388 isComment: true
389 }
390 if (process.env.NODE_ENV !== 'production' && options.outputSourceRange) {
391 child.start = start
392 child.end = end
393 }
394 currentParent.children.push(child)
395 }
396 }
397 })
398 return root
399}
400
401function processPre (el) {
402 if (getAndRemoveAttr(el, 'v-pre') != null) {
403 el.pre = true
404 }
405}
406
407function processRawAttrs (el) {
408 const list = el.attrsList
409 const len = list.length
410 if (len) {
411 const attrs: Array<ASTAttr> = el.attrs = new Array(len)
412 for (let i = 0; i < len; i++) {
413 attrs[i] = {
414 name: list[i].name,
415 value: JSON.stringify(list[i].value)
416 }
417 if (list[i].start != null) {
418 attrs[i].start = list[i].start
419 attrs[i].end = list[i].end
420 }
421 }
422 } else if (!el.pre) {
423 // non root node in pre blocks with no attributes
424 el.plain = true
425 }
426}
427
428export function processElement (
429 element: ASTElement,
430 options: CompilerOptions
431) {
432 processKey(element)
433
434 // determine whether this is a plain element after
435 // removing structural attributes
436 element.plain = (
437 !element.key &&
438 !element.scopedSlots &&
439 !element.attrsList.length
440 )
441
442 processRef(element)
443 processSlotContent(element)
444 processSlotOutlet(element)
445 processComponent(element)
446 for (let i = 0; i < transforms.length; i++) {
447 element = transforms[i](element, options) || element
448 }
449 processAttrs(element)
450 return element
451}
452
453function processKey (el) {
454 const exp = getBindingAttr(el, 'key')
455 if (exp) {
456 if (process.env.NODE_ENV !== 'production') {
457 if (el.tag === 'template') {
458 warn(
459 `<template> cannot be keyed. Place the key on real elements instead.`,
460 getRawBindingAttr(el, 'key')
461 )
462 }
463 if (el.for) {
464 const iterator = el.iterator2 || el.iterator1
465 const parent = el.parent
466 if (iterator && iterator === exp && parent && parent.tag === 'transition-group') {
467 warn(
468 `Do not use v-for index as key on <transition-group> children, ` +
469 `this is the same as not using keys.`,
470 getRawBindingAttr(el, 'key'),
471 true /* tip */
472 )
473 }
474 }
475 }
476 el.key = exp
477 }
478}
479
480function processRef (el) {
481 const ref = getBindingAttr(el, 'ref')
482 if (ref) {
483 el.ref = ref
484 el.refInFor = checkInFor(el)
485 }
486}
487
488export function processFor (el: ASTElement) {
489 let exp
490 if ((exp = getAndRemoveAttr(el, 'v-for'))) {
491 const res = parseFor(exp)
492 if (res) {
493 extend(el, res)
494 } else if (process.env.NODE_ENV !== 'production') {
495 warn(
496 `Invalid v-for expression: ${exp}`,
497 el.rawAttrsMap['v-for']
498 )
499 }
500 }
501}
502
503type ForParseResult = {
504 for: string;
505 alias: string;
506 iterator1?: string;
507 iterator2?: string;
508};
509
510export function parseFor (exp: string): ?ForParseResult {
511 const inMatch = exp.match(forAliasRE)
512 if (!inMatch) return
513 const res = {}
514 res.for = inMatch[2].trim()
515 const alias = inMatch[1].trim().replace(stripParensRE, '')
516 const iteratorMatch = alias.match(forIteratorRE)
517 if (iteratorMatch) {
518 res.alias = alias.replace(forIteratorRE, '').trim()
519 res.iterator1 = iteratorMatch[1].trim()
520 if (iteratorMatch[2]) {
521 res.iterator2 = iteratorMatch[2].trim()
522 }
523 } else {
524 res.alias = alias
525 }
526 return res
527}
528
529function processIf (el) {
530 const exp = getAndRemoveAttr(el, 'v-if')
531 if (exp) {
532 el.if = exp
533 addIfCondition(el, {
534 exp: exp,
535 block: el
536 })
537 } else {
538 if (getAndRemoveAttr(el, 'v-else') != null) {
539 el.else = true
540 }
541 const elseif = getAndRemoveAttr(el, 'v-else-if')
542 if (elseif) {
543 el.elseif = elseif
544 }
545 }
546}
547
548function processIfConditions (el, parent) {
549 const prev = findPrevElement(parent.children)
550 if (prev && prev.if) {
551 addIfCondition(prev, {
552 exp: el.elseif,
553 block: el
554 })
555 } else if (process.env.NODE_ENV !== 'production') {
556 warn(
557 `v-${el.elseif ? ('else-if="' + el.elseif + '"') : 'else'} ` +
558 `used on element <${el.tag}> without corresponding v-if.`,
559 el.rawAttrsMap[el.elseif ? 'v-else-if' : 'v-else']
560 )
561 }
562}
563
564function findPrevElement (children: Array<any>): ASTElement | void {
565 let i = children.length
566 while (i--) {
567 if (children[i].type === 1) {
568 return children[i]
569 } else {
570 if (process.env.NODE_ENV !== 'production' && children[i].text !== ' ') {
571 warn(
572 `text "${children[i].text.trim()}" between v-if and v-else(-if) ` +
573 `will be ignored.`,
574 children[i]
575 )
576 }
577 children.pop()
578 }
579 }
580}
581
582export function addIfCondition (el: ASTElement, condition: ASTIfCondition) {
583 if (!el.ifConditions) {
584 el.ifConditions = []
585 }
586 el.ifConditions.push(condition)
587}
588
589function processOnce (el) {
590 const once = getAndRemoveAttr(el, 'v-once')
591 if (once != null) {
592 el.once = true
593 }
594}
595
596// handle content being passed to a component as slot,
597// e.g. <template slot="xxx">, <div slot-scope="xxx">
598function processSlotContent (el) {
599 let slotScope
600 if (el.tag === 'template') {
601 slotScope = getAndRemoveAttr(el, 'scope')
602 /* istanbul ignore if */
603 if (process.env.NODE_ENV !== 'production' && slotScope) {
604 warn(
605 `the "scope" attribute for scoped slots have been deprecated and ` +
606 `replaced by "slot-scope" since 2.5. The new "slot-scope" attribute ` +
607 `can also be used on plain elements in addition to <template> to ` +
608 `denote scoped slots.`,
609 el.rawAttrsMap['scope'],
610 true
611 )
612 }
613 el.slotScope = slotScope || getAndRemoveAttr(el, 'slot-scope')
614 } else if ((slotScope = getAndRemoveAttr(el, 'slot-scope'))) {
615 /* istanbul ignore if */
616 if (process.env.NODE_ENV !== 'production' && el.attrsMap['v-for']) {
617 warn(
618 `Ambiguous combined usage of slot-scope and v-for on <${el.tag}> ` +
619 `(v-for takes higher priority). Use a wrapper <template> for the ` +
620 `scoped slot to make it clearer.`,
621 el.rawAttrsMap['slot-scope'],
622 true
623 )
624 }
625 el.slotScope = slotScope
626 }
627
628 // slot="xxx"
629 const slotTarget = getBindingAttr(el, 'slot')
630 if (slotTarget) {
631 el.slotTarget = slotTarget === '""' ? '"default"' : slotTarget
632 el.slotTargetDynamic = !!(el.attrsMap[':slot'] || el.attrsMap['v-bind:slot'])
633 // preserve slot as an attribute for native shadow DOM compat
634 // only for non-scoped slots.
635 if (el.tag !== 'template' && !el.slotScope) {
636 addAttr(el, 'slot', slotTarget, getRawBindingAttr(el, 'slot'))
637 }
638 }
639
640 // 2.6 v-slot syntax
641 if (process.env.NEW_SLOT_SYNTAX) {
642 if (el.tag === 'template') {
643 // v-slot on <template>
644 const slotBinding = getAndRemoveAttrByRegex(el, slotRE)
645 if (slotBinding) {
646 if (process.env.NODE_ENV !== 'production') {
647 if (el.slotTarget || el.slotScope) {
648 warn(
649 `Unexpected mixed usage of different slot syntaxes.`,
650 el
651 )
652 }
653 if (el.parent && !maybeComponent(el.parent)) {
654 warn(
655 `<template v-slot> can only appear at the root level inside ` +
656 `the receiving the component`,
657 el
658 )
659 }
660 }
661 const { name, dynamic } = getSlotName(slotBinding)
662 el.slotTarget = name
663 el.slotTargetDynamic = dynamic
664 el.slotScope = slotBinding.value || emptySlotScopeToken // force it into a scoped slot for perf
665 }
666 } else {
667 // v-slot on component, denotes default slot
668 const slotBinding = getAndRemoveAttrByRegex(el, slotRE)
669 if (slotBinding) {
670 if (process.env.NODE_ENV !== 'production') {
671 if (!maybeComponent(el)) {
672 warn(
673 `v-slot can only be used on components or <template>.`,
674 slotBinding
675 )
676 }
677 if (el.slotScope || el.slotTarget) {
678 warn(
679 `Unexpected mixed usage of different slot syntaxes.`,
680 el
681 )
682 }
683 if (el.scopedSlots) {
684 warn(
685 `To avoid scope ambiguity, the default slot should also use ` +
686 `<template> syntax when there are other named slots.`,
687 slotBinding
688 )
689 }
690 }
691 // add the component's children to its default slot
692 const slots = el.scopedSlots || (el.scopedSlots = {})
693 const { name, dynamic } = getSlotName(slotBinding)
694 const slotContainer = slots[name] = createASTElement('template', [], el)
695 slotContainer.slotTarget = name
696 slotContainer.slotTargetDynamic = dynamic
697 slotContainer.children = el.children.filter((c: any) => {
698 if (!c.slotScope) {
699 c.parent = slotContainer
700 return true
701 }
702 })
703 slotContainer.slotScope = slotBinding.value || emptySlotScopeToken
704 // remove children as they are returned from scopedSlots now
705 el.children = []
706 // mark el non-plain so data gets generated
707 el.plain = false
708 }
709 }
710 }
711}
712
713function getSlotName (binding) {
714 let name = binding.name.replace(slotRE, '')
715 if (!name) {
716 if (binding.name[0] !== '#') {
717 name = 'default'
718 } else if (process.env.NODE_ENV !== 'production') {
719 warn(
720 `v-slot shorthand syntax requires a slot name.`,
721 binding
722 )
723 }
724 }
725 return dynamicArgRE.test(name)
726 // dynamic [name]
727 ? { name: name.slice(1, -1), dynamic: true }
728 // static name
729 : { name: `"${name}"`, dynamic: false }
730}
731
732// handle <slot/> outlets
733function processSlotOutlet (el) {
734 if (el.tag === 'slot') {
735 el.slotName = getBindingAttr(el, 'name')
736 if (process.env.NODE_ENV !== 'production' && el.key) {
737 warn(
738 `\`key\` does not work on <slot> because slots are abstract outlets ` +
739 `and can possibly expand into multiple elements. ` +
740 `Use the key on a wrapping element instead.`,
741 getRawBindingAttr(el, 'key')
742 )
743 }
744 }
745}
746
747function processComponent (el) {
748 let binding
749 if ((binding = getBindingAttr(el, 'is'))) {
750 el.component = binding
751 }
752 if (getAndRemoveAttr(el, 'inline-template') != null) {
753 el.inlineTemplate = true
754 }
755}
756
757function processAttrs (el) {
758 const list = el.attrsList
759 let i, l, name, rawName, value, modifiers, syncGen, isDynamic
760 for (i = 0, l = list.length; i < l; i++) {
761 name = rawName = list[i].name
762 value = list[i].value
763 if (dirRE.test(name)) {
764 // mark element as dynamic
765 el.hasBindings = true
766 // modifiers
767 modifiers = parseModifiers(name.replace(dirRE, ''))
768 // support .foo shorthand syntax for the .prop modifier
769 if (process.env.VBIND_PROP_SHORTHAND && propBindRE.test(name)) {
770 (modifiers || (modifiers = {})).prop = true
771 name = `.` + name.slice(1).replace(modifierRE, '')
772 } else if (modifiers) {
773 name = name.replace(modifierRE, '')
774 }
775 if (bindRE.test(name)) { // v-bind
776 name = name.replace(bindRE, '')
777 value = parseFilters(value)
778 isDynamic = dynamicArgRE.test(name)
779 if (isDynamic) {
780 name = name.slice(1, -1)
781 }
782 if (
783 process.env.NODE_ENV !== 'production' &&
784 value.trim().length === 0
785 ) {
786 warn(
787 `The value for a v-bind expression cannot be empty. Found in "v-bind:${name}"`
788 )
789 }
790 if (modifiers) {
791 if (modifiers.prop && !isDynamic) {
792 name = camelize(name)
793 if (name === 'innerHtml') name = 'innerHTML'
794 }
795 if (modifiers.camel && !isDynamic) {
796 name = camelize(name)
797 }
798 if (modifiers.sync) {
799 syncGen = genAssignmentCode(value, `$event`)
800 if (!isDynamic) {
801 addHandler(
802 el,
803 `update:${camelize(name)}`,
804 syncGen,
805 null,
806 false,
807 warn,
808 list[i]
809 )
810 if (hyphenate(name) !== camelize(name)) {
811 addHandler(
812 el,
813 `update:${hyphenate(name)}`,
814 syncGen,
815 null,
816 false,
817 warn,
818 list[i]
819 )
820 }
821 } else {
822 // handler w/ dynamic event name
823 addHandler(
824 el,
825 `"update:"+(${name})`,
826 syncGen,
827 null,
828 false,
829 warn,
830 list[i],
831 true // dynamic
832 )
833 }
834 }
835 }
836 if ((modifiers && modifiers.prop) || (
837 !el.component && platformMustUseProp(el.tag, el.attrsMap.type, name)
838 )) {
839 addProp(el, name, value, list[i], isDynamic)
840 } else {
841 addAttr(el, name, value, list[i], isDynamic)
842 }
843 } else if (onRE.test(name)) { // v-on
844 name = name.replace(onRE, '')
845 isDynamic = dynamicArgRE.test(name)
846 if (isDynamic) {
847 name = name.slice(1, -1)
848 }
849 addHandler(el, name, value, modifiers, false, warn, list[i], isDynamic)
850 } else { // normal directives
851 name = name.replace(dirRE, '')
852 // parse arg
853 const argMatch = name.match(argRE)
854 let arg = argMatch && argMatch[1]
855 isDynamic = false
856 if (arg) {
857 name = name.slice(0, -(arg.length + 1))
858 if (dynamicArgRE.test(arg)) {
859 arg = arg.slice(1, -1)
860 isDynamic = true
861 }
862 }
863 addDirective(el, name, rawName, value, arg, isDynamic, modifiers, list[i])
864 if (process.env.NODE_ENV !== 'production' && name === 'model') {
865 checkForAliasModel(el, value)
866 }
867 }
868 } else {
869 // literal attribute
870 if (process.env.NODE_ENV !== 'production') {
871 const res = parseText(value, delimiters)
872 if (res) {
873 warn(
874 `${name}="${value}": ` +
875 'Interpolation inside attributes has been removed. ' +
876 'Use v-bind or the colon shorthand instead. For example, ' +
877 'instead of <div id="{{ val }}">, use <div :id="val">.',
878 list[i]
879 )
880 }
881 }
882 addAttr(el, name, JSON.stringify(value), list[i])
883 // #6887 firefox doesn't update muted state if set via attribute
884 // even immediately after element creation
885 if (!el.component &&
886 name === 'muted' &&
887 platformMustUseProp(el.tag, el.attrsMap.type, name)) {
888 addProp(el, name, 'true', list[i])
889 }
890 }
891 }
892}
893
894function checkInFor (el: ASTElement): boolean {
895 let parent = el
896 while (parent) {
897 if (parent.for !== undefined) {
898 return true
899 }
900 parent = parent.parent
901 }
902 return false
903}
904
905function parseModifiers (name: string): Object | void {
906 const match = name.match(modifierRE)
907 if (match) {
908 const ret = {}
909 match.forEach(m => { ret[m.slice(1)] = true })
910 return ret
911 }
912}
913
914function makeAttrsMap (attrs: Array<Object>): Object {
915 const map = {}
916 for (let i = 0, l = attrs.length; i < l; i++) {
917 if (
918 process.env.NODE_ENV !== 'production' &&
919 map[attrs[i].name] && !isIE && !isEdge
920 ) {
921 warn('duplicate attribute: ' + attrs[i].name, attrs[i])
922 }
923 map[attrs[i].name] = attrs[i].value
924 }
925 return map
926}
927
928// for script (e.g. type="x/template") or style, do not decode content
929function isTextTag (el): boolean {
930 return el.tag === 'script' || el.tag === 'style'
931}
932
933function isForbiddenTag (el): boolean {
934 return (
935 el.tag === 'style' ||
936 (el.tag === 'script' && (
937 !el.attrsMap.type ||
938 el.attrsMap.type === 'text/javascript'
939 ))
940 )
941}
942
943const ieNSBug = /^xmlns:NS\d+/
944const ieNSPrefix = /^NS\d+:/
945
946/* istanbul ignore next */
947function guardIESVGBug (attrs) {
948 const res = []
949 for (let i = 0; i < attrs.length; i++) {
950 const attr = attrs[i]
951 if (!ieNSBug.test(attr.name)) {
952 attr.name = attr.name.replace(ieNSPrefix, '')
953 res.push(attr)
954 }
955 }
956 return res
957}
958
959function checkForAliasModel (el, value) {
960 let _el = el
961 while (_el) {
962 if (_el.for && _el.alias === value) {
963 warn(
964 `<${el.tag} v-model="${value}">: ` +
965 `You are binding v-model directly to a v-for iteration alias. ` +
966 `This will not be able to modify the v-for source array because ` +
967 `writing to the alias is like modifying a function local variable. ` +
968 `Consider using an array of objects and use v-model on an object property instead.`,
969 el.rawAttrsMap['v-model']
970 )
971 }
972 _el = _el.parent
973 }
974}