diff --git a/.oxlintrc.json b/.oxlintrc.json index 695f7bab6..c769b754c 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -37,7 +37,8 @@ "allow": ["URL", "URLSearchParams"] } ], - "jsx-a11y/interactive-supports-focus": "off" + "jsx-a11y/interactive-supports-focus": "off", + "unicorn/prefer-spread": "error" }, "overrides": [ { diff --git a/apps/docs-mcp/src/agent/query-agent.ts b/apps/docs-mcp/src/agent/query-agent.ts index 809e6908f..6e3af3806 100644 --- a/apps/docs-mcp/src/agent/query-agent.ts +++ b/apps/docs-mcp/src/agent/query-agent.ts @@ -52,10 +52,10 @@ function canonicalizeDocsUrl(value: string): string { url.search = ""; url.hash = ""; - url.pathname = url.pathname + const pathname = url.pathname .replace(TRAILING_MD_RE, "") - .replace(TRAILING_SLASHES_RE, "") - .concat("/"); + .replace(TRAILING_SLASHES_RE, ""); + url.pathname = `${pathname}/`; return url.toString(); } diff --git a/docs/contributing/coding-guidelines.md b/docs/contributing/coding-guidelines.md index 33a808fa7..f0f5f40ab 100644 --- a/docs/contributing/coding-guidelines.md +++ b/docs/contributing/coding-guidelines.md @@ -35,6 +35,14 @@ product-specific implementation patterns. write `return await` in async functions so callers stay in async stack traces. Type-aware correctness rules that still have a backlog are turned off at the root; #1573 tracks enabling them. +- Copy and combine iterables with spread: `[...set]`, `[...a, ...b]`, + `[...path, key]`. `unicorn/prefer-spread` enforces this repo-wide and + autofixes `Array.from(x)` and `.slice()`. Spread rejects non-iterables at + compile time and at runtime, where `Array.from` accepts any `{ length }` + object. Keep `Array.from` for array-likes and `Array.from({ length: n }, fn)`. + To walk a string's UTF-16 code units, use `split("")`. Spread a string only + when you mean code points, and suppress `typescript/no-misused-spread` with + the reason. - Don't pass an async callback to `.catch()`. Use `try`/`await`/`catch`, or a wrapper such as `withDashboardErrors(ctx, fn)`. The `no-async-catch-callback` ast-grep rule enforces this. diff --git a/packages/cli/script/generate-docs-sections.ts b/packages/cli/script/generate-docs-sections.ts index 5e09c7b15..209dc8f6d 100644 --- a/packages/cli/script/generate-docs-sections.ts +++ b/packages/cli/script/generate-docs-sections.ts @@ -162,9 +162,7 @@ function getSubcommandLabel(route: RouteInfo): string { const parts = cmd.path.split(" "); return parts.at(-1) ?? route.name; }); - return Array.from(new Set(raw)) - .sort((a, b) => a.localeCompare(b)) - .join(", "); + return [...new Set(raw)].sort((a, b) => a.localeCompare(b)).join(", "); } /** diff --git a/packages/cli/src/commands/explore.ts b/packages/cli/src/commands/explore.ts index 2efd67cb2..3b8a40e1a 100644 --- a/packages/cli/src/commands/explore.ts +++ b/packages/cli/src/commands/explore.ts @@ -103,7 +103,7 @@ const PAGINATION_KEY = "explore"; const DATASETS = buildDatasetParser( ["errors", "spans", "tracemetrics", "logs", "replays"], - Array.from(LEGACY_TRANSACTION_DATASETS), + [...LEGACY_TRANSACTION_DATASETS], ); // --------------------------------------------------------------------------- diff --git a/packages/cli/src/commands/issue/merge.ts b/packages/cli/src/commands/issue/merge.ts index 94b7cc180..c59387409 100644 --- a/packages/cli/src/commands/issue/merge.ts +++ b/packages/cli/src/commands/issue/merge.ts @@ -118,7 +118,7 @@ async function resolveAllIssues( const orgs = new Set(resolved.map((r) => r.org as string)); if (orgs.size > 1) { throw new ValidationError( - `Cannot merge issues across organizations (${Array.from(orgs).join(", ")}).\n\n` + + `Cannot merge issues across organizations (${[...orgs].join(", ")}).\n\n` + "All issues must belong to the same organization.", ); } diff --git a/packages/cli/src/commands/span/list.ts b/packages/cli/src/commands/span/list.ts index 6b73a079a..7982278f5 100644 --- a/packages/cli/src/commands/span/list.ts +++ b/packages/cli/src/commands/span/list.ts @@ -143,7 +143,7 @@ function extractExtraApiFields( expanded.delete(known); } - return expanded.size > 0 ? Array.from(expanded) : undefined; + return expanded.size > 0 ? [...expanded] : undefined; } /** Accepted values for the --sort flag (matches trace list) */ diff --git a/packages/cli/src/commands/trace/view.ts b/packages/cli/src/commands/trace/view.ts index 3aa370eff..0d32210b2 100644 --- a/packages/cli/src/commands/trace/view.ts +++ b/packages/cli/src/commands/trace/view.ts @@ -200,7 +200,7 @@ const PROGRESS_THRESHOLD = 20; export function flattenSpanTree(spans: TraceSpan[]): TraceSpan[] { const result: TraceSpan[] = []; // Reverse so the first child is popped first (depth-first order) - const stack = Array.from(spans).reverse(); + const stack = [...spans].reverse(); let span = stack.pop(); while (span) { result.push(span); diff --git a/packages/cli/src/lib/api/attribute-pagination.ts b/packages/cli/src/lib/api/attribute-pagination.ts index f6a2860d3..dacd47d73 100644 --- a/packages/cli/src/lib/api/attribute-pagination.ts +++ b/packages/cli/src/lib/api/attribute-pagination.ts @@ -39,7 +39,7 @@ function encodeBookmark( return ( CURSOR_PREFIX + Buffer.from( - JSON.stringify({ cursor: cursor ?? null, seen: Array.from(seen) }), + JSON.stringify({ cursor: cursor ?? null, seen: [...seen] }), ).toString("base64url") ); } @@ -126,7 +126,7 @@ export async function fetchAttributeTypePage( pending || next ? encodeBookmark( next, - Array.from(seen).filter((key) => keysOnPage.has(key)), + [...seen].filter((key) => keysOnPage.has(key)), ) : undefined, }; @@ -170,6 +170,6 @@ export function attributeCursorAfter( const keysOnPage = page.pageKeys.get(pageId(replay)) ?? new Set(); return encodeBookmark( replay, - Array.from(seen).filter((key) => keysOnPage.has(key)), + [...seen].filter((key) => keysOnPage.has(key)), ); } diff --git a/packages/cli/src/lib/api/issues.ts b/packages/cli/src/lib/api/issues.ts index 40a4132c1..654744594 100644 --- a/packages/cli/src/lib/api/issues.ts +++ b/packages/cli/src/lib/api/issues.ts @@ -167,9 +167,7 @@ export async function listIssuesPaginated( ): Promise> { // Prefer the API's repeated `project` parameter. A lone slug retains the // search-query fallback for callers that have not resolved project identity. - const projects = options.projects?.length - ? Array.from(options.projects) - : undefined; + const projects = options.projects?.length ? [...options.projects] : undefined; const projectFilter = projects === undefined && projectSlug ? `project:${projectSlug}` : ""; const fullQuery = [projectFilter, options.query].filter(Boolean).join(" "); diff --git a/packages/cli/src/lib/api/traces.ts b/packages/cli/src/lib/api/traces.ts index 5bdbb81cd..e7a67e362 100644 --- a/packages/cli/src/lib/api/traces.ts +++ b/packages/cli/src/lib/api/traces.ts @@ -718,7 +718,7 @@ async function fetchSpansPage( const fullQuery = [projectFilter, options.query].filter(Boolean).join(" "); const fields = options.extraFields?.length - ? SPAN_FIELDS.concat(options.extraFields) + ? [...SPAN_FIELDS, ...options.extraFields] : SPAN_FIELDS; let projectParam: string | undefined; diff --git a/packages/cli/src/lib/attribute-search.ts b/packages/cli/src/lib/attribute-search.ts index 48f6ab799..3031e214f 100644 --- a/packages/cli/src/lib/attribute-search.ts +++ b/packages/cli/src/lib/attribute-search.ts @@ -76,7 +76,7 @@ export function parseAttributeFilter( ); } return { - query: hasTerms.size > 0 ? Array.from(hasTerms).join(" ") : undefined, + query: hasTerms.size > 0 ? [...hasTerms].join(" ") : undefined, name, }; } @@ -109,5 +109,5 @@ export function parseAttributeTypes( } types.add(value); } - return types.size > 0 ? Array.from(types) : undefined; + return types.size > 0 ? [...types] : undefined; } diff --git a/packages/cli/src/lib/banner.ts b/packages/cli/src/lib/banner.ts index 3554468f5..26da1d7a1 100644 --- a/packages/cli/src/lib/banner.ts +++ b/packages/cli/src/lib/banner.ts @@ -101,7 +101,8 @@ export const FULL_BANNER_LINES: BannerLine[] = colorize(BANNER_ROWS_FULL); export function bannerLinesWidth(lines: readonly BannerLine[]): number { let max = 0; for (const { content } of lines) { - const width = Array.from(content).length; + // oxlint-disable-next-line typescript/no-misused-spread -- width is measured in code points + const width = [...content].length; if (width > max) { max = width; } diff --git a/packages/cli/src/lib/complete.ts b/packages/cli/src/lib/complete.ts index ab89246ab..59dd28bc4 100644 --- a/packages/cli/src/lib/complete.ts +++ b/packages/cli/src/lib/complete.ts @@ -320,7 +320,7 @@ export function completeOrgSlugs(partial: string, suffix = ""): Completion[] { } const nameMap = new Map(orgs.map((o) => [o.slug, o.name])); - const matched = fuzzyMatch(partial, Array.from(nameMap.keys()), { + const matched = fuzzyMatch(partial, [...nameMap.keys()], { mode: "search", }); @@ -402,7 +402,7 @@ export function completeProjectSlugs( } const nameMap = new Map(projects.map((p) => [p.projectSlug, p.projectName])); - const matched = fuzzyMatch(projectPartial, Array.from(nameMap.keys()), { + const matched = fuzzyMatch(projectPartial, [...nameMap.keys()], { mode: "search", }); diff --git a/packages/cli/src/lib/formatters/json.ts b/packages/cli/src/lib/formatters/json.ts index 2b795c8ed..3fd925922 100644 --- a/packages/cli/src/lib/formatters/json.ts +++ b/packages/cli/src/lib/formatters/json.ts @@ -221,7 +221,7 @@ export function parseFieldsList(input: string): string[] { const { parse } = _require( "../../generated/field-list-grammar.js", ) as typeof import("../../generated/field-list-grammar.js"); - return Array.from(new Set(parse(input))); + return [...new Set(parse(input))]; } /** diff --git a/packages/cli/src/lib/formatters/pixel-canvas.ts b/packages/cli/src/lib/formatters/pixel-canvas.ts index 1ffceaa4f..793e21ecf 100644 --- a/packages/cli/src/lib/formatters/pixel-canvas.ts +++ b/packages/cli/src/lib/formatters/pixel-canvas.ts @@ -216,7 +216,8 @@ function* normalizeBitmapText(text: string): Iterable { .replace(COMBINING_MARK_RE, ""); if ( decomposed.length > 0 && - Array.from(decomposed).every((candidate) => hasSpleenGlyph(candidate)) + // oxlint-disable-next-line typescript/no-misused-spread -- glyphs are looked up per code point + [...decomposed].every((candidate) => hasSpleenGlyph(candidate)) ) { yield* decomposed; continue; diff --git a/packages/cli/src/lib/hex-id-recovery.ts b/packages/cli/src/lib/hex-id-recovery.ts index e6d7dadfe..be644f974 100644 --- a/packages/cli/src/lib/hex-id-recovery.ts +++ b/packages/cli/src/lib/hex-id-recovery.ts @@ -445,7 +445,7 @@ export type FuzzyLookupAdapter = (ctx: LookupContext) => Promise; /** Deduplicate while preserving order, then filter by prefix + suffix. */ function filterByCandidate(ids: string[], candidate: HexCandidate): string[] { - const unique = Array.from(new Set(ids)); + const unique = [...new Set(ids)]; return unique.filter( (id) => id.startsWith(candidate.prefix) && diff --git a/packages/cli/src/lib/init/clack-utils.ts b/packages/cli/src/lib/init/clack-utils.ts index 25c103033..a644c70fc 100644 --- a/packages/cli/src/lib/init/clack-utils.ts +++ b/packages/cli/src/lib/init/clack-utils.ts @@ -124,7 +124,7 @@ const FEATURE_DISPLAY_ORDER = [ /** Sort features into the canonical order used by summaries and final output. */ export function sortFeatures(features: string[]): string[] { - return features.slice().sort((a, b) => { + return [...features].sort((a, b) => { const ai = FEATURE_DISPLAY_ORDER.indexOf(a); const bi = FEATURE_DISPLAY_ORDER.indexOf(b); return ( diff --git a/packages/cli/src/lib/init/interactive.ts b/packages/cli/src/lib/init/interactive.ts index 6c040de37..c481990e2 100644 --- a/packages/cli/src/lib/init/interactive.ts +++ b/packages/cli/src/lib/init/interactive.ts @@ -56,7 +56,7 @@ const FEATURE_SELECTION_CONTEXT = "Based on your project, these features are available to set up."; function sortFeatureOptions(features: string[]): string[] { - return features.slice().sort((a, b) => { + return [...features].sort((a, b) => { const rankDifference = (DEFAULT_FEATURE_RANK.get(a) ?? Number.MAX_SAFE_INTEGER) - (DEFAULT_FEATURE_RANK.get(b) ?? Number.MAX_SAFE_INTEGER); diff --git a/packages/cli/src/lib/init/wizard-runner.ts b/packages/cli/src/lib/init/wizard-runner.ts index bc162d794..8f1e252ee 100644 --- a/packages/cli/src/lib/init/wizard-runner.ts +++ b/packages/cli/src/lib/init/wizard-runner.ts @@ -452,7 +452,7 @@ async function handleSuspendedStep( "done", ]); const history = stepHistory.get(stepId) ?? []; - const previousPhases = history.slice(); + const previousPhases = [...history]; history.push(summarizeToolPhaseForHistory(payload, phase, toolResult)); stepHistory.set(stepId, history); diff --git a/packages/cli/src/lib/metrics-transform.ts b/packages/cli/src/lib/metrics-transform.ts index bb3fd712c..ae6b34edd 100644 --- a/packages/cli/src/lib/metrics-transform.ts +++ b/packages/cli/src/lib/metrics-transform.ts @@ -56,9 +56,9 @@ export function resolveMetricField( const match = metrics.find((m) => m.name === metricName); if (!match) { - const candidateNames = Array.from( - new Set(metrics.map((m) => m.name).filter((n) => n.length > 0)), - ); + const candidateNames = [ + ...new Set(metrics.map((m) => m.name).filter((n) => n.length > 0)), + ]; const suggestions = fuzzyMatch(metricName, candidateNames, { maxResults: 5, }); diff --git a/packages/cli/src/lib/resolve-target.ts b/packages/cli/src/lib/resolve-target.ts index b8abff9ed..b3307918d 100644 --- a/packages/cli/src/lib/resolve-target.ts +++ b/packages/cli/src/lib/resolve-target.ts @@ -799,7 +799,7 @@ async function findSimilarProjectsAcrossOrgs( // Deduplicate slugs so fuzzyMatch doesn't waste slots on the same // project appearing in multiple orgs. - const uniqueSlugs = Array.from(new Set(allProjects.map((p) => p.slug))); + const uniqueSlugs = [...new Set(allProjects.map((p) => p.slug))]; const slugMatches = fuzzyMatch(slug, uniqueSlugs, { maxResults: 5 }); // When a display name is provided (input contained spaces), also @@ -816,7 +816,7 @@ async function findSimilarProjectsAcrossOrgs( nameToSlug.set(p.name, p.slug); } } - const uniqueNames = Array.from(nameToSlug.keys()); + const uniqueNames = [...nameToSlug.keys()]; const matchedNames = fuzzyMatch(displayName, uniqueNames, { maxResults: 5, }); @@ -826,9 +826,7 @@ async function findSimilarProjectsAcrossOrgs( } // Merge slug matches and name matches, preferring slug matches. - const mergedSlugs = Array.from( - new Set(slugMatches.concat(nameMatchedSlugs)), - ); + const mergedSlugs = [...new Set([...slugMatches, ...nameMatchedSlugs])]; // Expand matched slugs back to org-qualified entries (may include // the same slug from multiple orgs — that's correct for suggestions). diff --git a/packages/cli/test/lib/api/infrastructure.test.ts b/packages/cli/test/lib/api/infrastructure.test.ts index 8e187c099..4b070e74d 100644 --- a/packages/cli/test/lib/api/infrastructure.test.ts +++ b/packages/cli/test/lib/api/infrastructure.test.ts @@ -778,11 +778,11 @@ describe("rawApiRequest binary handling", () => { expect(result.status).toBe(200); expect(result.body).toBeInstanceOf(Uint8Array); const body = result.body as Uint8Array; - expect(Array.from(body.slice(0, 8))).toEqual([ + expect([...body.subarray(0, 8)]).toEqual([ 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, ]); // Must NOT contain the UTF-8 replacement sequence EF BF BD - expect(Array.from(body.slice(0, 3))).not.toEqual([0xef, 0xbf, 0xbd]); + expect([...body.subarray(0, 3)]).not.toEqual([0xef, 0xbf, 0xbd]); expect(body).toEqual(pngBytes); }); diff --git a/packages/cli/test/lib/db/sqlite.test.ts b/packages/cli/test/lib/db/sqlite.test.ts index eaa8198f7..91f49f4ef 100644 --- a/packages/cli/test/lib/db/sqlite.test.ts +++ b/packages/cli/test/lib/db/sqlite.test.ts @@ -169,7 +169,7 @@ describe.each(DRIVERS)("sqlite adapter [%s driver]", (kind) => { const row = db.query("SELECT data FROM t").get() as { data: Uint8Array; }; - expect(Array.from(row.data)).toEqual([1, 2, 3, 255]); + expect([...row.data]).toEqual([1, 2, 3, 255]); db.close(); }); diff --git a/packages/cli/test/lib/delta-upgrade.mocked.test.ts b/packages/cli/test/lib/delta-upgrade.mocked.test.ts index a6e6b2e22..0131a923a 100644 --- a/packages/cli/test/lib/delta-upgrade.mocked.test.ts +++ b/packages/cli/test/lib/delta-upgrade.mocked.test.ts @@ -74,7 +74,8 @@ function mockFetch( const BINARY_NAME = getPlatformBinaryName(); function versionHex(version: string): string { - return Array.from(version) + return version + .split("") .map((c) => c.charCodeAt(0).toString(16).padStart(2, "0")) .join(""); } diff --git a/packages/cli/test/lib/delta-upgrade.test.ts b/packages/cli/test/lib/delta-upgrade.test.ts index 898f4bc88..3d56f96c0 100644 --- a/packages/cli/test/lib/delta-upgrade.test.ts +++ b/packages/cli/test/lib/delta-upgrade.test.ts @@ -219,7 +219,8 @@ describe("extractStableChain", () => { * Converts each char to its hex code to produce valid [0-9a-f]+ output. */ function versionToHex(version: string): string { - return Array.from(version) + return version + .split("") .map((c) => c.charCodeAt(0).toString(16).padStart(2, "0")) .join(""); } @@ -943,7 +944,8 @@ describe("resolveStableChain", () => { * Reuses the same approach as the extractStableChain tests above. */ function versionHex(version: string): string { - return Array.from(version) + return version + .split("") .map((c) => c.charCodeAt(0).toString(16).padStart(2, "0")) .join(""); } diff --git a/packages/cli/test/lib/dsn/code-scanner.property.test.ts b/packages/cli/test/lib/dsn/code-scanner.property.test.ts index 8847b9c52..4226bb7d6 100644 --- a/packages/cli/test/lib/dsn/code-scanner.property.test.ts +++ b/packages/cli/test/lib/dsn/code-scanner.property.test.ts @@ -49,7 +49,7 @@ const contentArb = array(constantFrom(...BENIGN_CHARS), { const DSN_RE = /https?:\/\/[a-z0-9]+(?::[a-z0-9]+)?@[a-z0-9.-]+(?:\.[a-z]+|:[0-9]+)\/\d+/gi; function referenceMatches(content: string): string[] { - return Array.from(new Set(Array.from(content.matchAll(DSN_RE), (m) => m[0]))); + return [...new Set(Array.from(content.matchAll(DSN_RE), (m) => m[0]))]; } describe("property: extractDsnsFromContent fast-path invariance", () => { diff --git a/packages/cli/test/lib/formatters/output.test.ts b/packages/cli/test/lib/formatters/output.test.ts index 6501af33c..a091593b9 100644 --- a/packages/cli/test/lib/formatters/output.test.ts +++ b/packages/cli/test/lib/formatters/output.test.ts @@ -125,7 +125,7 @@ describe("renderCommandOutput", () => { render(w, png, config, { json: false }); expect(w.chunks).toHaveLength(1); expect(w.chunks[0]).toBeInstanceOf(Uint8Array); - expect(Array.from(w.bytes)).toEqual([ + expect([...w.bytes]).toEqual([ 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, ]); // No trailing newline added by the text formatter path @@ -139,7 +139,7 @@ describe("renderCommandOutput", () => { }; const bytes = new Uint8Array([0xff, 0x00, 0x80]); render(w, bytes, config, { json: true }); - expect(Array.from(w.bytes)).toEqual([0xff, 0x00, 0x80]); + expect([...w.bytes]).toEqual([0xff, 0x00, 0x80]); }); test("jsonExclude strips fields from JSON output", () => { diff --git a/packages/cli/test/lib/help.test.ts b/packages/cli/test/lib/help.test.ts index 160e30460..5e8e3d7f6 100644 --- a/packages/cli/test/lib/help.test.ts +++ b/packages/cli/test/lib/help.test.ts @@ -24,7 +24,8 @@ function maxLineWidth(banner: string): number { 0, ...stripAnsi(banner) .split("\n") - .map((line) => Array.from(line).length), + // oxlint-disable-next-line typescript/no-misused-spread -- width is measured in code points + .map((line) => [...line].length), ); } diff --git a/packages/cli/test/lib/init/ui/wizard-store.test.ts b/packages/cli/test/lib/init/ui/wizard-store.test.ts index a24d166e7..0d6f5aee2 100644 --- a/packages/cli/test/lib/init/ui/wizard-store.test.ts +++ b/packages/cli/test/lib/init/ui/wizard-store.test.ts @@ -24,9 +24,9 @@ describe("WizardStore step progress", () => { test("pre-populates the checklist from CHECKLIST_VISIBLE_STEPS", () => { const store = new WizardStore(); const snapshot = store.getSnapshot(); - expect(snapshot.steps.map((entry) => entry.id)).toEqual( - CHECKLIST_VISIBLE_STEPS.slice(), - ); + expect(snapshot.steps.map((entry) => entry.id)).toEqual([ + ...CHECKLIST_VISIBLE_STEPS, + ]); expect(snapshot.steps.every((entry) => entry.status === "pending")).toBe( true, ); diff --git a/packages/cli/test/lib/sdk-invoke.test.ts b/packages/cli/test/lib/sdk-invoke.test.ts index 39c54d1bc..d8fcfc52f 100644 --- a/packages/cli/test/lib/sdk-invoke.test.ts +++ b/packages/cli/test/lib/sdk-invoke.test.ts @@ -237,7 +237,7 @@ describe("parseOutput: binary output", () => { ]); const result = parseOutput(undefined, [png]); expect(result).toBeInstanceOf(Uint8Array); - expect(Array.from(result)).toEqual([ + expect([...result]).toEqual([ 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, ]); }); @@ -247,7 +247,7 @@ describe("parseOutput: binary output", () => { "AB", new Uint8Array([0x00, 0xff]), ]); - expect(Array.from(result)).toEqual([0x41, 0x42, 0x00, 0xff]); + expect([...result]).toEqual([0x41, 0x42, 0x00, 0xff]); }); test("still JSON-parses textual chunks", () => { diff --git a/packages/cli/test/lib/telemetry/zstd-transport.test.ts b/packages/cli/test/lib/telemetry/zstd-transport.test.ts index 4ec12ba64..bf1499486 100644 --- a/packages/cli/test/lib/telemetry/zstd-transport.test.ts +++ b/packages/cli/test/lib/telemetry/zstd-transport.test.ts @@ -365,14 +365,14 @@ describe("normalizeBody", () => { const src = new Uint8Array([1, 2, 3, 4, 5]); const buf = normalizeBody(src); expect(buf.length).toBe(5); - expect(Array.from(buf)).toEqual([1, 2, 3, 4, 5]); + expect([...buf]).toEqual([1, 2, 3, 4, 5]); }); test("Uint8Array with non-zero byteOffset", () => { const backing = new Uint8Array([9, 9, 1, 2, 3, 9, 9]); const view = new Uint8Array(backing.buffer, 2, 3); const buf = normalizeBody(view); - expect(Array.from(buf)).toEqual([1, 2, 3]); + expect([...buf]).toEqual([1, 2, 3]); }); test("empty string", () => { diff --git a/packages/cli/test/lib/wasm/helpers.ts b/packages/cli/test/lib/wasm/helpers.ts index 1b948a596..c1379731b 100644 --- a/packages/cli/test/lib/wasm/helpers.ts +++ b/packages/cli/test/lib/wasm/helpers.ts @@ -38,9 +38,7 @@ export function fromHex(hex: string): Uint8Array { /** Render bytes as lowercase hex. */ export function toHex(bytes: Uint8Array): string { - return Array.from(bytes) - .map((byte) => byte.toString(16).padStart(2, "0")) - .join(""); + return [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join(""); } /** Join byte ranges into one buffer. */ diff --git a/packages/mcp-cloudflare/src/client/components/chat/chat.tsx b/packages/mcp-cloudflare/src/client/components/chat/chat.tsx index 2e4c57dc0..51a2949ee 100644 --- a/packages/mcp-cloudflare/src/client/components/chat/chat.tsx +++ b/packages/mcp-cloudflare/src/client/components/chat/chat.tsx @@ -117,8 +117,7 @@ export function Chat({ isOpen, onClose, onLogout }: ChatProps) { const definitionsByName = new Map( TOOL_DEFINITIONS.map((t) => [t.name, t]), ); - const detailed = metadata.tools - .slice() + const detailed = [...metadata.tools] .sort((a, b) => a.localeCompare(b)) .map((name) => { const def = definitionsByName.get(name); diff --git a/packages/mcp-cloudflare/src/server/lib/approval-dialog.ts b/packages/mcp-cloudflare/src/server/lib/approval-dialog.ts index ab46f290e..a90dacb14 100644 --- a/packages/mcp-cloudflare/src/server/lib/approval-dialog.ts +++ b/packages/mcp-cloudflare/src/server/lib/approval-dialog.ts @@ -64,7 +64,7 @@ async function signData(key: CryptoKey, data: string): Promise { enc.encode(data), ); // Convert ArrayBuffer to hex string - return Array.from(new Uint8Array(signatureBuffer)) + return [...new Uint8Array(signatureBuffer)] .map((b) => b.toString(16).padStart(2, "0")) .join(""); } @@ -215,14 +215,14 @@ async function getApprovedClientsFromCookie( function filterApprovableSkillIds(input: unknown): string[] { if (!Array.isArray(input)) return []; - return Array.from( - new Set( + return [ + ...new Set( input.filter( (skill): skill is string => typeof skill === "string" && APPROVABLE_SKILL_IDS.has(skill), ), ), - ); + ]; } /** @@ -1130,9 +1130,9 @@ export async function parseRedirectApproval( (await getApprovedClientsFromCookie(cookieHeader, cookieSecret)) || []; // Add the newly approved client ID (avoid duplicates) - const updatedApprovedClients = Array.from( - new Set([...existingApprovedClients, clientId]), - ); + const updatedApprovedClients = [ + ...new Set([...existingApprovedClients, clientId]), + ]; // Sign the updated approval list const payload = JSON.stringify(updatedApprovedClients); diff --git a/packages/mcp-cloudflare/src/server/lib/mcp-handler.ts b/packages/mcp-cloudflare/src/server/lib/mcp-handler.ts index d31978e62..05775ec24 100644 --- a/packages/mcp-cloudflare/src/server/lib/mcp-handler.ts +++ b/packages/mcp-cloudflare/src/server/lib/mcp-handler.ts @@ -344,7 +344,7 @@ async function handleAuthenticatedMcpRequest( } if (auth.kind === "oauth") { - const grantedSkills = Array.from(auth.grantedSkills).sort((a, b) => + const grantedSkills = [...auth.grantedSkills].sort((a, b) => a.localeCompare(b), ); for (const skill of grantedSkills) { diff --git a/packages/mcp-cloudflare/src/server/oauth/routes/callback.ts b/packages/mcp-cloudflare/src/server/oauth/routes/callback.ts index 0a7698769..f2e997520 100644 --- a/packages/mcp-cloudflare/src/server/oauth/routes/callback.ts +++ b/packages/mcp-cloudflare/src/server/oauth/routes/callback.ts @@ -51,7 +51,7 @@ function parseUpstreamScopes(scope: string | undefined): string[] | undefined { if (typeof scope !== "string") { return undefined; } - return Array.from(new Set(scope.split(/\s+/).filter(Boolean))).sort(); + return [...new Set(scope.split(/\s+/).filter(Boolean))].sort(); } function getUpstreamUserLabel(payload: { @@ -141,7 +141,7 @@ export default new Hono<{ Bindings: Env }>().get("/", async (c) => { }, }, extra: { - queryKeys: Array.from(new URL(c.req.url).searchParams.keys()), + queryKeys: [...new URL(c.req.url).searchParams.keys()], }, loggerScope: ["cloudflare", "oauth", "callback"], } as const; @@ -168,7 +168,7 @@ export default new Hono<{ Bindings: Env }>().get("/", async (c) => { }, }, extra: { - queryKeys: Array.from(new URL(c.req.url).searchParams.keys()), + queryKeys: [...new URL(c.req.url).searchParams.keys()], }, loggerScope: ["cloudflare", "oauth", "callback"], }); @@ -288,7 +288,7 @@ export default new Hono<{ Bindings: Env }>().get("/", async (c) => { const grantedScopes = await getScopesForSkills(validSkills); // Convert valid skills Set to array for OAuth props - const grantedSkills = Array.from(validSkills); + const grantedSkills = [...validSkills]; const resource = (oauthReqInfo as AuthRequestWithSkills).resource; const resourceScope = parseResourceMcpConstraints( @@ -334,7 +334,7 @@ export default new Hono<{ Bindings: Env }>().get("/", async (c) => { scope: oauthReqInfo.scope.join(" "), // Scopes derived from skills - for backward compatibility with old MCP clients // that don't support grantedSkills and only understand grantedScopes - grantedScopes: Array.from(grantedScopes), + grantedScopes: [...grantedScopes], // Scopes Sentry actually granted on the upstream token (from the token // response), which can differ from the skill-derived grantedScopes upstreamScopes: parseUpstreamScopes(payload.scope), diff --git a/packages/mcp-cloudflare/src/server/oauth/state.ts b/packages/mcp-cloudflare/src/server/oauth/state.ts index d36230ede..02b1be76b 100644 --- a/packages/mcp-cloudflare/src/server/oauth/state.ts +++ b/packages/mcp-cloudflare/src/server/oauth/state.ts @@ -56,7 +56,7 @@ async function signHex(key: CryptoKey, data: string): Promise { key, enc.encode(data), ); - return Array.from(new Uint8Array(signatureBuffer)) + return [...new Uint8Array(signatureBuffer)] .map((b) => b.toString(16).padStart(2, "0")) .join(""); } diff --git a/packages/mcp-cloudflare/src/server/routes/chat.ts b/packages/mcp-cloudflare/src/server/routes/chat.ts index fc62a0e24..71a28dc02 100644 --- a/packages/mcp-cloudflare/src/server/routes/chat.ts +++ b/packages/mcp-cloudflare/src/server/routes/chat.ts @@ -182,7 +182,7 @@ export default new Hono<{ Bindings: Env }>().post("/", async (c) => { const encoder = new TextEncoder(); const data = encoder.encode(accessToken); const hashBuffer = await crypto.subtle.digest("SHA-256", data); - const hashArray = Array.from(new Uint8Array(hashBuffer)); + const hashArray = [...new Uint8Array(hashBuffer)]; const hashHex = hashArray .map((b) => b.toString(16).padStart(2, "0")) .join(""); diff --git a/packages/mcp-cloudflare/src/server/routes/search.ts b/packages/mcp-cloudflare/src/server/routes/search.ts index b8ab104b7..662c4e2c2 100644 --- a/packages/mcp-cloudflare/src/server/routes/search.ts +++ b/packages/mcp-cloudflare/src/server/routes/search.ts @@ -38,7 +38,7 @@ export default new Hono<{ Bindings: Env }>().post("/", async (c) => { const encoder = new TextEncoder(); const data = encoder.encode(clientIP); const hashBuffer = await crypto.subtle.digest("SHA-256", data); - const hashArray = Array.from(new Uint8Array(hashBuffer)); + const hashArray = [...new Uint8Array(hashBuffer)]; const hashHex = hashArray .map((b) => b.toString(16).padStart(2, "0")) .join(""); diff --git a/packages/mcp-cloudflare/src/server/utils/rate-limiter.ts b/packages/mcp-cloudflare/src/server/utils/rate-limiter.ts index 2e0116a36..f7334d236 100644 --- a/packages/mcp-cloudflare/src/server/utils/rate-limiter.ts +++ b/packages/mcp-cloudflare/src/server/utils/rate-limiter.ts @@ -29,7 +29,7 @@ async function hashValue(value: string): Promise { const encoder = new TextEncoder(); const data = encoder.encode(value); const hashBuffer = await crypto.subtle.digest("SHA-256", data); - const hashArray = Array.from(new Uint8Array(hashBuffer)); + const hashArray = [...new Uint8Array(hashBuffer)]; const hashHex = hashArray .map((b) => b.toString(16).padStart(2, "0")) .join(""); diff --git a/packages/mcp-core/scripts/validate-skills-mapping.ts b/packages/mcp-core/scripts/validate-skills-mapping.ts index 4723e74ab..b9ec7eb54 100755 --- a/packages/mcp-core/scripts/validate-skills-mapping.ts +++ b/packages/mcp-core/scripts/validate-skills-mapping.ts @@ -119,7 +119,7 @@ function displayResults(results: ValidationResult[]): void { for (const result of results) { const skill = SKILLS[result.skill]; - const scopeList = Array.from(result.requiredScopes).sort(); + const scopeList = [...result.requiredScopes].sort(); console.log( `\n${colors.bright}${colors.cyan}Skill: ${skill.name}${colors.reset}`, diff --git a/packages/mcp-core/src/internal/agents/callEmbeddedAgent.ts b/packages/mcp-core/src/internal/agents/callEmbeddedAgent.ts index 69b145183..02800bb86 100644 --- a/packages/mcp-core/src/internal/agents/callEmbeddedAgent.ts +++ b/packages/mcp-core/src/internal/agents/callEmbeddedAgent.ts @@ -370,7 +370,7 @@ function extractJsonCandidates(text: string): string[] { // Strategy 1: Extract from markdown code blocks (```json ... ``` or ``` ... ```) const codeBlockRegex = /```(?:json)?\s*\n?([\s\S]*?)\n?```/g; - const allMatches = Array.from(text.matchAll(codeBlockRegex)); + const allMatches = [...text.matchAll(codeBlockRegex)]; for (const match of allMatches) { candidates.add(match[1].trim()); } @@ -380,7 +380,7 @@ function extractJsonCandidates(text: string): string[] { candidates.add(jsonObject); } - return Array.from(candidates); + return [...candidates]; } /** diff --git a/packages/mcp-core/src/internal/tool-helpers/validate-region-url.ts b/packages/mcp-core/src/internal/tool-helpers/validate-region-url.ts index ddf4292a0..2d3c5b57a 100644 --- a/packages/mcp-core/src/internal/tool-helpers/validate-region-url.ts +++ b/packages/mcp-core/src/internal/tool-helpers/validate-region-url.ts @@ -60,7 +60,7 @@ export function validateRegionUrl(regionUrl: string, baseHost: string): string { !isSaaSTrustOrigin(regionUrl) ) { throw new UserInputError( - `Invalid regionUrl. The domain '${regionHost}' is not allowed. Allowed domains are: ${Array.from(SENTRY_ALLOWED_REGION_DOMAINS).join(", ")}`, + `Invalid regionUrl. The domain '${regionHost}' is not allowed. Allowed domains are: ${[...SENTRY_ALLOWED_REGION_DOMAINS].join(", ")}`, ); } diff --git a/packages/mcp-core/src/internal/user-formatting.ts b/packages/mcp-core/src/internal/user-formatting.ts index e7d88994f..ef3865e15 100644 --- a/packages/mcp-core/src/internal/user-formatting.ts +++ b/packages/mcp-core/src/internal/user-formatting.ts @@ -20,5 +20,5 @@ export function formatUserGeoSummary(value: unknown): string | null { return null; } - return Array.from(new Set(parts)).join(", "); + return [...new Set(parts)].join(", "); } diff --git a/packages/mcp-core/src/server.ts b/packages/mcp-core/src/server.ts index fd2d3e831..fc8df3d30 100644 --- a/packages/mcp-core/src/server.ts +++ b/packages/mcp-core/src/server.ts @@ -216,9 +216,7 @@ function configureServer({ const grantedSkills: Set | undefined = context.grantedSkills ? new Set(context.grantedSkills) : undefined; - const grantedSkillIds = grantedSkills - ? Array.from(grantedSkills).sort() - : undefined; + const grantedSkillIds = grantedSkills ? [...grantedSkills].sort() : undefined; const availableTools = getAvailableTools({ tools: registry, context, diff --git a/packages/mcp-core/src/tools/catalog-runtime/schema.ts b/packages/mcp-core/src/tools/catalog-runtime/schema.ts index 5b682667d..a028c1906 100644 --- a/packages/mcp-core/src/tools/catalog-runtime/schema.ts +++ b/packages/mcp-core/src/tools/catalog-runtime/schema.ts @@ -45,7 +45,7 @@ export function findIncompatibleJsonSchemaUnions( return schema.flatMap((item, index) => findIncompatibleJsonSchemaUnions( item, - path.concat(String(index)), + [...path, String(index)], insideUnion, ), ); @@ -61,7 +61,7 @@ export function findIncompatibleJsonSchemaUnions( continue; } - const nextPath = path.concat(keyword); + const nextPath = [...path, keyword]; const pathLabel = nextPath.join("."); if (atRoot) { @@ -88,7 +88,7 @@ export function findIncompatibleJsonSchemaUnions( continue; } violations.push( - ...findIncompatibleJsonSchemaUnions(value, path.concat(key), insideUnion), + ...findIncompatibleJsonSchemaUnions(value, [...path, key], insideUnion), ); } diff --git a/packages/mcp-core/src/tools/catalog/find-dashboards.ts b/packages/mcp-core/src/tools/catalog/find-dashboards.ts index 1952730df..12032f719 100644 --- a/packages/mcp-core/src/tools/catalog/find-dashboards.ts +++ b/packages/mcp-core/src/tools/catalog/find-dashboards.ts @@ -327,7 +327,7 @@ export default defineTool({ id: String(dashboard.id), title: dashboard.title, widgetCount: dashboard.widgetPreview.length, - widgetTypes: Array.from(new Set(dashboard.widgetDisplay)), + widgetTypes: [...new Set(dashboard.widgetDisplay)], projects: dashboard.projects.map(String), environments: dashboard.environment, createdBy: dashboard.createdBy diff --git a/packages/mcp-core/src/tools/support/issue-linking/index.ts b/packages/mcp-core/src/tools/support/issue-linking/index.ts index ab5397ade..e3e0f7cb2 100644 --- a/packages/mcp-core/src/tools/support/issue-linking/index.ts +++ b/packages/mcp-core/src/tools/support/issue-linking/index.ts @@ -23,9 +23,11 @@ function parseUrl(value: string): URL { !["https:", "http:"].includes(url.protocol) || url.username || url.password || - Array.from(value).some( - (character) => character.charCodeAt(0) <= 32 || character === "\\", - ) + value + .split("") + .some( + (character) => character.charCodeAt(0) <= 32 || character === "\\", + ) ) { throw new Error("Invalid URL"); } diff --git a/packages/mcp-core/src/tools/support/profile/analyzer.ts b/packages/mcp-core/src/tools/support/profile/analyzer.ts index 6aab73db3..e80112eb9 100644 --- a/packages/mcp-core/src/tools/support/profile/analyzer.ts +++ b/packages/mcp-core/src/tools/support/profile/analyzer.ts @@ -139,7 +139,7 @@ export function analyzeHotPathsFromFlamegraph( } // Convert merged stacks to hot paths - const hotPaths = Array.from(mergedStacks.values()).map((merged) => { + const hotPaths = [...mergedStacks.values()].map((merged) => { // Reconstruct call stack - keep all frames, filter during formatting // Store leaf-to-root for efficient merging (reverse the API order) const callStack = reconstructCallStack( diff --git a/packages/mcp-core/src/tools/support/profile/formatter.ts b/packages/mcp-core/src/tools/support/profile/formatter.ts index fd2ed99c9..2a8bb5f0b 100644 --- a/packages/mcp-core/src/tools/support/profile/formatter.ts +++ b/packages/mcp-core/src/tools/support/profile/formatter.ts @@ -621,7 +621,7 @@ function formatTopFramesByOccurrence( } } - const sortedFrames = Array.from(frameCounts.entries()) + const sortedFrames = [...frameCounts.entries()] .filter(([frameIdx]) => { const frame = profile.frames[frameIdx]; return frame !== undefined && (!options.focusOnUserCode || frame.in_app); diff --git a/packages/mcp-core/src/tools/support/search-events/search.ts b/packages/mcp-core/src/tools/support/search-events/search.ts index 029a02406..5d01854c9 100644 --- a/packages/mcp-core/src/tools/support/search-events/search.ts +++ b/packages/mcp-core/src/tools/support/search-events/search.ts @@ -131,7 +131,7 @@ function buildRequestFields( return dataset !== "replays" && isMetricsDataset(dataset) && !isAggregateQuery(fields) - ? Array.from(new Set([...fields, ...TRACE_METRICS_SAMPLE_IDENTITY_FIELDS])) + ? [...new Set([...fields, ...TRACE_METRICS_SAMPLE_IDENTITY_FIELDS])] : fields; } diff --git a/packages/mcp-core/src/tools/support/snapshots/handlers.ts b/packages/mcp-core/src/tools/support/snapshots/handlers.ts index 5bf1361c4..856168f31 100644 --- a/packages/mcp-core/src/tools/support/snapshots/handlers.ts +++ b/packages/mcp-core/src/tools/support/snapshots/handlers.ts @@ -426,9 +426,9 @@ export async function fetchSnapshotSummary( if (hasDiffs) { sections.push("\n## Changes\n"); - const sortedChanged = changed - .slice() - .sort((left, right) => (right.diff ?? -1) - (left.diff ?? -1)); + const sortedChanged = [...changed].sort( + (left, right) => (right.diff ?? -1) - (left.diff ?? -1), + ); sections.push( ...renderTreeSections([ diff --git a/packages/mcp-core/src/utils/url-utils.ts b/packages/mcp-core/src/utils/url-utils.ts index 41bb1f64e..6c78abc65 100644 --- a/packages/mcp-core/src/utils/url-utils.ts +++ b/packages/mcp-core/src/utils/url-utils.ts @@ -59,9 +59,9 @@ function deriveSelectedFields( return fields; } - return Array.from( - new Set([...(groupByFields ?? []), ...(aggregateFunctions ?? [])]), - ); + return [ + ...new Set([...(groupByFields ?? []), ...(aggregateFunctions ?? [])]), + ]; } function getSentryWebBaseUrl( diff --git a/packages/mcp-test-client/src/index.ts b/packages/mcp-test-client/src/index.ts index 282a6c4a5..b90d44839 100644 --- a/packages/mcp-test-client/src/index.ts +++ b/packages/mcp-test-client/src/index.ts @@ -156,7 +156,7 @@ program try { if (listToolsOnly) { - const toolNames = Array.from(connection.tools.keys()).sort(); + const toolNames = [...connection.tools.keys()].sort(); logInfo("Available tools", `${toolNames.length} tools`); for (const toolName of toolNames) { console.log(chalk.gray(` - ${toolName}`)); diff --git a/scripts/ci-projects.mjs b/scripts/ci-projects.mjs index 357629593..26d4c2f75 100644 --- a/scripts/ci-projects.mjs +++ b/scripts/ci-projects.mjs @@ -70,7 +70,7 @@ function optionalScript(scripts, configured, candidates) { function safeRelativePath(value, field) { const hasUnsafeDelimiter = typeof value === "string" && - Array.from(value).some((character) => { + value.split("").some((character) => { const codeUnit = character.charCodeAt(0); return ( codeUnit <= 0x1f ||