fix(linter/unicorn/prefer-spread): stop checking string split calls (#26935)

Stop reporting and fixing `String#split('')`, matching Unicorn's removal of this check in v66. Splitting into UTF-16 code units and spreading into code points can produce different results, for example for `"🦄"`.

Fixes #26873
This commit is contained in:
camc314 committed 2026-09-22 14:32:22 +00:00
1 parent d79ad19796
commit feb733b5a1
2 files changed
+18 -125

No files matched your search

@@ -5,7 +5,7 @@ use oxc_ast::{
};
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_span::{GetSpan, Span};
use oxc_span::Span;
use crate::{AstNode, ast_util, context::LintContext, rule::Rule};
@@ -169,39 +169,6 @@ fn check_unicorn_prefer_spread<'a>(
member_expr.object(),
);
}
// `string.split()`
"split" => {
if call_expr.arguments.len() != 1 {
return;
}
let Some(expr) = call_expr.arguments[0].as_expression() else {
return;
};
let Expression::StringLiteral(string_lit) = expr.without_parentheses() else {
return;
};
if !string_lit.value.is_empty() {
return;
}
ctx.diagnostic_with_fix(
unicorn_prefer_spread_diagnostic(call_expr.span, "string.split()"),
|fixer| {
let needs_semi = ast_util::could_be_asi_hazard(node, ctx);
let callee_obj = member_expr.object().without_parentheses();
let prefix = if needs_semi { ";" } else { "" };
fixer.replace(
call_expr.span,
format!(
"{prefix}[...{}]",
callee_obj.span().source_text(ctx.source_text())
),
)
},
);
}
_ => {}
}
}
@@ -436,7 +403,23 @@ fn test() {
r#""".split(string)"#,
"string.split()",
r#"string.notSplit("")"#,
// r#"const notString = 0; notString.split("")"#,
r#"const notString = 0; notString.split("")"#,
r#""string".split("")"#,
r#""string".split('')"#,
r#"unknown.split("")"#,
r#"const characters = "string".split("")"#,
r#"(( (( (( "string" )).split ))( (("")) ) ))"#,
r#"bar()
foo.split("")"#,
r#"unknown.split("")"#,
r#""🦄".split("")"#,
r#"const {length} = "🦄".split("")"#,
r#""foo bar baz".split("")"#,
"foo()\nstr.split(\"\")",
r#"'string'
str.split("")"#,
r#""string"
str.split("")"#,
];
let fail = vec![
@@ -598,16 +581,6 @@ fn test() {
foo.toSpliced()",
r#""".toSpliced()"#,
"new Uint8Array([10, 20, 30, 40, 50]).toSpliced()",
r#""string".split("")"#,
r#""string".split('')"#,
r#"unknown.split("")"#,
r#"const characters = "string".split("")"#,
r#"(( (( (( "string" )).split ))( (("")) ) ))"#,
r#"bar()
foo.split("")"#,
r#"unknown.split("")"#,
r#""🦄".split("")"#,
r#"const {length} = "🦄".split("")"#,
];
let fix = vec![
@@ -644,11 +617,6 @@ fn test() {
("const copy = array.toSpliced()", "const copy = [...array]"),
// `array.toSpliced()` - ASI hazard cases
("foo()\narray.toSpliced()", "foo()\n;[...array]"),
// `string.split()`
(r#""🦄".split("")"#, r#"[..."🦄"]"#),
(r#""foo bar baz".split("")"#, r#"[..."foo bar baz"]"#),
// `string.split()` - ASI hazard cases
("foo()\nstr.split(\"\")", "foo()\n;[...str]"),
(
r"Array.from(path.matchAll(/\{([^{}?]+\??)\}/g))",
"[...path.matchAll(/\\{([^{}?]+\\??)\\}/g)]",
@@ -680,17 +648,6 @@ fn test() {
("for (array.slice();;) {}", "for ([...array];;) {}"),
("switch (array.slice()[0]) {}", "switch ([...array][0]) {}"),
("`template`\narray.toSpliced()", "`template`\n;[...array]"),
(
r#"'string'
str.split("")"#,
"'string'\n;[...str]",
),
(
r#""string"
str.split("")"#,
r#""string"
;[...str]"#,
),
(
"foo()\nArray.from(set).map(x => x).filter(Boolean).length",
"foo()\n;[...set].map(x => x).filter(Boolean).length",
@@ -915,67 +915,3 @@ source: crates/oxc_linter/src/tester.rs
· ────────────────────────────────────────────────
╰────
help: The spread operator (`...`) is more concise and readable.
⚠ unicorn(prefer-spread): Prefer the spread operator (`...`) over string.split()
╭─[prefer_spread.tsx:1:1]
1 │ "string".split("")
· ──────────────────
╰────
help: The spread operator (`...`) is more concise and readable.
⚠ unicorn(prefer-spread): Prefer the spread operator (`...`) over string.split()
╭─[prefer_spread.tsx:1:1]
1 │ "string".split('')
· ──────────────────
╰────
help: The spread operator (`...`) is more concise and readable.
⚠ unicorn(prefer-spread): Prefer the spread operator (`...`) over string.split()
╭─[prefer_spread.tsx:1:1]
1 │ unknown.split("")
· ─────────────────
╰────
help: The spread operator (`...`) is more concise and readable.
⚠ unicorn(prefer-spread): Prefer the spread operator (`...`) over string.split()
╭─[prefer_spread.tsx:1:20]
1 │ const characters = "string".split("")
· ──────────────────
╰────
help: The spread operator (`...`) is more concise and readable.
⚠ unicorn(prefer-spread): Prefer the spread operator (`...`) over string.split()
╭─[prefer_spread.tsx:1:4]
1 │ (( (( (( "string" )).split ))( (("")) ) ))
· ────────────────────────────────────
╰────
help: The spread operator (`...`) is more concise and readable.
⚠ unicorn(prefer-spread): Prefer the spread operator (`...`) over string.split()
╭─[prefer_spread.tsx:2:13]
1 │ bar()
2 │ foo.split("")
· ─────────────
╰────
help: The spread operator (`...`) is more concise and readable.
⚠ unicorn(prefer-spread): Prefer the spread operator (`...`) over string.split()
╭─[prefer_spread.tsx:1:1]
1 │ unknown.split("")
· ─────────────────
╰────
help: The spread operator (`...`) is more concise and readable.
⚠ unicorn(prefer-spread): Prefer the spread operator (`...`) over string.split()
╭─[prefer_spread.tsx:1:1]
1 │ "🦄".split("")
· ──────────────
╰────
help: The spread operator (`...`) is more concise and readable.
⚠ unicorn(prefer-spread): Prefer the spread operator (`...`) over string.split()
╭─[prefer_spread.tsx:1:18]
1 │ const {length} = "🦄".split("")
· ──────────────
╰────
help: The spread operator (`...`) is more concise and readable.