v-on-event-hyphenation.js 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. 'use strict'
  2. const utils = require('../utils')
  3. const casing = require('../utils/casing')
  4. module.exports = {
  5. meta: {
  6. docs: {
  7. description:
  8. 'enforce v-on event naming style on custom components in template',
  9. categories: ['vue3-strongly-recommended'],
  10. url: 'https://eslint.vuejs.org/rules/v-on-event-hyphenation.html'
  11. },
  12. fixable: 'code',
  13. schema: [
  14. {
  15. enum: ['always', 'never']
  16. },
  17. {
  18. type: 'object',
  19. properties: {
  20. autofix: { type: 'boolean' },
  21. ignore: {
  22. type: 'array',
  23. items: {
  24. allOf: [
  25. { type: 'string' },
  26. { not: { type: 'string', pattern: ':exit$' } },
  27. { not: { type: 'string', pattern: '^\\s*$' } }
  28. ]
  29. },
  30. uniqueItems: true,
  31. additionalItems: false
  32. }
  33. },
  34. additionalProperties: false
  35. }
  36. ],
  37. type: 'suggestion'
  38. },
  39. /** @param {RuleContext} context */
  40. create(context) {
  41. const sourceCode = context.getSourceCode()
  42. const option = context.options[0]
  43. const optionsPayload = context.options[1]
  44. const useHyphenated = option !== 'never'
  45. /** @type {string[]} */
  46. const ignoredAttributes = (optionsPayload && optionsPayload.ignore) || []
  47. const autofix = Boolean(optionsPayload && optionsPayload.autofix)
  48. const caseConverter = casing.getConverter(
  49. useHyphenated ? 'kebab-case' : 'camelCase'
  50. )
  51. /**
  52. * @param {VDirective} node
  53. * @param {VIdentifier} argument
  54. * @param {string} name
  55. */
  56. function reportIssue(node, argument, name) {
  57. const text = sourceCode.getText(node.key)
  58. context.report({
  59. node: node.key,
  60. loc: node.loc,
  61. message: useHyphenated
  62. ? "v-on event '{{text}}' must be hyphenated."
  63. : "v-on event '{{text}}' can't be hyphenated.",
  64. data: {
  65. text
  66. },
  67. fix:
  68. autofix &&
  69. // It cannot be converted in snake_case.
  70. !name.includes('_')
  71. ? (fixer) => {
  72. return fixer.replaceText(argument, caseConverter(name))
  73. }
  74. : null
  75. })
  76. }
  77. /**
  78. * @param {string} value
  79. */
  80. function isIgnoredAttribute(value) {
  81. const isIgnored = ignoredAttributes.some((attr) => {
  82. return value.includes(attr)
  83. })
  84. if (isIgnored) {
  85. return true
  86. }
  87. return useHyphenated ? value.toLowerCase() === value : !/-/.test(value)
  88. }
  89. return utils.defineTemplateBodyVisitor(context, {
  90. "VAttribute[directive=true][key.name.name='on']"(node) {
  91. if (!utils.isCustomComponent(node.parent.parent)) return
  92. if (!node.key.argument || node.key.argument.type !== 'VIdentifier') {
  93. return
  94. }
  95. const name = node.key.argument.rawName
  96. if (!name || isIgnoredAttribute(name)) return
  97. reportIssue(node, node.key.argument, name)
  98. }
  99. })
  100. }
  101. }