valid-v-on.js 3.6 KB

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