no-unused-components.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. /**
  2. * @fileoverview Report used components
  3. * @author Michał Sajnóg
  4. */
  5. 'use strict'
  6. // ------------------------------------------------------------------------------
  7. // Requirements
  8. // ------------------------------------------------------------------------------
  9. const utils = require('../utils')
  10. const casing = require('../utils/casing')
  11. // ------------------------------------------------------------------------------
  12. // Rule Definition
  13. // ------------------------------------------------------------------------------
  14. module.exports = {
  15. meta: {
  16. type: 'suggestion',
  17. docs: {
  18. description: 'disallow registering components that are not used inside templates',
  19. category: 'essential',
  20. url: 'https://eslint.vuejs.org/rules/no-unused-components.html'
  21. },
  22. fixable: null,
  23. schema: [{
  24. type: 'object',
  25. properties: {
  26. ignoreWhenBindingPresent: {
  27. type: 'boolean'
  28. }
  29. },
  30. additionalProperties: false
  31. }]
  32. },
  33. create (context) {
  34. const options = context.options[0] || {}
  35. const ignoreWhenBindingPresent = options.ignoreWhenBindingPresent !== undefined ? options.ignoreWhenBindingPresent : true
  36. const usedComponents = new Set()
  37. let registeredComponents = []
  38. let ignoreReporting = false
  39. let templateLocation
  40. return utils.defineTemplateBodyVisitor(context, {
  41. VElement (node) {
  42. if (
  43. (!utils.isHtmlElementNode(node) && !utils.isSvgElementNode(node)) ||
  44. utils.isHtmlWellKnownElementName(node.rawName) ||
  45. utils.isSvgWellKnownElementName(node.rawName)
  46. ) {
  47. return
  48. }
  49. usedComponents.add(node.rawName)
  50. },
  51. "VAttribute[directive=true][key.name='bind'][key.argument='is']" (node) {
  52. if (
  53. !node.value || // `<component :is>`
  54. node.value.type !== 'VExpressionContainer' ||
  55. !node.value.expression // `<component :is="">`
  56. ) return
  57. if (node.value.expression.type === 'Literal') {
  58. usedComponents.add(node.value.expression.value)
  59. } else if (ignoreWhenBindingPresent) {
  60. ignoreReporting = true
  61. }
  62. },
  63. "VAttribute[directive=false][key.name='is']" (node) {
  64. usedComponents.add(node.value.value)
  65. },
  66. "VElement[name='template']" (rootNode) {
  67. templateLocation = templateLocation || rootNode.loc.start
  68. },
  69. "VElement[name='template']:exit" (rootNode) {
  70. if (
  71. rootNode.loc.start !== templateLocation ||
  72. ignoreReporting ||
  73. utils.hasAttribute(rootNode, 'src')
  74. ) return
  75. registeredComponents
  76. .filter(({ name }) => {
  77. // If the component name is PascalCase or camelCase
  78. // it can be used in various of ways inside template,
  79. // like "theComponent", "The-component" etc.
  80. // but except snake_case
  81. if (casing.pascalCase(name) === name || casing.camelCase(name) === name) {
  82. return ![...usedComponents].some(n => {
  83. return n.indexOf('_') === -1 && (name === casing.pascalCase(n) || casing.camelCase(n) === name)
  84. })
  85. } else {
  86. // In any other case the used component name must exactly match
  87. // the registered name
  88. return !usedComponents.has(name)
  89. }
  90. })
  91. .forEach(({ node, name }) => context.report({
  92. node,
  93. message: 'The "{{name}}" component has been registered but not used.',
  94. data: {
  95. name
  96. }
  97. }))
  98. }
  99. }, utils.executeOnVue(context, (obj) => {
  100. registeredComponents = utils.getRegisteredComponents(obj)
  101. }))
  102. }
  103. }