v-on-event-hyphenation.js 3.0 KB

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