valid-v-on.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  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. const keyAliases = require('../utils/key-aliases.json')
  12. // ------------------------------------------------------------------------------
  13. // Helpers
  14. // ------------------------------------------------------------------------------
  15. const VALID_MODIFIERS = new Set([
  16. 'stop',
  17. 'prevent',
  18. 'capture',
  19. 'self',
  20. 'ctrl',
  21. 'shift',
  22. 'alt',
  23. 'meta',
  24. 'native',
  25. 'once',
  26. 'left',
  27. 'right',
  28. 'middle',
  29. 'passive',
  30. 'esc',
  31. 'tab',
  32. 'enter',
  33. 'space',
  34. 'up',
  35. 'left',
  36. 'right',
  37. 'down',
  38. 'delete',
  39. 'exact'
  40. ])
  41. const VERB_MODIFIERS = new Set(['stop', 'prevent'])
  42. // https://www.w3.org/TR/uievents-key/
  43. const KEY_ALIASES = new Set(keyAliases)
  44. /**
  45. * @param {VIdentifier} modifierNode
  46. * @param {Set<string>} customModifiers
  47. */
  48. function isValidModifier(modifierNode, customModifiers) {
  49. const modifier = modifierNode.name
  50. return (
  51. // built-in aliases
  52. VALID_MODIFIERS.has(modifier) ||
  53. // keyCode
  54. Number.isInteger(parseInt(modifier, 10)) ||
  55. // keyAlias (an Unicode character)
  56. Array.from(modifier).length === 1 ||
  57. // keyAlias (special keys)
  58. KEY_ALIASES.has(modifier) ||
  59. // custom modifiers
  60. customModifiers.has(modifier)
  61. )
  62. }
  63. // ------------------------------------------------------------------------------
  64. // Rule Definition
  65. // ------------------------------------------------------------------------------
  66. module.exports = {
  67. meta: {
  68. type: 'problem',
  69. docs: {
  70. description: 'enforce valid `v-on` directives',
  71. categories: ['vue3-essential', 'essential'],
  72. url: 'https://eslint.vuejs.org/rules/valid-v-on.html'
  73. },
  74. fixable: null,
  75. schema: [
  76. {
  77. type: 'object',
  78. properties: {
  79. modifiers: {
  80. type: 'array'
  81. }
  82. },
  83. additionalProperties: false
  84. }
  85. ]
  86. },
  87. /** @param {RuleContext} context */
  88. create(context) {
  89. const options = context.options[0] || {}
  90. /** @type {Set<string>} */
  91. const customModifiers = new Set(options.modifiers || [])
  92. const sourceCode = context.getSourceCode()
  93. return utils.defineTemplateBodyVisitor(context, {
  94. /** @param {VDirective} node */
  95. "VAttribute[directive=true][key.name.name='on']"(node) {
  96. for (const modifier of node.key.modifiers) {
  97. if (!isValidModifier(modifier, customModifiers)) {
  98. context.report({
  99. node,
  100. loc: node.loc,
  101. message:
  102. "'v-on' directives don't support the modifier '{{modifier}}'.",
  103. data: { modifier: modifier.name }
  104. })
  105. }
  106. }
  107. if (
  108. (!node.value || !node.value.expression) &&
  109. !node.key.modifiers.some((modifier) =>
  110. VERB_MODIFIERS.has(modifier.name)
  111. )
  112. ) {
  113. if (node.value && !utils.isEmptyValueDirective(node, context)) {
  114. const valueText = sourceCode.getText(node.value)
  115. let innerText = valueText
  116. if (
  117. (valueText[0] === '"' || valueText[0] === "'") &&
  118. valueText[0] === valueText[valueText.length - 1]
  119. ) {
  120. // quoted
  121. innerText = valueText.slice(1, -1)
  122. }
  123. if (/^\w+$/.test(innerText)) {
  124. context.report({
  125. node,
  126. loc: node.loc,
  127. message:
  128. 'Avoid using JavaScript keyword as "v-on" value: {{value}}.',
  129. data: { value: valueText }
  130. })
  131. }
  132. } else {
  133. context.report({
  134. node,
  135. loc: node.loc,
  136. message:
  137. "'v-on' directives require a value or verb modifier (like 'stop' or 'prevent')."
  138. })
  139. }
  140. }
  141. }
  142. })
  143. }
  144. }