feat(linter) eslint plugin unicorn: no array reduce (restriction) (#1610)

This commit is contained in:
Cameron 2023-12-03 09:03:23 +00:00 committed by GitHub
parent 9af24842a8
commit eaffb1d87c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 879 additions and 50 deletions

View file

@ -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,

View file

@ -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();
}

View file

@ -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"))
}

View file

@ -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.

View file

@ -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,
}
}