From eaffb1d87c44994d427ca233e12201ed86ed1ace Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 3 Dec 2023 09:03:23 +0000 Subject: [PATCH] feat(linter) eslint plugin unicorn: no array reduce (restriction) (#1610) --- crates/oxc_linter/src/rules.rs | 4 +- .../src/rules/unicorn/no_array_reduce.rs | 477 ++++++++++++++++++ .../unicorn/require_array_join_separator.rs | 52 +- .../src/snapshots/no_array_reduce.snap | 347 +++++++++++++ crates/oxc_linter/src/utils/unicorn.rs | 49 +- 5 files changed, 879 insertions(+), 50 deletions(-) create mode 100644 crates/oxc_linter/src/rules/unicorn/no_array_reduce.rs create mode 100644 crates/oxc_linter/src/snapshots/no_array_reduce.snap diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index f3e4de3482d14..b6cb1e6668e1a 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -154,6 +154,7 @@ mod unicorn { pub mod filename_case; pub mod new_for_builtins; pub mod no_abusive_eslint_disable; + pub mod no_array_reduce; pub mod no_await_expression_member; pub mod no_console_spaces; pub mod no_document_cookie; @@ -331,12 +332,12 @@ oxc_macros::declare_all_lint_rules! { jest::valid_title, unicorn::catch_error_name, unicorn::empty_brace_spaces, - unicorn::prefer_array_some, unicorn::error_message, unicorn::escape_case, unicorn::filename_case, unicorn::new_for_builtins, unicorn::no_abusive_eslint_disable, + unicorn::no_array_reduce, unicorn::no_await_expression_member, unicorn::no_console_spaces, unicorn::no_document_cookie, @@ -366,6 +367,7 @@ oxc_macros::declare_all_lint_rules! { unicorn::numeric_separators_style, unicorn::prefer_add_event_listener, unicorn::prefer_array_flat_map, + unicorn::prefer_array_some, unicorn::prefer_blob_reading_methods, unicorn::prefer_code_point, unicorn::prefer_date_now, diff --git a/crates/oxc_linter/src/rules/unicorn/no_array_reduce.rs b/crates/oxc_linter/src/rules/unicorn/no_array_reduce.rs new file mode 100644 index 0000000000000..8482e47622095 --- /dev/null +++ b/crates/oxc_linter/src/rules/unicorn/no_array_reduce.rs @@ -0,0 +1,477 @@ +use oxc_ast::{ + ast::{Argument, CallExpression, Expression, Statement}, + AstKind, +}; +use oxc_diagnostics::{ + miette::{self, Diagnostic}, + thiserror::Error, +}; +use oxc_macros::declare_oxc_lint; +use oxc_span::Span; + +use crate::{ + ast_util::is_method_call, context::LintContext, rule::Rule, utils::is_prototype_property, + AstNode, +}; + +#[derive(Debug, Error, Diagnostic)] +#[error("eslint-plugin-unicorn(no-array-reduce): Don't use `Array#reduce()` and `Array#reduceRight()`, use `for` loops instead.")] +#[diagnostic(severity(warning), help("Refactor your code to use `for` loops instead."))] +struct NoArrayReduceDiagnostic(#[label] pub Span); + +#[derive(Debug, Clone)] +pub struct NoArrayReduce { + pub allow_simple_operations: bool, +} + +impl Default for NoArrayReduce { + fn default() -> Self { + Self { allow_simple_operations: true } + } +} + +declare_oxc_lint!( + /// ### What it does + /// + /// Disallow `Array#reduce()` and `Array#reduceRight()`. + /// + /// ### Why is this bad? + /// + /// `Array#reduce()` and `Array#reduceRight()` usually result in [hard-to-read](https://twitter.com/jaffathecake/status/1213077702300852224) and [less performant](https://www.richsnapp.com/article/2019/06-09-reduce-spread-anti-pattern) code. In almost every case, it can be replaced by `.map`, `.filter`, or a `for-of` loop. + /// + /// It's only somewhat useful in the rare case of summing up numbers, which is allowed by default. + /// + /// ### Example + /// ```javascript + /// ``` + NoArrayReduce, + restriction +); + +impl Rule for NoArrayReduce { + fn from_configuration(value: serde_json::Value) -> Self { + let allow_simple_operations = value + .as_object() + .and_then(|v| v.get("allowSimpleOperations")) + .and_then(serde_json::Value::as_bool) + .unwrap_or(true); + + Self { allow_simple_operations } + } + + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + let AstKind::CallExpression(call_expr) = node.kind() else { + return; + }; + + let Some(member_expr) = (call_expr).callee.get_member_expr() else { + return; + }; + + let Some((span, _)) = member_expr.static_property_info() else { + return; + }; + + if is_method_call(call_expr, None, Some(&["reduce", "reduceRight"]), Some(1), Some(2)) + && !matches!(call_expr.arguments.get(0), Some(Argument::SpreadElement(_))) + && !call_expr.optional + && !member_expr.is_computed() + { + if self.allow_simple_operations && is_simple_operation(call_expr) { + return; + } + ctx.diagnostic(NoArrayReduceDiagnostic(span)); + } + + if let Expression::MemberExpression(member_expr_obj) = member_expr.object() { + if is_method_call(call_expr, None, Some(&["call", "apply"]), None, None) + && !member_expr.optional() + && !member_expr.is_computed() + && !call_expr.optional + && !member_expr_obj.is_computed() + && (is_prototype_property(member_expr_obj, "reduce", Some("Array")) + || is_prototype_property(member_expr_obj, "reduceRight", Some("Array"))) + { + ctx.diagnostic(NoArrayReduceDiagnostic(span)); + } + } + } +} + +fn is_simple_operation(node: &CallExpression) -> bool { + let Some(Argument::Expression(callback_arg)) = node.arguments.get(0) else { + return false; + }; + let function_body = match callback_arg { + // `array.reduce((accumulator, element) => accumulator + element)` + Expression::ArrowExpression(callback) => &callback.body, + Expression::FunctionExpression(callback) => { + let Some(body) = &callback.body else { + return false; + }; + body + } + _ => return false, + }; + + if function_body.statements.len() != 1 { + return false; + } + + match &function_body.statements[0] { + Statement::ExpressionStatement(expr) => { + matches!(expr.expression, Expression::BinaryExpression(_)) + } + Statement::ReturnStatement(ret) => { + matches!(&ret.argument, Some(Expression::BinaryExpression(_))) + } + Statement::BlockStatement(block) => { + if block.body.len() != 1 { + return false; + } + + match &block.body[0] { + Statement::ReturnStatement(ret) => { + matches!(&ret.argument, Some(Expression::BinaryExpression(_))) + } + _ => false, + } + } + _ => false, + } +} + +#[test] +fn test() { + use crate::tester::Tester; + use serde_json::json; + + let pass = vec![ + (r"a[b.reduce]()", None), + (r"a(b.reduce)", None), + (r"a.reduce()", None), + (r"a.reduce(1, 2, 3)", None), + (r"a.reduce(b, c, d)", None), + (r"[][reduce].call()", None), + (r"[1, 2].reduce.call(() => {}, 34)", None), + // Test `.reduce` + // Not `CallExpression` + (r"new foo.reduce(fn);", None), + // Not `MemberExpression` + (r"reduce(fn);", None), + // `callee.property` is not a `Identifier` + (r#"foo["reduce"](fn);"#, None), + // Computed + (r"foo[reduce](fn);", None), + // Not listed method or property + (r"foo.notListed(fn);", None), + // More or less argument(s) + (r"foo.reduce();", None), + (r"foo.reduce(fn, extraArgument1, extraArgument2);", None), + (r"foo.reduce(...argumentsArray)", None), + // Test `[].reduce.{call,apply}` + // Not `CallExpression` + (r"new [].reduce.call(foo, fn);", None), + // Not `MemberExpression` + (r"call(foo, fn);", None), + (r"reduce.call(foo, fn);", None), + // `callee.property` is not a `Identifier` + (r#"[].reduce["call"](foo, fn);"#, None), + (r#"[]["reduce"].call(foo, fn);"#, None), + // Computed + (r"[].reduce[call](foo, fn);", None), + (r"[][reduce].call(foo, fn);", None), + // Not listed method or property + (r"[].reduce.notListed(foo, fn);", None), + (r"[].notListed.call(foo, fn);", None), + // Not empty + (r"[1].reduce.call(foo, fn)", None), + // Not ArrayExpression + (r#""".reduce.call(foo, fn)"#, None), + // More or less argument(s) + // We are not checking arguments length + + // Test `Array.prototype.{call,apply}` + // Not `CallExpression` + (r"new Array.prototype.reduce.call(foo, fn);", None), + // Not `MemberExpression` + (r"call(foo, fn);", None), + (r"reduce.call(foo, fn);", None), + // `callee.property` is not a `Identifier` + (r#"Array.prototype.reduce["call"](foo, fn);"#, None), + (r#"Array.prototype[",educe"].call(foo, fn);"#, None), + (r#""Array".prototype.reduce.call(foo, fn);"#, None), + // Computed + (r"Array.prototype.reduce[call](foo, fn);", None), + (r"Array.prototype[reduce].call(foo, fn);", None), + (r"Array[prototype].reduce.call(foo, fn);", None), + // Not listed method + (r"Array.prototype.reduce.notListed(foo, fn);", None), + (r"Array.prototype.notListed.call(foo, fn);", None), + (r"Array.notListed.reduce.call(foo, fn);", None), + // Not `Array` + (r"NotArray.prototype.reduce.call(foo, fn);", None), + // More or less argument(s) + // We are not checking arguments length + + // `reduce-like` + (r"array.reducex(foo)", None), + (r"array.xreduce(foo)", None), + (r"[].reducex.call(array, foo)", None), + (r"[].xreduce.call(array, foo)", None), + (r"Array.prototype.reducex.call(array, foo)", None), + (r"Array.prototype.xreduce.call(array, foo)", None), + // Option: allowSimpleOperations + (r"array.reduce((total, item) => total + item)", None), + (r"array.reduce((total, item) => { return total - item })", None), + (r"array.reduce(function (total, item) { return total * item })", None), + (r"array.reduce((total, item) => total + item, 0)", None), + (r"array.reduce((total, item) => { return total - item }, 0 )", None), + (r"array.reduce(function (total, item) { return total * item }, 0)", None), + ( + r" + array.reduce((total, item) => { + return (total / item) * 100; + }, 0); + ", + None, + ), + (r"array.reduce((total, item) => { return total + item }, 0)", None), + (r"a[b.reduceRight]()", None), + (r"a(b.reduceRight)", None), + (r"a.reduceRight()", None), + (r"a.reduceRight(1, 2, 3)", None), + (r"a.reduceRight(b, c, d)", None), + (r"[][reduceRight].call()", None), + (r"[1, 2].reduceRight.call(() => {}, 34)", None), + // Test `.reduceRight` + // Not `CallExpression` + (r"new foo.reduceRight(fn);", None), + // Not `MemberExpression` + (r"reduce(fn);", None), + // `callee.property` is not a `Identifier` + (r#"foo["reduce"](fn);"#, None), + // Computed + (r"foo[reduceRight](fn);", None), + // Not listed method or property + (r"foo.notListed(fn);", None), + // More or less argument(s) + (r"foo.reduceRight();", None), + (r"foo.reduceRight(fn, extraArgument1, extraArgument2);", None), + (r"foo.reduceRight(...argumentsArray)", None), + // Test `[].reduceRight.{call,apply}` + // Not `CallExpression` + (r"new [].reduceRight.call(foo, fn);", None), + // Not `MemberExpression` + (r"call(foo, fn);", None), + (r"reduce.call(foo, fn);", None), + // `callee.property` is not a `Identifier` + (r#"[].reduceRight["call"](foo, fn);"#, None), + (r#"[]["reduce"].call(foo, fn);"#, None), + // Computed + (r"[].reduceRight[call](foo, fn);", None), + (r"[][reduceRight].call(foo, fn);", None), + // Not listed method or property + (r"[].reduceRight.notListed(foo, fn);", None), + (r"[].notListed.call(foo, fn);", None), + // Not empty + (r"[1].reduceRight.call(foo, fn)", None), + // Not ArrayExpression + (r#""".reduceRight.call(foo, fn)"#, None), + // More or less argument(s) + // We are not checking arguments length + + // Test `Array.prototype.{call,apply}` + // Not `CallExpression` + (r"new Array.prototype.reduceRight.call(foo, fn);", None), + // Not `MemberExpression` + (r"call(foo, fn);", None), + (r"reduce.call(foo, fn);", None), + // `callee.property` is not a `Identifier` + (r#"Array.prototype.reduceRight["call"](foo, fn);"#, None), + (r#"Array.prototype["reeduce"].call(foo, fn);"#, None), + (r#""Array".prototype.reduceRight.call(foo, fn);"#, None), + // Computed + (r"Array.prototype.reduceRight[call](foo, fn);", None), + (r"Array.prototype[reduceRight].call(foo, fn);", None), + (r"Array[prototype].reduceRight.call(foo, fn);", None), + // Not listed method + (r"Array.prototype.reduceRight.notListed(foo, fn);", None), + (r"Array.prototype.notListed.call(foo, fn);", None), + (r"Array.notListed.reduceRight.call(foo, fn);", None), + // Not `Array` + (r"NotArray.prototype.reduceRight.call(foo, fn);", None), + // More or less argument(s) + // We are not checking arguments length + + // `reduceRight-like` + (r"array.reduceRightx(foo)", None), + (r"array.xreduceRight(foo)", None), + (r"[].reduceRightx.call(array, foo)", None), + (r"[].xreduceRight.call(array, foo)", None), + (r"Array.prototype.reduceRightx.call(array, foo)", None), + (r"Array.prototype.xreduceRight.call(array, foo)", None), + // Option: allowSimpleOperations + (r"array.reduceRight((total, item) => total + item)", None), + (r"array.reduceRight((total, item) => { return total - item })", None), + (r"array.reduceRight(function (total, item) { return total * item })", None), + (r"array.reduceRight((total, item) => total + item, 0)", None), + (r"array.reduceRight((total, item) => { return total - item }, 0 )", None), + (r"array.reduceRight(function (total, item) { return total * item }, 0)", None), + ( + r" + array.reduceRight((total, item) => { + return (total / item) * 100; + }, 0); + ", + None, + ), + (r"array.reduceRight((total, item) => { return total + item }, 0)", None), + ]; + + let fail = vec![ + (r#"array.reduce((str, item) => str += item, "")"#, None), + ( + r" + array.reduce((obj, item) => { + obj[item] = null; + return obj; + }, {}) + ", + None, + ), + (r"array.reduce((obj, item) => ({ [item]: null }), {})", None), + ( + r#" + const hyphenate = (str, char) => \`\${str}-\${char}\`; + ["a", "b", "c"].reduce(hyphenate); + "#, + None, + ), + (r"[].reduce.call(array, (s, i) => s + i)", None), + (r"[].reduce.call(array, sum);", None), + (r"[].reduce.call(sum);", None), + (r"Array.prototype.reduce.call(array, (s, i) => s + i)", None), + (r"Array.prototype.reduce.call(array, sum);", None), + (r"[].reduce.apply(array, [(s, i) => s + i])", None), + (r"[].reduce.apply(array, [sum]);", None), + (r"Array.prototype.reduce.apply(array, [(s, i) => s + i])", None), + (r"Array.prototype.reduce.apply(array, [sum]);", None), + ( + r" + array.reduce((total, item) => { + return total + doComplicatedThings(item); + function doComplicatedThings(item) { + return item + 1; + } + }, 0); + ", + None, + ), + // Option: allowSimpleOperations + ( + r"array.reduce((total, item) => total + item)", + Some(json!({ "allowSimpleOperations": false})), + ), + ( + r"array.reduce((total, item) => { return total - item })", + Some(json!({ "allowSimpleOperations": false})), + ), + ( + r"array.reduce(function (total, item) { return total * item })", + Some(json!({ "allowSimpleOperations": false})), + ), + ( + r"array.reduce((total, item) => total + item, 0)", + Some(json!({ "allowSimpleOperations": false})), + ), + ( + r"array.reduce((total, item) => { return total - item }, 0 )", + Some(json!({ "allowSimpleOperations": false})), + ), + ( + r"array.reduce(function (total, item) { return total * item }, 0)", + Some(json!({ "allowSimpleOperations": false})), + ), + ( + r" + array.reduce((total, item) => { + return (total / item) * 100; + }, 0); + ", + Some(json!({ "allowSimpleOperations": false})), + ), + (r#"array.reduceRight((str, item) => str += item, "")"#, None), + ( + r" + array.reduceRight((obj, item) => { + obj[item] = null; + return obj; + }, {}) + ", + None, + ), + (r"array.reduceRight((obj, item) => ({ [item]: null }), {})", None), + ( + r#" + const hyphenate = (str, char) => \`\${str}-\${char}\`; + ["a", "b", "c"].reduceRight(hyphenate); + "#, + None, + ), + (r"[].reduceRight.call(array, (s, i) => s + i)", None), + (r"[].reduceRight.call(array, sum);", None), + (r"[].reduceRight.call(sum);", None), + (r"Array.prototype.reduceRight.call(array, (s, i) => s + i)", None), + (r"Array.prototype.reduceRight.call(array, sum);", None), + (r"[].reduceRight.apply(array, [(s, i) => s + i])", None), + (r"[].reduceRight.apply(array, [sum]);", None), + (r"Array.prototype.reduceRight.apply(array, [(s, i) => s + i])", None), + (r"Array.prototype.reduceRight.apply(array, [sum]);", None), + ( + r" + array.reduceRight((total, item) => { + return total + doComplicatedThings(item); + function doComplicatedThings(item) { + return item + 1; + } + }, 0); + ", + None, + ), + // Option: allowSimpleOperations + ( + r"array.reduceRight((total, item) => total + item)", + Some(json!({ "allowSimpleOperations": false})), + ), + ( + r"array.reduceRight((total, item) => { return total - item })", + Some(json!({ "allowSimpleOperations": false})), + ), + ( + r"array.reduceRight(function (total, item) { return total * item })", + Some(json!({ "allowSimpleOperations": false})), + ), + ( + r"array.reduceRight((total, item) => total + item, 0)", + Some(json!({ "allowSimpleOperations": false})), + ), + ( + r"array.reduceRight((total, item) => { return total - item }, 0 )", + Some(json!({ "allowSimpleOperations": false})), + ), + ( + r"array.reduceRight(function (total, item) { return total * item }, 0)", + Some(json!({ "allowSimpleOperations": false})), + ), + ( + r" + array.reduceRight((total, item) => { + return (total / item) * 100; + }, 0); + ", + Some(json!({ "allowSimpleOperations": false})), + ), + ]; + Tester::new(NoArrayReduce::NAME, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/rules/unicorn/require_array_join_separator.rs b/crates/oxc_linter/src/rules/unicorn/require_array_join_separator.rs index 27d667ee760ca..10eb8a627933b 100644 --- a/crates/oxc_linter/src/rules/unicorn/require_array_join_separator.rs +++ b/crates/oxc_linter/src/rules/unicorn/require_array_join_separator.rs @@ -9,7 +9,10 @@ use oxc_diagnostics::{ use oxc_macros::declare_oxc_lint; 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, rule::Rule, utils::is_prototype_property, + AstNode, +}; #[derive(Debug, Error, Diagnostic)] #[error("eslint-plugin-unicorn(require-array-join-separator): Enforce using the separator argument with Array#join()")] @@ -41,53 +44,6 @@ declare_oxc_lint!( style ); -// ref: https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/rules/utils/array-or-object-prototype-property.js -fn is_prototype_property( - member_expr: &MemberExpression, - property: &str, - object: Option<&str>, -) -> bool { - if !member_expr.static_property_name().is_some_and(|name| name == property) - || member_expr.optional() - { - return false; - } - - // `Object.prototype.method` or `Array.prototype.method` - if let Expression::MemberExpression(member_expr_obj) = member_expr.object() { - if let Expression::Identifier(iden) = member_expr_obj.object() { - if member_expr_obj.static_property_name().is_some_and(|name| name == "prototype") - && object.is_some_and(|val| val == iden.name) - && !member_expr.optional() - && !member_expr_obj.optional() - { - return true; - } - } - }; - - match object { - // `[].method` - Some("Array") => { - if let Expression::ArrayExpression(array_expr) = member_expr.object() { - array_expr.elements.len() == 0 - } else { - false - } - } - - // `{}.method` - Some("Object") => { - if let Expression::ObjectExpression(obj_expr) = member_expr.object() { - obj_expr.properties.len() == 0 - } else { - false - } - } - _ => false, - } -} - fn is_array_prototype_property(member_expr: &MemberExpression, property: &str) -> bool { is_prototype_property(member_expr, property, Some("Array")) } diff --git a/crates/oxc_linter/src/snapshots/no_array_reduce.snap b/crates/oxc_linter/src/snapshots/no_array_reduce.snap new file mode 100644 index 0000000000000..c63cc69eb6c71 --- /dev/null +++ b/crates/oxc_linter/src/snapshots/no_array_reduce.snap @@ -0,0 +1,347 @@ +--- +source: crates/oxc_linter/src/tester.rs +expression: no_array_reduce +--- + ⚠ eslint-plugin-unicorn(no-array-reduce): Don't use `Array#reduce()` and `Array#reduceRight()`, use `for` loops instead. + ╭─[no_array_reduce.tsx:1:1] + 1 │ array.reduce((str, item) => str += item, "") + · ────── + ╰──── + help: Refactor your code to use `for` loops instead. + + ⚠ eslint-plugin-unicorn(no-array-reduce): Don't use `Array#reduce()` and `Array#reduceRight()`, use `for` loops instead. + ╭─[no_array_reduce.tsx:1:1] + 1 │ + 2 │ array.reduce((obj, item) => { + · ────── + 3 │ obj[item] = null; + ╰──── + help: Refactor your code to use `for` loops instead. + + ⚠ eslint-plugin-unicorn(no-array-reduce): Don't use `Array#reduce()` and `Array#reduceRight()`, use `for` loops instead. + ╭─[no_array_reduce.tsx:1:1] + 1 │ array.reduce((obj, item) => ({ [item]: null }), {}) + · ────── + ╰──── + help: Refactor your code to use `for` loops instead. + + × Invalid Unicode escape sequence + ╭─[no_array_reduce.tsx:1:1] + 1 │ + 2 │ const hyphenate = (str, char) => \`\${str}-\${char}\`; + · ─ + 3 │ ["a", "b", "c"].reduce(hyphenate); + ╰──── + + × Invalid Unicode escape sequence + ╭─[no_array_reduce.tsx:1:1] + 1 │ + 2 │ const hyphenate = (str, char) => \`\${str}-\${char}\`; + · ─ + 3 │ ["a", "b", "c"].reduce(hyphenate); + ╰──── + + × Expected a semicolon or an implicit semicolon after a statement, but found none + ╭─[no_array_reduce.tsx:1:1] + 1 │ + 2 │ const hyphenate = (str, char) => \`\${str}-\${char}\`; + · ▲ + 3 │ ["a", "b", "c"].reduce(hyphenate); + ╰──── + help: Try insert a semicolon here + + ⚠ eslint-plugin-unicorn(no-array-reduce): Don't use `Array#reduce()` and `Array#reduceRight()`, use `for` loops instead. + ╭─[no_array_reduce.tsx:1:1] + 1 │ [].reduce.call(array, (s, i) => s + i) + · ──── + ╰──── + help: Refactor your code to use `for` loops instead. + + ⚠ eslint-plugin-unicorn(no-array-reduce): Don't use `Array#reduce()` and `Array#reduceRight()`, use `for` loops instead. + ╭─[no_array_reduce.tsx:1:1] + 1 │ [].reduce.call(array, sum); + · ──── + ╰──── + help: Refactor your code to use `for` loops instead. + + ⚠ eslint-plugin-unicorn(no-array-reduce): Don't use `Array#reduce()` and `Array#reduceRight()`, use `for` loops instead. + ╭─[no_array_reduce.tsx:1:1] + 1 │ [].reduce.call(sum); + · ──── + ╰──── + help: Refactor your code to use `for` loops instead. + + ⚠ eslint-plugin-unicorn(no-array-reduce): Don't use `Array#reduce()` and `Array#reduceRight()`, use `for` loops instead. + ╭─[no_array_reduce.tsx:1:1] + 1 │ Array.prototype.reduce.call(array, (s, i) => s + i) + · ──── + ╰──── + help: Refactor your code to use `for` loops instead. + + ⚠ eslint-plugin-unicorn(no-array-reduce): Don't use `Array#reduce()` and `Array#reduceRight()`, use `for` loops instead. + ╭─[no_array_reduce.tsx:1:1] + 1 │ Array.prototype.reduce.call(array, sum); + · ──── + ╰──── + help: Refactor your code to use `for` loops instead. + + ⚠ eslint-plugin-unicorn(no-array-reduce): Don't use `Array#reduce()` and `Array#reduceRight()`, use `for` loops instead. + ╭─[no_array_reduce.tsx:1:1] + 1 │ [].reduce.apply(array, [(s, i) => s + i]) + · ───── + ╰──── + help: Refactor your code to use `for` loops instead. + + ⚠ eslint-plugin-unicorn(no-array-reduce): Don't use `Array#reduce()` and `Array#reduceRight()`, use `for` loops instead. + ╭─[no_array_reduce.tsx:1:1] + 1 │ [].reduce.apply(array, [sum]); + · ───── + ╰──── + help: Refactor your code to use `for` loops instead. + + ⚠ eslint-plugin-unicorn(no-array-reduce): Don't use `Array#reduce()` and `Array#reduceRight()`, use `for` loops instead. + ╭─[no_array_reduce.tsx:1:1] + 1 │ Array.prototype.reduce.apply(array, [(s, i) => s + i]) + · ───── + ╰──── + help: Refactor your code to use `for` loops instead. + + ⚠ eslint-plugin-unicorn(no-array-reduce): Don't use `Array#reduce()` and `Array#reduceRight()`, use `for` loops instead. + ╭─[no_array_reduce.tsx:1:1] + 1 │ Array.prototype.reduce.apply(array, [sum]); + · ───── + ╰──── + help: Refactor your code to use `for` loops instead. + + ⚠ eslint-plugin-unicorn(no-array-reduce): Don't use `Array#reduce()` and `Array#reduceRight()`, use `for` loops instead. + ╭─[no_array_reduce.tsx:1:1] + 1 │ + 2 │ array.reduce((total, item) => { + · ────── + 3 │ return total + doComplicatedThings(item); + ╰──── + help: Refactor your code to use `for` loops instead. + + ⚠ eslint-plugin-unicorn(no-array-reduce): Don't use `Array#reduce()` and `Array#reduceRight()`, use `for` loops instead. + ╭─[no_array_reduce.tsx:1:1] + 1 │ array.reduce((total, item) => total + item) + · ────── + ╰──── + help: Refactor your code to use `for` loops instead. + + ⚠ eslint-plugin-unicorn(no-array-reduce): Don't use `Array#reduce()` and `Array#reduceRight()`, use `for` loops instead. + ╭─[no_array_reduce.tsx:1:1] + 1 │ array.reduce((total, item) => { return total - item }) + · ────── + ╰──── + help: Refactor your code to use `for` loops instead. + + ⚠ eslint-plugin-unicorn(no-array-reduce): Don't use `Array#reduce()` and `Array#reduceRight()`, use `for` loops instead. + ╭─[no_array_reduce.tsx:1:1] + 1 │ array.reduce(function (total, item) { return total * item }) + · ────── + ╰──── + help: Refactor your code to use `for` loops instead. + + ⚠ eslint-plugin-unicorn(no-array-reduce): Don't use `Array#reduce()` and `Array#reduceRight()`, use `for` loops instead. + ╭─[no_array_reduce.tsx:1:1] + 1 │ array.reduce((total, item) => total + item, 0) + · ────── + ╰──── + help: Refactor your code to use `for` loops instead. + + ⚠ eslint-plugin-unicorn(no-array-reduce): Don't use `Array#reduce()` and `Array#reduceRight()`, use `for` loops instead. + ╭─[no_array_reduce.tsx:1:1] + 1 │ array.reduce((total, item) => { return total - item }, 0 ) + · ────── + ╰──── + help: Refactor your code to use `for` loops instead. + + ⚠ eslint-plugin-unicorn(no-array-reduce): Don't use `Array#reduce()` and `Array#reduceRight()`, use `for` loops instead. + ╭─[no_array_reduce.tsx:1:1] + 1 │ array.reduce(function (total, item) { return total * item }, 0) + · ────── + ╰──── + help: Refactor your code to use `for` loops instead. + + ⚠ eslint-plugin-unicorn(no-array-reduce): Don't use `Array#reduce()` and `Array#reduceRight()`, use `for` loops instead. + ╭─[no_array_reduce.tsx:1:1] + 1 │ + 2 │ array.reduce((total, item) => { + · ────── + 3 │ return (total / item) * 100; + ╰──── + help: Refactor your code to use `for` loops instead. + + ⚠ eslint-plugin-unicorn(no-array-reduce): Don't use `Array#reduce()` and `Array#reduceRight()`, use `for` loops instead. + ╭─[no_array_reduce.tsx:1:1] + 1 │ array.reduceRight((str, item) => str += item, "") + · ─────────── + ╰──── + help: Refactor your code to use `for` loops instead. + + ⚠ eslint-plugin-unicorn(no-array-reduce): Don't use `Array#reduce()` and `Array#reduceRight()`, use `for` loops instead. + ╭─[no_array_reduce.tsx:1:1] + 1 │ + 2 │ array.reduceRight((obj, item) => { + · ─────────── + 3 │ obj[item] = null; + ╰──── + help: Refactor your code to use `for` loops instead. + + ⚠ eslint-plugin-unicorn(no-array-reduce): Don't use `Array#reduce()` and `Array#reduceRight()`, use `for` loops instead. + ╭─[no_array_reduce.tsx:1:1] + 1 │ array.reduceRight((obj, item) => ({ [item]: null }), {}) + · ─────────── + ╰──── + help: Refactor your code to use `for` loops instead. + + × Invalid Unicode escape sequence + ╭─[no_array_reduce.tsx:1:1] + 1 │ + 2 │ const hyphenate = (str, char) => \`\${str}-\${char}\`; + · ─ + 3 │ ["a", "b", "c"].reduceRight(hyphenate); + ╰──── + + × Invalid Unicode escape sequence + ╭─[no_array_reduce.tsx:1:1] + 1 │ + 2 │ const hyphenate = (str, char) => \`\${str}-\${char}\`; + · ─ + 3 │ ["a", "b", "c"].reduceRight(hyphenate); + ╰──── + + × Expected a semicolon or an implicit semicolon after a statement, but found none + ╭─[no_array_reduce.tsx:1:1] + 1 │ + 2 │ const hyphenate = (str, char) => \`\${str}-\${char}\`; + · ▲ + 3 │ ["a", "b", "c"].reduceRight(hyphenate); + ╰──── + help: Try insert a semicolon here + + ⚠ eslint-plugin-unicorn(no-array-reduce): Don't use `Array#reduce()` and `Array#reduceRight()`, use `for` loops instead. + ╭─[no_array_reduce.tsx:1:1] + 1 │ [].reduceRight.call(array, (s, i) => s + i) + · ──── + ╰──── + help: Refactor your code to use `for` loops instead. + + ⚠ eslint-plugin-unicorn(no-array-reduce): Don't use `Array#reduce()` and `Array#reduceRight()`, use `for` loops instead. + ╭─[no_array_reduce.tsx:1:1] + 1 │ [].reduceRight.call(array, sum); + · ──── + ╰──── + help: Refactor your code to use `for` loops instead. + + ⚠ eslint-plugin-unicorn(no-array-reduce): Don't use `Array#reduce()` and `Array#reduceRight()`, use `for` loops instead. + ╭─[no_array_reduce.tsx:1:1] + 1 │ [].reduceRight.call(sum); + · ──── + ╰──── + help: Refactor your code to use `for` loops instead. + + ⚠ eslint-plugin-unicorn(no-array-reduce): Don't use `Array#reduce()` and `Array#reduceRight()`, use `for` loops instead. + ╭─[no_array_reduce.tsx:1:1] + 1 │ Array.prototype.reduceRight.call(array, (s, i) => s + i) + · ──── + ╰──── + help: Refactor your code to use `for` loops instead. + + ⚠ eslint-plugin-unicorn(no-array-reduce): Don't use `Array#reduce()` and `Array#reduceRight()`, use `for` loops instead. + ╭─[no_array_reduce.tsx:1:1] + 1 │ Array.prototype.reduceRight.call(array, sum); + · ──── + ╰──── + help: Refactor your code to use `for` loops instead. + + ⚠ eslint-plugin-unicorn(no-array-reduce): Don't use `Array#reduce()` and `Array#reduceRight()`, use `for` loops instead. + ╭─[no_array_reduce.tsx:1:1] + 1 │ [].reduceRight.apply(array, [(s, i) => s + i]) + · ───── + ╰──── + help: Refactor your code to use `for` loops instead. + + ⚠ eslint-plugin-unicorn(no-array-reduce): Don't use `Array#reduce()` and `Array#reduceRight()`, use `for` loops instead. + ╭─[no_array_reduce.tsx:1:1] + 1 │ [].reduceRight.apply(array, [sum]); + · ───── + ╰──── + help: Refactor your code to use `for` loops instead. + + ⚠ eslint-plugin-unicorn(no-array-reduce): Don't use `Array#reduce()` and `Array#reduceRight()`, use `for` loops instead. + ╭─[no_array_reduce.tsx:1:1] + 1 │ Array.prototype.reduceRight.apply(array, [(s, i) => s + i]) + · ───── + ╰──── + help: Refactor your code to use `for` loops instead. + + ⚠ eslint-plugin-unicorn(no-array-reduce): Don't use `Array#reduce()` and `Array#reduceRight()`, use `for` loops instead. + ╭─[no_array_reduce.tsx:1:1] + 1 │ Array.prototype.reduceRight.apply(array, [sum]); + · ───── + ╰──── + help: Refactor your code to use `for` loops instead. + + ⚠ eslint-plugin-unicorn(no-array-reduce): Don't use `Array#reduce()` and `Array#reduceRight()`, use `for` loops instead. + ╭─[no_array_reduce.tsx:1:1] + 1 │ + 2 │ array.reduceRight((total, item) => { + · ─────────── + 3 │ return total + doComplicatedThings(item); + ╰──── + help: Refactor your code to use `for` loops instead. + + ⚠ eslint-plugin-unicorn(no-array-reduce): Don't use `Array#reduce()` and `Array#reduceRight()`, use `for` loops instead. + ╭─[no_array_reduce.tsx:1:1] + 1 │ array.reduceRight((total, item) => total + item) + · ─────────── + ╰──── + help: Refactor your code to use `for` loops instead. + + ⚠ eslint-plugin-unicorn(no-array-reduce): Don't use `Array#reduce()` and `Array#reduceRight()`, use `for` loops instead. + ╭─[no_array_reduce.tsx:1:1] + 1 │ array.reduceRight((total, item) => { return total - item }) + · ─────────── + ╰──── + help: Refactor your code to use `for` loops instead. + + ⚠ eslint-plugin-unicorn(no-array-reduce): Don't use `Array#reduce()` and `Array#reduceRight()`, use `for` loops instead. + ╭─[no_array_reduce.tsx:1:1] + 1 │ array.reduceRight(function (total, item) { return total * item }) + · ─────────── + ╰──── + help: Refactor your code to use `for` loops instead. + + ⚠ eslint-plugin-unicorn(no-array-reduce): Don't use `Array#reduce()` and `Array#reduceRight()`, use `for` loops instead. + ╭─[no_array_reduce.tsx:1:1] + 1 │ array.reduceRight((total, item) => total + item, 0) + · ─────────── + ╰──── + help: Refactor your code to use `for` loops instead. + + ⚠ eslint-plugin-unicorn(no-array-reduce): Don't use `Array#reduce()` and `Array#reduceRight()`, use `for` loops instead. + ╭─[no_array_reduce.tsx:1:1] + 1 │ array.reduceRight((total, item) => { return total - item }, 0 ) + · ─────────── + ╰──── + help: Refactor your code to use `for` loops instead. + + ⚠ eslint-plugin-unicorn(no-array-reduce): Don't use `Array#reduce()` and `Array#reduceRight()`, use `for` loops instead. + ╭─[no_array_reduce.tsx:1:1] + 1 │ array.reduceRight(function (total, item) { return total * item }, 0) + · ─────────── + ╰──── + help: Refactor your code to use `for` loops instead. + + ⚠ eslint-plugin-unicorn(no-array-reduce): Don't use `Array#reduce()` and `Array#reduceRight()`, use `for` loops instead. + ╭─[no_array_reduce.tsx:1:1] + 1 │ + 2 │ array.reduceRight((total, item) => { + · ─────────── + 3 │ return (total / item) * 100; + ╰──── + help: Refactor your code to use `for` loops instead. + + diff --git a/crates/oxc_linter/src/utils/unicorn.rs b/crates/oxc_linter/src/utils/unicorn.rs index ea8269bc662c5..e2671b30f3bd3 100644 --- a/crates/oxc_linter/src/utils/unicorn.rs +++ b/crates/oxc_linter/src/utils/unicorn.rs @@ -1,4 +1,4 @@ -use oxc_ast::ast::{Expression, Statement}; +use oxc_ast::ast::{Expression, MemberExpression, Statement}; pub fn is_node_value_not_dom_node(expr: &Expression) -> bool { matches!( @@ -26,3 +26,50 @@ pub fn is_empty_stmt(stmt: &Statement) -> bool { _ => false, } } + +// ref: https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/rules/utils/array-or-object-prototype-property.js +pub fn is_prototype_property( + member_expr: &MemberExpression, + property: &str, + object: Option<&str>, +) -> bool { + if !member_expr.static_property_name().is_some_and(|name| name == property) + || member_expr.optional() + { + return false; + } + + // `Object.prototype.method` or `Array.prototype.method` + if let Expression::MemberExpression(member_expr_obj) = member_expr.object() { + if let Expression::Identifier(iden) = member_expr_obj.object() { + if member_expr_obj.static_property_name().is_some_and(|name| name == "prototype") + && object.is_some_and(|val| val == iden.name) + && !member_expr.optional() + && !member_expr_obj.optional() + { + return true; + } + } + }; + + match object { + // `[].method` + Some("Array") => { + if let Expression::ArrayExpression(array_expr) = member_expr.object() { + array_expr.elements.len() == 0 + } else { + false + } + } + + // `{}.method` + Some("Object") => { + if let Expression::ObjectExpression(obj_expr) = member_expr.object() { + obj_expr.properties.len() == 0 + } else { + false + } + } + _ => false, + } +}