multi-word-component-names.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. /**
  2. * @author Marton Csordas
  3. * See LICENSE file in root directory for full license.
  4. */
  5. 'use strict'
  6. // ------------------------------------------------------------------------------
  7. // Requirements
  8. // ------------------------------------------------------------------------------
  9. const casing = require('../utils/casing')
  10. const utils = require('../utils')
  11. const RESERVED_NAMES_IN_VUE3 = new Set(
  12. require('../utils/vue3-builtin-components')
  13. )
  14. // ------------------------------------------------------------------------------
  15. // Helpers
  16. // ------------------------------------------------------------------------------
  17. /**
  18. * Returns true if the given component name is valid, otherwise false.
  19. * @param {string} name
  20. * */
  21. function isValidComponentName(name) {
  22. if (name.toLowerCase() === 'app' || RESERVED_NAMES_IN_VUE3.has(name)) {
  23. return true
  24. } else {
  25. const elements = casing.kebabCase(name).split('-')
  26. return elements.length > 1
  27. }
  28. }
  29. // ------------------------------------------------------------------------------
  30. // Rule Definition
  31. // ------------------------------------------------------------------------------
  32. module.exports = {
  33. meta: {
  34. type: 'suggestion',
  35. docs: {
  36. description: 'require component names to be always multi-word',
  37. categories: undefined,
  38. url: 'https://eslint.vuejs.org/rules/multi-word-component-names.html'
  39. },
  40. schema: [],
  41. messages: {
  42. unexpected: 'Component name "{{value}}" should always be multi-word.'
  43. }
  44. },
  45. /** @param {RuleContext} context */
  46. create(context) {
  47. const fileName = context.getFilename()
  48. let componentName = fileName.replace(/\.[^/.]+$/, '')
  49. return utils.compositingVisitors(
  50. {
  51. /** @param {Program} node */
  52. Program(node) {
  53. if (
  54. !node.body.length &&
  55. utils.isVueFile(fileName) &&
  56. !isValidComponentName(componentName)
  57. ) {
  58. context.report({
  59. messageId: 'unexpected',
  60. data: {
  61. value: componentName
  62. },
  63. loc: { line: 1, column: 0 }
  64. })
  65. }
  66. }
  67. },
  68. utils.executeOnVue(context, (obj) => {
  69. const node = utils.findProperty(obj, 'name')
  70. /** @type {SourceLocation | null} */
  71. let loc = null
  72. // Check if the component has a name property.
  73. if (node) {
  74. const valueNode = node.value
  75. if (valueNode.type !== 'Literal') return
  76. componentName = `${valueNode.value}`
  77. loc = node.loc
  78. } else if (
  79. obj.parent.type === 'CallExpression' &&
  80. obj.parent.arguments.length === 2
  81. ) {
  82. // The component is registered globally with 'Vue.component', where
  83. // the first paremter is the component name.
  84. const argument = obj.parent.arguments[0]
  85. if (argument.type !== 'Literal') return
  86. componentName = `${argument.value}`
  87. loc = argument.loc
  88. }
  89. if (!isValidComponentName(componentName)) {
  90. context.report({
  91. messageId: 'unexpected',
  92. data: {
  93. value: componentName
  94. },
  95. loc: loc || { line: 1, column: 0 }
  96. })
  97. }
  98. })
  99. )
  100. }
  101. }