From d178360a6dbd721bc20523c6d450e564c1ba33a2 Mon Sep 17 00:00:00 2001 From: tbashiyy <40194351+tbashiyy@users.noreply.github.com> Date: Mon, 13 Jan 2025 23:25:51 +0900 Subject: [PATCH] feat(linter): implement `eslint/prefer-promise-reject-errors` (#8254) implement https://eslint.org/docs/latest/rules/prefer-promise-reject-errors (ref: #479 ) --- crates/oxc_linter/src/ast_util.rs | 88 ++++++ crates/oxc_linter/src/rules.rs | 2 + .../src/rules/eslint/no_throw_literal.rs | 103 +------ .../eslint/prefer_promise_reject_errors.rs | 262 ++++++++++++++++++ .../eslint_prefer_promise_reject_errors.snap | 242 ++++++++++++++++ 5 files changed, 597 insertions(+), 100 deletions(-) create mode 100644 crates/oxc_linter/src/rules/eslint/prefer_promise_reject_errors.rs create mode 100644 crates/oxc_linter/src/snapshots/eslint_prefer_promise_reject_errors.snap diff --git a/crates/oxc_linter/src/ast_util.rs b/crates/oxc_linter/src/ast_util.rs index 6751ef4606453..f0f0d0720d8e1 100644 --- a/crates/oxc_linter/src/ast_util.rs +++ b/crates/oxc_linter/src/ast_util.rs @@ -7,6 +7,8 @@ use oxc_semantic::{AstNode, IsGlobalReference, NodeId, ReferenceId, Semantic, Sy use oxc_span::{GetSpan, Span}; use oxc_syntax::operator::{AssignmentOperator, BinaryOperator, LogicalOperator, UnaryOperator}; +use crate::LintContext; + /// Test if an AST node is a boolean value that never changes. Specifically we /// test for: /// 1. Literal booleans (`true` or `false`) @@ -469,3 +471,89 @@ pub fn leftmost_identifier_reference<'a, 'b: 'a>( _ => Err(expr), } } + +fn is_definitely_non_error_type(ty: &TSType) -> bool { + match ty { + TSType::TSNumberKeyword(_) + | TSType::TSStringKeyword(_) + | TSType::TSBooleanKeyword(_) + | TSType::TSNullKeyword(_) + | TSType::TSUndefinedKeyword(_) => true, + TSType::TSUnionType(union) => union.types.iter().all(is_definitely_non_error_type), + TSType::TSIntersectionType(intersect) => { + intersect.types.iter().all(is_definitely_non_error_type) + } + _ => false, + } +} + +pub fn could_be_error(ctx: &LintContext, expr: &Expression) -> bool { + match expr.get_inner_expression() { + Expression::NewExpression(_) + | Expression::AwaitExpression(_) + | Expression::CallExpression(_) + | Expression::ChainExpression(_) + | Expression::YieldExpression(_) + | Expression::PrivateFieldExpression(_) + | Expression::StaticMemberExpression(_) + | Expression::ComputedMemberExpression(_) + | Expression::TaggedTemplateExpression(_) => true, + Expression::AssignmentExpression(expr) => { + if matches!(expr.operator, AssignmentOperator::Assign | AssignmentOperator::LogicalAnd) + { + return could_be_error(ctx, &expr.right); + } + + if matches!( + expr.operator, + AssignmentOperator::LogicalOr | AssignmentOperator::LogicalNullish + ) { + return expr.left.get_expression().map_or(true, |expr| could_be_error(ctx, expr)) + || could_be_error(ctx, &expr.right); + } + + false + } + Expression::SequenceExpression(expr) => { + expr.expressions.last().is_some_and(|expr| could_be_error(ctx, expr)) + } + Expression::LogicalExpression(expr) => { + if matches!(expr.operator, LogicalOperator::And) { + return could_be_error(ctx, &expr.right); + } + + could_be_error(ctx, &expr.left) || could_be_error(ctx, &expr.right) + } + Expression::ConditionalExpression(expr) => { + could_be_error(ctx, &expr.consequent) || could_be_error(ctx, &expr.alternate) + } + Expression::Identifier(ident) => { + let reference = ctx.symbols().get_reference(ident.reference_id()); + let Some(symbol_id) = reference.symbol_id() else { + return true; + }; + let decl = ctx.nodes().get_node(ctx.symbols().get_declaration(symbol_id)); + match decl.kind() { + AstKind::VariableDeclarator(decl) => { + if let Some(init) = &decl.init { + could_be_error(ctx, init) + } else { + // TODO: warn about throwing undefined + false + } + } + AstKind::Function(_) + | AstKind::Class(_) + | AstKind::TSModuleDeclaration(_) + | AstKind::TSEnumDeclaration(_) => false, + AstKind::FormalParameter(param) => !param + .pattern + .type_annotation + .as_ref() + .is_some_and(|annot| is_definitely_non_error_type(&annot.type_annotation)), + _ => true, + } + } + _ => false, + } +} diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index c0aa53f178a28..bcc4347eec8ab 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -146,6 +146,7 @@ mod eslint { pub mod prefer_exponentiation_operator; pub mod prefer_numeric_literals; pub mod prefer_object_has_own; + pub mod prefer_promise_reject_errors; pub mod prefer_rest_params; pub mod prefer_spread; pub mod radix; @@ -643,6 +644,7 @@ oxc_macros::declare_all_lint_rules! { eslint::no_var, eslint::no_void, eslint::no_with, + eslint::prefer_promise_reject_errors, eslint::prefer_rest_params, eslint::prefer_exponentiation_operator, eslint::prefer_numeric_literals, diff --git a/crates/oxc_linter/src/rules/eslint/no_throw_literal.rs b/crates/oxc_linter/src/rules/eslint/no_throw_literal.rs index 0ecd1d608886c..00108666652d7 100644 --- a/crates/oxc_linter/src/rules/eslint/no_throw_literal.rs +++ b/crates/oxc_linter/src/rules/eslint/no_throw_literal.rs @@ -1,12 +1,9 @@ -use oxc_ast::{ - ast::{AssignmentOperator, Expression, LogicalOperator, TSType}, - AstKind, -}; +use oxc_ast::{ast::Expression, AstKind}; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_span::{GetSpan, Span}; -use crate::{context::LintContext, rule::Rule, AstNode}; +use crate::{ast_util::could_be_error, context::LintContext, rule::Rule, AstNode}; fn no_throw_literal_diagnostic(span: Span, is_undef: bool) -> OxcDiagnostic { let message = @@ -92,7 +89,7 @@ impl Rule for NoThrowLiteral { Expression::Identifier(id) if SPECIAL_IDENTIFIERS.contains(&id.name.as_str()) => { ctx.diagnostic(no_throw_literal_diagnostic(expr.span(), true)); } - expr if !Self::could_be_error(ctx, expr) => { + expr if !could_be_error(ctx, expr) => { ctx.diagnostic(no_throw_literal_diagnostic(expr.span(), false)); } _ => {} @@ -100,100 +97,6 @@ impl Rule for NoThrowLiteral { } } -impl NoThrowLiteral { - fn could_be_error(ctx: &LintContext, expr: &Expression) -> bool { - match expr.get_inner_expression() { - Expression::NewExpression(_) - | Expression::AwaitExpression(_) - | Expression::CallExpression(_) - | Expression::ChainExpression(_) - | Expression::YieldExpression(_) - | Expression::PrivateFieldExpression(_) - | Expression::StaticMemberExpression(_) - | Expression::ComputedMemberExpression(_) - | Expression::TaggedTemplateExpression(_) => true, - Expression::AssignmentExpression(expr) => { - if matches!( - expr.operator, - AssignmentOperator::Assign | AssignmentOperator::LogicalAnd - ) { - return Self::could_be_error(ctx, &expr.right); - } - - if matches!( - expr.operator, - AssignmentOperator::LogicalOr | AssignmentOperator::LogicalNullish - ) { - return expr - .left - .get_expression() - .map_or(true, |expr| Self::could_be_error(ctx, expr)) - || Self::could_be_error(ctx, &expr.right); - } - - false - } - Expression::SequenceExpression(expr) => { - expr.expressions.last().is_some_and(|expr| Self::could_be_error(ctx, expr)) - } - Expression::LogicalExpression(expr) => { - if matches!(expr.operator, LogicalOperator::And) { - return Self::could_be_error(ctx, &expr.right); - } - - Self::could_be_error(ctx, &expr.left) || Self::could_be_error(ctx, &expr.right) - } - Expression::ConditionalExpression(expr) => { - Self::could_be_error(ctx, &expr.consequent) - || Self::could_be_error(ctx, &expr.alternate) - } - Expression::Identifier(ident) => { - let reference = ctx.symbols().get_reference(ident.reference_id()); - let Some(symbol_id) = reference.symbol_id() else { - return true; - }; - let decl = ctx.nodes().get_node(ctx.symbols().get_declaration(symbol_id)); - match decl.kind() { - AstKind::VariableDeclarator(decl) => { - if let Some(init) = &decl.init { - Self::could_be_error(ctx, init) - } else { - // TODO: warn about throwing undefined - false - } - } - AstKind::Function(_) - | AstKind::Class(_) - | AstKind::TSModuleDeclaration(_) - | AstKind::TSEnumDeclaration(_) => false, - AstKind::FormalParameter(param) => { - !param.pattern.type_annotation.as_ref().is_some_and(|annot| { - is_definitely_non_error_type(&annot.type_annotation) - }) - } - _ => true, - } - } - _ => false, - } - } -} - -fn is_definitely_non_error_type(ty: &TSType) -> bool { - match ty { - TSType::TSNumberKeyword(_) - | TSType::TSStringKeyword(_) - | TSType::TSBooleanKeyword(_) - | TSType::TSNullKeyword(_) - | TSType::TSUndefinedKeyword(_) => true, - TSType::TSUnionType(union) => union.types.iter().all(is_definitely_non_error_type), - TSType::TSIntersectionType(intersect) => { - intersect.types.iter().all(is_definitely_non_error_type) - } - _ => false, - } -} - #[test] fn test() { use crate::tester::Tester; diff --git a/crates/oxc_linter/src/rules/eslint/prefer_promise_reject_errors.rs b/crates/oxc_linter/src/rules/eslint/prefer_promise_reject_errors.rs new file mode 100644 index 0000000000000..9deb69900804b --- /dev/null +++ b/crates/oxc_linter/src/rules/eslint/prefer_promise_reject_errors.rs @@ -0,0 +1,262 @@ +use oxc_allocator::Box; +use oxc_ast::{ + ast::{Argument, CallExpression, Expression, FormalParameters}, + AstKind, +}; +use oxc_diagnostics::OxcDiagnostic; +use oxc_macros::declare_oxc_lint; +use oxc_span::Span; + +use crate::{ + ast_util::{could_be_error, is_method_call}, + context::LintContext, + rule::Rule, + AstNode, +}; + +fn prefer_promise_reject_errors_diagnostic(span: Span) -> OxcDiagnostic { + OxcDiagnostic::warn("Expected the Promise rejection reason to be an Error").with_label(span) +} + +#[derive(Debug, Default, Clone)] +pub struct PreferPromiseRejectErrors { + allow_empty_reject: bool, +} + +declare_oxc_lint!( + /// ### What it does + /// + /// Require using Error objects as Promise rejection reasons + /// + /// ### Why is this bad? + /// + /// It is considered good practice to only pass instances of the built-in `Error` object to the `reject()` function for user-defined errors in Promises. `Error` objects automatically store a stack trace, which can be used to debug an error by determining where it came from. If a Promise is rejected with a non-`Error` value, it can be difficult to determine where the rejection occurred. + /// + /// ### Options + /// + /// This rule takes one optional object argument: + /// - `allowEmptyReject: true` (`false` by default) allows calls to `Promise.reject()` with no arguments. + /// + /// ### Examples + /// + /// Examples of **incorrect** code for this rule: + /// ```js + /// Promise.reject("something bad happened"); + /// + /// Promise.reject(5); + /// + /// Promise.reject(); + /// + /// new Promise(function(resolve, reject) { + /// reject("something bad happened") + /// }); + /// + /// new Promise(function(resolve, reject) { + /// reject(); + /// }); + /// ``` + /// + /// Examples of **correct** code for this rule: + /// ```js + /// Promise.reject(new Error("something bad happened")); + /// + /// Promise.reject(new TypeError("something bad happened")); + /// + /// new Promise(function(resolve, reject) { + /// reject(new Error("something bad happened")); + /// }); + /// + /// var foo = getUnknownValue(); + /// Promise.reject(foo); + /// ``` + PreferPromiseRejectErrors, + eslint, + style, + none +); + +impl Rule for PreferPromiseRejectErrors { + fn from_configuration(value: serde_json::Value) -> Self { + let allow_empty_reject = value.get(0).is_some_and(|v| { + v.get("allowEmptyReject").is_some_and(|b| b.as_bool().unwrap_or(false)) + }); + + Self { allow_empty_reject } + } + + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + match node.kind() { + AstKind::CallExpression(call_expr) => { + if !is_method_call(call_expr, Some(&["Promise"]), Some(&["reject"]), None, None) { + return; + } + + check_reject_call(call_expr, ctx, self.allow_empty_reject); + } + AstKind::NewExpression(new_expr) => { + let Expression::Identifier(ident) = &new_expr.callee else { + return; + }; + + if ident.name != "Promise" || new_expr.arguments.len() == 0 { + return; + } + + let Some(arg) = + new_expr.arguments[0].as_expression().map(Expression::get_inner_expression) + else { + return; + }; + + match arg { + Expression::FunctionExpression(func) => { + check_reject_in_function(&func.params, ctx, self.allow_empty_reject); + } + Expression::ArrowFunctionExpression(func) => { + check_reject_in_function(&func.params, ctx, self.allow_empty_reject); + } + _ => {} + } + } + _ => {} + } + } +} + +fn check_reject_call(call_expr: &CallExpression, ctx: &LintContext, allow_empty_reject: bool) { + if call_expr.arguments.len() == 0 && allow_empty_reject { + return; + } + + if call_expr.arguments.len() == 0 + || call_expr.arguments[0].as_expression().is_some_and(|e| !could_be_error(ctx, e)) + || is_undefined(&call_expr.arguments[0]) + { + ctx.diagnostic(prefer_promise_reject_errors_diagnostic(call_expr.span)); + } +} + +fn check_reject_in_function( + params: &Box<'_, FormalParameters<'_>>, + ctx: &LintContext, + allow_empty_reject: bool, +) { + if params.parameters_count() <= 1 { + return; + } + + let Some(reject_arg) = params.items[1].pattern.get_binding_identifier() else { + return; + }; + + ctx.symbol_references(reject_arg.symbol_id()).for_each(|reference| { + let Some(node) = ctx.nodes().parent_node(reference.node_id()) else { + return; + }; + if let AstKind::CallExpression(call_expr) = node.kind() { + check_reject_call(call_expr, ctx, allow_empty_reject); + } + }); +} + +fn is_undefined(arg: &Argument) -> bool { + match arg.as_expression().map(oxc_ast::ast::Expression::get_inner_expression) { + Some(Expression::Identifier(ident)) => ident.name == "undefined", + _ => false, + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + ("Promise.resolve(5)", None), + ("Foo.reject(5)", None), + ("Promise.reject(foo)", None), + ("Promise.reject(foo.bar)", None), + ("Promise.reject(foo.bar())", None), + ("Promise.reject(new Error())", None), + ("Promise.reject(new TypeError)", None), + ("Promise.reject(new Error('foo'))", None), + ("Promise.reject(foo || 5)", None), + ("Promise.reject(5 && foo)", None), + ("new Foo((resolve, reject) => reject(5))", None), + ("new Promise(function(resolve, reject) { return function(reject) { reject(5) } })", None), + ("new Promise(function(resolve, reject) { if (foo) { const reject = somethingElse; reject(5) } })", None), + ("new Promise(function(resolve, {apply}) { apply(5) })", None), + ("new Promise(function(resolve, reject) { resolve(5, reject) })", None), + ("async function foo() { Promise.reject(await foo); }", None), + ("Promise.reject()", Some(serde_json::json!([{ "allowEmptyReject": true }]))), + ("new Promise(function(resolve, reject) { reject() })", Some(serde_json::json!([{ "allowEmptyReject": true }]))), + ("Promise.reject(obj?.foo)", None), + ("Promise.reject(obj?.foo())", None), + ("Promise.reject(foo = new Error())", None), + ("Promise.reject(foo ||= 5)", None), + ("Promise.reject(foo.bar ??= 5)", None), + ("Promise.reject(foo[bar] ??= 5)", None), + ("class C { #reject; foo() { Promise.#reject(5); } }", None), + ("class C { #error; foo() { Promise.reject(this.#error); } }", None) + ]; + + let fail = vec![ + ("Promise.reject(5)", None), + ("Promise.reject('foo')", None), + ("Promise.reject(`foo`)", None), + ("Promise.reject(!foo)", None), + ("Promise.reject(void foo)", None), + ("Promise.reject()", None), + ("Promise.reject(undefined)", None), + ("Promise.reject({ foo: 1 })", None), + ("Promise.reject([1, 2, 3])", None), + ("Promise.reject()", Some(serde_json::json!([{ "allowEmptyReject": false }]))), + ( + "new Promise(function(resolve, reject) { reject() })", + Some(serde_json::json!([{ "allowEmptyReject": false }])), + ), + ("Promise.reject(undefined)", Some(serde_json::json!([{ "allowEmptyReject": true }]))), + ("Promise.reject('foo', somethingElse)", None), + ("new Promise(function(resolve, reject) { reject(5) })", None), + ("new Promise((resolve, reject) => { reject(5) })", None), + ("new Promise((resolve, reject) => reject(5))", None), + ("new Promise((resolve, reject) => reject())", None), + ("new Promise(function(yes, no) { no(5) })", None), + ( + " + new Promise((resolve, reject) => { + fs.readFile('foo.txt', (err, file) => { + if (err) reject('File not found') + else resolve(file) + }) + }) + ", + None, + ), + ("new Promise(({foo, bar, baz}, reject) => reject(5))", None), + ("new Promise(function(reject, reject) { reject(5) })", None), + ("new Promise(function(foo, arguments) { arguments(5) })", None), + ("new Promise((foo, arguments) => arguments(5))", None), + ("new Promise(function({}, reject) { reject(5) })", None), + ("new Promise(({}, reject) => reject(5))", None), + ("new Promise((resolve, reject, somethingElse = reject(5)) => {})", None), + // Optional chaining + ("Promise.reject?.(5)", None), + ("Promise?.reject(5)", None), + ("Promise?.reject?.(5)", None), + ("(Promise?.reject)(5)", None), + ("(Promise?.reject)?.(5)", None), + // Assignments with mathematical operators will either evaluate to a primitive value or throw a TypeError + ("Promise.reject(foo += new Error())", None), + ("Promise.reject(foo -= new Error())", None), + ("Promise.reject(foo **= new Error())", None), + ("Promise.reject(foo <<= new Error())", None), + ("Promise.reject(foo |= new Error())", None), + ("Promise.reject(foo &= new Error())", None), + // 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), + ]; + + Tester::new(PreferPromiseRejectErrors::NAME, PreferPromiseRejectErrors::PLUGIN, pass, fail) + .test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/eslint_prefer_promise_reject_errors.snap b/crates/oxc_linter/src/snapshots/eslint_prefer_promise_reject_errors.snap new file mode 100644 index 0000000000000..cdad9439423b1 --- /dev/null +++ b/crates/oxc_linter/src/snapshots/eslint_prefer_promise_reject_errors.snap @@ -0,0 +1,242 @@ +--- +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 + ╭─[prefer_promise_reject_errors.tsx:1:1] + 1 │ Promise.reject(5) + · ───────────────── + ╰──── + + ⚠ eslint(prefer-promise-reject-errors): Expected the Promise rejection reason to be an Error + ╭─[prefer_promise_reject_errors.tsx:1:1] + 1 │ Promise.reject('foo') + · ───────────────────── + ╰──── + + ⚠ eslint(prefer-promise-reject-errors): Expected the Promise rejection reason to be an Error + ╭─[prefer_promise_reject_errors.tsx:1:1] + 1 │ Promise.reject(`foo`) + · ───────────────────── + ╰──── + + ⚠ eslint(prefer-promise-reject-errors): Expected the Promise rejection reason to be an Error + ╭─[prefer_promise_reject_errors.tsx:1:1] + 1 │ Promise.reject(!foo) + · ──────────────────── + ╰──── + + ⚠ eslint(prefer-promise-reject-errors): Expected the Promise rejection reason to be an Error + ╭─[prefer_promise_reject_errors.tsx:1:1] + 1 │ Promise.reject(void foo) + · ──────────────────────── + ╰──── + + ⚠ eslint(prefer-promise-reject-errors): Expected the Promise rejection reason to be an Error + ╭─[prefer_promise_reject_errors.tsx:1:1] + 1 │ Promise.reject() + · ──────────────── + ╰──── + + ⚠ eslint(prefer-promise-reject-errors): Expected the Promise rejection reason to be an Error + ╭─[prefer_promise_reject_errors.tsx:1:1] + 1 │ Promise.reject(undefined) + · ───────────────────────── + ╰──── + + ⚠ eslint(prefer-promise-reject-errors): Expected the Promise rejection reason to be an Error + ╭─[prefer_promise_reject_errors.tsx:1:1] + 1 │ Promise.reject({ foo: 1 }) + · ────────────────────────── + ╰──── + + ⚠ eslint(prefer-promise-reject-errors): Expected the Promise rejection reason to be an Error + ╭─[prefer_promise_reject_errors.tsx:1:1] + 1 │ Promise.reject([1, 2, 3]) + · ───────────────────────── + ╰──── + + ⚠ eslint(prefer-promise-reject-errors): Expected the Promise rejection reason to be an Error + ╭─[prefer_promise_reject_errors.tsx:1:1] + 1 │ Promise.reject() + · ──────────────── + ╰──── + + ⚠ eslint(prefer-promise-reject-errors): Expected the Promise rejection reason to be an Error + ╭─[prefer_promise_reject_errors.tsx:1:41] + 1 │ new Promise(function(resolve, reject) { reject() }) + · ──────── + ╰──── + + ⚠ eslint(prefer-promise-reject-errors): Expected the Promise rejection reason to be an Error + ╭─[prefer_promise_reject_errors.tsx:1:1] + 1 │ Promise.reject(undefined) + · ───────────────────────── + ╰──── + + ⚠ eslint(prefer-promise-reject-errors): Expected the Promise rejection reason to be an Error + ╭─[prefer_promise_reject_errors.tsx:1:1] + 1 │ Promise.reject('foo', somethingElse) + · ──────────────────────────────────── + ╰──── + + ⚠ eslint(prefer-promise-reject-errors): Expected the Promise rejection reason to be an Error + ╭─[prefer_promise_reject_errors.tsx:1:41] + 1 │ new Promise(function(resolve, reject) { reject(5) }) + · ───────── + ╰──── + + ⚠ eslint(prefer-promise-reject-errors): Expected the Promise rejection reason to be an Error + ╭─[prefer_promise_reject_errors.tsx:1:36] + 1 │ new Promise((resolve, reject) => { reject(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((resolve, reject) => reject(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((resolve, reject) => reject()) + · ──────── + ╰──── + + ⚠ eslint(prefer-promise-reject-errors): Expected the Promise rejection reason to be an Error + ╭─[prefer_promise_reject_errors.tsx:1:33] + 1 │ new Promise(function(yes, no) { no(5) }) + · ───── + ╰──── + + ⚠ eslint(prefer-promise-reject-errors): Expected the Promise rejection reason to be an Error + ╭─[prefer_promise_reject_errors.tsx:4:26] + 3 │ fs.readFile('foo.txt', (err, file) => { + 4 │ if (err) reject('File not found') + · ──────────────────────── + 5 │ else resolve(file) + ╰──── + + ⚠ eslint(prefer-promise-reject-errors): Expected the Promise rejection reason to be an Error + ╭─[prefer_promise_reject_errors.tsx:1:42] + 1 │ new Promise(({foo, bar, baz}, reject) => reject(5)) + · ───────── + ╰──── + + × Identifier `reject` has already been declared + ╭─[prefer_promise_reject_errors.tsx:1:22] + 1 │ new Promise(function(reject, reject) { reject(5) }) + · ───┬── ───┬── + · │ ╰── It can not be redeclared here + · ╰── `reject` has already been declared here + ╰──── + + ⚠ eslint(prefer-promise-reject-errors): Expected the Promise rejection reason to be an Error + ╭─[prefer_promise_reject_errors.tsx:1:40] + 1 │ new Promise(function(foo, arguments) { arguments(5) }) + · ──────────── + ╰──── + + ⚠ eslint(prefer-promise-reject-errors): Expected the Promise rejection reason to be an Error + ╭─[prefer_promise_reject_errors.tsx:1:33] + 1 │ new Promise((foo, arguments) => arguments(5)) + · ──────────── + ╰──── + + ⚠ eslint(prefer-promise-reject-errors): Expected the Promise rejection reason to be an Error + ╭─[prefer_promise_reject_errors.tsx:1:36] + 1 │ new Promise(function({}, reject) { reject(5) }) + · ───────── + ╰──── + + ⚠ eslint(prefer-promise-reject-errors): Expected the Promise rejection reason to be an Error + ╭─[prefer_promise_reject_errors.tsx:1:29] + 1 │ new Promise(({}, reject) => reject(5)) + · ───────── + ╰──── + + ⚠ eslint(prefer-promise-reject-errors): Expected the Promise rejection reason to be an Error + ╭─[prefer_promise_reject_errors.tsx:1:47] + 1 │ new Promise((resolve, reject, somethingElse = reject(5)) => {}) + · ───────── + ╰──── + + ⚠ eslint(prefer-promise-reject-errors): Expected the Promise rejection reason to be an Error + ╭─[prefer_promise_reject_errors.tsx:1:1] + 1 │ Promise.reject?.(5) + · ─────────────────── + ╰──── + + ⚠ eslint(prefer-promise-reject-errors): Expected the Promise rejection reason to be an Error + ╭─[prefer_promise_reject_errors.tsx:1:1] + 1 │ Promise?.reject(5) + · ────────────────── + ╰──── + + ⚠ eslint(prefer-promise-reject-errors): Expected the Promise rejection reason to be an Error + ╭─[prefer_promise_reject_errors.tsx:1:1] + 1 │ Promise?.reject?.(5) + · ──────────────────── + ╰──── + + ⚠ eslint(prefer-promise-reject-errors): Expected the Promise rejection reason to be an Error + ╭─[prefer_promise_reject_errors.tsx:1:1] + 1 │ (Promise?.reject)(5) + · ──────────────────── + ╰──── + + ⚠ eslint(prefer-promise-reject-errors): Expected the Promise rejection reason to be an Error + ╭─[prefer_promise_reject_errors.tsx:1:1] + 1 │ (Promise?.reject)?.(5) + · ────────────────────── + ╰──── + + ⚠ eslint(prefer-promise-reject-errors): Expected the Promise rejection reason to be an Error + ╭─[prefer_promise_reject_errors.tsx:1:1] + 1 │ Promise.reject(foo += new Error()) + · ────────────────────────────────── + ╰──── + + ⚠ eslint(prefer-promise-reject-errors): Expected the Promise rejection reason to be an Error + ╭─[prefer_promise_reject_errors.tsx:1:1] + 1 │ Promise.reject(foo -= new Error()) + · ────────────────────────────────── + ╰──── + + ⚠ eslint(prefer-promise-reject-errors): Expected the Promise rejection reason to be an Error + ╭─[prefer_promise_reject_errors.tsx:1:1] + 1 │ Promise.reject(foo **= new Error()) + · ─────────────────────────────────── + ╰──── + + ⚠ eslint(prefer-promise-reject-errors): Expected the Promise rejection reason to be an Error + ╭─[prefer_promise_reject_errors.tsx:1:1] + 1 │ Promise.reject(foo <<= new Error()) + · ─────────────────────────────────── + ╰──── + + ⚠ eslint(prefer-promise-reject-errors): Expected the Promise rejection reason to be an Error + ╭─[prefer_promise_reject_errors.tsx:1:1] + 1 │ Promise.reject(foo |= new Error()) + · ────────────────────────────────── + ╰──── + + ⚠ eslint(prefer-promise-reject-errors): Expected the Promise rejection reason to be an Error + ╭─[prefer_promise_reject_errors.tsx:1:1] + 1 │ Promise.reject(foo &= new Error()) + · ────────────────────────────────── + ╰──── + + ⚠ eslint(prefer-promise-reject-errors): Expected the Promise rejection reason to be an Error + ╭─[prefer_promise_reject_errors.tsx:1:1] + 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:1] + 1 │ Promise.reject(foo &&= 5) + · ───────────────────────── + ╰────