diff --git a/docs/rules/no_await_in_sync_fn.md b/docs/rules/no_await_in_sync_fn.md new file mode 100644 index 000000000..587af20d0 --- /dev/null +++ b/docs/rules/no_await_in_sync_fn.md @@ -0,0 +1,37 @@ +Disallow `await` keyword inside a non-async function + +Using the `await` keyword inside a non-async function is a syntax error. To be +able to use `await` inside a function, the function needs to be marked as async +via the `async` keyword + +### Invalid: + +```javascript +function foo() { + await bar(); +} + +const fooFn = function foo() { + await bar(); +}; + +const fooFn = () => { + await bar(); +}; +``` + +### Valid: + +```javascript +async function foo() { + await bar(); +} + +const fooFn = async function foo() { + await bar(); +}; + +const fooFn = async () => { + await bar(); +}; +``` diff --git a/src/rules.rs b/src/rules.rs index f9ccc33dc..c6cd53e3a 100644 --- a/src/rules.rs +++ b/src/rules.rs @@ -26,6 +26,7 @@ pub mod guard_for_in; pub mod no_array_constructor; pub mod no_async_promise_executor; pub mod no_await_in_loop; +pub mod no_await_in_sync_fn; pub mod no_case_declarations; pub mod no_class_assign; pub mod no_compare_neg_zero; @@ -244,6 +245,7 @@ fn get_all_rules_raw() -> Vec<&'static dyn LintRule> { &no_array_constructor::NoArrayConstructor, &no_async_promise_executor::NoAsyncPromiseExecutor, &no_await_in_loop::NoAwaitInLoop, + &no_await_in_sync_fn::NoAwaitInSyncFn, &no_case_declarations::NoCaseDeclarations, &no_class_assign::NoClassAssign, &no_compare_neg_zero::NoCompareNegZero, diff --git a/src/rules/no_await_in_sync_fn.rs b/src/rules/no_await_in_sync_fn.rs new file mode 100644 index 000000000..ec2854c2d --- /dev/null +++ b/src/rules/no_await_in_sync_fn.rs @@ -0,0 +1,124 @@ +// Copyright 2020-2021 the Deno authors. All rights reserved. MIT license. +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 NoAwaitInSyncFn; + +const CODE: &str = "no-await-in-sync-fn"; +const MESSAGE: &str = "Unexpected `await` inside a non-async function."; +const HINT: &str = "Remove `await` in the function body or change the function to an async function."; + +impl LintRule for NoAwaitInSyncFn { + fn tags(&self) -> &'static [&'static str] { + &["recommended"] + } + + fn code(&self) -> &'static str { + CODE + } + + fn lint_program_with_ast_view( + &self, + context: &mut Context, + program: Program<'_>, + ) { + NoAwaitInSyncFnHandler.traverse(program, context); + } + + #[cfg(feature = "docs")] + fn docs(&self) -> &'static str { + include_str!("../../docs/rules/no_await_in_sync_fn.md") + } +} + +struct NoAwaitInSyncFnHandler; + +impl Handler for NoAwaitInSyncFnHandler { + fn await_expr( + &mut self, + await_expr: &ast_view::AwaitExpr, + ctx: &mut Context, + ) { + fn inside_sync_fn(node: ast_view::Node) -> bool { + use deno_ast::view::Node::*; + match node { + FnDecl(decl) => !decl.function.is_async(), + FnExpr(decl) => !decl.function.is_async(), + ArrowExpr(decl) => !decl.is_async(), + _ => { + let parent = match node.parent() { + Some(p) => p, + None => return false, + }; + inside_sync_fn(parent) + } + } + } + + if inside_sync_fn(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_sync_fn_valid() { + assert_lint_ok! { + NoAwaitInSyncFn, + r#" + async function foo(things) { + await bar(); + } + "#, + r#" + const foo = async (things) => { + await bar(); + } + "#, + r#" + const foo = async function(things) { + await bar(); + } + "#, + r#" + class Foo { + async foo(things) { + await bar(); + } + } + "#, + } + } + + #[test] + fn no_await_in_sync_fn_invalid() { + assert_lint_err! { + NoAwaitInSyncFn, + MESSAGE, + HINT, + r#" + function foo(things) { + await bar(); + } + "#: [{ line: 3, col: 8 }], + r#" + const foo = things => { + await bar(); + } + "#: [{ line: 3, col: 8 }], + r#" + const foo = function (things) { + await bar(); + } + "#: [{ line: 3, col: 8 }], + } + } +} diff --git a/www/static/docs.json b/www/static/docs.json index 825a26b14..4d4cd3c07 100644 --- a/www/static/docs.json +++ b/www/static/docs.json @@ -130,6 +130,13 @@ "docs": "Requires `await` is not used in a for loop body\n\nAsync and await are used in Javascript to provide parallel execution. If each\nelement in the for loop is waited upon using `await`, then this negates the\nbenefits of using async/await as no more elements in the loop can be processed\nuntil the current element finishes.\n\nA common solution is to refactor the code to run the loop body asynchronously\nand capture the promises generated. After the loop finishes you can then await\nall the promises at once.\n\n### Invalid:\n\n```javascript\nasync function doSomething(items) {\n const results = [];\n for (const item of items) {\n // Each item in the array blocks on the previous one finishing\n results.push(await someAsyncProcessing(item));\n }\n return processResults(results);\n}\n```\n\n### Valid:\n\n```javascript\nasync function doSomething(items) {\n const results = [];\n for (const item of items) {\n // Kick off all item processing asynchronously...\n results.push(someAsyncProcessing(item));\n }\n // ...and then await their completion after the loop\n return processResults(await Promise.all(results));\n}\n```\n", "tags": [] }, + { + "code": "no-await-in-sync-fn", + "docs": "Disallow `await` keyword inside a non-async function\n\nUsing the `await` keyword inside a non-async function is a syntax error. To be\nable to use `await` inside a function, the function needs to be marked as async\nvia the `async` keyword\n\n### Invalid:\n\n```javascript\nfunction foo() {\n await bar();\n}\n\nconst fooFn = function foo() {\n await bar();\n};\n\nconst fooFn = () => {\n await bar();\n};\n```\n\n### Valid:\n\n```javascript\nasync function foo() {\n await bar();\n}\n\nconst fooFn = async function foo() {\n await bar();\n};\n\nconst fooFn = async () => {\n await bar();\n};\n```\n", + "tags": [ + "recommended" + ] + }, { "code": "no-case-declarations", "docs": "Requires lexical declarations (`let`, `const`, `function` and `class`) in switch\n`case` or `default` clauses to be scoped with brackets.\n\nWithout brackets in the `case` or `default` block, the lexical declarations are\nvisible to the entire switch block but only get initialized when they are\nassigned, which only happens if that case/default is reached. This can lead to\nunexpected errors. The solution is to ensure each `case` or `default` block is\nwrapped in brackets to scope limit the declarations.\n\n### Invalid:\n\n```typescript\nswitch (choice) {\n // `let`, `const`, `function` and `class` are scoped the entire switch statement here\n case 1:\n let a = \"choice 1\";\n break;\n case 2:\n const b = \"choice 2\";\n break;\n case 3:\n function f() {\n return \"choice 3\";\n }\n break;\n default:\n class C {}\n}\n```\n\n### Valid:\n\n```typescript\nswitch (choice) {\n // The following `case` and `default` clauses are wrapped into blocks using brackets\n case 1: {\n let a = \"choice 1\";\n break;\n }\n case 2: {\n const b = \"choice 2\";\n break;\n }\n case 3: {\n function f() {\n return \"choice 3\";\n }\n break;\n }\n default: {\n class C {}\n }\n}\n```\n",