valid-v-model.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. /**
  2. * @author Toru Nagashima
  3. * @copyright 2017 Toru Nagashima. All rights reserved.
  4. * See LICENSE file in root directory for full license.
  5. */
  6. 'use strict'
  7. // ------------------------------------------------------------------------------
  8. // Requirements
  9. // ------------------------------------------------------------------------------
  10. const utils = require('../utils')
  11. // ------------------------------------------------------------------------------
  12. // Helpers
  13. // ------------------------------------------------------------------------------
  14. const VALID_MODIFIERS = new Set(['lazy', 'number', 'trim'])
  15. /**
  16. * Check whether the given node is valid or not.
  17. * @param {ASTNode} node The element node to check.
  18. * @returns {boolean} `true` if the node is valid.
  19. */
  20. function isValidElement (node) {
  21. const name = node.name
  22. return (
  23. name === 'input' ||
  24. name === 'select' ||
  25. name === 'textarea' ||
  26. (
  27. name !== 'keep-alive' &&
  28. name !== 'slot' &&
  29. name !== 'transition' &&
  30. name !== 'transition-group' &&
  31. utils.isCustomComponent(node)
  32. )
  33. )
  34. }
  35. /**
  36. * Check whether the given node can be LHS.
  37. * @param {ASTNode} node The node to check.
  38. * @returns {boolean} `true` if the node can be LHS.
  39. */
  40. function isLhs (node) {
  41. return node != null && (
  42. node.type === 'Identifier' ||
  43. node.type === 'MemberExpression'
  44. )
  45. }
  46. /**
  47. * Get the variable by names.
  48. * @param {string} name The variable name to find.
  49. * @param {ASTNode} leafNode The node to look up.
  50. * @returns {Variable|null} The found variable or null.
  51. */
  52. function getVariable (name, leafNode) {
  53. let node = leafNode
  54. while (node != null) {
  55. const variables = node.variables
  56. const variable = variables && variables.find(v => v.id.name === name)
  57. if (variable != null) {
  58. return variable
  59. }
  60. node = node.parent
  61. }
  62. return null
  63. }
  64. // ------------------------------------------------------------------------------
  65. // Rule Definition
  66. // ------------------------------------------------------------------------------
  67. module.exports = {
  68. meta: {
  69. docs: {
  70. description: 'enforce valid `v-model` directives',
  71. category: 'essential',
  72. url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v4.7.1/docs/rules/valid-v-model.md'
  73. },
  74. fixable: null,
  75. schema: []
  76. },
  77. create (context) {
  78. return utils.defineTemplateBodyVisitor(context, {
  79. "VAttribute[directive=true][key.name='model']" (node) {
  80. const element = node.parent.parent
  81. const name = element.name
  82. if (!isValidElement(element)) {
  83. context.report({
  84. node,
  85. loc: node.loc,
  86. message: "'v-model' directives aren't supported on <{{name}}> elements.",
  87. data: { name }
  88. })
  89. }
  90. if (name === 'input' && utils.hasAttribute(element, 'type', 'file')) {
  91. context.report({
  92. node,
  93. loc: node.loc,
  94. message: "'v-model' directives don't support 'file' input type."
  95. })
  96. }
  97. if (node.key.argument) {
  98. context.report({
  99. node,
  100. loc: node.loc,
  101. message: "'v-model' directives require no argument."
  102. })
  103. }
  104. for (const modifier of node.key.modifiers) {
  105. if (!VALID_MODIFIERS.has(modifier)) {
  106. context.report({
  107. node,
  108. loc: node.loc,
  109. message: "'v-model' directives don't support the modifier '{{name}}'.",
  110. data: { name: modifier }
  111. })
  112. }
  113. }
  114. if (!utils.hasAttributeValue(node)) {
  115. context.report({
  116. node,
  117. loc: node.loc,
  118. message: "'v-model' directives require that attribute value."
  119. })
  120. }
  121. if (node.value) {
  122. if (!isLhs(node.value.expression)) {
  123. context.report({
  124. node,
  125. loc: node.loc,
  126. message: "'v-model' directives require the attribute value which is valid as LHS."
  127. })
  128. }
  129. for (const reference of node.value.references) {
  130. const id = reference.id
  131. if (id.parent.type === 'MemberExpression' || id.parent.type === 'BinaryExpression') {
  132. continue
  133. }
  134. const variable = getVariable(id.name, element)
  135. if (variable != null) {
  136. context.report({
  137. node,
  138. loc: node.loc,
  139. message: "'v-model' directives cannot update the iteration variable 'x' itself."
  140. })
  141. }
  142. }
  143. }
  144. }
  145. })
  146. }
  147. }