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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  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. const PROMISE_FUNCTIONS = [
  8. 'then',
  9. 'catch',
  10. 'finally'
  11. ]
  12. const PROMISE_METHODS = [
  13. 'all',
  14. 'race',
  15. 'reject',
  16. 'resolve'
  17. ]
  18. const TIMED_FUNCTIONS = [
  19. 'setTimeout',
  20. 'setInterval',
  21. 'setImmediate',
  22. 'requestAnimationFrame'
  23. ]
  24. function isTimedFunction (node) {
  25. return ((
  26. node.type === 'CallExpression' &&
  27. node.callee.type === 'Identifier' &&
  28. TIMED_FUNCTIONS.indexOf(node.callee.name) !== -1
  29. ) || (
  30. node.type === 'CallExpression' &&
  31. node.callee.type === 'MemberExpression' &&
  32. node.callee.object.type === 'Identifier' &&
  33. node.callee.object.name === 'window' && (
  34. TIMED_FUNCTIONS.indexOf(node.callee.property.name) !== -1
  35. )
  36. )) && node.arguments.length
  37. }
  38. function isPromise (node) {
  39. if (node.type === 'CallExpression' && node.callee.type === 'MemberExpression') {
  40. return ( // hello.PROMISE_FUNCTION()
  41. node.callee.property.type === 'Identifier' &&
  42. PROMISE_FUNCTIONS.indexOf(node.callee.property.name) !== -1
  43. ) || ( // Promise.PROMISE_METHOD()
  44. node.callee.object.type === 'Identifier' &&
  45. node.callee.object.name === 'Promise' &&
  46. PROMISE_METHODS.indexOf(node.callee.property.name) !== -1
  47. )
  48. }
  49. return false
  50. }
  51. // ------------------------------------------------------------------------------
  52. // Rule Definition
  53. // ------------------------------------------------------------------------------
  54. module.exports = {
  55. meta: {
  56. type: 'problem',
  57. docs: {
  58. description: 'disallow asynchronous actions in computed properties',
  59. category: 'essential',
  60. url: 'https://eslint.vuejs.org/rules/no-async-in-computed-properties.html'
  61. },
  62. fixable: null,
  63. schema: []
  64. },
  65. create (context) {
  66. const forbiddenNodes = []
  67. const allowedScopes = []
  68. const expressionTypes = {
  69. promise: 'asynchronous action',
  70. await: 'await operator',
  71. async: 'async function declaration',
  72. new: 'Promise object',
  73. timed: 'timed function'
  74. }
  75. function onFunctionEnter (node) {
  76. if (node.async) {
  77. forbiddenNodes.push({
  78. node: node,
  79. type: 'async'
  80. })
  81. } else if (node.parent.type === 'ReturnStatement') {
  82. allowedScopes.push(node)
  83. }
  84. }
  85. return Object.assign({},
  86. {
  87. FunctionDeclaration: onFunctionEnter,
  88. FunctionExpression: onFunctionEnter,
  89. ArrowFunctionExpression: onFunctionEnter,
  90. NewExpression (node) {
  91. if (node.callee.name === 'Promise') {
  92. forbiddenNodes.push({
  93. node: node,
  94. type: 'new'
  95. })
  96. } else if (node.parent.type === 'ReturnStatement') {
  97. allowedScopes.push(node)
  98. }
  99. },
  100. CallExpression (node) {
  101. if (isPromise(node)) {
  102. forbiddenNodes.push({
  103. node: node,
  104. type: 'promise'
  105. })
  106. } else if (isTimedFunction(node)) {
  107. forbiddenNodes.push({
  108. node: node,
  109. type: 'timed'
  110. })
  111. } else if (node.parent.type === 'ReturnStatement') {
  112. allowedScopes.push(node)
  113. }
  114. },
  115. AwaitExpression (node) {
  116. forbiddenNodes.push({
  117. node: node,
  118. type: 'await'
  119. })
  120. },
  121. 'ReturnStatement' (node) {
  122. if (
  123. node.argument &&
  124. (
  125. node.argument.type === 'ObjectExpression' ||
  126. node.argument.type === 'ArrayExpression'
  127. )
  128. ) {
  129. allowedScopes.push(node.argument)
  130. }
  131. }
  132. },
  133. utils.executeOnVue(context, (obj) => {
  134. const computedProperties = utils.getComputedProperties(obj)
  135. computedProperties.forEach(cp => {
  136. forbiddenNodes.forEach(el => {
  137. if (
  138. cp.value &&
  139. el.node.loc.start.line >= cp.value.loc.start.line &&
  140. el.node.loc.end.line <= cp.value.loc.end.line &&
  141. !allowedScopes.some(scope =>
  142. scope.range[0] < el.node.range[0] &&
  143. scope.range[1] > el.node.range[1]
  144. )
  145. ) {
  146. context.report({
  147. node: el.node,
  148. message: 'Unexpected {{expressionName}} in "{{propertyName}}" computed property.',
  149. data: {
  150. expressionName: expressionTypes[el.type],
  151. propertyName: cp.key
  152. }
  153. })
  154. }
  155. })
  156. })
  157. })
  158. )
  159. }
  160. }