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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  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. let scopeStack = { upper: null, body: null }
  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. targetBody: node.body
  81. })
  82. }
  83. scopeStack = { upper: scopeStack, body: node.body }
  84. }
  85. function onFunctionExit () {
  86. scopeStack = scopeStack.upper
  87. }
  88. return Object.assign({},
  89. {
  90. ':function': onFunctionEnter,
  91. ':function:exit': onFunctionExit,
  92. NewExpression (node) {
  93. if (node.callee.name === 'Promise') {
  94. forbiddenNodes.push({
  95. node: node,
  96. type: 'new',
  97. targetBody: scopeStack.body
  98. })
  99. }
  100. },
  101. CallExpression (node) {
  102. if (isPromise(node)) {
  103. forbiddenNodes.push({
  104. node: node,
  105. type: 'promise',
  106. targetBody: scopeStack.body
  107. })
  108. } else if (isTimedFunction(node)) {
  109. forbiddenNodes.push({
  110. node: node,
  111. type: 'timed',
  112. targetBody: scopeStack.body
  113. })
  114. }
  115. },
  116. AwaitExpression (node) {
  117. forbiddenNodes.push({
  118. node: node,
  119. type: 'await',
  120. targetBody: scopeStack.body
  121. })
  122. }
  123. },
  124. utils.executeOnVue(context, (obj) => {
  125. const computedProperties = utils.getComputedProperties(obj)
  126. computedProperties.forEach(cp => {
  127. forbiddenNodes.forEach(el => {
  128. if (
  129. cp.value &&
  130. el.node.loc.start.line >= cp.value.loc.start.line &&
  131. el.node.loc.end.line <= cp.value.loc.end.line &&
  132. el.targetBody === cp.value
  133. ) {
  134. context.report({
  135. node: el.node,
  136. message: 'Unexpected {{expressionName}} in "{{propertyName}}" computed property.',
  137. data: {
  138. expressionName: expressionTypes[el.type],
  139. propertyName: cp.key
  140. }
  141. })
  142. }
  143. })
  144. })
  145. })
  146. )
  147. }
  148. }