use super::{Context, LintRule};
use crate::handler::{Handler, Traverse};
use crate::Program;
use deno_ast::view::NodeTrait;
use deno_ast::{view as ast_view, SourceRanged};
#[derive(Debug)]
pub struct NoAwaitInLoop;
const CODE: &str = "no-await-in-loop";
const MESSAGE: &str = "Unexpected `await` inside a loop.";
const HINT: &str = "Remove `await` in loop body, store all promises generated and then `await Promise.all(storedPromises)` after the loop";
impl LintRule for NoAwaitInLoop {
fn code(&self) -> &'static str {
CODE
}
fn lint_program_with_ast_view(
&self,
context: &mut Context,
program: Program<'_>,
) {
NoAwaitInLoopHandler.traverse(program, context);
}
}
struct NoAwaitInLoopHandler;
impl Handler for NoAwaitInLoopHandler {
fn await_expr(
&mut self,
await_expr: &ast_view::AwaitExpr,
ctx: &mut Context,
) {
fn inside_loop(
await_expr: &ast_view::AwaitExpr,
node: ast_view::Node,
) -> bool {
use deno_ast::view::Node::*;
match node {
FnDecl(_) | FnExpr(_) | ArrowExpr(_) => false,
ForOfStmt(stmt) if stmt.is_await() => {
false
}
ForInStmt(ast_view::ForInStmt { right, .. })
| ForOfStmt(ast_view::ForOfStmt { right, .. }) => {
!right.range().contains(&await_expr.range())
}
ForStmt(stmt) => {
stmt
.init
.as_ref()
.is_none_or(|init| !init.range().contains(&await_expr.range()))
}
WhileStmt(_) | DoWhileStmt(_) => true,
_ => {
let parent = match node.parent() {
Some(p) => p,
None => return false,
};
inside_loop(await_expr, parent)
}
}
}
if inside_loop(await_expr, await_expr.as_node()) {
ctx.add_diagnostic_with_hint(await_expr.range(), CODE, MESSAGE, HINT);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn no_await_in_loop_valid() {
assert_lint_ok! {
NoAwaitInLoop,
r#"
async function foo(things) {
const results = [];
for (const thing of things) {
results.push(bar(thing));
}
return baz(await Promise.all(results));
}
"#,
r#"
async function foo(things) {
for (const thing of things) {
const a = async () => await bar(thing);
}
}
"#,
r#"
async function foo(things) {
for (const thing of things) {
async function a() {
return await bar(42);
}
}
}
"#,
r#"
async function foo(things) {
for (const thing of things) {
const a = async function() {
return await bar(42);
}
}
}
"#,
r#"
async function foo(things) {
for await (const thing of things) {
console.log(await bar(thing));
}
}
"#,
r#"
async function foo(things) {
for await (const thing of await things) {
console.log(await bar(thing));
}
}
"#,
r#"
async function foo() {
for (let i = await bar(); i < n; i++) {
baz(i);
}
}
"#,
r#"
async function foo() {
for (const thing of await things) {
bar(thing);
}
}
"#,
r#"
async function foo() {
for (let thing in await things) {
bar(thing);
}
}
"#,
r#"
function foo() {
async function bar() {
for (const thing of await things) {}
}
}
"#,
r#"
for (const thing of things) {
const a = async () => await bar(thing);
}
"#,
r#"
for (const thing of things) {
async function a() {
return await bar(42);
}
}
"#,
r#"
for (const thing of things) {
const a = async function() {
return await bar(42);
}
}
"#,
r#"
for await (const thing of things) {
console.log(await bar(thing));
}
"#,
r#"
for await (const thing of await things) {
console.log(await bar(thing));
}
"#,
r#"
for (let i = await bar(); i < n; i++) {
baz(i);
}
"#,
r#"
for (const thing of await things) {
bar(thing);
}
"#,
r#"
for (let thing in await things) {
bar(thing);
}
"#,
};
}
#[test]
fn no_await_in_loop_invalid() {
assert_lint_err! {
NoAwaitInLoop,
MESSAGE,
HINT,
r#"
async function foo(things) {
const results = [];
for (const thing of things) {
results.push(await bar(thing));
}
return baz(results);
}
"#: [{ line: 5, col: 17 }],
r#"
for (const thing of things) {
results.push(await foo(thing));
}
"#: [{ line: 3, col: 15 }],
r#"
for (let i = 0; i < await foo(); i++) {
bar();
}
"#: [{ line: 2, col: 20 }],
r#"
for (let i = 0; i < 42; await foo(i)) {
bar();
}
"#: [{ line: 2, col: 24 }],
r#"
for (let i = 0; i < 42; i++) {
await bar();
}
"#: [{ line: 3, col: 2 }],
r#"
for (const thing in things) {
await foo(thing);
}
"#: [{ line: 3, col: 2 }],
r#"
while (await foo()) {
bar();
}
"#: [{ line: 2, col: 7 }],
r#"
while (true) {
await foo();
}
"#: [{ line: 3, col: 2 }],
r#"
while (true) {
await foo();
}
"#: [{ line: 3, col: 2 }],
r#"
do {
foo();
} while (await bar());
"#: [{ line: 4, col: 9 }],
r#"
do {
await foo();
} while (true);
"#: [{ line: 3, col: 2 }],
r#"
for await (const thing of things) {
async function foo() {
for (const one of them) {
await bar(one);
}
}
await baz();
}
"#: [{ line: 5, col: 6 }],
r#"
function foo() {
async function bar() {
for (const thing of things) {
await baz(thing);
}
}
}
"#: [{ line: 5, col: 6 }],
r#"
async function foo() {
for (const thing of things) {
const xs = bar(thing);
for (const x in xs) {
await baz(x);
}
}
}
"#: [{ line: 6, col: 6 }]
}
}
}