diff --git a/crates/oxc_linter/src/rules/unicorn/no_single_promise_in_promise_methods.rs b/crates/oxc_linter/src/rules/unicorn/no_single_promise_in_promise_methods.rs index 614f5c8f20f25..8b1b304f22bf6 100644 --- a/crates/oxc_linter/src/rules/unicorn/no_single_promise_in_promise_methods.rs +++ b/crates/oxc_linter/src/rules/unicorn/no_single_promise_in_promise_methods.rs @@ -5,9 +5,9 @@ use oxc_ast::{ use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; -use oxc_span::Span; +use oxc_span::{GetSpan, Span}; -use crate::{ast_util::is_method_call, context::LintContext, rule::Rule, AstNode}; +use crate::{ast_util::is_method_call, context::LintContext, fixer::Fix, rule::Rule, AstNode}; fn no_single_promise_in_promise_methods_diagnostic(span0: Span, x1: &str) -> OxcDiagnostic { OxcDiagnostic::warn(format!("eslint-plugin-unicorn(no-single-promise-in-promise-methods): Wrapping single-element array with `Promise.{x1}()` is unnecessary.")) @@ -57,7 +57,23 @@ impl Rule for NoSinglePromiseInPromiseMethods { return; }; - if !is_promise_method_with_single_element_array(call_expr) { + if !is_promise_method_with_single_argument(call_expr) { + return; + } + let Some(first_argument) = call_expr.arguments[0].as_expression() else { + return; + }; + let first_argument = first_argument.without_parenthesized(); + let Expression::ArrayExpression(first_argument_array_expr) = first_argument else { + return; + }; + + if first_argument_array_expr.elements.len() != 1 { + return; + } + + let first = &first_argument_array_expr.elements[0]; + if !first.is_expression() { return; } @@ -68,34 +84,33 @@ impl Rule for NoSinglePromiseInPromiseMethods { .static_property_info() .expect("callee is a static property"); - ctx.diagnostic(no_single_promise_in_promise_methods_diagnostic(info.0, info.1)); + let diagnostic = no_single_promise_in_promise_methods_diagnostic(info.0, info.1); + ctx.diagnostic_with_fix(diagnostic, || { + let elem_text = first.span().source_text(ctx.source_text()); + + let is_directly_in_await = ctx + .semantic() + .nodes() + // get first non-parenthesis parent node + .iter_parents(node.id()) + .skip(1) // first node is the call expr + .find(|parent| !matches!(parent.kind(), AstKind::ParenthesizedExpression(_))) + // check if it's an `await ...` expression + .is_some_and(|parent| matches!(parent.kind(), AstKind::AwaitExpression(_))); + + let call_span = call_expr.span; + + if is_directly_in_await { + Fix::new(elem_text, call_span) + } else { + Fix::new(format!("Promise.resolve({elem_text})"), call_span) + } + }); } } -fn is_promise_method_with_single_element_array(call_expr: &CallExpression) -> bool { - if !is_method_call( - call_expr, - Some(&["Promise"]), - Some(&["all", "any", "race"]), - Some(1), - Some(1), - ) { - return false; - } - - let Some(first_argument) = call_expr.arguments[0].as_expression() else { - return false; - }; - let first_argument = first_argument.without_parenthesized(); - let Expression::ArrayExpression(first_argument_array_expr) = first_argument else { - return false; - }; - - if first_argument_array_expr.elements.len() != 1 { - return false; - } - - first_argument_array_expr.elements[0].is_expression() +fn is_promise_method_with_single_argument(call_expr: &CallExpression) -> bool { + is_method_call(call_expr, Some(&["Promise"]), Some(&["all", "any", "race"]), Some(1), Some(1)) } #[test] @@ -183,5 +198,13 @@ fn test() { "Promise.all([null]).then()", ]; - Tester::new(NoSinglePromiseInPromiseMethods::NAME, pass, fail).test_and_snapshot(); + let fix = vec![ + ("Promise.all([null]).then()", "Promise.resolve(null).then()", None), + ("let x = await Promise.all([foo()])", "let x = await foo()", None), + ("let x = await (Promise.all([foo()]))", "let x = await (foo())", None), + ]; + + Tester::new(NoSinglePromiseInPromiseMethods::NAME, pass, fail) + .expect_fix(fix) + .test_and_snapshot(); }