src/compiler/parser/index.ts TYPESCRIPT 1,000 lines View on github.com → Search inside
1import he from 'he'2import { parseHTML } from './html-parser'3import { parseText } from './text-parser'4import { parseFilters } from './filter-parser'5import { genAssignmentCode } from '../directives/model'6import { extend, cached, no, camelize, hyphenate } from 'shared/util'7import { isIE, isEdge, isServerRendering } from 'core/util/env'89import {10  addProp,11  addAttr,12  baseWarn,13  addHandler,14  addDirective,15  getBindingAttr,16  getAndRemoveAttr,17  getRawBindingAttr,18  pluckModuleFunction,19  getAndRemoveAttrByRegex20} from '../helpers'2122import {23  ASTAttr,24  ASTElement,25  ASTIfCondition,26  ASTNode,27  ASTText,28  CompilerOptions29} from 'types/compiler'3031export const onRE = /^@|^v-on:/32export const dirRE = process.env.VBIND_PROP_SHORTHAND33  ? /^v-|^@|^:|^\.|^#/34  : /^v-|^@|^:|^#/35export const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/36export const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/37const stripParensRE = /^\(|\)$/g38const dynamicArgRE = /^\[.*\]$/3940const argRE = /:(.*)$/41export const bindRE = /^:|^\.|^v-bind:/42const propBindRE = /^\./43const modifierRE = /\.[^.\]]+(?=[^\]]*$)/g4445export const slotRE = /^v-slot(:|$)|^#/4647const lineBreakRE = /[\r\n]/48const whitespaceRE = /[ \f\t\r\n]+/g4950const invalidAttributeRE = /[\s"'<>\/=]/5152const decodeHTMLCached = cached(he.decode)5354export const emptySlotScopeToken = `_empty_`5556// configurable state57export let warn: any58let delimiters59let transforms60let preTransforms61let postTransforms62let platformIsPreTag63let platformMustUseProp64let platformGetTagNamespace65let maybeComponent6667export function createASTElement(68  tag: string,69  attrs: Array<ASTAttr>,70  parent: ASTElement | void71): ASTElement {72  return {73    type: 1,74    tag,75    attrsList: attrs,76    attrsMap: makeAttrsMap(attrs),77    rawAttrsMap: {},78    parent,79    children: []80  }81}8283/**84 * Convert HTML string to AST.85 */86export function parse(template: string, options: CompilerOptions): ASTElement {87  warn = options.warn || baseWarn8889  platformIsPreTag = options.isPreTag || no90  platformMustUseProp = options.mustUseProp || no91  platformGetTagNamespace = options.getTagNamespace || no92  const isReservedTag = options.isReservedTag || no93  maybeComponent = (el: ASTElement) =>94    !!(95      el.component ||96      el.attrsMap[':is'] ||97      el.attrsMap['v-bind:is'] ||98      !(el.attrsMap.is ? isReservedTag(el.attrsMap.is) : isReservedTag(el.tag))99    )100  transforms = pluckModuleFunction(options.modules, 'transformNode')101  preTransforms = pluckModuleFunction(options.modules, 'preTransformNode')102  postTransforms = pluckModuleFunction(options.modules, 'postTransformNode')103104  delimiters = options.delimiters105106  const stack: any[] = []107  const preserveWhitespace = options.preserveWhitespace !== false108  const whitespaceOption = options.whitespace109  let root110  let currentParent111  let inVPre = false112  let inPre = false113  let warned = false114115  function warnOnce(msg, range) {116    if (!warned) {117      warned = true118      warn(msg, range)119    }120  }121122  function closeElement(element) {123    trimEndingWhitespace(element)124    if (!inVPre && !element.processed) {125      element = processElement(element, options)126    }127    // tree management128    if (!stack.length && element !== root) {129      // allow root elements with v-if, v-else-if and v-else130      if (root.if && (element.elseif || element.else)) {131        if (__DEV__) {132          checkRootConstraints(element)133        }134        addIfCondition(root, {135          exp: element.elseif,136          block: element137        })138      } else if (__DEV__) {139        warnOnce(140          `Component template should contain exactly one root element. ` +141            `If you are using v-if on multiple elements, ` +142            `use v-else-if to chain them instead.`,143          { start: element.start }144        )145      }146    }147    if (currentParent && !element.forbidden) {148      if (element.elseif || element.else) {149        processIfConditions(element, currentParent)150      } else {151        if (element.slotScope) {152          // scoped slot153          // keep it in the children list so that v-else(-if) conditions can154          // find it as the prev node.155          const name = element.slotTarget || '"default"'156          ;(currentParent.scopedSlots || (currentParent.scopedSlots = {}))[157            name158          ] = element159        }160        currentParent.children.push(element)161        element.parent = currentParent162      }163    }164165    // final children cleanup166    // filter out scoped slots167    element.children = element.children.filter(c => !c.slotScope)168    // remove trailing whitespace node again169    trimEndingWhitespace(element)170171    // check pre state172    if (element.pre) {173      inVPre = false174    }175    if (platformIsPreTag(element.tag)) {176      inPre = false177    }178    // apply post-transforms179    for (let i = 0; i < postTransforms.length; i++) {180      postTransforms[i](element, options)181    }182  }183184  function trimEndingWhitespace(el) {185    // remove trailing whitespace node186    if (!inPre) {187      let lastNode188      while (189        (lastNode = el.children[el.children.length - 1]) &&190        lastNode.type === 3 &&191        lastNode.text === ' '192      ) {193        el.children.pop()194      }195    }196  }197198  function checkRootConstraints(el) {199    if (el.tag === 'slot' || el.tag === 'template') {200      warnOnce(201        `Cannot use <${el.tag}> as component root element because it may ` +202          'contain multiple nodes.',203        { start: el.start }204      )205    }206    if (el.attrsMap.hasOwnProperty('v-for')) {207      warnOnce(208        'Cannot use v-for on stateful component root element because ' +209          'it renders multiple elements.',210        el.rawAttrsMap['v-for']211      )212    }213  }214215  parseHTML(template, {216    warn,217    expectHTML: options.expectHTML,218    isUnaryTag: options.isUnaryTag,219    canBeLeftOpenTag: options.canBeLeftOpenTag,220    shouldDecodeNewlines: options.shouldDecodeNewlines,221    shouldDecodeNewlinesForHref: options.shouldDecodeNewlinesForHref,222    shouldKeepComment: options.comments,223    outputSourceRange: options.outputSourceRange,224    start(tag, attrs, unary, start, end) {225      // check namespace.226      // inherit parent ns if there is one227      const ns =228        (currentParent && currentParent.ns) || platformGetTagNamespace(tag)229230      // handle IE svg bug231      /* istanbul ignore if */232      if (isIE && ns === 'svg') {233        attrs = guardIESVGBug(attrs)234      }235236      let element: ASTElement = createASTElement(tag, attrs, currentParent)237      if (ns) {238        element.ns = ns239      }240241      if (__DEV__) {242        if (options.outputSourceRange) {243          element.start = start244          element.end = end245          element.rawAttrsMap = element.attrsList.reduce((cumulated, attr) => {246            cumulated[attr.name] = attr247            return cumulated248          }, {})249        }250        attrs.forEach(attr => {251          if (invalidAttributeRE.test(attr.name)) {252            warn(253              `Invalid dynamic argument expression: attribute names cannot contain ` +254                `spaces, quotes, <, >, / or =.`,255              options.outputSourceRange256                ? {257                    start: attr.start! + attr.name.indexOf(`[`),258                    end: attr.start! + attr.name.length259                  }260                : undefined261            )262          }263        })264      }265266      if (isForbiddenTag(element) && !isServerRendering()) {267        element.forbidden = true268        __DEV__ &&269          warn(270            'Templates should only be responsible for mapping the state to the ' +271              'UI. Avoid placing tags with side-effects in your templates, such as ' +272              `<${tag}>` +273              ', as they will not be parsed.',274            { start: element.start }275          )276      }277278      // apply pre-transforms279      for (let i = 0; i < preTransforms.length; i++) {280        element = preTransforms[i](element, options) || element281      }282283      if (!inVPre) {284        processPre(element)285        if (element.pre) {286          inVPre = true287        }288      }289      if (platformIsPreTag(element.tag)) {290        inPre = true291      }292      if (inVPre) {293        processRawAttrs(element)294      } else if (!element.processed) {295        // structural directives296        processFor(element)297        processIf(element)298        processOnce(element)299      }300301      if (!root) {302        root = element303        if (__DEV__) {304          checkRootConstraints(root)305        }306      }307308      if (!unary) {309        currentParent = element310        stack.push(element)311      } else {312        closeElement(element)313      }314    },315316    end(tag, start, end) {317      const element = stack[stack.length - 1]318      // pop stack319      stack.length -= 1320      currentParent = stack[stack.length - 1]321      if (__DEV__ && options.outputSourceRange) {322        element.end = end323      }324      closeElement(element)325    },326327    chars(text: string, start?: number, end?: number) {328      if (!currentParent) {329        if (__DEV__) {330          if (text === template) {331            warnOnce(332              'Component template requires a root element, rather than just text.',333              { start }334            )335          } else if ((text = text.trim())) {336            warnOnce(`text "${text}" outside root element will be ignored.`, {337              start338            })339          }340        }341        return342      }343      // IE textarea placeholder bug344      /* istanbul ignore if */345      if (346        isIE &&347        currentParent.tag === 'textarea' &&348        currentParent.attrsMap.placeholder === text349      ) {350        return351      }352      const children = currentParent.children353      if (inPre || text.trim()) {354        text = isTextTag(currentParent)355          ? text356          : (decodeHTMLCached(text) as string)357      } else if (!children.length) {358        // remove the whitespace-only node right after an opening tag359        text = ''360      } else if (whitespaceOption) {361        if (whitespaceOption === 'condense') {362          // in condense mode, remove the whitespace node if it contains363          // line break, otherwise condense to a single space364          text = lineBreakRE.test(text) ? '' : ' '365        } else {366          text = ' '367        }368      } else {369        text = preserveWhitespace ? ' ' : ''370      }371      if (text) {372        if (!inPre && whitespaceOption === 'condense') {373          // condense consecutive whitespaces into single space374          text = text.replace(whitespaceRE, ' ')375        }376        let res377        let child: ASTNode | undefined378        if (!inVPre && text !== ' ' && (res = parseText(text, delimiters))) {379          child = {380            type: 2,381            expression: res.expression,382            tokens: res.tokens,383            text384          }385        } else if (386          text !== ' ' ||387          !children.length ||388          children[children.length - 1].text !== ' '389        ) {390          child = {391            type: 3,392            text393          }394        }395        if (child) {396          if (__DEV__ && options.outputSourceRange) {397            child.start = start398            child.end = end399          }400          children.push(child)401        }402      }403    },404    comment(text: string, start, end) {405      // adding anything as a sibling to the root node is forbidden406      // comments should still be allowed, but ignored407      if (currentParent) {408        const child: ASTText = {409          type: 3,410          text,411          isComment: true412        }413        if (__DEV__ && options.outputSourceRange) {414          child.start = start415          child.end = end416        }417        currentParent.children.push(child)418      }419    }420  })421  return root422}423424function processPre(el) {425  if (getAndRemoveAttr(el, 'v-pre') != null) {426    el.pre = true427  }428}429430function processRawAttrs(el) {431  const list = el.attrsList432  const len = list.length433  if (len) {434    const attrs: Array<ASTAttr> = (el.attrs = new Array(len))435    for (let i = 0; i < len; i++) {436      attrs[i] = {437        name: list[i].name,438        value: JSON.stringify(list[i].value)439      }440      if (list[i].start != null) {441        attrs[i].start = list[i].start442        attrs[i].end = list[i].end443      }444    }445  } else if (!el.pre) {446    // non root node in pre blocks with no attributes447    el.plain = true448  }449}450451export function processElement(element: ASTElement, options: CompilerOptions) {452  processKey(element)453454  // determine whether this is a plain element after455  // removing structural attributes456  element.plain =457    !element.key && !element.scopedSlots && !element.attrsList.length458459  processRef(element)460  processSlotContent(element)461  processSlotOutlet(element)462  processComponent(element)463  for (let i = 0; i < transforms.length; i++) {464    element = transforms[i](element, options) || element465  }466  processAttrs(element)467  return element468}469470function processKey(el) {471  const exp = getBindingAttr(el, 'key')472  if (exp) {473    if (__DEV__) {474      if (el.tag === 'template') {475        warn(476          `<template> cannot be keyed. Place the key on real elements instead.`,477          getRawBindingAttr(el, 'key')478        )479      }480      if (el.for) {481        const iterator = el.iterator2 || el.iterator1482        const parent = el.parent483        if (484          iterator &&485          iterator === exp &&486          parent &&487          parent.tag === 'transition-group'488        ) {489          warn(490            `Do not use v-for index as key on <transition-group> children, ` +491              `this is the same as not using keys.`,492            getRawBindingAttr(el, 'key'),493            true /* tip */494          )495        }496      }497    }498    el.key = exp499  }500}501502function processRef(el) {503  const ref = getBindingAttr(el, 'ref')504  if (ref) {505    el.ref = ref506    el.refInFor = checkInFor(el)507  }508}509510export function processFor(el: ASTElement) {511  let exp512  if ((exp = getAndRemoveAttr(el, 'v-for'))) {513    const res = parseFor(exp)514    if (res) {515      extend(el, res)516    } else if (__DEV__) {517      warn(`Invalid v-for expression: ${exp}`, el.rawAttrsMap['v-for'])518    }519  }520}521522type ForParseResult = {523  for: string524  alias: string525  iterator1?: string526  iterator2?: string527}528529export function parseFor(exp: string): ForParseResult | undefined {530  const inMatch = exp.match(forAliasRE)531  if (!inMatch) return532  const res: any = {}533  res.for = inMatch[2].trim()534  const alias = inMatch[1].trim().replace(stripParensRE, '')535  const iteratorMatch = alias.match(forIteratorRE)536  if (iteratorMatch) {537    res.alias = alias.replace(forIteratorRE, '').trim()538    res.iterator1 = iteratorMatch[1].trim()539    if (iteratorMatch[2]) {540      res.iterator2 = iteratorMatch[2].trim()541    }542  } else {543    res.alias = alias544  }545  return res546}547548function processIf(el) {549  const exp = getAndRemoveAttr(el, 'v-if')550  if (exp) {551    el.if = exp552    addIfCondition(el, {553      exp: exp,554      block: el555    })556  } else {557    if (getAndRemoveAttr(el, 'v-else') != null) {558      el.else = true559    }560    const elseif = getAndRemoveAttr(el, 'v-else-if')561    if (elseif) {562      el.elseif = elseif563    }564  }565}566567function processIfConditions(el, parent) {568  const prev = findPrevElement(parent.children)569  if (prev && prev.if) {570    addIfCondition(prev, {571      exp: el.elseif,572      block: el573    })574  } else if (__DEV__) {575    warn(576      `v-${el.elseif ? 'else-if="' + el.elseif + '"' : 'else'} ` +577        `used on element <${el.tag}> without corresponding v-if.`,578      el.rawAttrsMap[el.elseif ? 'v-else-if' : 'v-else']579    )580  }581}582583function findPrevElement(children: Array<any>): ASTElement | void {584  let i = children.length585  while (i--) {586    if (children[i].type === 1) {587      return children[i]588    } else {589      if (__DEV__ && children[i].text !== ' ') {590        warn(591          `text "${children[i].text.trim()}" between v-if and v-else(-if) ` +592            `will be ignored.`,593          children[i]594        )595      }596      children.pop()597    }598  }599}600601export function addIfCondition(el: ASTElement, condition: ASTIfCondition) {602  if (!el.ifConditions) {603    el.ifConditions = []604  }605  el.ifConditions.push(condition)606}607608function processOnce(el) {609  const once = getAndRemoveAttr(el, 'v-once')610  if (once != null) {611    el.once = true612  }613}614615// handle content being passed to a component as slot,616// e.g. <template slot="xxx">, <div slot-scope="xxx">617function processSlotContent(el) {618  let slotScope619  if (el.tag === 'template') {620    slotScope = getAndRemoveAttr(el, 'scope')621    /* istanbul ignore if */622    if (__DEV__ && slotScope) {623      warn(624        `the "scope" attribute for scoped slots have been deprecated and ` +625          `replaced by "slot-scope" since 2.5. The new "slot-scope" attribute ` +626          `can also be used on plain elements in addition to <template> to ` +627          `denote scoped slots.`,628        el.rawAttrsMap['scope'],629        true630      )631    }632    el.slotScope = slotScope || getAndRemoveAttr(el, 'slot-scope')633  } else if ((slotScope = getAndRemoveAttr(el, 'slot-scope'))) {634    /* istanbul ignore if */635    if (__DEV__ && el.attrsMap['v-for']) {636      warn(637        `Ambiguous combined usage of slot-scope and v-for on <${el.tag}> ` +638          `(v-for takes higher priority). Use a wrapper <template> for the ` +639          `scoped slot to make it clearer.`,640        el.rawAttrsMap['slot-scope'],641        true642      )643    }644    el.slotScope = slotScope645  }646647  // slot="xxx"648  const slotTarget = getBindingAttr(el, 'slot')649  if (slotTarget) {650    el.slotTarget = slotTarget === '""' ? '"default"' : slotTarget651    el.slotTargetDynamic = !!(652      el.attrsMap[':slot'] || el.attrsMap['v-bind:slot']653    )654    // preserve slot as an attribute for native shadow DOM compat655    // only for non-scoped slots.656    if (el.tag !== 'template' && !el.slotScope) {657      addAttr(el, 'slot', slotTarget, getRawBindingAttr(el, 'slot'))658    }659  }660661  // 2.6 v-slot syntax662  if (process.env.NEW_SLOT_SYNTAX) {663    if (el.tag === 'template') {664      // v-slot on <template>665      const slotBinding = getAndRemoveAttrByRegex(el, slotRE)666      if (slotBinding) {667        if (__DEV__) {668          if (el.slotTarget || el.slotScope) {669            warn(`Unexpected mixed usage of different slot syntaxes.`, el)670          }671          if (el.parent && !maybeComponent(el.parent)) {672            warn(673              `<template v-slot> can only appear at the root level inside ` +674                `the receiving component`,675              el676            )677          }678        }679        const { name, dynamic } = getSlotName(slotBinding)680        el.slotTarget = name681        el.slotTargetDynamic = dynamic682        el.slotScope = slotBinding.value || emptySlotScopeToken // force it into a scoped slot for perf683      }684    } else {685      // v-slot on component, denotes default slot686      const slotBinding = getAndRemoveAttrByRegex(el, slotRE)687      if (slotBinding) {688        if (__DEV__) {689          if (!maybeComponent(el)) {690            warn(691              `v-slot can only be used on components or <template>.`,692              slotBinding693            )694          }695          if (el.slotScope || el.slotTarget) {696            warn(`Unexpected mixed usage of different slot syntaxes.`, el)697          }698          if (el.scopedSlots) {699            warn(700              `To avoid scope ambiguity, the default slot should also use ` +701                `<template> syntax when there are other named slots.`,702              slotBinding703            )704          }705        }706        // add the component's children to its default slot707        const slots = el.scopedSlots || (el.scopedSlots = {})708        const { name, dynamic } = getSlotName(slotBinding)709        const slotContainer = (slots[name] = createASTElement(710          'template',711          [],712          el713        ))714        slotContainer.slotTarget = name715        slotContainer.slotTargetDynamic = dynamic716        slotContainer.children = el.children.filter((c: any) => {717          if (!c.slotScope) {718            c.parent = slotContainer719            return true720          }721        })722        slotContainer.slotScope = slotBinding.value || emptySlotScopeToken723        // remove children as they are returned from scopedSlots now724        el.children = []725        // mark el non-plain so data gets generated726        el.plain = false727      }728    }729  }730}731732function getSlotName(binding) {733  let name = binding.name.replace(slotRE, '')734  if (!name) {735    if (binding.name[0] !== '#') {736      name = 'default'737    } else if (__DEV__) {738      warn(`v-slot shorthand syntax requires a slot name.`, binding)739    }740  }741  return dynamicArgRE.test(name)742    ? // dynamic [name]743      { name: name.slice(1, -1), dynamic: true }744    : // static name745      { name: `"${name}"`, dynamic: false }746}747748// handle <slot/> outlets749function processSlotOutlet(el) {750  if (el.tag === 'slot') {751    el.slotName = getBindingAttr(el, 'name')752    if (__DEV__ && el.key) {753      warn(754        `\`key\` does not work on <slot> because slots are abstract outlets ` +755          `and can possibly expand into multiple elements. ` +756          `Use the key on a wrapping element instead.`,757        getRawBindingAttr(el, 'key')758      )759    }760  }761}762763function processComponent(el) {764  let binding765  if ((binding = getBindingAttr(el, 'is'))) {766    el.component = binding767  }768  if (getAndRemoveAttr(el, 'inline-template') != null) {769    el.inlineTemplate = true770  }771}772773function processAttrs(el) {774  const list = el.attrsList775  let i, l, name, rawName, value, modifiers, syncGen, isDynamic776  for (i = 0, l = list.length; i < l; i++) {777    name = rawName = list[i].name778    value = list[i].value779    if (dirRE.test(name)) {780      // mark element as dynamic781      el.hasBindings = true782      // modifiers783      modifiers = parseModifiers(name.replace(dirRE, ''))784      // support .foo shorthand syntax for the .prop modifier785      if (process.env.VBIND_PROP_SHORTHAND && propBindRE.test(name)) {786        ;(modifiers || (modifiers = {})).prop = true787        name = `.` + name.slice(1).replace(modifierRE, '')788      } else if (modifiers) {789        name = name.replace(modifierRE, '')790      }791      if (bindRE.test(name)) {792        // v-bind793        name = name.replace(bindRE, '')794        value = parseFilters(value)795        isDynamic = dynamicArgRE.test(name)796        if (isDynamic) {797          name = name.slice(1, -1)798        }799        if (__DEV__ && value.trim().length === 0) {800          warn(801            `The value for a v-bind expression cannot be empty. Found in "v-bind:${name}"`802          )803        }804        if (modifiers) {805          if (modifiers.prop && !isDynamic) {806            name = camelize(name)807            if (name === 'innerHtml') name = 'innerHTML'808          }809          if (modifiers.camel && !isDynamic) {810            name = camelize(name)811          }812          if (modifiers.sync) {813            syncGen = genAssignmentCode(value, `$event`)814            if (!isDynamic) {815              addHandler(816                el,817                `update:${camelize(name)}`,818                syncGen,819                null,820                false,821                warn,822                list[i]823              )824              if (hyphenate(name) !== camelize(name)) {825                addHandler(826                  el,827                  `update:${hyphenate(name)}`,828                  syncGen,829                  null,830                  false,831                  warn,832                  list[i]833                )834              }835            } else {836              // handler w/ dynamic event name837              addHandler(838                el,839                `"update:"+(${name})`,840                syncGen,841                null,842                false,843                warn,844                list[i],845                true // dynamic846              )847            }848          }849        }850        if (851          (modifiers && modifiers.prop) ||852          (!el.component && platformMustUseProp(el.tag, el.attrsMap.type, name))853        ) {854          addProp(el, name, value, list[i], isDynamic)855        } else {856          addAttr(el, name, value, list[i], isDynamic)857        }858      } else if (onRE.test(name)) {859        // v-on860        name = name.replace(onRE, '')861        isDynamic = dynamicArgRE.test(name)862        if (isDynamic) {863          name = name.slice(1, -1)864        }865        addHandler(el, name, value, modifiers, false, warn, list[i], isDynamic)866      } else {867        // normal directives868        name = name.replace(dirRE, '')869        // parse arg870        const argMatch = name.match(argRE)871        let arg = argMatch && argMatch[1]872        isDynamic = false873        if (arg) {874          name = name.slice(0, -(arg.length + 1))875          if (dynamicArgRE.test(arg)) {876            arg = arg.slice(1, -1)877            isDynamic = true878          }879        }880        addDirective(881          el,882          name,883          rawName,884          value,885          arg,886          isDynamic,887          modifiers,888          list[i]889        )890        if (__DEV__ && name === 'model') {891          checkForAliasModel(el, value)892        }893      }894    } else {895      // literal attribute896      if (__DEV__) {897        const res = parseText(value, delimiters)898        if (res) {899          warn(900            `${name}="${value}": ` +901              'Interpolation inside attributes has been removed. ' +902              'Use v-bind or the colon shorthand instead. For example, ' +903              'instead of <div id="{{ val }}">, use <div :id="val">.',904            list[i]905          )906        }907      }908      addAttr(el, name, JSON.stringify(value), list[i])909      // #6887 firefox doesn't update muted state if set via attribute910      // even immediately after element creation911      if (912        !el.component &&913        name === 'muted' &&914        platformMustUseProp(el.tag, el.attrsMap.type, name)915      ) {916        addProp(el, name, 'true', list[i])917      }918    }919  }920}921922function checkInFor(el: ASTElement): boolean {923  let parent: ASTElement | void = el924  while (parent) {925    if (parent.for !== undefined) {926      return true927    }928    parent = parent.parent929  }930  return false931}932933function parseModifiers(name: string): Object | void {934  const match = name.match(modifierRE)935  if (match) {936    const ret = {}937    match.forEach(m => {938      ret[m.slice(1)] = true939    })940    return ret941  }942}943944function makeAttrsMap(attrs: Array<Record<string, any>>): Record<string, any> {945  const map = {}946  for (let i = 0, l = attrs.length; i < l; i++) {947    if (__DEV__ && map[attrs[i].name] && !isIE && !isEdge) {948      warn('duplicate attribute: ' + attrs[i].name, attrs[i])949    }950    map[attrs[i].name] = attrs[i].value951  }952  return map953}954955// for script (e.g. type="x/template") or style, do not decode content956function isTextTag(el): boolean {957  return el.tag === 'script' || el.tag === 'style'958}959960function isForbiddenTag(el): boolean {961  return (962    el.tag === 'style' ||963    (el.tag === 'script' &&964      (!el.attrsMap.type || el.attrsMap.type === 'text/javascript'))965  )966}967968const ieNSBug = /^xmlns:NS\d+/969const ieNSPrefix = /^NS\d+:/970971/* istanbul ignore next */972function guardIESVGBug(attrs) {973  const res: any[] = []974  for (let i = 0; i < attrs.length; i++) {975    const attr = attrs[i]976    if (!ieNSBug.test(attr.name)) {977      attr.name = attr.name.replace(ieNSPrefix, '')978      res.push(attr)979    }980  }981  return res982}983984function checkForAliasModel(el, value) {985  let _el = el986  while (_el) {987    if (_el.for && _el.alias === value) {988      warn(989        `<${el.tag} v-model="${value}">: ` +990          `You are binding v-model directly to a v-for iteration alias. ` +991          `This will not be able to modify the v-for source array because ` +992          `writing to the alias is like modifying a function local variable. ` +993          `Consider using an array of objects and use v-model on an object property instead.`,994        el.rawAttrsMap['v-model']995      )996    }997    _el = _el.parent998  }999}

Findings

✓ No findings reported for this file.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.