no-unused-components.js 4.4 KB

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