no-unused-components.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. /**
  2. * @fileoverview Report used components
  3. * @author Michał Sajnóg
  4. */
  5. 'use strict'
  6. const utils = require('../utils')
  7. const casing = require('../utils/casing')
  8. module.exports = {
  9. meta: {
  10. type: 'suggestion',
  11. docs: {
  12. description:
  13. 'disallow registering components that are not used inside templates',
  14. categories: ['vue3-essential', 'essential'],
  15. url: 'https://eslint.vuejs.org/rules/no-unused-components.html'
  16. },
  17. fixable: null,
  18. schema: [
  19. {
  20. type: 'object',
  21. properties: {
  22. ignoreWhenBindingPresent: {
  23. type: 'boolean'
  24. }
  25. },
  26. additionalProperties: false
  27. }
  28. ],
  29. messages: {
  30. unused: 'The "{{name}}" component has been registered but not used.'
  31. }
  32. },
  33. /** @param {RuleContext} context */
  34. create(context) {
  35. const options = context.options[0] || {}
  36. const ignoreWhenBindingPresent =
  37. options.ignoreWhenBindingPresent === undefined
  38. ? true
  39. : options.ignoreWhenBindingPresent
  40. const usedComponents = new Set()
  41. /** @type { { node: Property, name: string }[] } */
  42. let registeredComponents = []
  43. let ignoreReporting = false
  44. /** @type {Position} */
  45. let templateLocation
  46. return utils.defineTemplateBodyVisitor(
  47. context,
  48. {
  49. /** @param {VElement} node */
  50. VElement(node) {
  51. if (
  52. (!utils.isHtmlElementNode(node) && !utils.isSvgElementNode(node)) ||
  53. utils.isHtmlWellKnownElementName(node.rawName) ||
  54. utils.isSvgWellKnownElementName(node.rawName)
  55. ) {
  56. return
  57. }
  58. usedComponents.add(node.rawName)
  59. },
  60. /** @param {VDirective} node */
  61. "VAttribute[directive=true][key.name.name='bind'][key.argument.name='is'], VAttribute[directive=true][key.name.name='is']"(
  62. node
  63. ) {
  64. if (
  65. !node.value || // `<component :is>`
  66. node.value.type !== 'VExpressionContainer' ||
  67. !node.value.expression // `<component :is="">`
  68. )
  69. return
  70. if (node.value.expression.type === 'Literal') {
  71. usedComponents.add(node.value.expression.value)
  72. } else if (ignoreWhenBindingPresent) {
  73. ignoreReporting = true
  74. }
  75. },
  76. /** @param {VAttribute} node */
  77. "VAttribute[directive=false][key.name='is']"(node) {
  78. if (!node.value) {
  79. return
  80. }
  81. const value = node.value.value.startsWith('vue:') // Usage on native elements 3.1+
  82. ? node.value.value.slice(4)
  83. : node.value.value
  84. usedComponents.add(value)
  85. },
  86. /** @param {VElement} node */
  87. "VElement[name='template']"(node) {
  88. templateLocation = templateLocation || node.loc.start
  89. },
  90. /** @param {VElement} node */
  91. "VElement[name='template']:exit"(node) {
  92. if (
  93. node.loc.start !== templateLocation ||
  94. ignoreReporting ||
  95. utils.hasAttribute(node, 'src')
  96. )
  97. return
  98. for (const { node, name } of registeredComponents) {
  99. // If the component name is PascalCase or camelCase
  100. // it can be used in various of ways inside template,
  101. // like "theComponent", "The-component" etc.
  102. // but except snake_case
  103. if (casing.isPascalCase(name) || casing.isCamelCase(name)) {
  104. if (
  105. [...usedComponents].some(
  106. (n) =>
  107. !n.includes('_') &&
  108. (name === casing.pascalCase(n) ||
  109. name === casing.camelCase(n))
  110. )
  111. ) {
  112. continue
  113. }
  114. } else {
  115. // In any other case the used component name must exactly match
  116. // the registered name
  117. if (usedComponents.has(name)) {
  118. continue
  119. }
  120. }
  121. context.report({
  122. node,
  123. messageId: 'unused',
  124. data: {
  125. name
  126. }
  127. })
  128. }
  129. }
  130. },
  131. utils.executeOnVue(context, (obj) => {
  132. registeredComponents = utils.getRegisteredComponents(obj)
  133. })
  134. )
  135. }
  136. }