no-async-in-computed-properties.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. /**
  2. * @fileoverview Check if there are no asynchronous actions inside computed properties.
  3. * @author Armano
  4. */
  5. 'use strict'
  6. const utils = require('../utils')
  7. /**
  8. * @typedef {import('../utils').VueObjectData} VueObjectData
  9. * @typedef {import('../utils').ComponentComputedProperty} ComponentComputedProperty
  10. */
  11. const PROMISE_FUNCTIONS = ['then', 'catch', 'finally']
  12. const PROMISE_METHODS = ['all', 'race', 'reject', 'resolve']
  13. const TIMED_FUNCTIONS = [
  14. 'setTimeout',
  15. 'setInterval',
  16. 'setImmediate',
  17. 'requestAnimationFrame'
  18. ]
  19. /**
  20. * @param {CallExpression} node
  21. */
  22. function isTimedFunction(node) {
  23. const callee = utils.skipChainExpression(node.callee)
  24. return (
  25. ((node.type === 'CallExpression' &&
  26. callee.type === 'Identifier' &&
  27. TIMED_FUNCTIONS.indexOf(callee.name) !== -1) ||
  28. (node.type === 'CallExpression' &&
  29. callee.type === 'MemberExpression' &&
  30. callee.object.type === 'Identifier' &&
  31. callee.object.name === 'window' &&
  32. callee.property.type === 'Identifier' &&
  33. TIMED_FUNCTIONS.indexOf(callee.property.name) !== -1)) &&
  34. node.arguments.length
  35. )
  36. }
  37. /**
  38. * @param {CallExpression} node
  39. */
  40. function isPromise(node) {
  41. const callee = utils.skipChainExpression(node.callee)
  42. if (node.type === 'CallExpression' && callee.type === 'MemberExpression') {
  43. return (
  44. // hello.PROMISE_FUNCTION()
  45. (callee.property.type === 'Identifier' &&
  46. PROMISE_FUNCTIONS.indexOf(callee.property.name) !== -1) || // Promise.PROMISE_METHOD()
  47. (callee.object.type === 'Identifier' &&
  48. callee.object.name === 'Promise' &&
  49. callee.property.type === 'Identifier' &&
  50. PROMISE_METHODS.indexOf(callee.property.name) !== -1)
  51. )
  52. }
  53. return false
  54. }
  55. // ------------------------------------------------------------------------------
  56. // Rule Definition
  57. // ------------------------------------------------------------------------------
  58. module.exports = {
  59. meta: {
  60. type: 'problem',
  61. docs: {
  62. description: 'disallow asynchronous actions in computed properties',
  63. categories: ['vue3-essential', 'essential'],
  64. url: 'https://eslint.vuejs.org/rules/no-async-in-computed-properties.html'
  65. },
  66. fixable: null,
  67. schema: []
  68. },
  69. /** @param {RuleContext} context */
  70. create(context) {
  71. /**
  72. * @typedef {object} ScopeStack
  73. * @property {ScopeStack | null} upper
  74. * @property {BlockStatement | Expression} body
  75. */
  76. /** @type {Map<ObjectExpression, ComponentComputedProperty[]>} */
  77. const computedPropertiesMap = new Map()
  78. /** @type {ScopeStack | null} */
  79. let scopeStack = null
  80. const expressionTypes = {
  81. promise: 'asynchronous action',
  82. await: 'await operator',
  83. async: 'async function declaration',
  84. new: 'Promise object',
  85. timed: 'timed function'
  86. }
  87. /**
  88. * @param {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression} node
  89. * @param {VueObjectData} data
  90. */
  91. function onFunctionEnter(node, { node: vueNode }) {
  92. if (node.async) {
  93. verify(node, node.body, 'async', computedPropertiesMap.get(vueNode))
  94. }
  95. scopeStack = {
  96. upper: scopeStack,
  97. body: node.body
  98. }
  99. }
  100. function onFunctionExit() {
  101. scopeStack = scopeStack && scopeStack.upper
  102. }
  103. /**
  104. * @param {ESNode} node
  105. * @param {BlockStatement | Expression} targetBody
  106. * @param {keyof expressionTypes} type
  107. * @param {ComponentComputedProperty[]} computedProperties
  108. */
  109. function verify(node, targetBody, type, computedProperties = []) {
  110. computedProperties.forEach((cp) => {
  111. if (
  112. cp.value &&
  113. node.loc.start.line >= cp.value.loc.start.line &&
  114. node.loc.end.line <= cp.value.loc.end.line &&
  115. targetBody === cp.value
  116. ) {
  117. context.report({
  118. node,
  119. message:
  120. 'Unexpected {{expressionName}} in "{{propertyName}}" computed property.',
  121. data: {
  122. expressionName: expressionTypes[type],
  123. propertyName: cp.key || 'unknown'
  124. }
  125. })
  126. }
  127. })
  128. }
  129. return utils.defineVueVisitor(context, {
  130. onVueObjectEnter(node) {
  131. computedPropertiesMap.set(node, utils.getComputedProperties(node))
  132. },
  133. ':function': onFunctionEnter,
  134. ':function:exit': onFunctionExit,
  135. NewExpression(node, { node: vueNode }) {
  136. if (!scopeStack) {
  137. return
  138. }
  139. if (
  140. node.callee.type === 'Identifier' &&
  141. node.callee.name === 'Promise'
  142. ) {
  143. verify(
  144. node,
  145. scopeStack.body,
  146. 'new',
  147. computedPropertiesMap.get(vueNode)
  148. )
  149. }
  150. },
  151. CallExpression(node, { node: vueNode }) {
  152. if (!scopeStack) {
  153. return
  154. }
  155. if (isPromise(node)) {
  156. verify(
  157. node,
  158. scopeStack.body,
  159. 'promise',
  160. computedPropertiesMap.get(vueNode)
  161. )
  162. } else if (isTimedFunction(node)) {
  163. verify(
  164. node,
  165. scopeStack.body,
  166. 'timed',
  167. computedPropertiesMap.get(vueNode)
  168. )
  169. }
  170. },
  171. AwaitExpression(node, { node: vueNode }) {
  172. if (!scopeStack) {
  173. return
  174. }
  175. verify(
  176. node,
  177. scopeStack.body,
  178. 'await',
  179. computedPropertiesMap.get(vueNode)
  180. )
  181. }
  182. })
  183. }
  184. }