| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167 | /** * @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// ------------------------------------------------------------------------------const VALID_MODIFIERS = new Set(['lazy', 'number', 'trim'])/** * Check whether the given node is valid or not. * @param {ASTNode} node The element node to check. * @returns {boolean} `true` if the node is valid. */function isValidElement (node) {  const name = node.name  return (    name === 'input' ||    name === 'select' ||    name === 'textarea' ||    (      name !== 'keep-alive' &&      name !== 'slot' &&      name !== 'transition' &&      name !== 'transition-group' &&      utils.isCustomComponent(node)    )  )}/** * Check whether the given node can be LHS. * @param {ASTNode} node The node to check. * @returns {boolean} `true` if the node can be LHS. */function isLhs (node) {  return node != null && (    node.type === 'Identifier' ||    node.type === 'MemberExpression'  )}/** * Get the variable by names. * @param {string} name The variable name to find. * @param {ASTNode} leafNode The node to look up. * @returns {Variable|null} The found variable or null. */function getVariable (name, leafNode) {  let node = leafNode  while (node != null) {    const variables = node.variables    const variable = variables && variables.find(v => v.id.name === name)    if (variable != null) {      return variable    }    node = node.parent  }  return null}// ------------------------------------------------------------------------------// Rule Definition// ------------------------------------------------------------------------------module.exports = {  meta: {    docs: {      description: 'enforce valid `v-model` directives',      category: 'essential',      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.7.1/docs/rules/valid-v-model.md'    },    fixable: null,    schema: []  },  create (context) {    return utils.defineTemplateBodyVisitor(context, {      "VAttribute[directive=true][key.name='model']" (node) {        const element = node.parent.parent        const name = element.name        if (!isValidElement(element)) {          context.report({            node,            loc: node.loc,            message: "'v-model' directives aren't supported on <{{name}}> elements.",            data: { name }          })        }        if (name === 'input' && utils.hasAttribute(element, 'type', 'file')) {          context.report({            node,            loc: node.loc,            message: "'v-model' directives don't support 'file' input type."          })        }        if (node.key.argument) {          context.report({            node,            loc: node.loc,            message: "'v-model' directives require no argument."          })        }        for (const modifier of node.key.modifiers) {          if (!VALID_MODIFIERS.has(modifier)) {            context.report({              node,              loc: node.loc,              message: "'v-model' directives don't support the modifier '{{name}}'.",              data: { name: modifier }            })          }        }        if (!utils.hasAttributeValue(node)) {          context.report({            node,            loc: node.loc,            message: "'v-model' directives require that attribute value."          })        }        if (node.value) {          if (!isLhs(node.value.expression)) {            context.report({              node,              loc: node.loc,              message: "'v-model' directives require the attribute value which is valid as LHS."            })          }          for (const reference of node.value.references) {            const id = reference.id            if (id.parent.type === 'MemberExpression' || id.parent.type === 'BinaryExpression') {              continue            }            const variable = getVariable(id.name, element)            if (variable != null) {              context.report({                node,                loc: node.loc,                message: "'v-model' directives cannot update the iteration variable 'x' itself."              })            }          }        }      }    })  }}
 |