Skip to content

ref: Prefer spread over Array.from() repo-wide - #1724

Merged
BYK merged 1 commit into
mainfrom
feat/prefer-spread
Oct 29, 2026
Merged

BYK merged 1 commit into
mainfrom
feat/prefer-spread

Conversation

@sentry-junior

@sentry-junior sentry-junior Bot commented Oct 29, 2026

Copy link
Copy Markdown
Contributor

Replaces #1721. That PR standardized the CLI on Array.from(x). After looking into why the ecosystem defaults to spread, the evidence points the other way, so this enables Oxlint's built-in unicorn/prefer-spread at the root and applies it across the repo.

Why spread

  • It's stricter. TypeScript rejects [...x] when x isn't iterable, and the runtime throws. Array.from accepts any { length } object (it type-checks as unknown[]) and quietly returns [] for numbers and ArrayBuffers. Sindre Sorhus gave this as the reason for the rule (unicorn#120): array-likes are a legacy quirk, and iterables are the real protocol.
  • Performance is the same. V8 7.2 added fast paths for spreading arrays, strings, Sets, and Map keys/values, and Array.from(iterable) reuses them (v8.dev). On Node 22 here, the two are within noise for arrays, Sets, map.values(), and strings at 10, 1k, and 100k elements. Results for Map entries and generators are mixed, but small.
  • The Array.from(x, fn) advantage doesn't show up. Airbnb 4.6 recommends it to avoid an intermediate array. On Node 22, Array.from(set, fn) measured 2–4× slower than [...set].map(fn), because the mapping form skips the fast path. The rule still allows it, but it buys nothing.
  • One syntax for copying and combining. [...a], [...a, ...b], and [...path, key] read as one family. ref(cli): Prefer Array.from() over spread to copy iterables #1721's rule left a split where a lone spread became Array.from but a multi-part spread stayed.
  • Less to maintain. The rule is built into Oxlint and already autofixes. There's no custom rule. The unicorn rule leaves alone the cases where Array.from is the right tool: Array.from({ length: n }, fn), array-likes, and any call with a map function.

What changed

  • .oxlintrc.json: "unicorn/prefer-spread": "error" in the root rules.
  • The autofix rewrote single-argument Array.from(x) and array .slice() copies across the CLI, MCP core, Cloudflare, docs-mcp, test client, and scripts (75 sites). The rest were fixed by hand:
    • Array .concat() (5 sites): now [...a, ...b] and [...path, key].
    • String#concat in apps/docs-mcp (false positive): now a template literal.
  • Strings: turning Array.from(str) into [...str] trips the already-enabled typescript/no-misused-spread. The two forms behave the same at runtime, so the lint split is only syntax. I went by what each site means:
    • Code-unit checks (charCodeAt predicates in issue-linking, ci-projects.mjs, and the delta-upgrade test helpers) now use split(""). That's typescript-eslint's recommended form, and it matches the charCodeAt logic exactly.
    • Three places count code points on purpose: banner width, the pixel-canvas glyph check, and the help test. They keep the spread with a reasoned oxlint-disable-next-line.
  • Uint8Array assertions in infrastructure.test.ts use subarray(), so [...] still produces an Array. no-useless-spread assumes .slice() returns an Array.
  • The convention is documented in docs/contributing/coding-guidelines.md.

Verification

  • pnpm run lint (oxlint, oxfmt, ast-grep): clean
  • pnpm run tsc (all packages): clean
  • Tests:
    • mcp-core: 1,885 passed
    • mcp-cloudflare: 443 passed
    • docs-mcp: 49 passed
    • mcp-test-client: 80 passed
    • test:ci-projects: 94 passed
    • CLI unit: 11,758 passed, 6 failed. The 6 are the same permission tests that fail on clean main in this sandbox (completions, code-scanner, file-changes, telemetry, upgrade).

via BYK.

--

View Junior Session in Sentry

Enable unicorn/prefer-spread at the root and apply it everywhere.
Single-argument Array.from(x), array .slice() copies, and array
.concat() now use spread.

Spread is the stricter choice. TypeScript rejects spreading a
non-iterable, and so does the runtime. Array.from accepts any
{ length } object and returns [] for numbers and ArrayBuffers.
V8 runs both forms through the same fast path. Array.from(x, fn)
and Array.from({ length }) are still allowed.

Turning Array.from(string) into a spread trips
typescript/no-misused-spread. Code-unit checks now use split(""),
which matches their charCodeAt logic. The three places that count
code points on purpose keep the spread with a reasoned suppression.

String#concat in docs-mcp was a false positive and is now a template
literal. Uint8Array test assertions use subarray() so the copy
stays an Array.

Co-Authored-By: BYK <burak.kaya@sentry.io>
@vercel

vercel Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
sentry-local Ready Ready Preview Oct 29, 2026 2:04pm UTC

Request Review

@BYK
BYK merged commit 66d19a1 into main Oct 29, 2026
40 checks passed
@BYK
BYK deleted the feat/prefer-spread branch October 29, 2026 18:27

This branch was successfully deployed

2 active deployments
Preview — 5d5c1af7 Deployed Oct 29, 2026 by vercel[bot]
Actions — 5d5c1af7 Deployed Oct 29, 2026 by sentry-junior[bot] via eval #1397
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk: low PR risk score: low

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant