From 2fa0b25ab4038d3bd581b4f8f46f6d54121d6d87 Mon Sep 17 00:00:00 2001 From: yefan Date: Mon, 26 May 2025 21:41:45 +0800 Subject: [PATCH 1/2] implement --- .../unicorn/no_await_expression_member.rs | 86 ++++++++++++++++++- 1 file changed, 83 insertions(+), 3 deletions(-) diff --git a/crates/oxc_linter/src/rules/unicorn/no_await_expression_member.rs b/crates/oxc_linter/src/rules/unicorn/no_await_expression_member.rs index bd83d40e12a79..27974bb7ae423 100644 --- a/crates/oxc_linter/src/rules/unicorn/no_await_expression_member.rs +++ b/crates/oxc_linter/src/rules/unicorn/no_await_expression_member.rs @@ -1,4 +1,7 @@ -use oxc_ast::{AstKind, ast::Expression}; +use oxc_ast::{ + AstKind, + ast::{BindingPatternKind, Expression, MemberExpression}, +}; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_span::{GetSpan, Span}; @@ -42,7 +45,7 @@ declare_oxc_lint!( NoAwaitExpressionMember, unicorn, style, - pending + fix_dangerous, ); impl Rule for NoAwaitExpressionMember { @@ -57,7 +60,65 @@ impl Rule for NoAwaitExpressionMember { if matches!(paren_expr.expression, Expression::AwaitExpression(_)) { let node_span = member_expr.span(); - ctx.diagnostic(no_await_expression_member_diagnostic(node_span)); + ctx.diagnostic_with_dangerous_fix( + no_await_expression_member_diagnostic(node_span), + |fixer| { + let Some(AstKind::VariableDeclarator(parent)) = + ctx.nodes().parent_kind(node.id()) + else { + return fixer.noop(); + }; + if parent.id.type_annotation.is_some() { + return fixer.noop(); + } + let BindingPatternKind::BindingIdentifier(id) = &parent.id.kind else { + return fixer.noop(); + }; + let name = id.name.as_str(); + let inner_text = ctx.source_range(Span::new( + paren_expr.span.start + 1, + paren_expr.span.end - 1, + )); + let fixer = fixer.for_multifix(); + let mut rule_fixes = fixer.new_fix_with_capacity(5); + + match member_expr { + // e.g. "const a = (await b())[0]" => "const {a} = await b()" + MemberExpression::ComputedMemberExpression(computed_member_expr) => { + let Expression::NumericLiteral(prop) = &computed_member_expr.expression + else { + return fixer.noop(); + }; + let Some(value) = prop.raw.map(|v| v.as_str()) else { + return fixer.noop(); + }; + if value != "0" && value != "1" { + return fixer.noop(); + } + // a => [a] or [, a] + let replacement = if value == "0" { + format!("[{name}]") + } else { + format!("[, {name}]") + }; + rule_fixes.push(fixer.replace(id.span, replacement)); + } + MemberExpression::StaticMemberExpression(static_member_expr) + if static_member_expr.property.name.as_str() == name => + { + // e.g. "const a = (await b()).a" => "const {a} = await b()" + rule_fixes.push(fixer.replace(id.span, format!("{{{name}}}"))); + } + _ => { + return fixer.noop(); + } + } + // (await b())[0] => await b() + // (await b()).a => await b() + rule_fixes.push(fixer.replace(member_expr.span(), inner_text)); + rule_fixes.with_message("Assign the result of the await expression to a variable, then access the member from that variable.") + }, + ); } } } @@ -109,6 +170,25 @@ fn test() { (r"const foo: Type | A = (await promise).foo", None), ]; + let fix = vec![ + ("const a = (await b()).a", "const {a} = await b()"), + ("const {a} = (await b()).a", "const {a} = (await b()).a"), + ("const a = (await b()).b", "const a = (await b()).b"), + ("const [a] = (await foo()).a", "const [a] = (await foo()).a"), + ("const a = (await b())[0]", "const [a] = await b()"), + ("const a = (await b())[1]", "const [, a] = await b()"), + ("const a = (await b())[2]", "const a = (await b())[2]"), + ("const [a] = (await b())[1]", "const [a] = (await b())[1]"), + ("let b, a = (await f()).a", "let b, {a} = await f()"), + ("const a = (/** comments */await b())[1]", "const [, a] = /** comments */await b()"), + ( + "const a = (/** comments */await b() /** comments */)[1]", + "const [, a] = /** comments */await b() /** comments */", + ), + ("const foo: Type = (await promise)[0]", "const foo: Type = (await promise)[0]"), + ]; + Tester::new(NoAwaitExpressionMember::NAME, NoAwaitExpressionMember::PLUGIN, pass, fail) + .expect_fix(fix) .test_and_snapshot(); } From 8c7543e035dea4920888a09dc9b6c354c30291e8 Mon Sep 17 00:00:00 2001 From: yefan Date: Mon, 26 May 2025 21:52:00 +0800 Subject: [PATCH 2/2] add test case --- .../src/rules/unicorn/no_await_expression_member.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/oxc_linter/src/rules/unicorn/no_await_expression_member.rs b/crates/oxc_linter/src/rules/unicorn/no_await_expression_member.rs index 27974bb7ae423..f99bd8d6f21b2 100644 --- a/crates/oxc_linter/src/rules/unicorn/no_await_expression_member.rs +++ b/crates/oxc_linter/src/rules/unicorn/no_await_expression_member.rs @@ -63,6 +63,9 @@ impl Rule for NoAwaitExpressionMember { ctx.diagnostic_with_dangerous_fix( no_await_expression_member_diagnostic(node_span), |fixer| { + if member_expr.optional() { + return fixer.noop(); + } let Some(AstKind::VariableDeclarator(parent)) = ctx.nodes().parent_kind(node.id()) else { @@ -186,6 +189,8 @@ fn test() { "const [, a] = /** comments */await b() /** comments */", ), ("const foo: Type = (await promise)[0]", "const foo: Type = (await promise)[0]"), + ("const a = (await b())?.a", "const a = (await b())?.a"), + ("const a = (await b())?.[0]", "const a = (await b())?.[0]"), ]; Tester::new(NoAwaitExpressionMember::NAME, NoAwaitExpressionMember::PLUGIN, pass, fail)