Skip to content

Commit

Permalink
feat(linter): Add "strict" option to promise/prefer-await-to-then r…
Browse files Browse the repository at this point in the history
…ule (#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 => {})
}
```
  • Loading branch information
NeilTheFisher authored Jan 23, 2025
1 parent 75a579b commit dcaebe6
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 30 deletions.
111 changes: 81 additions & 30 deletions crates/oxc_linter/src/rules/promise/prefer_await_to_then.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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,
Expand All @@ -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;
Expand All @@ -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));
Expand All @@ -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,
),
// <https://github.com/tc39/proposal-top-level-await>
// 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();
Expand Down
12 changes: 12 additions & 0 deletions crates/oxc_linter/src/snapshots/promise_prefer_await_to_then.snap
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,15 @@ snapshot_kind: text
1something().then(async () => await somethingElse())
· ───────────────────────────────────────────────────
╰────

eslint-plugin-promise(prefer-await-to-then): Prefer await to then()/catch()/finally()
╭─[prefer_await_to_then.tsx:1:30]
1async 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]
1async function foo() { thing().then() }
· ──────────────
╰────

0 comments on commit dcaebe6

Please sign in to comment.