no-reserved-component-names.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  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. /**
  32. * @param {Expression | SpreadElement} node
  33. * @returns {node is (Literal | TemplateLiteral)}
  34. */
  35. function canVerify(node) {
  36. return (
  37. node.type === 'Literal' ||
  38. (node.type === 'TemplateLiteral' &&
  39. node.expressions.length === 0 &&
  40. node.quasis.length === 1)
  41. )
  42. }
  43. /**
  44. * @template T
  45. * @param {Set<T>} set
  46. * @param {Iterable<T>} iterable
  47. */
  48. function addAll(set, iterable) {
  49. for (const element of iterable) {
  50. set.add(element)
  51. }
  52. }
  53. module.exports = {
  54. meta: {
  55. type: 'suggestion',
  56. docs: {
  57. description:
  58. 'disallow the use of reserved names in component definitions',
  59. categories: ['vue3-essential', 'vue2-essential'],
  60. url: 'https://eslint.vuejs.org/rules/no-reserved-component-names.html'
  61. },
  62. fixable: null,
  63. schema: [
  64. {
  65. type: 'object',
  66. properties: {
  67. disallowVueBuiltInComponents: {
  68. type: 'boolean'
  69. },
  70. disallowVue3BuiltInComponents: {
  71. type: 'boolean'
  72. },
  73. htmlElementCaseSensitive: {
  74. type: 'boolean'
  75. }
  76. },
  77. additionalProperties: false
  78. }
  79. ],
  80. messages: {
  81. reserved: 'Name "{{name}}" is reserved.',
  82. reservedInHtml: 'Name "{{name}}" is reserved in HTML.',
  83. reservedInVue: 'Name "{{name}}" is reserved in Vue.js.',
  84. reservedInVue3: 'Name "{{name}}" is reserved in Vue.js 3.x.'
  85. }
  86. },
  87. /** @param {RuleContext} context */
  88. create(context) {
  89. const options = context.options[0] || {}
  90. const disallowVueBuiltInComponents =
  91. options.disallowVueBuiltInComponents === true
  92. const disallowVue3BuiltInComponents =
  93. options.disallowVue3BuiltInComponents === true
  94. const htmlElementCaseSensitive = options.htmlElementCaseSensitive === true
  95. const RESERVED_NAMES_IN_HTML = new Set(htmlElements)
  96. const RESERVED_NAMES_IN_OTHERS = new Set([
  97. ...deprecatedHtmlElements,
  98. ...kebabCaseElements,
  99. ...svgElements
  100. ])
  101. if (!htmlElementCaseSensitive) {
  102. addAll(RESERVED_NAMES_IN_HTML, htmlElements.map(casing.capitalize))
  103. addAll(RESERVED_NAMES_IN_OTHERS, [
  104. ...deprecatedHtmlElements.map(casing.capitalize),
  105. ...kebabCaseElements.map(casing.pascalCase),
  106. ...svgElements.filter(isLowercase).map(casing.capitalize)
  107. ])
  108. }
  109. const reservedNames = new Set([
  110. ...RESERVED_NAMES_IN_HTML,
  111. ...(disallowVueBuiltInComponents ? RESERVED_NAMES_IN_VUE : []),
  112. ...(disallowVue3BuiltInComponents ? RESERVED_NAMES_IN_VUE3 : []),
  113. ...RESERVED_NAMES_IN_OTHERS
  114. ])
  115. /**
  116. * @param {string} name
  117. * @returns {string}
  118. */
  119. function getMessageId(name) {
  120. if (RESERVED_NAMES_IN_HTML.has(name)) return 'reservedInHtml'
  121. if (RESERVED_NAMES_IN_VUE.has(name)) return 'reservedInVue'
  122. if (RESERVED_NAMES_IN_VUE3.has(name)) return 'reservedInVue3'
  123. return 'reserved'
  124. }
  125. /**
  126. * @param {Literal | TemplateLiteral} node
  127. */
  128. function reportIfInvalid(node) {
  129. let name
  130. if (node.type === 'TemplateLiteral') {
  131. const quasis = node.quasis[0]
  132. name = quasis.value.cooked
  133. } else {
  134. name = `${node.value}`
  135. }
  136. if (reservedNames.has(name)) {
  137. report(node, name)
  138. }
  139. }
  140. /**
  141. * @param {ESNode} node
  142. * @param {string} name
  143. */
  144. function report(node, name) {
  145. context.report({
  146. node,
  147. messageId: getMessageId(name),
  148. data: {
  149. name
  150. }
  151. })
  152. }
  153. return utils.compositingVisitors(
  154. utils.executeOnCallVueComponent(context, (node) => {
  155. if (node.arguments.length === 2) {
  156. const argument = node.arguments[0]
  157. if (canVerify(argument)) {
  158. reportIfInvalid(argument)
  159. }
  160. }
  161. }),
  162. utils.executeOnVue(context, (obj) => {
  163. // Report if a component has been registered locally with a reserved name.
  164. for (const { node, name } of utils.getRegisteredComponents(obj)) {
  165. if (reservedNames.has(name)) {
  166. report(node, name)
  167. }
  168. }
  169. const node = utils.findProperty(obj, 'name')
  170. if (!node) return
  171. if (!canVerify(node.value)) return
  172. reportIfInvalid(node.value)
  173. }),
  174. utils.defineScriptSetupVisitor(context, {
  175. onDefineOptionsEnter(node) {
  176. if (node.arguments.length === 0) return
  177. const define = node.arguments[0]
  178. if (define.type !== 'ObjectExpression') return
  179. const nameNode = utils.findProperty(define, 'name')
  180. if (!nameNode) return
  181. if (!canVerify(nameNode.value)) return
  182. reportIfInvalid(nameNode.value)
  183. }
  184. })
  185. )
  186. }
  187. }