fix(linter): support rest params for prefer_promise_reject_errors (#8468)

fixed:
https://github.com/oxc-project/oxc/pull/8254#issuecomment-2587461210
This commit is contained in:
Yuichiro Yamashita 2025-01-14 18:40:52 +09:00 committed by GitHub
parent de5b28809a
commit c6260c278b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 64 additions and 12 deletions

View file

@ -1,4 +1,5 @@
use oxc_allocator::Box; use oxc_allocator::Box;
use oxc_ast::ast::MemberExpression;
use oxc_ast::{ use oxc_ast::{
ast::{Argument, CallExpression, Expression, FormalParameters}, ast::{Argument, CallExpression, Expression, FormalParameters},
AstKind, AstKind,
@ -136,15 +137,13 @@ fn check_reject_call(call_expr: &CallExpression, ctx: &LintContext, allow_empty_
} }
} }
#[allow(clippy::float_cmp, clippy::cast_precision_loss)]
fn check_reject_in_function( fn check_reject_in_function(
params: &Box<'_, FormalParameters<'_>>, params: &Box<'_, FormalParameters<'_>>,
ctx: &LintContext, ctx: &LintContext,
allow_empty_reject: bool, allow_empty_reject: bool,
) { ) {
if params.parameters_count() <= 1 { if params.items.len() >= 2 {
return;
}
let Some(reject_arg) = params.items[1].pattern.get_binding_identifier() else { let Some(reject_arg) = params.items[1].pattern.get_binding_identifier() else {
return; return;
}; };
@ -157,10 +156,46 @@ fn check_reject_in_function(
check_reject_call(call_expr, ctx, allow_empty_reject); check_reject_call(call_expr, ctx, allow_empty_reject);
} }
}); });
return;
}
let Some(rest_param) = &params.rest else { return };
let Some(rest_arg) = rest_param.argument.get_binding_identifier() else { return };
let rest_index = (1 - params.items.len()) as f64;
for reference in ctx.symbol_references(rest_arg.symbol_id()) {
let node = ctx.nodes().get_node(reference.node_id());
if !matches!(node.kind(), AstKind::IdentifierReference(_)) {
continue;
}
let Some(parent) = ctx.nodes().parent_node(reference.node_id()) else { continue };
let AstKind::MemberExpression(MemberExpression::ComputedMemberExpression(member_expr)) =
parent.kind()
else {
continue;
};
let Expression::NumericLiteral(literal) = &member_expr.expression else {
continue;
};
if literal.value != rest_index {
continue;
}
let Some(node) = ctx.nodes().parent_node(parent.id()) else {
continue;
};
if let AstKind::CallExpression(call_expr) = node.kind() {
check_reject_call(call_expr, ctx, allow_empty_reject);
}
}
} }
fn is_undefined(arg: &Argument) -> bool { fn is_undefined(arg: &Argument) -> bool {
match arg.as_expression().map(oxc_ast::ast::Expression::get_inner_expression) { match arg.as_expression().map(Expression::get_inner_expression) {
Some(Expression::Identifier(ident)) => ident.name == "undefined", Some(Expression::Identifier(ident)) => ident.name == "undefined",
_ => false, _ => false,
} }
@ -196,7 +231,12 @@ fn test() {
("Promise.reject(foo.bar ??= 5)", None), ("Promise.reject(foo.bar ??= 5)", None),
("Promise.reject(foo[bar] ??= 5)", None), ("Promise.reject(foo[bar] ??= 5)", None),
("class C { #reject; foo() { Promise.#reject(5); } }", None), ("class C { #reject; foo() { Promise.#reject(5); } }", None),
("class C { #error; foo() { Promise.reject(this.#error); } }", None) ("class C { #error; foo() { Promise.reject(this.#error); } }", None),
("new Promise(function (resolve, ...rest) { rest[0](new Error('')); });", None),
("new Promise(function (...rest) { rest[0](new Error('')); });", None),
("new Promise(function (...rest) { rest[1](new Error('')); });", None),
// This is fundamentally false, but we can not recognize the value of `i`.
("new Promise(function (resolve, ...rest) { rest[i](5); });", None),
]; ];
let fail = vec![ let fail = vec![
@ -255,6 +295,8 @@ fn test() {
// evaluates either to a falsy value of `foo` (which, then, cannot be an Error object), or to `5` // evaluates either to a falsy value of `foo` (which, then, cannot be an Error object), or to `5`
("Promise.reject(foo && 5)", None), ("Promise.reject(foo && 5)", None),
("Promise.reject(foo &&= 5)", None), ("Promise.reject(foo &&= 5)", None),
("new Promise(function (resolve, ...rest) { rest[0](5); });", None),
("new Promise(function (...rest) { rest[1](5); });", None),
]; ];
Tester::new(PreferPromiseRejectErrors::NAME, PreferPromiseRejectErrors::PLUGIN, pass, fail) Tester::new(PreferPromiseRejectErrors::NAME, PreferPromiseRejectErrors::PLUGIN, pass, fail)

View file

@ -1,7 +1,5 @@
--- ---
source: crates/oxc_linter/src/tester.rs source: crates/oxc_linter/src/tester.rs
assertion_line: 356
snapshot_kind: text
--- ---
⚠ eslint(prefer-promise-reject-errors): Expected the Promise rejection reason to be an Error ⚠ eslint(prefer-promise-reject-errors): Expected the Promise rejection reason to be an Error
╭─[prefer_promise_reject_errors.tsx:1:1] ╭─[prefer_promise_reject_errors.tsx:1:1]
@ -240,3 +238,15 @@ snapshot_kind: text
1 │ Promise.reject(foo &&= 5) 1 │ Promise.reject(foo &&= 5)
· ───────────────────────── · ─────────────────────────
╰──── ╰────
⚠ eslint(prefer-promise-reject-errors): Expected the Promise rejection reason to be an Error
╭─[prefer_promise_reject_errors.tsx:1:43]
1 │ new Promise(function (resolve, ...rest) { rest[0](5); });
· ──────────
╰────
⚠ eslint(prefer-promise-reject-errors): Expected the Promise rejection reason to be an Error
╭─[prefer_promise_reject_errors.tsx:1:34]
1 │ new Promise(function (...rest) { rest[1](5); });
· ──────────
╰────