From ed2f10a560aae1fa1d5f5899f2d80a72d095663b Mon Sep 17 00:00:00 2001 From: Don Isaac Date: Tue, 4 Jun 2024 19:39:27 -0400 Subject: [PATCH 1/3] up(linter): add fixer for no-single-promise-in-promise-methods --- .../no_single_promise_in_promise_methods.rs | 94 +++++++++++++------ 1 file changed, 65 insertions(+), 29 deletions(-) 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..afeb944fc05b2 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 as _, 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,9 +57,38 @@ impl Rule for NoSinglePromiseInPromiseMethods { return; }; - if !is_promise_method_with_single_element_array(call_expr) { + // if !is_method_call( + // call_expr, + // Some(&["Promise"]), + // Some(&["all", "any", "race"]), + // Some(1), + // Some(1), + // ) { + // return false; + // } + 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; + } + + // if !is_promise_method_with_single_element_array(call_expr) { + // return; + // } let info = call_expr .callee @@ -68,34 +97,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 +211,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(); } From ab06d816bab51889b212780c98af4d6b5cb2cabf Mon Sep 17 00:00:00 2001 From: Don Isaac Date: Wed, 5 Jun 2024 13:28:31 -0400 Subject: [PATCH 2/3] Update crates/oxc_linter/src/rules/unicorn/no_single_promise_in_promise_methods.rs Co-authored-by: Boshen --- .../src/rules/unicorn/no_single_promise_in_promise_methods.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 afeb944fc05b2..c533f2e845f02 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,7 +5,7 @@ use oxc_ast::{ use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; -use oxc_span::{GetSpan as _, Span}; +use oxc_span::{GetSpan, Span}; use crate::{ast_util::is_method_call, context::LintContext, fixer::Fix, rule::Rule, AstNode}; From a15185ceab1f921ea67fe9c380914523f9af64e5 Mon Sep 17 00:00:00 2001 From: Don Isaac Date: Wed, 5 Jun 2024 13:31:41 -0400 Subject: [PATCH 3/3] chore: remove commented code --- .../unicorn/no_single_promise_in_promise_methods.rs | 13 ------------- 1 file changed, 13 deletions(-) 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 c533f2e845f02..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 @@ -57,15 +57,6 @@ impl Rule for NoSinglePromiseInPromiseMethods { return; }; - // if !is_method_call( - // call_expr, - // Some(&["Promise"]), - // Some(&["all", "any", "race"]), - // Some(1), - // Some(1), - // ) { - // return false; - // } if !is_promise_method_with_single_argument(call_expr) { return; } @@ -86,10 +77,6 @@ impl Rule for NoSinglePromiseInPromiseMethods { return; } - // if !is_promise_method_with_single_element_array(call_expr) { - // return; - // } - let info = call_expr .callee .get_member_expr()