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