fix(linter/unicorn/prefer-array-flat): fix concat.apply suggestions (#25373)

## Summary

- Add fixes for identifier-backed `[].concat.apply([], array)` and
`Array.prototype.concat.apply([], array)` cases.
- Keep other `apply` and `call` patterns diagnostic-only.

---------

Co-authored-by: camc314 <18101008+camc314@users.noreply.github.com>
This commit is contained in:
Cameronandcamc314 authored and GitHub committed 2026-08-08 09:42:25 +00:00
1 parent 7d8a9b5f2f
commit 44e73fde5f
2 files changed
+55 -12

No files matched your search

@@ -359,22 +359,34 @@ fn check_array_prototype_concat_case<'a>(call_expr: &CallExpression<'a>, ctx: &L
if let Some(member_expr_obj) = member_expr.object().as_member_expression() {
let is_call_call = is_method_call(call_expr, None, Some(&["call"]), Some(2), Some(2));
let is_apply_call = is_method_call(call_expr, None, Some(&["apply"]), Some(2), Some(2));
if (is_call_call || is_method_call(call_expr, None, Some(&["apply"]), Some(2), Some(2)))
if (is_call_call || is_apply_call)
&& is_prototype_property(member_expr_obj, "concat", Some("Array"))
&& let Some(first_argument) = call_expr.arguments[0].as_expression()
&& is_empty_array_expression(first_argument)
&& (is_call_call
|| !matches!(call_expr.arguments.get(1), Some(Argument::SpreadElement(_))))
{
ctx.diagnostic(prefer_array_flat_diagnostic(call_expr.span));
if is_apply_call
&& let Some(Argument::Identifier(array)) = call_expr.arguments.get(1)
&& array.name != "arguments"
{
let replacement = format!("{}.flat()", ctx.source_range(array.span));
ctx.diagnostic_with_dangerous_fix(
prefer_array_flat_diagnostic(call_expr.span),
|fixer| fixer.replace(call_expr.span, replacement),
);
} else {
ctx.diagnostic(prefer_array_flat_diagnostic(call_expr.span));
}
}
}
}
#[test]
fn test() {
use crate::tester::Tester;
use crate::{fixer::FixKind, tester::Tester};
let pass = vec![
"array.flatMap",
@@ -557,6 +569,7 @@ fn test() {
"[].concat.apply([], ((array)))",
"[].concat.apply([], [foo])",
"[].concat.apply([], [[foo]])",
"function flatten() { return [].concat.apply([], arguments); }",
"[].concat.call([], maybeArray)",
"[].concat.call([], ((0, maybeArray)))",
"[].concat.call([], ((maybeArray)))",
@@ -573,6 +586,7 @@ fn test() {
"Array.prototype.concat.apply([], ((array)))",
"Array.prototype.concat.apply([], [foo])",
"Array.prototype.concat.apply([], [[foo]])",
"function flatten() { return Array.prototype.concat.apply([], arguments); }",
"Array.prototype.concat.call([], maybeArray)",
"Array.prototype.concat.call([], ((0, maybeArray)))",
"Array.prototype.concat.call([], ((maybeArray)))",
@@ -623,16 +637,28 @@ fn test() {
];
let fix = vec![
("array.flatMap(x => x)", "array.flat()"),
("array.reduce((a, b) => a.concat(b), [])", "array.flat()"),
("array.reduce((a, b) => [...a, ...b], [])", "array.flat()"),
("Foo.bar.flatMap(x => x)", "Foo.bar.flat()"),
("array.flatMap(x => x)", "array.flat()", None, FixKind::SafeFix),
("array.reduce((a, b) => a.concat(b), [])", "array.flat()", None, FixKind::SafeFix),
("array.reduce((a, b) => [...a, ...b], [])", "array.flat()", None, FixKind::SafeFix),
("Foo.bar.flatMap(x => x)", "Foo.bar.flat()", None, FixKind::SafeFix),
(
"const values = getValues(); values.flatMap(x => x);",
"const values = getValues(); values.flat();",
None,
FixKind::SafeFix,
),
(
"const values = []; values.flatMap(x => x);",
"const values = []; values.flat();",
None,
FixKind::SafeFix,
),
(
"const Items = []; Items.flatMap(x => x);",
"const Items = []; Items.flat();",
None,
FixKind::SafeFix,
),
("const values = []; values.flatMap(x => x);", "const values = []; values.flat();"),
("const Items = []; Items.flatMap(x => x);", "const Items = []; Items.flat();"),
(
"for (const value of values) {
value.flatMap(x => x);
@@ -640,14 +666,17 @@ fn test() {
"for (const value of values) {
value.flat();
}",
None,
FixKind::SafeFix,
),
(
"const Items = [] as unknown[]; Items.flatMap(x => x);",
"const Items = [] as unknown[]; Items.flat();",
None,
FixKind::SafeFix,
),
// TODO: Get these passing.
// ("/**/[].concat.apply([], array)", "/**/array.flat()"),
// ("Array.prototype.concat.apply([], array)", "array.flat()"),
("/**/[].concat.apply([], array)", "/**/array.flat()", None, FixKind::DangerousFix),
("Array.prototype.concat.apply([], array)", "array.flat()", None, FixKind::DangerousFix),
];
Tester::new(PreferArrayFlat::NAME, PreferArrayFlat::PLUGIN, pass, fail)
@@ -249,6 +249,13 @@ source: crates/oxc_linter/src/tester.rs
╰────
help: Call `.flat()` on the array instead.
⚠ unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays.
╭─[prefer_array_flat.tsx:1:29]
1 │ function flatten() { return [].concat.apply([], arguments); }
· ──────────────────────────────
╰────
help: Call `.flat()` on the array instead.
⚠ unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays.
╭─[prefer_array_flat.tsx:1:1]
1 │ [].concat.call([], maybeArray)
@@ -361,6 +368,13 @@ source: crates/oxc_linter/src/tester.rs
╰────
help: Call `.flat()` on the array instead.
⚠ unicorn(prefer-array-flat): Prefer Array#flat() over legacy techniques to flatten arrays.
╭─[prefer_array_flat.tsx:1:29]
1 │ function flatten() { return Array.prototype.concat.apply([], arguments); }
· ───────────────────────────────────────────
╰────
help: Call `.flat()` on the array instead.
⚠ 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)