require-slots-as-functions.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. /**
  2. * @author Yosuke Ota
  3. * See LICENSE file in root directory for full license.
  4. */
  5. 'use strict'
  6. // ------------------------------------------------------------------------------
  7. // Requirements
  8. // ------------------------------------------------------------------------------
  9. const utils = require('../utils')
  10. const { findVariable } = require('eslint-utils')
  11. // ------------------------------------------------------------------------------
  12. // Rule Definition
  13. // ------------------------------------------------------------------------------
  14. module.exports = {
  15. meta: {
  16. type: 'problem',
  17. docs: {
  18. description: 'enforce properties of `$slots` to be used as a function',
  19. categories: ['vue3-essential'],
  20. url: 'https://eslint.vuejs.org/rules/require-slots-as-functions.html'
  21. },
  22. fixable: null,
  23. schema: [],
  24. messages: {
  25. unexpected: 'Property in `$slots` should be used as function.'
  26. }
  27. },
  28. /** @param {RuleContext} context */
  29. create(context) {
  30. /**
  31. * Verify the given node
  32. * @param {MemberExpression | Identifier | ChainExpression} node The node to verify
  33. * @param {Expression} reportNode The node to report
  34. */
  35. function verify(node, reportNode) {
  36. const parent = node.parent
  37. if (
  38. parent.type === 'VariableDeclarator' &&
  39. parent.id.type === 'Identifier'
  40. ) {
  41. // const children = this.$slots.foo
  42. verifyReferences(parent.id, reportNode)
  43. return
  44. }
  45. if (
  46. parent.type === 'AssignmentExpression' &&
  47. parent.right === node &&
  48. parent.left.type === 'Identifier'
  49. ) {
  50. // children = this.$slots.foo
  51. verifyReferences(parent.left, reportNode)
  52. return
  53. }
  54. if (parent.type === 'ChainExpression') {
  55. // (this.$slots?.foo).x
  56. verify(parent, reportNode)
  57. return
  58. }
  59. if (
  60. // this.$slots.foo.xxx
  61. parent.type === 'MemberExpression' ||
  62. // var [foo] = this.$slots.foo
  63. parent.type === 'VariableDeclarator' ||
  64. // [...this.$slots.foo]
  65. parent.type === 'SpreadElement' ||
  66. // [this.$slots.foo]
  67. parent.type === 'ArrayExpression'
  68. ) {
  69. context.report({
  70. node: reportNode,
  71. messageId: 'unexpected'
  72. })
  73. }
  74. }
  75. /**
  76. * Verify the references of the given node.
  77. * @param {Identifier} node The node to verify
  78. * @param {Expression} reportNode The node to report
  79. */
  80. function verifyReferences(node, reportNode) {
  81. const variable = findVariable(context.getScope(), node)
  82. if (!variable) {
  83. return
  84. }
  85. for (const reference of variable.references) {
  86. if (!reference.isRead()) {
  87. continue
  88. }
  89. /** @type {Identifier} */
  90. const id = reference.identifier
  91. verify(id, reportNode)
  92. }
  93. }
  94. return utils.defineVueVisitor(context, {
  95. /** @param {MemberExpression} node */
  96. MemberExpression(node) {
  97. const object = utils.skipChainExpression(node.object)
  98. if (object.type !== 'MemberExpression') {
  99. return
  100. }
  101. if (utils.getStaticPropertyName(object) !== '$slots') {
  102. return
  103. }
  104. if (!utils.isThis(object.object, context)) {
  105. return
  106. }
  107. if (node.property.type === 'PrivateIdentifier') {
  108. // Unreachable
  109. return
  110. }
  111. verify(node, node.property)
  112. }
  113. })
  114. }
  115. }