no-setup-props-destructure.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. /**
  2. * @author Yosuke Ota
  3. * See LICENSE file in root directory for full license.
  4. */
  5. 'use strict'
  6. const { findVariable } = require('eslint-utils')
  7. const utils = require('../utils')
  8. module.exports = {
  9. meta: {
  10. type: 'suggestion',
  11. docs: {
  12. description: 'disallow destructuring of `props` passed to `setup`',
  13. categories: ['vue3-essential'],
  14. url: 'https://eslint.vuejs.org/rules/no-setup-props-destructure.html'
  15. },
  16. fixable: null,
  17. schema: [],
  18. messages: {
  19. destructuring:
  20. 'Destructuring the `props` will cause the value to lose reactivity.',
  21. getProperty:
  22. 'Getting a value from the `props` in root scope of `setup()` will cause the value to lose reactivity.'
  23. }
  24. },
  25. /** @param {RuleContext} context */
  26. create(context) {
  27. /** @type {Map<FunctionDeclaration | FunctionExpression | ArrowFunctionExpression, Set<Identifier>>} */
  28. const setupScopePropsReferenceIds = new Map()
  29. /**
  30. * @param {ESNode} node
  31. * @param {string} messageId
  32. */
  33. function report(node, messageId) {
  34. context.report({
  35. node,
  36. messageId
  37. })
  38. }
  39. /**
  40. * @param {Pattern} left
  41. * @param {Expression | null} right
  42. * @param {Set<Identifier>} propsReferenceIds
  43. */
  44. function verify(left, right, propsReferenceIds) {
  45. if (!right) {
  46. return
  47. }
  48. const rightNode = utils.skipChainExpression(right)
  49. if (
  50. left.type !== 'ArrayPattern' &&
  51. left.type !== 'ObjectPattern' &&
  52. rightNode.type !== 'MemberExpression'
  53. ) {
  54. return
  55. }
  56. /** @type {Expression | Super} */
  57. let rightId = rightNode
  58. while (rightId.type === 'MemberExpression') {
  59. rightId = utils.skipChainExpression(rightId.object)
  60. }
  61. if (rightId.type === 'Identifier' && propsReferenceIds.has(rightId)) {
  62. report(left, 'getProperty')
  63. }
  64. }
  65. /**
  66. * @typedef {object} ScopeStack
  67. * @property {ScopeStack | null} upper
  68. * @property {FunctionDeclaration | FunctionExpression | ArrowFunctionExpression} functionNode
  69. */
  70. /**
  71. * @type {ScopeStack | null}
  72. */
  73. let scopeStack = null
  74. return utils.defineVueVisitor(context, {
  75. ':function'(node) {
  76. scopeStack = {
  77. upper: scopeStack,
  78. functionNode: node
  79. }
  80. },
  81. onSetupFunctionEnter(node) {
  82. const propsParam = utils.skipDefaultParamValue(node.params[0])
  83. if (!propsParam) {
  84. // no arguments
  85. return
  86. }
  87. if (propsParam.type === 'RestElement') {
  88. // cannot check
  89. return
  90. }
  91. if (
  92. propsParam.type === 'ArrayPattern' ||
  93. propsParam.type === 'ObjectPattern'
  94. ) {
  95. report(propsParam, 'destructuring')
  96. return
  97. }
  98. const variable = findVariable(context.getScope(), propsParam)
  99. if (!variable) {
  100. return
  101. }
  102. const propsReferenceIds = new Set()
  103. for (const reference of variable.references) {
  104. if (!reference.isRead()) {
  105. continue
  106. }
  107. propsReferenceIds.add(reference.identifier)
  108. }
  109. setupScopePropsReferenceIds.set(node, propsReferenceIds)
  110. },
  111. VariableDeclarator(node) {
  112. if (!scopeStack) {
  113. return
  114. }
  115. const propsReferenceIds = setupScopePropsReferenceIds.get(
  116. scopeStack.functionNode
  117. )
  118. if (!propsReferenceIds) {
  119. return
  120. }
  121. verify(node.id, node.init, propsReferenceIds)
  122. },
  123. AssignmentExpression(node) {
  124. if (!scopeStack) {
  125. return
  126. }
  127. const propsReferenceIds = setupScopePropsReferenceIds.get(
  128. scopeStack.functionNode
  129. )
  130. if (!propsReferenceIds) {
  131. return
  132. }
  133. verify(node.left, node.right, propsReferenceIds)
  134. },
  135. ':function:exit'(node) {
  136. scopeStack = scopeStack && scopeStack.upper
  137. setupScopePropsReferenceIds.delete(node)
  138. }
  139. })
  140. }
  141. }