feat(linter): implement eslint rule no-return-await (#529)

This commit is contained in:
vagusX 2023-07-09 20:39:11 +08:00 committed by GitHub
parent 1b6fa7b5e0
commit 5d670943f9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 554 additions and 0 deletions

View file

@ -40,6 +40,7 @@ oxc_macros::declare_all_lint_rules! {
eslint::no_new_symbol,
eslint::no_obj_calls,
eslint::no_prototype_builtins,
eslint::no_return_await,
eslint::no_self_assign,
eslint::no_self_compare,
eslint::no_setter_return,

View file

@ -0,0 +1,257 @@
use oxc_ast::AstKind;
use oxc_diagnostics::{
miette::{self, Diagnostic},
thiserror::Error,
};
use oxc_macros::declare_oxc_lint;
use oxc_span::{Span, GetSpan};
use crate::{context::LintContext, rule::Rule, AstNode, fixer::Fix};
#[derive(Debug, Error, Diagnostic)]
#[error("eslint(no-return-await): Redundant use of `await` on a return value.")]
#[diagnostic(severity(warning), help("Remove redundant `await`."))]
struct NoReturnAwaitDiagnostic(#[label] pub Span);
#[derive(Debug, Default, Clone)]
pub struct NoReturnAwait;
declare_oxc_lint!(
/// ### What it does
/// Disallow unnecessary return await
///
/// ### Why is this bad?
/// This rule aims to prevent a likely common performance hazard due to a lack of understanding of the semantics of async function.
/// https://eslint.org/docs/latest/rules/no-return-await
///
/// ### Example
/// ```javascript
/// async function foo() {
/// return await bar();
/// }
/// ```
NoReturnAwait,
correctness
);
impl Rule for NoReturnAwait {
fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
if let AstKind::AwaitExpression(await_expr) = node.kind() {
if is_in_tail_call_position(node, ctx) && !has_error_handler(node, ctx) {
let start = await_expr.span.start;
let end = start + 5;
let await_keyword_span = Span::new(start, end);
ctx.diagnostic_with_fix(
NoReturnAwaitDiagnostic(await_keyword_span),
|| Fix::new("", await_keyword_span),
);
}
}
}
}
fn is_in_tail_call_position<'a>(node: &AstNode<'a>, ctx: &LintContext<'a>) -> bool {
if let Some(parent) = ctx.nodes().parent_node(node.id()) {
let parent_kind = parent.kind();
match parent_kind {
AstKind::ArrowExpression(_) => {
return true;
}
AstKind::ReturnStatement(_) => {
return !has_error_handler(node, ctx);
}
AstKind::ConditionalExpression(cond_expr) => {
if (cond_expr.consequent.span() == node.kind().span())
|| (cond_expr.alternate.span() == node.kind().span()) {
return is_in_tail_call_position(parent, ctx);
}
},
AstKind::LogicalExpression(logic_expr) => {
if logic_expr.right.span() == node.kind().span() {
return is_in_tail_call_position(parent, ctx);
}
},
AstKind::SequenceExpression(seq_expr) => {
if let Some(seq_expr_last) = seq_expr.expressions.last()
&& seq_expr_last.span() == node.kind().span(){
return is_in_tail_call_position(parent, ctx);
}
},
// `return (await b())`
AstKind::ParenthesizedExpression(paren_expr) => {
if paren_expr.expression.span() == node.kind().span() {
return is_in_tail_call_position(parent, ctx);
}
},
// async () => await bar()
AstKind::ExpressionStatement(expr_stat) => {
// expression state in the last line of a `function_body`
if expr_stat.expression.span() == node.kind().span() {
return is_in_tail_call_position(parent, ctx);
}
},
// last statement in `func_body`
AstKind::FunctionBody(func_body) => {
if let Some(func_body_stat_last) = func_body.statements.last() {
return func_body_stat_last.span() == node.kind().span();
}
},
_ => {
return false;
}
}
}
false
}
fn has_error_handler<'a>(node: &AstNode<'a>, ctx: &LintContext<'a>) -> bool {
let mut current_node = node;
loop {
if let Some(parent_node) = ctx.nodes().parent_node(current_node.id()) {
let parent_node_kind = parent_node.kind();
if matches!(parent_node_kind, AstKind::Program(_)) {
break;
}
if parent_node_kind.is_function_like() {
break;
}
if let AstKind::TryStatement(try_stat) = parent_node_kind {
let current_node_span = current_node.kind().span();
// try statement must have a `catch` or `finally`
if try_stat.block.span == current_node_span {
return true;
}
// return await in `catch clause` with `finally` would be passed
if let Some(catch_clause) = &try_stat.handler {
if catch_clause.span == current_node_span && try_stat.finalizer.is_some() {
return true;
}
}
}
current_node = parent_node;
}
}
false
}
#[allow(clippy::too_many_lines)]
#[test]
fn test() {
use crate::tester::Tester;
let pass = vec![
("\nasync function foo() {\n\tawait bar(); return;\n}\n", None),
("\nasync function foo() {\n\tconst x = await bar(); return x;\n}\n", None),
("\nasync () => { return bar(); }\n", None),
("\nasync () => bar()\n", None),
(
"\nasync function foo() {\nif (a) {\n\t\tif (b) {\n\t\t\treturn bar();\n\t\t}\n\t}\n}\n",
None,
),
("\nasync () => {\nif (a) {\n\t\tif (b) {\n\t\t\treturn bar();\n\t\t}\n\t}\n}\n", None),
("\nasync function foo() {\n\treturn (await bar() && a);\n}\n", None),
("\nasync function foo() {\n\treturn (await bar() || a);\n}\n", None),
("\nasync function foo() {\n\treturn (a && await baz() && b);\n}\n", None),
("\nasync function foo() {\n\treturn (await bar(), a);\n}\n", None),
("\nasync function foo() {\n\treturn (await baz(), await bar(), a);\n}\n", None),
("\nasync function foo() {\n\treturn (a, b, (await bar(), c));\n}\n", None),
("\nasync function foo() {\n\treturn (await bar() ? a : b);\n}\n", None),
("\nasync function foo() {\n\treturn ((a && await bar()) ? b : c);\n}\n", None),
("\nasync function foo() {\n\treturn (baz() ? (await bar(), a) : b);\n}\n", None),
("\nasync function foo() {\n\treturn (baz() ? (await bar() && a) : b);\n}\n", None),
("\nasync function foo() {\n\treturn (baz() ? a : (await bar(), b));\n}\n", None),
("\nasync function foo() {\n\treturn (baz() ? a : (await bar() && b));\n}\n", None),
("\nasync () => (await bar(), a)\n", None),
("\nasync () => (await bar() && a)\n", None),
("\nasync () => (await bar() || a)\n", None),
("\nasync () => (a && await bar() && b)\n", None),
("\nasync () => (await baz(), await bar(), a)\n", None),
("\nasync () => (a, b, (await bar(), c))\n", None),
("\nasync () => (await bar() ? a : b)\n", None),
("\nasync () => ((a && await bar()) ? b : c)\n", None),
("\nasync () => (baz() ? (await bar(), a) : b)\n", None),
("\nasync () => (baz() ? (await bar() && a) : b)\n", None),
("\nasync () => (baz() ? a : (await bar(), b))\n", None),
("\nasync () => (baz() ? a : (await bar() && b))\n", None),
(
"\n async function foo() {\n try {\n return await bar();\n } catch (e) {\n baz();\n }\n }\n ",
None,
),
(
"\n async function foo() {\n try {\n return await bar();\n } finally {\n baz();\n }\n }\n ",
None,
),
(
"\n async function foo() {\n try {}\n catch (e) {\n return await bar();\n } finally {\n baz();\n }\n }\n ",
None,
),
(
"\n async function foo() {\n try {\n try {}\n finally {\n return await bar();\n }\n } finally {\n baz();\n }\n }\n ",
None,
),
(
"\n async function foo() {\n try {\n try {}\n catch (e) {\n return await bar();\n }\n } finally {\n baz();\n }\n }\n ",
None,
),
(
"\n async function foo() {\n try {\n return (a, await bar());\n } catch (e) {\n baz();\n }\n }\n ",
None,
),
(
"\n async function foo() {\n try {\n return (qux() ? await bar() : b);\n } catch (e) {\n baz();\n }\n }\n ",
None,
),
(
"\n async function foo() {\n try {\n return (a && await bar());\n } catch (e) {\n baz();\n }\n }\n ",
None,
),
];
let fail = vec![
("\nasync function foo() {\n\treturn await bar();\n}\n", None),
("\nasync function foo() {\n\treturn await(bar());\n}\n", None),
("\nasync function foo() {\n\treturn (a, await bar());\n}\n", None),
("\nasync function foo() {\n\treturn (a, b, await bar());\n}\n", None),
("\nasync function foo() {\n\treturn (a && await bar());\n}\n", None),
("\nasync function foo() {\n\treturn (a && b && await bar());\n}\n", None),
("\nasync function foo() {\n\treturn (a || await bar());\n}\n", None),
("\nasync function foo() {\n\treturn (a, b, (c, d, await bar()));\n}\n", None),
("\nasync function foo() {\n\treturn (a, b, (c && await bar()));\n}\n", None),
("\nasync function foo() {\n\treturn (await baz(), b, await bar());\n}\n", None),
("\nasync function foo() {\n\treturn (baz() ? await bar() : b);\n}\n", None),
("\nasync function foo() {\n\treturn (baz() ? a : await bar());\n}\n", None),
("\nasync function foo() {\n\treturn (baz() ? (a, await bar()) : b);\n}\n", None),
("\nasync function foo() {\n\treturn (baz() ? a : (b, await bar()));\n}\n", None),
("\nasync function foo() {\n\treturn (baz() ? (a && await bar()) : b);\n}\n", None),
("\nasync function foo() {\n\treturn (baz() ? a : (b && await bar()));\n}\n", None),
("\nasync () => { return await bar(); }\n", None),
("\nasync () => await bar()\n", None),
("\nasync () => (a, b, await bar())\n", None),
("\nasync () => (a && await bar())\n", None),
("\nasync () => (baz() ? await bar() : b)\n", None),
("\nasync () => (baz() ? a : (b, await bar()))\n", None),
("\nasync () => (baz() ? a : (b && await bar()))\n", None),
("\nasync function foo() {\nif (a) {\n\t\tif (b) {\n\t\t\treturn await bar();\n\t\t}\n\t}\n}\n", None),
("\nasync () => {\nif (a) {\n\t\tif (b) {\n\t\t\treturn await bar();\n\t\t}\n\t}\n}\n", None),
("\nasync function foo() { try {}\nfinally {\nreturn await bar();\n}\n}\n", None),
("\nasync function foo() {\ntry {}\ncatch (e) {\nreturn await bar();\n}\n}\n", None),
("\ntry {\nasync function foo() {\nreturn await bar();\n}\n} catch (e) {}\n", None),
("\ntry {\nasync () => await bar();\n} catch (e) {}\n", None),
(
"\nasync function foo() {\ntry {}\ncatch (e) {\ntry {}\ncatch (e) {\n return await bar();\n}\n}\n}\n",
None,
),
(
"\nasync function foo() {\nreturn await new Promise(resolve => {\nresolve(5);\n});\n}\n",
None,
),
("\nasync () => {\nreturn await (\nfoo()\n)\n};\n", None),
("\nasync function foo() {\nreturn await // Test\n5;\n}\n", None),
];
Tester::new(NoReturnAwait::NAME, pass, fail).test_and_snapshot();
}

View file

@ -0,0 +1,296 @@
---
source: crates/oxc_linter/src/tester.rs
assertion_line: 67
expression: no_return_await
---
⚠ eslint(no-return-await): Redundant use of `await` on a return value.
╭─[no_return_await.tsx:2:1]
2 │ async function foo() {
3 │ return await bar();
· ─────
4 │ }
╰────
help: Remove redundant `await`.
⚠ eslint(no-return-await): Redundant use of `await` on a return value.
╭─[no_return_await.tsx:2:1]
2 │ async function foo() {
3 │ return await(bar());
· ─────
4 │ }
╰────
help: Remove redundant `await`.
⚠ eslint(no-return-await): Redundant use of `await` on a return value.
╭─[no_return_await.tsx:2:1]
2 │ async function foo() {
3 │ return (a, await bar());
· ─────
4 │ }
╰────
help: Remove redundant `await`.
⚠ eslint(no-return-await): Redundant use of `await` on a return value.
╭─[no_return_await.tsx:2:1]
2 │ async function foo() {
3 │ return (a, b, await bar());
· ─────
4 │ }
╰────
help: Remove redundant `await`.
⚠ eslint(no-return-await): Redundant use of `await` on a return value.
╭─[no_return_await.tsx:2:1]
2 │ async function foo() {
3 │ return (a && await bar());
· ─────
4 │ }
╰────
help: Remove redundant `await`.
⚠ eslint(no-return-await): Redundant use of `await` on a return value.
╭─[no_return_await.tsx:2:1]
2 │ async function foo() {
3 │ return (a && b && await bar());
· ─────
4 │ }
╰────
help: Remove redundant `await`.
⚠ eslint(no-return-await): Redundant use of `await` on a return value.
╭─[no_return_await.tsx:2:1]
2 │ async function foo() {
3 │ return (a || await bar());
· ─────
4 │ }
╰────
help: Remove redundant `await`.
⚠ eslint(no-return-await): Redundant use of `await` on a return value.
╭─[no_return_await.tsx:2:1]
2 │ async function foo() {
3 │ return (a, b, (c, d, await bar()));
· ─────
4 │ }
╰────
help: Remove redundant `await`.
⚠ eslint(no-return-await): Redundant use of `await` on a return value.
╭─[no_return_await.tsx:2:1]
2 │ async function foo() {
3 │ return (a, b, (c && await bar()));
· ─────
4 │ }
╰────
help: Remove redundant `await`.
⚠ eslint(no-return-await): Redundant use of `await` on a return value.
╭─[no_return_await.tsx:2:1]
2 │ async function foo() {
3 │ return (await baz(), b, await bar());
· ─────
4 │ }
╰────
help: Remove redundant `await`.
⚠ eslint(no-return-await): Redundant use of `await` on a return value.
╭─[no_return_await.tsx:2:1]
2 │ async function foo() {
3 │ return (baz() ? await bar() : b);
· ─────
4 │ }
╰────
help: Remove redundant `await`.
⚠ eslint(no-return-await): Redundant use of `await` on a return value.
╭─[no_return_await.tsx:2:1]
2 │ async function foo() {
3 │ return (baz() ? a : await bar());
· ─────
4 │ }
╰────
help: Remove redundant `await`.
⚠ eslint(no-return-await): Redundant use of `await` on a return value.
╭─[no_return_await.tsx:2:1]
2 │ async function foo() {
3 │ return (baz() ? (a, await bar()) : b);
· ─────
4 │ }
╰────
help: Remove redundant `await`.
⚠ eslint(no-return-await): Redundant use of `await` on a return value.
╭─[no_return_await.tsx:2:1]
2 │ async function foo() {
3 │ return (baz() ? a : (b, await bar()));
· ─────
4 │ }
╰────
help: Remove redundant `await`.
⚠ eslint(no-return-await): Redundant use of `await` on a return value.
╭─[no_return_await.tsx:2:1]
2 │ async function foo() {
3 │ return (baz() ? (a && await bar()) : b);
· ─────
4 │ }
╰────
help: Remove redundant `await`.
⚠ eslint(no-return-await): Redundant use of `await` on a return value.
╭─[no_return_await.tsx:2:1]
2 │ async function foo() {
3 │ return (baz() ? a : (b && await bar()));
· ─────
4 │ }
╰────
help: Remove redundant `await`.
⚠ eslint(no-return-await): Redundant use of `await` on a return value.
╭─[no_return_await.tsx:1:1]
1 │
2 │ async () => { return await bar(); }
· ─────
╰────
help: Remove redundant `await`.
⚠ eslint(no-return-await): Redundant use of `await` on a return value.
╭─[no_return_await.tsx:1:1]
1 │
2 │ async () => await bar()
· ─────
╰────
help: Remove redundant `await`.
⚠ eslint(no-return-await): Redundant use of `await` on a return value.
╭─[no_return_await.tsx:1:1]
1 │
2 │ async () => (a, b, await bar())
· ─────
╰────
help: Remove redundant `await`.
⚠ eslint(no-return-await): Redundant use of `await` on a return value.
╭─[no_return_await.tsx:1:1]
1 │
2 │ async () => (a && await bar())
· ─────
╰────
help: Remove redundant `await`.
⚠ eslint(no-return-await): Redundant use of `await` on a return value.
╭─[no_return_await.tsx:1:1]
1 │
2 │ async () => (baz() ? await bar() : b)
· ─────
╰────
help: Remove redundant `await`.
⚠ eslint(no-return-await): Redundant use of `await` on a return value.
╭─[no_return_await.tsx:1:1]
1 │
2 │ async () => (baz() ? a : (b, await bar()))
· ─────
╰────
help: Remove redundant `await`.
⚠ eslint(no-return-await): Redundant use of `await` on a return value.
╭─[no_return_await.tsx:1:1]
1 │
2 │ async () => (baz() ? a : (b && await bar()))
· ─────
╰────
help: Remove redundant `await`.
⚠ eslint(no-return-await): Redundant use of `await` on a return value.
╭─[no_return_await.tsx:4:1]
4 │ if (b) {
5 │ return await bar();
· ─────
6 │ }
╰────
help: Remove redundant `await`.
⚠ eslint(no-return-await): Redundant use of `await` on a return value.
╭─[no_return_await.tsx:4:1]
4 │ if (b) {
5 │ return await bar();
· ─────
6 │ }
╰────
help: Remove redundant `await`.
⚠ eslint(no-return-await): Redundant use of `await` on a return value.
╭─[no_return_await.tsx:3:1]
3 │ finally {
4 │ return await bar();
· ─────
5 │ }
╰────
help: Remove redundant `await`.
⚠ eslint(no-return-await): Redundant use of `await` on a return value.
╭─[no_return_await.tsx:4:1]
4 │ catch (e) {
5 │ return await bar();
· ─────
6 │ }
╰────
help: Remove redundant `await`.
⚠ eslint(no-return-await): Redundant use of `await` on a return value.
╭─[no_return_await.tsx:3:1]
3 │ async function foo() {
4 │ return await bar();
· ─────
5 │ }
╰────
help: Remove redundant `await`.
⚠ eslint(no-return-await): Redundant use of `await` on a return value.
╭─[no_return_await.tsx:2:1]
2 │ try {
3 │ async () => await bar();
· ─────
4 │ } catch (e) {}
╰────
help: Remove redundant `await`.
⚠ eslint(no-return-await): Redundant use of `await` on a return value.
╭─[no_return_await.tsx:6:1]
6 │ catch (e) {
7 │ return await bar();
· ─────
8 │ }
╰────
help: Remove redundant `await`.
⚠ eslint(no-return-await): Redundant use of `await` on a return value.
╭─[no_return_await.tsx:2:1]
2 │ async function foo() {
3 │ return await new Promise(resolve => {
· ─────
4 │ resolve(5);
╰────
help: Remove redundant `await`.
⚠ eslint(no-return-await): Redundant use of `await` on a return value.
╭─[no_return_await.tsx:2:1]
2 │ async () => {
3 │ return await (
· ─────
4 │ foo()
╰────
help: Remove redundant `await`.
⚠ eslint(no-return-await): Redundant use of `await` on a return value.
╭─[no_return_await.tsx:2:1]
2 │ async function foo() {
3 │ return await // Test
· ─────
4 │ 5;
╰────
help: Remove redundant `await`.