| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191 | /** * @author Toru Nagashima * @copyright 2017 Toru Nagashima. All rights reserved. * See LICENSE file in root directory for full license. */'use strict'// ------------------------------------------------------------------------------// Requirements// ------------------------------------------------------------------------------const utils = require('../utils')// ------------------------------------------------------------------------------// Helpers// ------------------------------------------------------------------------------/** * Check whether the given attribute is using the variables which are defined by `v-for` directives. * @param {ASTNode} vFor The attribute node of `v-for` to check. * @param {ASTNode} vBindKey The attribute node of `v-bind:key` to check. * @returns {boolean} `true` if the node is using the variables which are defined by `v-for` directives. */function isUsingIterationVar (vFor, vBindKey) {  if (vBindKey.value == null) {    return false  }  const references = vBindKey.value.references  const variables = vFor.parent.parent.variables  return references.some(reference =>    variables.some(variable =>      variable.id.name === reference.id.name &&            variable.kind === 'v-for'    )  )}/** * Check the child element in tempalte v-for about `v-bind:key` attributes. * @param {RuleContext} context The rule context to report. * @param {ASTNode} vFor The attribute node of `v-for` to check. * @param {ASTNode} child The child node to check. */function checkChildKey (context, vFor, child) {  const childFor = utils.getDirective(child, 'for')  // if child has v-for, check if parent iterator is used in v-for  if (childFor != null) {    const childForRefs = childFor.value.references    const variables = vFor.parent.parent.variables    const usedInFor = childForRefs.some(cref =>      variables.some(variable =>        cref.id.name === variable.id.name &&        variable.kind === 'v-for'      )    )    // if parent iterator is used, skip other checks    // iterator usage will be checked later by child v-for    if (usedInFor) {      return    }  }  // otherwise, check if parent iterator is directly used in child's key  checkKey(context, vFor, child)}/** * Check the given element about `v-bind:key` attributes. * @param {RuleContext} context The rule context to report. * @param {ASTNode} vFor The attribute node of `v-for` to check. * @param {ASTNode} element The element node to check. */function checkKey (context, vFor, element) {  if (element.name === 'template') {    for (const child of element.children) {      if (child.type === 'VElement') {        checkChildKey(context, vFor, child)      }    }    return  }  const vBindKey = utils.getDirective(element, 'bind', 'key')  if (utils.isCustomComponent(element) && vBindKey == null) {    context.report({      node: element.startTag,      loc: element.startTag.loc,      message: "Custom elements in iteration require 'v-bind:key' directives."    })  }  if (vBindKey != null && !isUsingIterationVar(vFor, vBindKey)) {    context.report({      node: vBindKey,      loc: vBindKey.loc,      message: "Expected 'v-bind:key' directive to use the variables which are defined by the 'v-for' directive."    })  }}// ------------------------------------------------------------------------------// Rule Definition// ------------------------------------------------------------------------------module.exports = {  meta: {    docs: {      description: 'enforce valid `v-for` directives',      category: 'essential',      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.7.1/docs/rules/valid-v-for.md'    },    fixable: null,    schema: []  },  create (context) {    const sourceCode = context.getSourceCode()    return utils.defineTemplateBodyVisitor(context, {      "VAttribute[directive=true][key.name='for']" (node) {        const element = node.parent.parent        checkKey(context, node, element)        if (node.key.argument) {          context.report({            node,            loc: node.loc,            message: "'v-for' directives require no argument."          })        }        if (node.key.modifiers.length > 0) {          context.report({            node,            loc: node.loc,            message: "'v-for' directives require no modifier."          })        }        if (!utils.hasAttributeValue(node)) {          context.report({            node,            loc: node.loc,            message: "'v-for' directives require that attribute value."          })          return        }        const expr = node.value.expression        if (expr == null) {          return        }        if (expr.type !== 'VForExpression') {          context.report({            node: node.value,            loc: node.value.loc,            message: "'v-for' directives require the special syntax '<alias> in <expression>'."          })          return        }        const lhs = expr.left        const value = lhs[0]        const key = lhs[1]        const index = lhs[2]        if (value === null) {          context.report({            node: value || expr,            loc: value && value.loc,            message: "Invalid alias ''."          })        }        if (key !== undefined && (!key || key.type !== 'Identifier')) {          context.report({            node: key || expr,            loc: key && key.loc,            message: "Invalid alias '{{text}}'.",            data: { text: key ? sourceCode.getText(key) : '' }          })        }        if (index !== undefined && (!index || index.type !== 'Identifier')) {          context.report({            node: index || expr,            loc: index && index.loc,            message: "Invalid alias '{{text}}'.",            data: { text: index ? sourceCode.getText(index) : '' }          })        }      }    })  }}
 |