no-await-in-loop.js 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. /**
  2. * @fileoverview Rule to disallow uses of await inside of loops.
  3. * @author Nat Mote (nmote)
  4. */
  5. "use strict";
  6. /**
  7. * Check whether it should stop traversing ancestors at the given node.
  8. * @param {ASTNode} node A node to check.
  9. * @returns {boolean} `true` if it should stop traversing.
  10. */
  11. function isBoundary(node) {
  12. const t = node.type;
  13. return (
  14. t === "FunctionDeclaration" ||
  15. t === "FunctionExpression" ||
  16. t === "ArrowFunctionExpression" ||
  17. /*
  18. * Don't report the await expressions on for-await-of loop since it's
  19. * asynchronous iteration intentionally.
  20. */
  21. (t === "ForOfStatement" && node.await === true)
  22. );
  23. }
  24. /**
  25. * Check whether the given node is in loop.
  26. * @param {ASTNode} node A node to check.
  27. * @param {ASTNode} parent A parent node to check.
  28. * @returns {boolean} `true` if the node is in loop.
  29. */
  30. function isLooped(node, parent) {
  31. switch (parent.type) {
  32. case "ForStatement":
  33. return (
  34. node === parent.test ||
  35. node === parent.update ||
  36. node === parent.body
  37. );
  38. case "ForOfStatement":
  39. case "ForInStatement":
  40. return (
  41. node === parent.body ||
  42. (node === parent.left && node.kind === "await using")
  43. );
  44. case "WhileStatement":
  45. case "DoWhileStatement":
  46. return node === parent.test || node === parent.body;
  47. default:
  48. return false;
  49. }
  50. }
  51. /** @type {import('../types').Rule.RuleModule} */
  52. module.exports = {
  53. meta: {
  54. type: "problem",
  55. docs: {
  56. description: "Disallow `await` inside of loops",
  57. recommended: false,
  58. url: "https://eslint.org/docs/latest/rules/no-await-in-loop",
  59. },
  60. schema: [],
  61. messages: {
  62. unexpectedAwait: "Unexpected `await` inside a loop.",
  63. },
  64. },
  65. create(context) {
  66. /**
  67. * Validate an await expression.
  68. * @param {ASTNode} awaitNode An AwaitExpression or ForOfStatement node to validate.
  69. * @returns {void}
  70. */
  71. function validate(awaitNode) {
  72. if (
  73. awaitNode.type === "VariableDeclaration" &&
  74. awaitNode.kind !== "await using"
  75. ) {
  76. return;
  77. }
  78. if (awaitNode.type === "ForOfStatement" && !awaitNode.await) {
  79. return;
  80. }
  81. let node = awaitNode;
  82. let parent = node.parent;
  83. while (parent && !isBoundary(parent)) {
  84. if (isLooped(node, parent)) {
  85. context.report({
  86. node: awaitNode,
  87. messageId: "unexpectedAwait",
  88. });
  89. return;
  90. }
  91. node = parent;
  92. parent = parent.parent;
  93. }
  94. }
  95. return {
  96. AwaitExpression: validate,
  97. ForOfStatement: validate,
  98. VariableDeclaration: validate,
  99. };
  100. },
  101. };