From dcaebe69b15ad13b341dca7073bde49d7713a6be Mon Sep 17 00:00:00 2001 From: Neil Fisher <47160993+NeilTheFisher@users.noreply.github.com> Date: Thu, 23 Jan 2025 08:13:50 -0500 Subject: [PATCH] feat(linter): Add "strict" option to `promise/prefer-await-to-then` rule (#8674) Implementation of the strict option [specified in the eslint docs for `promise/prefer-await-to-then`](https://github.com/eslint-community/eslint-plugin-promise/blob/main/docs/rules/prefer-await-to-then.md) ```js // Examples of **incorrect** code with `{ strict: true }`: async function hi() { await thing().then(x => {}) } ``` --- .../src/rules/promise/prefer_await_to_then.rs | 111 +++++++++++++----- .../promise_prefer_await_to_then.snap | 12 ++ 2 files changed, 93 insertions(+), 30 deletions(-) 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 index 5512a25adc120..bbda254d02c42 100644 --- a/crates/oxc_linter/src/rules/promise/prefer_await_to_then.rs +++ b/crates/oxc_linter/src/rules/promise/prefer_await_to_then.rs @@ -2,6 +2,7 @@ use oxc_ast::AstKind; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_span::Span; +use serde_json::Value; fn prefer_wait_to_then_diagnostic(span: Span) -> OxcDiagnostic { OxcDiagnostic::warn("Prefer await to then()/catch()/finally()").with_label(span) @@ -10,7 +11,20 @@ fn prefer_wait_to_then_diagnostic(span: Span) -> OxcDiagnostic { use crate::{context::LintContext, rule::Rule, utils::is_promise, AstNode}; #[derive(Debug, Default, Clone)] -pub struct PreferAwaitToThen; +pub struct PreferAwaitToThen(PreferAwaitToThenConfig); + +impl std::ops::Deref for PreferAwaitToThen { + type Target = PreferAwaitToThenConfig; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[derive(Debug, Default, Clone)] +pub struct PreferAwaitToThenConfig { + strict: bool, +} declare_oxc_lint!( /// ### What it does @@ -32,6 +46,14 @@ declare_oxc_lint!( /// ```javascript /// async function hi() { await thing() } /// ``` + /// + /// ### Example with strict mode + /// Examples of **incorrect** code with `{ strict: true }`: + /// ```javascript + /// async function hi() { + /// await thing().then(x => {}) + /// } + /// ``` PreferAwaitToThen, promise, style, @@ -42,6 +64,15 @@ fn is_inside_yield_or_await(node: &AstNode) -> bool { } impl Rule for PreferAwaitToThen { + fn from_configuration(value: serde_json::Value) -> Self { + let strict = match value { + Value::Object(obj) => obj.get("strict").and_then(Value::as_bool).unwrap_or(false), + _ => false, + }; + + Self(PreferAwaitToThenConfig { strict }) + } + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { let AstKind::CallExpression(call_expr) = node.kind() else { return; @@ -51,13 +82,15 @@ impl Rule for PreferAwaitToThen { return; } - // Already inside a yield or await - if ctx - .nodes() - .ancestor_ids(node.id()) - .any(|node_id| is_inside_yield_or_await(ctx.nodes().get_node(node_id))) - { - return; + if !self.strict { + // Already inside a yield or await + if ctx + .nodes() + .ancestor_ids(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)); @@ -69,33 +102,51 @@ 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() } - }", + ("async function hi() { await thing() }", None), + ("async function hi() { await thing().then() }", None), + ("async function hi() { await thing().catch() }", None), + ("a = async () => (await something())", None), + ( + "a = async () => { + try { await something() } catch (error) { somethingElse() } + }", + None, + ), // // Top level await is allowed now, so comment this out - // "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'; - }", + // ("something().then(async () => await somethingElse())", None), + ("function foo() { hey.somethingElse(x => {}) }", None), + ( + "const isThenable = (obj) => { + return obj && typeof obj.then === 'function'; + };", + None, + ), + ( + "function isThenable(obj) { + return obj && typeof obj.then === 'function'; + }", + None, + ), + ( + "async function hi() { await thing().then() }", + Some(serde_json::json!({ "strict": false })), + ), ]; 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 => {}) }", - "something().then(async () => await somethingElse())", + ("function foo() { hey.then(x => {}) }", None), + ("function foo() { hey.then(function() { }).then() }", None), + ("function foo() { hey.then(function() { }).then(x).catch() }", None), + ("async function a() { hey.then(function() { }).then(function() { }) }", None), + ("function foo() { hey.catch(x => {}) }", None), + ("function foo() { hey.finally(x => {}) }", None), + ("something().then(async () => await somethingElse())", None), + ( + "async function foo() { await thing().then() }", + Some(serde_json::json!({ "strict": true })), + ), + ("async function foo() { thing().then() }", Some(serde_json::json!({ "strict": false }))), ]; Tester::new(PreferAwaitToThen::NAME, PreferAwaitToThen::PLUGIN, pass, fail).test_and_snapshot(); diff --git a/crates/oxc_linter/src/snapshots/promise_prefer_await_to_then.snap b/crates/oxc_linter/src/snapshots/promise_prefer_await_to_then.snap index 967c41f8594ba..7155123b4ead1 100644 --- a/crates/oxc_linter/src/snapshots/promise_prefer_await_to_then.snap +++ b/crates/oxc_linter/src/snapshots/promise_prefer_await_to_then.snap @@ -67,3 +67,15 @@ snapshot_kind: text 1 │ something().then(async () => await somethingElse()) · ─────────────────────────────────────────────────── ╰──── + + ⚠ eslint-plugin-promise(prefer-await-to-then): Prefer await to then()/catch()/finally() + ╭─[prefer_await_to_then.tsx:1:30] + 1 │ async function foo() { await thing().then() } + · ────────────── + ╰──── + + ⚠ eslint-plugin-promise(prefer-await-to-then): Prefer await to then()/catch()/finally() + ╭─[prefer_await_to_then.tsx:1:24] + 1 │ async function foo() { thing().then() } + · ────────────── + ╰────