Skip to content

ref(cli): Prefer Array.from() over spread to copy iterables - #1721

Open
sentry-junior[bot] wants to merge 2 commits into
mainfrom
feat/cli-prefer-array-from
Open

sentry-junior[bot] wants to merge 2 commits into
mainfrom
feat/cli-prefer-array-from

Conversation

@sentry-junior

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

Copy link
Copy Markdown
Contributor

The CLI copied iterables both ways: about 71 [...x] against 12 Array.from(x) in src/, and more in tests. No existing rule settles it. Oxlint's unicorn/prefer-spread pushes the other way and isn't enabled.

This adds sentry-cli/prefer-array-from to the CLI Oxlint plugin. It flags an array literal whose only element is a spread ([...set], [...map.values()], [...(a ?? b)]) and autofixes it to Array.from(...). It leaves these alone:

  • spreads that build an array from several parts: [...a, ...b], [...items, tail]
  • call-argument spreads: fn(...args)
  • object spreads

The autofix is applied across packages/cli/src and packages/cli/test (≈180 sites, 101 files). tsc stays clean, so no tuple types were relied on. The convention is documented under "Copying Iterables" in packages/cli/AGENTS.md.

This is a consistency rule, not a performance fix. Array.from reads more clearly as a conversion, also accepts array-likes, and takes a map callback. Spread is just as correct for iterables.

Verification

  • node --test scripts/cli-lint-rules.test.mjs: 57 passed, including new valid and invalid cases with fix output
  • pnpm run lint (oxlint, oxfmt, ast-grep): clean
  • tsc --noEmit in packages/cli: clean
  • CLI unit tests: 11,732 passed, 6 failed. The same 6 fail on clean main in this sandbox. They are all permission tests (completions, code-scanner, file-changes, telemetry, upgrade), so they're environment failures and not caused by this diff.

via BYK.

--

View Junior Session in Sentry

Add the sentry-cli/prefer-array-from lint rule. It flags array
literals whose only element is a spread, such as [...set], and
autofixes them to Array.from(set). Spreads that build arrays from
several parts, call arguments, and object spreads are left alone.

Apply the autofix across CLI src and tests and document the
convention in packages/cli/AGENTS.md.

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 12:44pm UTC

Request Review

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit a24f451. Configure here.

Comment thread packages/cli/lint-rules/cli-oxlint-plugin.js Outdated
@sentry-junior

sentry-junior Bot commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #1724. Research showed spread is the better default: it's stricter about non-iterables, performs the same on V8, and unicorn/prefer-spread is built into Oxlint. Details are in #1724.

BYK added a commit that referenced this pull request Oct 29, 2026
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 `ArrayBuffer`s. Sindre Sorhus gave this as the reason for
the rule
([unicorn#120](sindresorhus/eslint-plugin-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](https://v8.dev/blog/spread-elements)). 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. #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`).

<!-- junior-request-attribution:start -->
via **BYK**.
<!-- junior-request-attribution:end -->

<!-- junior-session-footer:start -->
<!-- junior-conversation-id:local%3Aweb%3A7fba2603229556152deec3d2 -->

--

[View Junior Session in
Sentry](https://sentry.sentry.io/explore/conversations/local%3Aweb%3A7fba2603229556152deec3d2/?project=4510944073809921)

<!-- junior-session-footer:end -->

Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com>
Co-authored-by: BYK <burak.kaya@sentry.io>

This branch was successfully deployed

1 active deployment
Preview — d408298f Deployed Oct 29, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant