no-reserved-component-names.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. /**
  2. * @fileoverview disallow the use of reserved names in component definitions
  3. * @author Jake Hassel <https://github.com/shadskii>
  4. */
  5. 'use strict'
  6. const utils = require('../utils')
  7. const casing = require('../utils/casing')
  8. const htmlElements = require('../utils/html-elements.json')
  9. const deprecatedHtmlElements = require('../utils/deprecated-html-elements.json')
  10. const svgElements = require('../utils/svg-elements.json')
  11. const RESERVED_NAMES_IN_VUE = new Set(
  12. require('../utils/vue2-builtin-components')
  13. )
  14. const RESERVED_NAMES_IN_VUE3 = new Set(
  15. require('../utils/vue3-builtin-components')
  16. )
  17. const kebabCaseElements = [
  18. 'annotation-xml',
  19. 'color-profile',
  20. 'font-face',
  21. 'font-face-src',
  22. 'font-face-uri',
  23. 'font-face-format',
  24. 'font-face-name',
  25. 'missing-glyph'
  26. ]
  27. /** @param {string} word */
  28. function isLowercase(word) {
  29. return /^[a-z]*$/.test(word)
  30. }
  31. const RESERVED_NAMES_IN_HTML = new Set([
  32. ...htmlElements,
  33. ...htmlElements.map(casing.capitalize)
  34. ])
  35. const RESERVED_NAMES_IN_OTHERS = new Set([
  36. ...deprecatedHtmlElements,
  37. ...deprecatedHtmlElements.map(casing.capitalize),
  38. ...kebabCaseElements,
  39. ...kebabCaseElements.map(casing.pascalCase),
  40. ...svgElements,
  41. ...svgElements.filter(isLowercase).map(casing.capitalize)
  42. ])
  43. // ------------------------------------------------------------------------------
  44. // Rule Definition
  45. // ------------------------------------------------------------------------------
  46. module.exports = {
  47. meta: {
  48. type: 'suggestion',
  49. docs: {
  50. description:
  51. 'disallow the use of reserved names in component definitions',
  52. categories: undefined,
  53. url: 'https://eslint.vuejs.org/rules/no-reserved-component-names.html'
  54. },
  55. fixable: null,
  56. schema: [
  57. {
  58. type: 'object',
  59. properties: {
  60. disallowVueBuiltInComponents: {
  61. type: 'boolean'
  62. },
  63. disallowVue3BuiltInComponents: {
  64. type: 'boolean'
  65. }
  66. },
  67. additionalProperties: false
  68. }
  69. ],
  70. messages: {
  71. reserved: 'Name "{{name}}" is reserved.',
  72. reservedInHtml: 'Name "{{name}}" is reserved in HTML.',
  73. reservedInVue: 'Name "{{name}}" is reserved in Vue.js.',
  74. reservedInVue3: 'Name "{{name}}" is reserved in Vue.js 3.x.'
  75. }
  76. },
  77. /** @param {RuleContext} context */
  78. create(context) {
  79. const options = context.options[0] || {}
  80. const disallowVueBuiltInComponents =
  81. options.disallowVueBuiltInComponents === true
  82. const disallowVue3BuiltInComponents =
  83. options.disallowVue3BuiltInComponents === true
  84. const reservedNames = new Set([
  85. ...RESERVED_NAMES_IN_HTML,
  86. ...(disallowVueBuiltInComponents ? RESERVED_NAMES_IN_VUE : []),
  87. ...(disallowVue3BuiltInComponents ? RESERVED_NAMES_IN_VUE3 : []),
  88. ...RESERVED_NAMES_IN_OTHERS
  89. ])
  90. /**
  91. * @param {Expression | SpreadElement} node
  92. * @returns {node is (Literal | TemplateLiteral)}
  93. */
  94. function canVerify(node) {
  95. return (
  96. node.type === 'Literal' ||
  97. (node.type === 'TemplateLiteral' &&
  98. node.expressions.length === 0 &&
  99. node.quasis.length === 1)
  100. )
  101. }
  102. /**
  103. * @param {Literal | TemplateLiteral} node
  104. */
  105. function reportIfInvalid(node) {
  106. let name
  107. if (node.type === 'TemplateLiteral') {
  108. const quasis = node.quasis[0]
  109. name = quasis.value.cooked
  110. } else {
  111. name = `${node.value}`
  112. }
  113. if (reservedNames.has(name)) {
  114. report(node, name)
  115. }
  116. }
  117. /**
  118. * @param {ESNode} node
  119. * @param {string} name
  120. */
  121. function report(node, name) {
  122. context.report({
  123. node,
  124. messageId: RESERVED_NAMES_IN_HTML.has(name)
  125. ? 'reservedInHtml'
  126. : RESERVED_NAMES_IN_VUE.has(name)
  127. ? 'reservedInVue'
  128. : RESERVED_NAMES_IN_VUE3.has(name)
  129. ? 'reservedInVue3'
  130. : 'reserved',
  131. data: {
  132. name
  133. }
  134. })
  135. }
  136. return Object.assign(
  137. {},
  138. utils.executeOnCallVueComponent(context, (node) => {
  139. if (node.arguments.length === 2) {
  140. const argument = node.arguments[0]
  141. if (canVerify(argument)) {
  142. reportIfInvalid(argument)
  143. }
  144. }
  145. }),
  146. utils.executeOnVue(context, (obj) => {
  147. // Report if a component has been registered locally with a reserved name.
  148. utils
  149. .getRegisteredComponents(obj)
  150. .filter(({ name }) => reservedNames.has(name))
  151. .forEach(({ node, name }) => report(node, name))
  152. const node = utils.findProperty(obj, 'name')
  153. if (!node) return
  154. if (!canVerify(node.value)) return
  155. reportIfInvalid(node.value)
  156. })
  157. )
  158. }
  159. }