attribute-hyphenation.js 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. /**
  2. * @fileoverview Define a style for the props casing in templates.
  3. * @author Armano
  4. */
  5. 'use strict'
  6. const utils = require('../utils')
  7. const casing = require('../utils/casing')
  8. // ------------------------------------------------------------------------------
  9. // Rule Definition
  10. // ------------------------------------------------------------------------------
  11. module.exports = {
  12. meta: {
  13. type: 'suggestion',
  14. docs: {
  15. description: 'enforce attribute naming style on custom components in template',
  16. category: 'strongly-recommended',
  17. url: 'https://eslint.vuejs.org/rules/attribute-hyphenation.html'
  18. },
  19. fixable: 'code',
  20. schema: [
  21. {
  22. enum: ['always', 'never']
  23. },
  24. {
  25. type: 'object',
  26. properties: {
  27. 'ignore': {
  28. type: 'array',
  29. items: {
  30. allOf: [
  31. { type: 'string' },
  32. { not: { type: 'string', pattern: ':exit$' }},
  33. { not: { type: 'string', pattern: '^\\s*$' }}
  34. ]
  35. },
  36. uniqueItems: true,
  37. additionalItems: false
  38. }
  39. },
  40. additionalProperties: false
  41. }
  42. ]
  43. },
  44. create (context) {
  45. const sourceCode = context.getSourceCode()
  46. const option = context.options[0]
  47. const optionsPayload = context.options[1]
  48. const useHyphenated = option !== 'never'
  49. let ignoredAttributes = ['data-', 'aria-', 'slot-scope']
  50. if (optionsPayload && optionsPayload.ignore) {
  51. ignoredAttributes = ignoredAttributes.concat(optionsPayload.ignore)
  52. }
  53. const caseConverter = casing.getConverter(useHyphenated ? 'kebab-case' : 'camelCase')
  54. function reportIssue (node, name) {
  55. const text = sourceCode.getText(node.key)
  56. context.report({
  57. node: node.key,
  58. loc: node.loc,
  59. message: useHyphenated ? "Attribute '{{text}}' must be hyphenated." : "Attribute '{{text}}' can't be hyphenated.",
  60. data: {
  61. text
  62. },
  63. fix: fixer => fixer.replaceText(node.key, text.replace(name, caseConverter(name)))
  64. })
  65. }
  66. function isIgnoredAttribute (value) {
  67. const isIgnored = ignoredAttributes.some(function (attr) {
  68. return value.indexOf(attr) !== -1
  69. })
  70. if (isIgnored) {
  71. return true
  72. }
  73. return useHyphenated ? value.toLowerCase() === value : !/-/.test(value)
  74. }
  75. // ----------------------------------------------------------------------
  76. // Public
  77. // ----------------------------------------------------------------------
  78. return utils.defineTemplateBodyVisitor(context, {
  79. VAttribute (node) {
  80. if (!utils.isCustomComponent(node.parent.parent)) return
  81. const name = !node.directive ? node.key.rawName : node.key.name === 'bind' ? node.key.raw.argument : false
  82. if (!name || isIgnoredAttribute(name)) return
  83. reportIssue(node, name)
  84. }
  85. })
  86. }
  87. }