no-lifecycle-after-await.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. /**
  2. * @author Yosuke Ota
  3. * See LICENSE file in root directory for full license.
  4. */
  5. 'use strict'
  6. const { ReferenceTracker } = require('@eslint-community/eslint-utils')
  7. const utils = require('../utils')
  8. /**
  9. * @typedef {import('@eslint-community/eslint-utils').TYPES.TraceMap} TraceMap
  10. */
  11. const LIFECYCLE_HOOKS = [
  12. 'onBeforeMount',
  13. 'onBeforeUnmount',
  14. 'onBeforeUpdate',
  15. 'onErrorCaptured',
  16. 'onMounted',
  17. 'onRenderTracked',
  18. 'onRenderTriggered',
  19. 'onUnmounted',
  20. 'onUpdated',
  21. 'onActivated',
  22. 'onDeactivated'
  23. ]
  24. module.exports = {
  25. meta: {
  26. type: 'suggestion',
  27. docs: {
  28. description: 'disallow asynchronously registered lifecycle hooks',
  29. categories: ['vue3-essential'],
  30. url: 'https://eslint.vuejs.org/rules/no-lifecycle-after-await.html'
  31. },
  32. fixable: null,
  33. schema: [],
  34. messages: {
  35. forbidden: 'Lifecycle hooks are forbidden after an `await` expression.'
  36. }
  37. },
  38. /** @param {RuleContext} context */
  39. create(context) {
  40. /**
  41. * @typedef {object} SetupScopeData
  42. * @property {boolean} afterAwait
  43. * @property {[number,number]} range
  44. */
  45. /**
  46. * @typedef {object} ScopeStack
  47. * @property {ScopeStack | null} upper
  48. * @property {FunctionDeclaration | FunctionExpression | ArrowFunctionExpression} scopeNode
  49. */
  50. /** @type {Set<ESNode>} */
  51. const lifecycleHookCallNodes = new Set()
  52. /** @type {Map<FunctionDeclaration | FunctionExpression | ArrowFunctionExpression, SetupScopeData>} */
  53. const setupScopes = new Map()
  54. /** @type {ScopeStack | null} */
  55. let scopeStack = null
  56. return utils.compositingVisitors(
  57. {
  58. /** @param {Program} program */
  59. Program(program) {
  60. const tracker = new ReferenceTracker(utils.getScope(context, program))
  61. /** @type {TraceMap} */
  62. const traceMap = {}
  63. for (const lifecycleHook of LIFECYCLE_HOOKS) {
  64. traceMap[lifecycleHook] = {
  65. [ReferenceTracker.CALL]: true
  66. }
  67. }
  68. for (const { node } of utils.iterateReferencesTraceMap(
  69. tracker,
  70. traceMap
  71. )) {
  72. lifecycleHookCallNodes.add(node)
  73. }
  74. }
  75. },
  76. utils.defineVueVisitor(context, {
  77. onSetupFunctionEnter(node) {
  78. setupScopes.set(node, {
  79. afterAwait: false,
  80. range: node.range
  81. })
  82. },
  83. /**
  84. * @param {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression} node
  85. */
  86. ':function'(node) {
  87. scopeStack = {
  88. upper: scopeStack,
  89. scopeNode: node
  90. }
  91. },
  92. ':function:exit'() {
  93. scopeStack = scopeStack && scopeStack.upper
  94. },
  95. /** @param {AwaitExpression} node */
  96. AwaitExpression(node) {
  97. if (!scopeStack) {
  98. return
  99. }
  100. const setupScope = setupScopes.get(scopeStack.scopeNode)
  101. if (!setupScope || !utils.inRange(setupScope.range, node)) {
  102. return
  103. }
  104. setupScope.afterAwait = true
  105. },
  106. /** @param {CallExpression} node */
  107. CallExpression(node) {
  108. if (!scopeStack) {
  109. return
  110. }
  111. const setupScope = setupScopes.get(scopeStack.scopeNode)
  112. if (
  113. !setupScope ||
  114. !setupScope.afterAwait ||
  115. !utils.inRange(setupScope.range, node)
  116. ) {
  117. return
  118. }
  119. if (lifecycleHookCallNodes.has(node)) {
  120. if (node.arguments.length >= 2) {
  121. // Has target instance. e.g. `onMounted(() => {}, instance)`
  122. return
  123. }
  124. context.report({
  125. node,
  126. messageId: 'forbidden'
  127. })
  128. }
  129. },
  130. onSetupFunctionExit(node) {
  131. setupScopes.delete(node)
  132. }
  133. })
  134. )
  135. }
  136. }