feat(lint/eslint): implement require-await (#3406)

Implements https://eslint.org/docs/latest/rules/require-await.

Related to https://github.com/oxc-project/oxc/issues/479.

---------

Co-authored-by: wenzhe <mysteryven@gmail.com>
This commit is contained in:
Todor Andonov 2024-05-26 03:57:33 +03:00 committed by GitHub
parent 147864cfeb
commit 14ef4df349
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 228 additions and 0 deletions

View file

@ -107,6 +107,7 @@ mod eslint {
pub mod no_with;
pub mod prefer_exponentiation_operator;
pub mod radix;
pub mod require_await;
pub mod require_yield;
pub mod symbol_description;
pub mod unicode_bom;
@ -415,6 +416,7 @@ oxc_macros::declare_all_lint_rules! {
eslint::no_caller,
eslint::no_case_declarations,
eslint::no_class_assign,
eslint::require_await,
eslint::no_compare_neg_zero,
eslint::no_cond_assign,
eslint::no_console,

View file

@ -0,0 +1,163 @@
use oxc_ast::{
ast::{ArrowFunctionExpression, AwaitExpression, ForOfStatement, Function, PropertyKey},
AstKind, Visit,
};
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_semantic::ScopeFlags;
use oxc_span::Span;
use crate::{context::LintContext, rule::Rule, AstNode};
#[derive(Debug, Default, Clone)]
pub struct RequireAwait;
fn require_await_diagnostic(span0: Span) -> OxcDiagnostic {
OxcDiagnostic::warn("eslint(require-await): Async function has no 'await' expression.")
.with_labels([span0.into()])
}
declare_oxc_lint!(
/// ### What it does
/// Disallow async functions which have no await expression.
///
/// ### Why is this bad?
///
///
/// ### Example
/// ```javascript
/// async function foo() {
/// doSomething();
/// }
/// ```
RequireAwait,
pedantic,
);
impl Rule for RequireAwait {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
if let AstKind::FunctionBody(body) = node.kind() {
if body.is_empty() {
return;
}
let Some(parent) = ctx.nodes().parent_node(node.id()) else {
return;
};
match parent.kind() {
AstKind::Function(func) => {
if func.r#async && !func.generator {
let mut finder = AwaitFinder { found: false };
finder.visit_function_body(body);
if !finder.found {
if let Some(AstKind::ObjectProperty(p)) =
ctx.nodes().parent_kind(parent.id())
{
if let PropertyKey::StaticIdentifier(iden) = &p.key {
ctx.diagnostic(require_await_diagnostic(iden.span));
} else {
ctx.diagnostic(require_await_diagnostic(func.span));
}
} else {
ctx.diagnostic(require_await_diagnostic(
func.id.as_ref().map_or(func.span, |ident| ident.span),
));
}
}
}
}
AstKind::ArrowFunctionExpression(func) => {
if func.r#async {
let mut finder = AwaitFinder { found: false };
finder.visit_function_body(body);
if !finder.found {
ctx.diagnostic(require_await_diagnostic(func.span));
}
}
}
_ => {}
}
}
}
}
struct AwaitFinder {
found: bool,
}
impl<'a> Visit<'a> for AwaitFinder {
fn visit_await_expression(&mut self, _expr: &AwaitExpression) {
if self.found {
return;
}
self.found = true;
}
fn visit_for_of_statement(&mut self, stmt: &ForOfStatement) {
if stmt.r#await {
self.found = true;
}
}
fn visit_arrow_expression(&mut self, _expr: &ArrowFunctionExpression<'a>) {}
fn visit_function(&mut self, _func: &Function<'a>, _flags: Option<ScopeFlags>) {}
}
#[test]
fn test() {
use crate::tester::Tester;
let pass = vec![
"async function foo() { await doSomething() }",
"(async function() { await doSomething() })",
"async () => { await doSomething() }",
"async () => await doSomething()",
"({ async foo() { await doSomething() } })",
"class A { async foo() { await doSomething() } }",
"(class { async foo() { await doSomething() } })",
"async function foo() { await (async () => { await doSomething() }) }",
"async function foo() {}",
"async () => {}",
"function foo() { doSomething() }",
"async function foo() { for await (x of xs); }",
"await foo()",
"
for await (let num of asyncIterable) {
console.log(num);
}
",
"async function* run() { yield * anotherAsyncGenerator() }",
"async function* run() {
await new Promise(resolve => setTimeout(resolve, 100));
yield 'Hello';
console.log('World');
}
",
"
async function foo() {
{
await doSomething()
}
}
",
"async function* run() { }",
"const foo = async function *(){}",
r#"const foo = async function *(){ console.log("bar") }"#,
r#"async function* run() { console.log("bar") }"#,
];
let fail = vec![
"async function foo() { doSomething() }",
"(async function() { doSomething() })",
"async () => { doSomething() }",
"async () => doSomething()",
"({ async foo() { doSomething() } })",
"class A { async foo() { doSomething() } }",
"(class { async foo() { doSomething() } })",
"(class { async ''() { doSomething() } })",
"async function foo() { async () => { await doSomething() } }",
"async function foo() { await (async () => { doSomething() }) }",
];
Tester::new(RequireAwait::NAME, pass, fail).test_and_snapshot();
}

View file

@ -0,0 +1,63 @@
---
source: crates/oxc_linter/src/tester.rs
expression: require_await
---
⚠ eslint(require-await): Async function has no 'await' expression.
╭─[require_await.tsx:1:16]
1 │ async function foo() { doSomething() }
· ───
╰────
⚠ eslint(require-await): Async function has no 'await' expression.
╭─[require_await.tsx:1:2]
1 │ (async function() { doSomething() })
· ──────────────────────────────────
╰────
⚠ eslint(require-await): Async function has no 'await' expression.
╭─[require_await.tsx:1:1]
1 │ async () => { doSomething() }
· ─────────────────────────────
╰────
⚠ eslint(require-await): Async function has no 'await' expression.
╭─[require_await.tsx:1:1]
1 │ async () => doSomething()
· ─────────────────────────
╰────
⚠ eslint(require-await): Async function has no 'await' expression.
╭─[require_await.tsx:1:10]
1 │ ({ async foo() { doSomething() } })
· ───
╰────
⚠ eslint(require-await): Async function has no 'await' expression.
╭─[require_await.tsx:1:20]
1 │ class A { async foo() { doSomething() } }
· ────────────────────
╰────
⚠ eslint(require-await): Async function has no 'await' expression.
╭─[require_await.tsx:1:19]
1 │ (class { async foo() { doSomething() } })
· ────────────────────
╰────
⚠ eslint(require-await): Async function has no 'await' expression.
╭─[require_await.tsx:1:18]
1 │ (class { async ''() { doSomething() } })
· ────────────────────
╰────
⚠ eslint(require-await): Async function has no 'await' expression.
╭─[require_await.tsx:1:16]
1 │ async function foo() { async () => { await doSomething() } }
· ───
╰────
⚠ eslint(require-await): Async function has no 'await' expression.
╭─[require_await.tsx:1:31]
1 │ async function foo() { await (async () => { doSomething() }) }
· ─────────────────────────────
╰────