no-unused-components.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  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. },
  30. /** @param {RuleContext} context */
  31. create(context) {
  32. const options = context.options[0] || {}
  33. const ignoreWhenBindingPresent =
  34. options.ignoreWhenBindingPresent !== undefined
  35. ? options.ignoreWhenBindingPresent
  36. : true
  37. const usedComponents = new Set()
  38. /** @type { { node: Property, name: string }[] } */
  39. let registeredComponents = []
  40. let ignoreReporting = false
  41. /** @type {Position} */
  42. let templateLocation
  43. return utils.defineTemplateBodyVisitor(
  44. context,
  45. {
  46. /** @param {VElement} node */
  47. VElement(node) {
  48. if (
  49. (!utils.isHtmlElementNode(node) && !utils.isSvgElementNode(node)) ||
  50. utils.isHtmlWellKnownElementName(node.rawName) ||
  51. utils.isSvgWellKnownElementName(node.rawName)
  52. ) {
  53. return
  54. }
  55. usedComponents.add(node.rawName)
  56. },
  57. /** @param {VDirective} node */
  58. "VAttribute[directive=true][key.name.name='bind'][key.argument.name='is'], VAttribute[directive=true][key.name.name='is']"(
  59. node
  60. ) {
  61. if (
  62. !node.value || // `<component :is>`
  63. node.value.type !== 'VExpressionContainer' ||
  64. !node.value.expression // `<component :is="">`
  65. )
  66. return
  67. if (node.value.expression.type === 'Literal') {
  68. usedComponents.add(node.value.expression.value)
  69. } else if (ignoreWhenBindingPresent) {
  70. ignoreReporting = true
  71. }
  72. },
  73. /** @param {VAttribute} node */
  74. "VAttribute[directive=false][key.name='is']"(node) {
  75. if (!node.value) {
  76. return
  77. }
  78. const value = node.value.value.startsWith('vue:') // Usage on native elements 3.1+
  79. ? node.value.value.slice(4)
  80. : node.value.value
  81. usedComponents.add(value)
  82. },
  83. /** @param {VElement} node */
  84. "VElement[name='template']"(node) {
  85. templateLocation = templateLocation || node.loc.start
  86. },
  87. /** @param {VElement} node */
  88. "VElement[name='template']:exit"(node) {
  89. if (
  90. node.loc.start !== templateLocation ||
  91. ignoreReporting ||
  92. utils.hasAttribute(node, 'src')
  93. )
  94. return
  95. for (const { node, name } of registeredComponents) {
  96. // If the component name is PascalCase or camelCase
  97. // it can be used in various of ways inside template,
  98. // like "theComponent", "The-component" etc.
  99. // but except snake_case
  100. if (casing.isPascalCase(name) || casing.isCamelCase(name)) {
  101. if (
  102. [...usedComponents].some(
  103. (n) =>
  104. !n.includes('_') &&
  105. (name === casing.pascalCase(n) ||
  106. name === casing.camelCase(n))
  107. )
  108. ) {
  109. continue
  110. }
  111. } else {
  112. // In any other case the used component name must exactly match
  113. // the registered name
  114. if (usedComponents.has(name)) {
  115. continue
  116. }
  117. }
  118. context.report({
  119. node,
  120. message:
  121. 'The "{{name}}" component has been registered but not used.',
  122. data: {
  123. name
  124. }
  125. })
  126. }
  127. }
  128. },
  129. utils.executeOnVue(context, (obj) => {
  130. registeredComponents = utils.getRegisteredComponents(obj)
  131. })
  132. )
  133. }
  134. }