Skip to content

Commit

Permalink
feat: add no-await-in-sync-fn rule (#1176)
Browse files Browse the repository at this point in the history
* feat: add no-await-in-sync-fn rule

* chore: tag no_await_in_sync_fn to recommeded

* chore: fix formatting

* chore: fix clippy errors

* chore: run new rule command
  • Loading branch information
marvinhagemeister authored Jul 12, 2023
1 parent 23f15ca commit 950665f
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 0 deletions.
37 changes: 37 additions & 0 deletions docs/rules/no_await_in_sync_fn.md
Original file line number Diff line number Diff line change
@@ -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();
};
```
2 changes: 2 additions & 0 deletions src/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down
124 changes: 124 additions & 0 deletions src/rules/no_await_in_sync_fn.rs
Original file line number Diff line number Diff line change
@@ -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 }],
}
}
}
7 changes: 7 additions & 0 deletions www/static/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down

0 comments on commit 950665f

Please sign in to comment.