valid-v-on.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  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. messages: {
  87. unsupportedModifier:
  88. "'v-on' directives don't support the modifier '{{modifier}}'.",
  89. avoidKeyword:
  90. 'Avoid using JavaScript keyword as "v-on" value: {{value}}.',
  91. expectedValueOrVerb:
  92. "'v-on' directives require a value or verb modifier (like 'stop' or 'prevent')."
  93. }
  94. },
  95. /** @param {RuleContext} context */
  96. create(context) {
  97. const options = context.options[0] || {}
  98. /** @type {Set<string>} */
  99. const customModifiers = new Set(options.modifiers || [])
  100. const sourceCode = context.getSourceCode()
  101. return utils.defineTemplateBodyVisitor(context, {
  102. /** @param {VDirective} node */
  103. "VAttribute[directive=true][key.name.name='on']"(node) {
  104. for (const modifier of node.key.modifiers) {
  105. if (!isValidModifier(modifier, customModifiers)) {
  106. context.report({
  107. node: modifier,
  108. messageId: 'unsupportedModifier',
  109. data: { modifier: modifier.name }
  110. })
  111. }
  112. }
  113. if (
  114. (!node.value || !node.value.expression) &&
  115. !node.key.modifiers.some((modifier) =>
  116. VERB_MODIFIERS.has(modifier.name)
  117. )
  118. ) {
  119. if (node.value && !utils.isEmptyValueDirective(node, context)) {
  120. const valueText = sourceCode.getText(node.value)
  121. let innerText = valueText
  122. if (
  123. (valueText[0] === '"' || valueText[0] === "'") &&
  124. valueText[0] === valueText[valueText.length - 1]
  125. ) {
  126. // quoted
  127. innerText = valueText.slice(1, -1)
  128. }
  129. if (/^\w+$/.test(innerText)) {
  130. context.report({
  131. node: node.value,
  132. messageId: 'avoidKeyword',
  133. data: { value: valueText }
  134. })
  135. }
  136. } else {
  137. context.report({
  138. node,
  139. messageId: 'expectedValueOrVerb'
  140. })
  141. }
  142. }
  143. }
  144. })
  145. }
  146. }