no-reserved-component-names.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  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. * @param {Expression | SpreadElement} node
  45. * @returns {node is (Literal | TemplateLiteral)}
  46. */
  47. function canVerify(node) {
  48. return (
  49. node.type === 'Literal' ||
  50. (node.type === 'TemplateLiteral' &&
  51. node.expressions.length === 0 &&
  52. node.quasis.length === 1)
  53. )
  54. }
  55. /**
  56. * @param {string} name
  57. * @returns {string}
  58. */
  59. function getMessageId(name) {
  60. if (RESERVED_NAMES_IN_HTML.has(name)) return 'reservedInHtml'
  61. if (RESERVED_NAMES_IN_VUE.has(name)) return 'reservedInVue'
  62. if (RESERVED_NAMES_IN_VUE3.has(name)) return 'reservedInVue3'
  63. return 'reserved'
  64. }
  65. module.exports = {
  66. meta: {
  67. type: 'suggestion',
  68. docs: {
  69. description:
  70. 'disallow the use of reserved names in component definitions',
  71. categories: ['vue3-essential', 'vue2-essential'],
  72. url: 'https://eslint.vuejs.org/rules/no-reserved-component-names.html'
  73. },
  74. fixable: null,
  75. schema: [
  76. {
  77. type: 'object',
  78. properties: {
  79. disallowVueBuiltInComponents: {
  80. type: 'boolean'
  81. },
  82. disallowVue3BuiltInComponents: {
  83. type: 'boolean'
  84. }
  85. },
  86. additionalProperties: false
  87. }
  88. ],
  89. messages: {
  90. reserved: 'Name "{{name}}" is reserved.',
  91. reservedInHtml: 'Name "{{name}}" is reserved in HTML.',
  92. reservedInVue: 'Name "{{name}}" is reserved in Vue.js.',
  93. reservedInVue3: 'Name "{{name}}" is reserved in Vue.js 3.x.'
  94. }
  95. },
  96. /** @param {RuleContext} context */
  97. create(context) {
  98. const options = context.options[0] || {}
  99. const disallowVueBuiltInComponents =
  100. options.disallowVueBuiltInComponents === true
  101. const disallowVue3BuiltInComponents =
  102. options.disallowVue3BuiltInComponents === true
  103. const reservedNames = new Set([
  104. ...RESERVED_NAMES_IN_HTML,
  105. ...(disallowVueBuiltInComponents ? RESERVED_NAMES_IN_VUE : []),
  106. ...(disallowVue3BuiltInComponents ? RESERVED_NAMES_IN_VUE3 : []),
  107. ...RESERVED_NAMES_IN_OTHERS
  108. ])
  109. /**
  110. * @param {Literal | TemplateLiteral} node
  111. */
  112. function reportIfInvalid(node) {
  113. let name
  114. if (node.type === 'TemplateLiteral') {
  115. const quasis = node.quasis[0]
  116. name = quasis.value.cooked
  117. } else {
  118. name = `${node.value}`
  119. }
  120. if (reservedNames.has(name)) {
  121. report(node, name)
  122. }
  123. }
  124. /**
  125. * @param {ESNode} node
  126. * @param {string} name
  127. */
  128. function report(node, name) {
  129. context.report({
  130. node,
  131. messageId: getMessageId(name),
  132. data: {
  133. name
  134. }
  135. })
  136. }
  137. return utils.compositingVisitors(
  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. for (const { node, name } of utils.getRegisteredComponents(obj)) {
  149. if (reservedNames.has(name)) {
  150. report(node, name)
  151. }
  152. }
  153. const node = utils.findProperty(obj, 'name')
  154. if (!node) return
  155. if (!canVerify(node.value)) return
  156. reportIfInvalid(node.value)
  157. }),
  158. utils.defineScriptSetupVisitor(context, {
  159. onDefineOptionsEnter(node) {
  160. if (node.arguments.length === 0) return
  161. const define = node.arguments[0]
  162. if (define.type !== 'ObjectExpression') return
  163. const nameNode = utils.findProperty(define, 'name')
  164. if (!nameNode) return
  165. if (!canVerify(nameNode.value)) return
  166. reportIfInvalid(nameNode.value)
  167. }
  168. })
  169. )
  170. }
  171. }