From 295822d7d4e7a7f7e50b0e529a3d59d00a7a8199 Mon Sep 17 00:00:00 2001 From: Cameron Date: Sun, 10 Dec 2023 15:19:30 +0000 Subject: [PATCH] feat(linter) eslint plugin unicorn: prefer array flat (#1650) --- crates/oxc_linter/src/rules.rs | 2 + .../src/rules/unicorn/no_array_reduce.rs | 2 +- .../src/rules/unicorn/prefer_array_flat.rs | 416 +++++++++++++++++ .../src/snapshots/prefer_array_flat.snap | 425 ++++++++++++++++++ crates/oxc_linter/src/utils/unicorn.rs | 62 ++- 5 files changed, 898 insertions(+), 9 deletions(-) create mode 100644 crates/oxc_linter/src/rules/unicorn/prefer_array_flat.rs create mode 100644 crates/oxc_linter/src/snapshots/prefer_array_flat.snap diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs index 699a16a17..78f867560 100644 --- a/crates/oxc_linter/src/rules.rs +++ b/crates/oxc_linter/src/rules.rs @@ -187,6 +187,7 @@ mod unicorn { pub mod number_literal_case; pub mod numeric_separators_style; pub mod prefer_add_event_listener; + pub mod prefer_array_flat; pub mod prefer_array_flat_map; pub mod prefer_array_some; pub mod prefer_blob_reading_methods; @@ -384,6 +385,7 @@ oxc_macros::declare_all_lint_rules! { unicorn::numeric_separators_style, unicorn::prefer_add_event_listener, unicorn::prefer_array_flat_map, + unicorn::prefer_array_flat, unicorn::prefer_array_some, unicorn::prefer_blob_reading_methods, unicorn::prefer_code_point, diff --git a/crates/oxc_linter/src/rules/unicorn/no_array_reduce.rs b/crates/oxc_linter/src/rules/unicorn/no_array_reduce.rs index 1a85f596b..216211081 100644 --- a/crates/oxc_linter/src/rules/unicorn/no_array_reduce.rs +++ b/crates/oxc_linter/src/rules/unicorn/no_array_reduce.rs @@ -64,7 +64,7 @@ impl Rule for NoArrayReduce { return; }; - let Some(member_expr) = (call_expr).callee.get_member_expr() else { + let Some(member_expr) = call_expr.callee.get_member_expr() else { return; }; diff --git a/crates/oxc_linter/src/rules/unicorn/prefer_array_flat.rs b/crates/oxc_linter/src/rules/unicorn/prefer_array_flat.rs new file mode 100644 index 000000000..7aff888ef --- /dev/null +++ b/crates/oxc_linter/src/rules/unicorn/prefer_array_flat.rs @@ -0,0 +1,416 @@ +use oxc_ast::{ + ast::{ + Argument, ArrayExpressionElement, BindingPatternKind, 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::get_first_parameter_name, + utils::{get_return_identifier_name, is_empty_array_expression, is_prototype_property}, + AstNode, +}; + +#[derive(Debug, Error, Diagnostic)] +#[error("eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays.")] +#[diagnostic(severity(warning), help(r"Call `.flat()` on the array instead."))] +struct PreferArrayFlatDiagnostic(#[label] pub Span); + +#[derive(Debug, Default, Clone)] +pub struct PreferArrayFlat; + +declare_oxc_lint!( + /// ### What it does + /// + /// Prefers `Array#flat()` over legacy techniques to flatten arrays. /// + /// + /// ### Why is this bad? + /// + /// ES2019 introduced a new method [`Array#flat()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat) that flatten arrays. + /// + /// This rule aims to standardize the use of `Array#flat()` over legacy techniques to flatten arrays. + /// + /// ### Example + /// ```javascript + /// // Bad + /// const foo = array.flatMap(x => x); + /// const foo = array.reduce((a, b) => a.concat(b), []); + /// const foo = array.reduce((a, b) => [...a, ...b], []); + /// const foo = [].concat(maybeArray); + /// const foo = [].concat(...array); + /// const foo = [].concat.apply([], array); + /// const foo = Array.prototype.concat.apply([], array); + /// const foo = Array.prototype.concat.call([], maybeArray); + /// const foo = Array.prototype.concat.call([], ...array); + /// + /// // Good + /// const foo = array.flat(); + /// const foo = [maybeArray].flat(); + /// ``` + PreferArrayFlat, + correctness +); + +impl Rule for PreferArrayFlat { + fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) { + let AstKind::CallExpression(call_expr) = node.kind() else { + return; + }; + + check_array_flat_map_case(call_expr, ctx); + check_array_reduce_case(call_expr, ctx); + check_array_concat_case(call_expr, ctx); + check_array_prototype_concat_case(call_expr, ctx); + } +} + +// `array.flatMap(x => x)` +fn check_array_flat_map_case<'a>(call_expr: &CallExpression<'a>, ctx: &LintContext<'a>) { + if !is_method_call(call_expr, None, Some(&["flatMap"]), Some(1), Some(1)) { + return; + } + + let Argument::Expression(first_argument) = call_expr.arguments.get(0).unwrap() else { + return; + }; + + let Expression::ArrowExpression(first_argument) = first_argument else { + return; + }; + + if first_argument.r#async || first_argument.params.parameters_count() != 1 { + return; + } + + let Some(first_param_name) = get_first_parameter_name(&first_argument.params) else { + return; + }; + + let Some(return_param_name) = get_return_identifier_name(&first_argument.body) else { + return; + }; + + if first_param_name != return_param_name { + return; + } + + ctx.diagnostic(PreferArrayFlatDiagnostic(call_expr.span)); +} + +// `array.reduce((a, b) => a.concat(b), [])` +// `array.reduce((a, b) => [...a, ...b], [])` +fn check_array_reduce_case<'a>(call_expr: &CallExpression<'a>, ctx: &LintContext<'a>) { + if !is_method_call(call_expr, None, Some(&["reduce"]), Some(2), Some(2)) { + return; + } + let Argument::Expression(Expression::ArrowExpression(first_argument)) = + call_expr.arguments.get(0).unwrap() + else { + return; + }; + let Argument::Expression(second_argument) = call_expr.arguments.get(1).unwrap() else { + return; + }; + + if first_argument.r#async + || first_argument.params.parameters_count() != 2 + || !is_empty_array_expression(second_argument) + { + return; + } + + let Some((first_parameter, second_parameter)) = ({ + match ( + &first_argument.params.items[0].pattern.kind, + &first_argument.params.items[1].pattern.kind, + ) { + ( + BindingPatternKind::BindingIdentifier(ref first_param), + BindingPatternKind::BindingIdentifier(second_param), + ) => Some((&first_param.name, &second_param.name)), + + _ => None, + } + }) else { + return; + }; + + let Some(Statement::ExpressionStatement(expr_stmt)) = first_argument.body.statements.get(0) + else { + return; + }; + + // `array.reduce((a, b) => a.concat(b), [])` + if let Expression::CallExpression(concat_call_expr) = &expr_stmt.expression { + if is_method_call(concat_call_expr, None, Some(&["concat"]), Some(1), Some(1)) { + if let Argument::Expression(Expression::Identifier(first_argument_ident)) = + &concat_call_expr.arguments[0] + { + if first_argument_ident.name != second_parameter { + return; + } + + let Expression::Identifier(second_argument_ident) = + concat_call_expr.callee.get_member_expr().unwrap().object() + else { + return; + }; + + if second_argument_ident.name != first_parameter { + return; + } + + ctx.diagnostic(PreferArrayFlatDiagnostic(call_expr.span)); + } + } + } + + if let Expression::ArrayExpression(array_expr) = &expr_stmt.expression { + if array_expr.elements.len() != 2 { + return; + } + + let Some((first_element, second_element)) = ({ + match (&array_expr.elements[0], &array_expr.elements[1]) { + ( + ArrayExpressionElement::SpreadElement(first_element), + ArrayExpressionElement::SpreadElement(second_element), + ) => match (&first_element.argument, &second_element.argument) { + ( + Expression::Identifier(first_element), + Expression::Identifier(second_element), + ) => Some((first_element, second_element)), + _ => None, + }, + _ => None, + } + }) else { + return; + }; + + if first_element.name != first_parameter || second_element.name != second_parameter { + return; + } + + ctx.diagnostic(PreferArrayFlatDiagnostic(call_expr.span)); + }; +} + +// `[].concat(maybeArray)` +// `[].concat(...array)` +fn check_array_concat_case<'a>(call_expr: &CallExpression<'a>, ctx: &LintContext<'a>) { + if is_method_call(call_expr, None, Some(&["concat"]), Some(1), Some(1)) { + // `array.concat(maybeArray)` + if let Expression::ArrayExpression(array_expr) = + call_expr.callee.get_member_expr().unwrap().object() + { + if !array_expr.elements.is_empty() { + return; + } + ctx.diagnostic(PreferArrayFlatDiagnostic(call_expr.span)); + } + } +} + +// - `[].concat.apply([], array)` and `Array.prototype.concat.apply([], array)` +// - `[].concat.call([], maybeArray)` and `Array.prototype.concat.call([], maybeArray)` +// - `[].concat.call([], ...array)` and `Array.prototype.concat.call([], ...array)` +fn check_array_prototype_concat_case<'a>(call_expr: &CallExpression<'a>, ctx: &LintContext<'a>) { + let Some(member_expr) = call_expr.callee.get_member_expr() else { + return; + }; + + if let Expression::MemberExpression(member_expr_obj) = member_expr.object() { + let is_call_call = is_method_call(call_expr, None, Some(&["call"]), Some(2), Some(2)); + + if (is_call_call || is_method_call(call_expr, None, Some(&["apply"]), Some(2), Some(2))) + && is_prototype_property(member_expr_obj, "concat", Some("Array")) + { + if let Argument::Expression(first_argument) = &call_expr.arguments[0] { + if is_empty_array_expression(first_argument) + && (is_call_call + || !matches!(call_expr.arguments.get(1), Some(Argument::SpreadElement(_)))) + { + ctx.diagnostic(PreferArrayFlatDiagnostic(call_expr.span)); + } + } + } + } +} + +#[test] +fn test() { + use crate::tester::Tester; + + let pass = vec![ + r"array.flatMap", + r"new array.flatMap(x => x)", + r"flatMap(x => x)", + r"array.notFlatMap(x => x)", + r"array[flatMap](x => x)", + r"array.flatMap(x => x, thisArgument)", + r"array.flatMap(...[x => x])", + r"array.flatMap(function (x) { return x; })", + r"array.flatMap(async x => x)", + r"array.flatMap(function * (x) { return x;})", + r"array.flatMap(() => x)", + r"array.flatMap((x, y) => x)", + r"array.flatMap(x => y)", + r"new array.reduce((a, b) => a.concat(b), [])", + r"array.reduce", + r"reduce((a, b) => a.concat(b), [])", + r"array[reduce]((a, b) => a.concat(b), [])", + r"array.notReduce((a, b) => a.concat(b), [])", + r"array.reduce((a, b) => a.concat(b), [], EXTRA_ARGUMENT)", + r"array.reduce((a, b) => a.concat(b), NOT_EMPTY_ARRAY)", + r"array.reduce((a, b, extraParameter) => a.concat(b), [])", + r"array.reduce((a,) => a.concat(b), [])", + r"array.reduce(() => a.concat(b), [])", + r"array.reduce((a, b) => {return a.concat(b); }, [])", + r"array.reduce(function (a, b) { return a.concat(b); }, [])", + r"array.reduce((a, b) => b.concat(b), [])", + r"array.reduce((a, b) => a.concat(a), [])", + r"array.reduce((a, b) => b.concat(a), [])", + r"array.reduce((a, b) => a.notConcat(b), [])", + r"array.reduce((a, b) => a.concat, [])", + r"new array.reduce((a, b) => [...a, ...b], [])", + r"array[reduce]((a, b) => [...a, ...b], [])", + r"reduce((a, b) => [...a, ...b], [])", + r"array.notReduce((a, b) => [...a, ...b], [])", + r"array.reduce((a, b) => [...a, ...b], [], EXTRA_ARGUMENT)", + r"array.reduce((a, b) => [...a, ...b], NOT_EMPTY_ARRAY)", + r"array.reduce((a, b, extraParameter) => [...a, ...b], [])", + r"array.reduce((a,) => [...a, ...b], [])", + r"array.reduce(() => [...a, ...b], [])", + r"array.reduce((a, b) => {return [...a, ...b]; }, [])", + r"array.reduce(function (a, b) { return [...a, ...b]; }, [])", + r"array.reduce((a, b) => [...b, ...b], [])", + r"array.reduce((a, b) => [...a, ...a], [])", + r"array.reduce((a, b) => [...b, ...a], [])", + r"array.reduce((a, b) => [a, ...b], [])", + r"array.reduce((a, b) => [...a, b], [])", + r"array.reduce((a, b) => [a, b], [])", + r"array.reduce((a, b) => [...a, ...b, c], [])", + r"array.reduce((a, b) => [...a, ...b,,], [])", + r"array.reduce((a, b) => [,...a, ...b], [])", + r"array.reduce((a, b) => [, ], [])", + r"array.reduce((a, b) => [, ,], [])", + r"[].concat", + r"new [].concat(array)", + r"[][concat](array)", + r"[].notConcat(array)", + r"[,].concat(array)", + r"({}).concat(array)", + r"[].concat()", + r"[].concat(array, EXTRA_ARGUMENT)", + r"new [].concat(...array)", + r"[][concat](...array)", + r"[].notConcat(...array)", + r"[,].concat(...array)", + r"({}).concat(...array)", + r"[].concat()", + r"[].concat(...array, EXTRA_ARGUMENT)", + r"new [].concat.apply([], array)", + r"[].concat.apply", + r"[].concat.apply([], ...array)", + r"[].concat.apply([], array, EXTRA_ARGUMENT)", + r"[].concat.apply([])", + r"[].concat.apply(NOT_EMPTY_ARRAY, array)", + r"[].concat.apply([,], array)", + r"[,].concat.apply([], array)", + r"[].concat[apply]([], array)", + r"[][concat].apply([], array)", + r"[].concat.notApply([], array)", + r"[].notConcat.apply([], array)", + r"new Array.prototype.concat.apply([], array)", + r"Array.prototype.concat.apply", + r"Array.prototype.concat.apply([], ...array)", + r"Array.prototype.concat.apply([], array, EXTRA_ARGUMENT)", + r"Array.prototype.concat.apply([])", + r"Array.prototype.concat.apply(NOT_EMPTY_ARRAY, array)", + r"Array.prototype.concat.apply([,], array)", + r"Array.prototype.concat[apply]([], array)", + r"Array.prototype[concat].apply([], array)", + r"Array[prototype].concat.apply([], array)", + r"Array.prototype.concat.notApply([], array)", + r"Array.prototype.notConcat.apply([], array)", + r"Array.notPrototype.concat.apply([], array)", + r"NotArray.prototype.concat.apply([], array)", + r"Array.prototype?.concat.apply([], array)", + r"object.Array.prototype.concat.apply([], array)", + ]; + + let fail = vec![ + r"array.flatMap(x => x)", + r"function foo(){return[].flatMap(x => x)}", + r"foo.flatMap(x => x) instanceof Array", + r"array.reduce((a, b) => a.concat(b), [])", + r"function foo(){return[].reduce((a, b) => a.concat(b), [])}", + r"array.reduce((a, b) => [...a, ...b], [])", + r"array.reduce((a, b) => [...a, ...b,], [])", + r"function foo(){return[].reduce((a, b) => [...a, ...b,], [])}", + r"[].concat(maybeArray)", + r"[].concat( ((0, maybeArray)) )", + r"[].concat( ((maybeArray)) )", + r"[].concat( [foo] )", + r"[].concat( [[foo]] )", + r"function foo(){return[].concat(maybeArray)}", + r"[].concat(...array)", + r"[].concat(...(( array )))", + r"[].concat(...(( [foo] )))", + r"[].concat(...(( [[foo]] )))", + r"function foo(){return[].concat(...array)}", + r"class A extends[].concat(...array){}", + r"const A = class extends[].concat(...array){}", + r"[].concat.apply([], array)", + r"[].concat.apply([], ((0, array)))", + r"[].concat.apply([], ((array)))", + r"[].concat.apply([], [foo])", + r"[].concat.apply([], [[foo]])", + r"[].concat.call([], maybeArray)", + r"[].concat.call([], ((0, maybeArray)))", + r"[].concat.call([], ((maybeArray)))", + r"[].concat.call([], [foo])", + r"[].concat.call([], [[foo]])", + r"[].concat.call([], ...array)", + r"[].concat.call([], ...((0, array)))", + r"[].concat.call([], ...((array)))", + r"[].concat.call([], ...[foo])", + r"[].concat.call([], ...[[foo]])", + r"function foo(){return[].concat.call([], ...array)}", + r"Array.prototype.concat.apply([], array)", + r"Array.prototype.concat.apply([], ((0, array)))", + r"Array.prototype.concat.apply([], ((array)))", + r"Array.prototype.concat.apply([], [foo])", + r"Array.prototype.concat.apply([], [[foo]])", + r"Array.prototype.concat.call([], maybeArray)", + r"Array.prototype.concat.call([], ((0, maybeArray)))", + r"Array.prototype.concat.call([], ((maybeArray)))", + r"Array.prototype.concat.call([], [foo])", + r"Array.prototype.concat.call([], [[foo]])", + r"Array.prototype.concat.call([], ...array)", + r"Array.prototype.concat.call([], ...((0, array)))", + r"Array.prototype.concat.call([], ...((array)))", + r"Array.prototype.concat.call([], ...[foo])", + r"Array.prototype.concat.call([], ...[[foo]])", + r"[].concat.apply([], array)", + r"Array.prototype.concat.apply([], array)", + r"Array.prototype.concat.apply([], (0, array))", + r"Array.prototype.concat.call([], (0, array))", + r"async function a() { return [].concat(await getArray()); }", + r"[].concat(some./**/array)", + r"[/**/].concat(some./**/array)", + r"[/**/].concat(some.array)", + ]; + + Tester::new_without_config(PreferArrayFlat::NAME, pass, fail).test_and_snapshot(); +} diff --git a/crates/oxc_linter/src/snapshots/prefer_array_flat.snap b/crates/oxc_linter/src/snapshots/prefer_array_flat.snap new file mode 100644 index 000000000..e862e278d --- /dev/null +++ b/crates/oxc_linter/src/snapshots/prefer_array_flat.snap @@ -0,0 +1,425 @@ +--- +source: crates/oxc_linter/src/tester.rs +expression: prefer_array_flat +--- + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ array.flatMap(x => x) + · ───────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ function foo(){return[].flatMap(x => x)} + · ────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ foo.flatMap(x => x) instanceof Array + · ─────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ array.reduce((a, b) => a.concat(b), []) + · ─────────────────────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ function foo(){return[].reduce((a, b) => a.concat(b), [])} + · ──────────────────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ array.reduce((a, b) => [...a, ...b], []) + · ──────────────────────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ array.reduce((a, b) => [...a, ...b,], []) + · ───────────────────────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ function foo(){return[].reduce((a, b) => [...a, ...b,], [])} + · ────────────────────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ [].concat(maybeArray) + · ───────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ [].concat( ((0, maybeArray)) ) + · ────────────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ [].concat( ((maybeArray)) ) + · ─────────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ [].concat( [foo] ) + · ────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ [].concat( [[foo]] ) + · ──────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ function foo(){return[].concat(maybeArray)} + · ───────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ [].concat(...array) + · ─────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ [].concat(...(( array ))) + · ───────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ [].concat(...(( [foo] ))) + · ───────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ [].concat(...(( [[foo]] ))) + · ─────────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ function foo(){return[].concat(...array)} + · ─────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ class A extends[].concat(...array){} + · ─────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ const A = class extends[].concat(...array){} + · ─────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ [].concat.apply([], array) + · ────────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ [].concat.apply([], ((0, array))) + · ───────────────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ [].concat.apply([], ((array))) + · ────────────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ [].concat.apply([], [foo]) + · ────────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ [].concat.apply([], [[foo]]) + · ──────────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ [].concat.call([], maybeArray) + · ────────────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ [].concat.call([], ((0, maybeArray))) + · ───────────────────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ [].concat.call([], ((maybeArray))) + · ────────────────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ [].concat.call([], [foo]) + · ───────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ [].concat.call([], [[foo]]) + · ─────────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ [].concat.call([], ...array) + · ──────────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ [].concat.call([], ...((0, array))) + · ─────────────────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ [].concat.call([], ...((array))) + · ──────────────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ [].concat.call([], ...[foo]) + · ──────────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ [].concat.call([], ...[[foo]]) + · ────────────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ function foo(){return[].concat.call([], ...array)} + · ──────────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ Array.prototype.concat.apply([], array) + · ─────────────────────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ Array.prototype.concat.apply([], ((0, array))) + · ────────────────────────────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ Array.prototype.concat.apply([], ((array))) + · ─────────────────────────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ Array.prototype.concat.apply([], [foo]) + · ─────────────────────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ Array.prototype.concat.apply([], [[foo]]) + · ───────────────────────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ Array.prototype.concat.call([], maybeArray) + · ─────────────────────────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ Array.prototype.concat.call([], ((0, maybeArray))) + · ────────────────────────────────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ Array.prototype.concat.call([], ((maybeArray))) + · ─────────────────────────────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ Array.prototype.concat.call([], [foo]) + · ────────────────────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ Array.prototype.concat.call([], [[foo]]) + · ──────────────────────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ Array.prototype.concat.call([], ...array) + · ───────────────────────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ Array.prototype.concat.call([], ...((0, array))) + · ──────────────────────────────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ Array.prototype.concat.call([], ...((array))) + · ───────────────────────────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ Array.prototype.concat.call([], ...[foo]) + · ───────────────────────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ Array.prototype.concat.call([], ...[[foo]]) + · ─────────────────────────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ [].concat.apply([], array) + · ────────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ Array.prototype.concat.apply([], array) + · ─────────────────────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ Array.prototype.concat.apply([], (0, array)) + · ──────────────────────────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ Array.prototype.concat.call([], (0, array)) + · ─────────────────────────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ async function a() { return [].concat(await getArray()); } + · ─────────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ [].concat(some./**/array) + · ───────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ [/**/].concat(some./**/array) + · ───────────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + ⚠ eslint-plugin-unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays. + ╭─[prefer_array_flat.tsx:1:1] + 1 │ [/**/].concat(some.array) + · ───────────────────────── + ╰──── + help: Call `.flat()` on the array instead. + + diff --git a/crates/oxc_linter/src/utils/unicorn.rs b/crates/oxc_linter/src/utils/unicorn.rs index 2f107c07e..a17dfc851 100644 --- a/crates/oxc_linter/src/utils/unicorn.rs +++ b/crates/oxc_linter/src/utils/unicorn.rs @@ -1,7 +1,10 @@ mod boolean; pub use self::boolean::*; use oxc_ast::{ - ast::{Expression, LogicalExpression, MemberExpression, Statement}, + ast::{ + BindingPatternKind, Expression, FormalParameters, FunctionBody, LogicalExpression, + MemberExpression, Statement, + }, AstKind, }; use oxc_semantic::AstNode; @@ -61,13 +64,7 @@ pub fn is_prototype_property( match object { // `[].method` - Some("Array") => { - if let Expression::ArrayExpression(array_expr) = member_expr.object() { - array_expr.elements.len() == 0 - } else { - false - } - } + Some("Array") => is_empty_array_expression(member_expr.object()), // `{}.method` Some("Object") => { @@ -81,6 +78,14 @@ pub fn is_prototype_property( } } +pub fn is_empty_array_expression(expr: &Expression) -> bool { + if let Expression::ArrayExpression(array_expr) = expr { + array_expr.elements.len() == 0 + } else { + false + } +} + pub fn is_logical_expression(node: &AstNode) -> bool { matches!( node.kind(), @@ -90,3 +95,44 @@ pub fn is_logical_expression(node: &AstNode) -> bool { }) ) } + +// gets the name of the first parameter of a function +pub fn get_first_parameter_name<'a>(arg: &'a FormalParameters) -> Option<&'a str> { + let first_func_param = arg.items.get(0)?; + let BindingPatternKind::BindingIdentifier(first_func_param) = &first_func_param.pattern.kind + else { + return None; + }; + Some(first_func_param.name.as_str()) +} + +pub fn get_return_identifier_name<'a>(body: &'a FunctionBody<'_>) -> Option<&'a str> { + match body.statements.get(0)? { + Statement::BlockStatement(block_stmt) => { + let Statement::ReturnStatement(return_stmt) = block_stmt.body.get(0)? else { + return None; + }; + + let Some(Expression::Identifier(ident)) = return_stmt.argument.as_ref() else { + return None; + }; + + Some(ident.name.as_str()) + } + Statement::ReturnStatement(return_stmt) => { + let return_expr = return_stmt.argument.as_ref()?; + match return_expr { + Expression::Identifier(ident) => Some(ident.name.as_str()), + _ => None, + } + } + Statement::ExpressionStatement(expr_stmt) => { + let Expression::Identifier(ident) = &expr_stmt.expression else { + return None; + }; + + Some(ident.name.as_str()) + } + _ => None, + } +}