From a72bc7556a00b1d9ba1e5f28fe02c90919e69a45 Mon Sep 17 00:00:00 2001 From: Jelle van der Waa Date: Sun, 14 Jul 2024 16:13:49 +0200 Subject: [PATCH] feat(linter/eslint-plugin-promise): implement prefer-await-to-then Rule detail: [link](https://github.com/eslint-community/eslint-plugin-promise/blob/main/docs/rules/prefer-await-to-then.md) --- crates/oxc_linter/src/rules.rs | 2 + .../src/rules/promise/prefer_await_to_then.rs | 104 ++++++++++++++++++ .../src/snapshots/prefer_await_to_then.snap | 62 +++++++++++ 3 files changed, 168 insertions(+) create mode 100644 crates/oxc_linter/src/rules/promise/prefer_await_to_then.rs create mode 100644 crates/oxc_linter/src/snapshots/prefer_await_to_then.snap diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 4f74fafc355b1..344112dce1395 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -446,6 +446,7 @@ mod promise { pub mod no_new_statics; pub mod no_return_in_finally; pub mod param_names; + pub mod prefer_await_to_then; pub mod valid_params; } @@ -859,6 +860,7 @@ oxc_macros::declare_all_lint_rules! { promise::param_names, promise::valid_params, promise::no_return_in_finally, + promise::prefer_await_to_then, vitest::no_import_node_test, vitest::prefer_to_be_falsy, vitest::prefer_to_be_truthy, diff --git a/crates/oxc_linter/src/rules/promise/prefer_await_to_then.rs b/crates/oxc_linter/src/rules/promise/prefer_await_to_then.rs new file mode 100644 index 0000000000000..4bcac593404d8 --- /dev/null +++ b/crates/oxc_linter/src/rules/promise/prefer_await_to_then.rs @@ -0,0 +1,104 @@ +use oxc_ast::AstKind; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_span::Span; + +fn prefer_wait_to_then_diagnostic(span0: Span) -> OxcDiagnostic { + OxcDiagnostic::warn("Prefer await to then()/catch()/finally()").with_label(span0) +} + +use crate::{context::LintContext, rule::Rule, AstNode}; + +#[derive(Debug, Default, Clone)] +pub struct PreferAwaitToThen; + +declare_oxc_lint!( + /// ### What it does + /// + /// Prefer `await` to `then()`/`catch()`/`finally()` for reading Promise values + /// + /// ### Why is this bad? + /// + /// Async/await syntax can be seen as more readable. + /// + /// ### Example + /// ```javascript + /// myPromise.then(doSomething) + /// ``` + PreferAwaitToThen, + style, +); + +fn is_inside_yield_or_await(node: &AstNode) -> bool { + matches!(node.kind(), AstKind::YieldExpression(_) | AstKind::AwaitExpression(_)) +} + +impl Rule for PreferAwaitToThen { + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + let AstKind::CallExpression(call_expr) = node.kind() else { + return; + }; + + let Some(member_expr) = call_expr.callee.get_member_expr() else { + return; + }; + + let Some(prop_name) = member_expr.static_property_name() else { + return; + }; + + if !matches!(prop_name, "then" | "catch" | "finally") { + return; + } + + // Await statements cannot be added to the top level scope + if ctx.scopes().get_flags(node.scope_id()).is_top() { + return; + } + + // Already inside a yield or await + if ctx + .nodes() + .ancestors(node.id()) + .any(|node_id| is_inside_yield_or_await(ctx.nodes().get_node(node_id))) + { + return; + } + + ctx.diagnostic(prefer_wait_to_then_diagnostic(call_expr.span)); + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + "async function hi() { await thing() }", + "async function hi() { await thing().then() }", + "async function hi() { await thing().catch() }", + "a = async () => (await something())", + "a = async () => { + try { await something() } catch (error) { somethingElse() } + }", + "something().then(async () => await somethingElse())", + "function foo() { hey.somethingElse(x => {}) }", + "const isThenable = (obj) => { + return obj && typeof obj.then === 'function'; + };", + "function isThenable(obj) { + return obj && typeof obj.then === 'function'; + }", + ]; + + let fail = vec![ + "function foo() { hey.then(x => {}) }", + "function foo() { hey.then(function() { }).then() }", + "function foo() { hey.then(function() { }).then(x).catch() }", + "async function a() { hey.then(function() { }).then(function() { }) }", + "function foo() { hey.catch(x => {}) }", + "function foo() { hey.finally(x => {}) }", + ]; + + Tester::new(PreferAwaitToThen::NAME, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/prefer_await_to_then.snap b/crates/oxc_linter/src/snapshots/prefer_await_to_then.snap new file mode 100644 index 0000000000000..990f057288bac --- /dev/null +++ b/crates/oxc_linter/src/snapshots/prefer_await_to_then.snap @@ -0,0 +1,62 @@ +--- +source: crates/oxc_linter/src/tester.rs +--- + ⚠ eslint-plugin-promise(prefer-await-to-then): Prefer await to then()/catch()/finally() + ╭─[prefer_await_to_then.tsx:1:18] + 1 │ function foo() { hey.then(x => {}) } + · ───────────────── + ╰──── + + ⚠ eslint-plugin-promise(prefer-await-to-then): Prefer await to then()/catch()/finally() + ╭─[prefer_await_to_then.tsx:1:18] + 1 │ function foo() { hey.then(function() { }).then() } + · ─────────────────────────────── + ╰──── + + ⚠ eslint-plugin-promise(prefer-await-to-then): Prefer await to then()/catch()/finally() + ╭─[prefer_await_to_then.tsx:1:18] + 1 │ function foo() { hey.then(function() { }).then() } + · ──────────────────────── + ╰──── + + ⚠ eslint-plugin-promise(prefer-await-to-then): Prefer await to then()/catch()/finally() + ╭─[prefer_await_to_then.tsx:1:18] + 1 │ function foo() { hey.then(function() { }).then(x).catch() } + · ──────────────────────────────────────── + ╰──── + + ⚠ eslint-plugin-promise(prefer-await-to-then): Prefer await to then()/catch()/finally() + ╭─[prefer_await_to_then.tsx:1:18] + 1 │ function foo() { hey.then(function() { }).then(x).catch() } + · ──────────────────────────────── + ╰──── + + ⚠ eslint-plugin-promise(prefer-await-to-then): Prefer await to then()/catch()/finally() + ╭─[prefer_await_to_then.tsx:1:18] + 1 │ function foo() { hey.then(function() { }).then(x).catch() } + · ──────────────────────── + ╰──── + + ⚠ eslint-plugin-promise(prefer-await-to-then): Prefer await to then()/catch()/finally() + ╭─[prefer_await_to_then.tsx:1:22] + 1 │ async function a() { hey.then(function() { }).then(function() { }) } + · ───────────────────────────────────────────── + ╰──── + + ⚠ eslint-plugin-promise(prefer-await-to-then): Prefer await to then()/catch()/finally() + ╭─[prefer_await_to_then.tsx:1:22] + 1 │ async function a() { hey.then(function() { }).then(function() { }) } + · ──────────────────────── + ╰──── + + ⚠ eslint-plugin-promise(prefer-await-to-then): Prefer await to then()/catch()/finally() + ╭─[prefer_await_to_then.tsx:1:18] + 1 │ function foo() { hey.catch(x => {}) } + · ────────────────── + ╰──── + + ⚠ eslint-plugin-promise(prefer-await-to-then): Prefer await to then()/catch()/finally() + ╭─[prefer_await_to_then.tsx:1:18] + 1 │ function foo() { hey.finally(x => {}) } + · ──────────────────── + ╰────