From 5b795fb7eeb168868ac1d64f338f653f41b3b647 Mon Sep 17 00:00:00 2001 From: MK Date: Wed, 4 Nov 2026 22:40:48 +0800 Subject: [PATCH 01/21] perf(lint): benchmark the experimental tsrs-lint backend --- bench/tsrs-lint.md | 26 +++++++++++++++ bench/tsrs-lint.ts | 81 ++++++++++++++++++++++++++++++++++++++++++++++ docs/guide/lint.md | 11 +++++++ 3 files changed, 118 insertions(+) create mode 100644 bench/tsrs-lint.md create mode 100644 bench/tsrs-lint.ts diff --git a/bench/tsrs-lint.md b/bench/tsrs-lint.md new file mode 100644 index 0000000000..9c643ebf3d --- /dev/null +++ b/bench/tsrs-lint.md @@ -0,0 +1,26 @@ +# Type-aware linting with tsrs-lint + +On the Vite+ workspace at `a32a8cec2` with the benchmark script added, the experimental backend saved 0.51 seconds per lint run with full type checking. Both backends analyzed 385 files with 174 rules, reported zero diagnostics, and exited successfully on every run. + +| `vp lint` workload | Bundled tsgolint | tsrs-lint | Wall time saved | +| ------------------------------------------- | ---------------: | --------: | --------------: | +| Type-aware rules | 1.089 s | 0.822 s | 24.5% | +| Type-aware rules and TypeScript diagnostics | 1.340 s | 0.830 s | 38.1% | + +For the full `vp check` command, the median fell from 1.852 s to 1.335 s (27.9%). This command also checked formatting on 632 files and used its default 16 threads. + +Measured on an Apple M4 Max with macOS 27.0.1, Node.js 24.22.2, Vite+ 1.5.0, Oxlint 1.91.0, oxlint-tsgolint 7.0.2005, and tsrs 0.21.0-ts7.1.0-beta. The benchmark used the published Vite+ 1.5.0 CLI artifacts and the workspace's locked dependencies. Each sample started a fresh local CLI process and included config loading, JS plugins, the backend launcher, and diagnostic reporting. The `vp lint` runs used four Oxlint threads. Both backends used their default settings. + +The table shows the median of ten runs per backend after one warm-up each. The runner reverses the backend order between pairs and compares the exit code, file and rule counts, and sorted JSON diagnostics for every run. In a separate failing fixture, both backends reported `typescript/no-floating-promises` and `TS2322` at the same locations through `vp lint` and `vp check`. + +## Reproduce + +Start in a built Vite+ checkout. Install the experimental backend under the ignored `tmp` directory to keep it separate from workspace dependencies. + +```sh +npm install --prefix ./tmp/tsrs-lint --no-save --package-lock=false tsrs@0.21.0-ts7.1.0-beta +export OXLINT_TSGOLINT_PATH="$PWD/tmp/tsrs-lint/node_modules/.bin/tsrs-lint" +node bench/tsrs-lint.ts --type-check +``` + +For the rules-only row, temporarily set `lint.options.typeCheck` to `false` in the root `vite.config.ts`, run `node bench/tsrs-lint.ts` without `--type-check`, then restore the setting. The benchmark always enables `--type-aware` and prints the individual samples in seconds. diff --git a/bench/tsrs-lint.ts b/bench/tsrs-lint.ts new file mode 100644 index 0000000000..7c9088d805 --- /dev/null +++ b/bench/tsrs-lint.ts @@ -0,0 +1,81 @@ +// Run from the workspace root with OXLINT_TSGOLINT_PATH pointing to tsrs-lint. +// Additional arguments (e.g. --type-check or file paths) go to both vp lint runs. +import assert from 'node:assert/strict'; +import { spawnSync } from 'node:child_process'; +import { fileURLToPath } from 'node:url'; + +const { OXLINT_TSGOLINT_PATH: tsrs, ...baselineEnv } = process.env; +if (!tsrs) { + throw new Error('Set OXLINT_TSGOLINT_PATH to the tsrs-lint executable.'); +} +const vp = fileURLToPath(new URL('../packages/cli/bin/vp', import.meta.url)); +const args = [ + vp, + 'lint', + '--type-aware', + '--threads', + '4', + '--format', + 'json', + ...process.argv.slice(2), +]; + +function run(env: NodeJS.ProcessEnv) { + const start = performance.now(); + const { status, stdout, stderr, error } = spawnSync(process.execPath, args, { + env, + encoding: 'utf8', + maxBuffer: 64 * 1024 * 1024, + }); + const seconds = (performance.now() - start) / 1000; + if (error) { + throw error; + } + if ((status !== 0 && status !== 1) || !stdout.trimStart().startsWith('{')) { + throw new Error(`vp lint exited with ${status}\n${stderr}\n${stdout}`); + } + const report = JSON.parse(stdout) as { + diagnostics: unknown[]; + number_of_files: number; + number_of_rules: number; + }; + assert.ok(report.number_of_files > 0, 'Expected vp lint to analyze files'); + return { + seconds, + result: { + status, + files: report.number_of_files, + rules: report.number_of_rules, + diagnostics: report.diagnostics.map((d) => JSON.stringify(d)).toSorted(), + }, + }; +} + +const backends = [ + { name: 'tsgolint', env: baselineEnv, samples: [] as number[] }, + { + name: 'tsrs-lint', + env: { ...baselineEnv, OXLINT_TSGOLINT_PATH: tsrs }, + samples: [] as number[], + }, +]; +const [expected, candidate] = backends.map(({ env }) => run(env).result); +assert.deepEqual(candidate, expected); +const pairs = 10; +for (let pair = 0; pair < pairs; pair++) { + for (const backend of pair % 2 === 0 ? backends : backends.toReversed()) { + const { result, seconds } = run(backend.env); + assert.deepEqual(result, expected); + backend.samples.push(seconds); + } +} +console.log( + `${expected.files} files, ${expected.rules} rules, ${expected.diagnostics.length} diagnostics, exit ${expected.status}`, +); +for (const { name, samples } of backends) { + const sorted = samples.toSorted((a, b) => a - b); + const median = (sorted[pairs / 2 - 1] + sorted[pairs / 2]) / 2; + console.log( + `${name} median ${median.toFixed(3)} s [${samples.map((s) => s.toFixed(3)).join(', ')}]`, + ); +} diff --git a/docs/guide/lint.md b/docs/guide/lint.md index 211008821e..d2f7444545 100644 --- a/docs/guide/lint.md +++ b/docs/guide/lint.md @@ -51,6 +51,17 @@ We recommend enabling both `typeAware` and `typeCheck` in the `lint` block: This path is powered by [tsgolint](https://github.com/oxc-project/tsgolint) on top of the TypeScript 7 (aka TypeScript Go) toolchain. It gives Oxlint access to type information and allows type checking directly via `vp lint` and `vp check`. +### Try the experimental Rust backend + +To try the Rust backend, install `tsrs` in your project and point Oxlint at the `tsrs-lint` executable. Run these commands from the project root. + +```sh +vp add -D tsrs +OXLINT_TSGOLINT_PATH="$PWD/node_modules/.bin/tsrs-lint" vp lint --type-aware --type-check +``` + +The same environment variable works with `vp check` when type-aware linting is enabled. Omit it to use the bundled tsgolint again. + ## JS Plugins If you are migrating from ESLint and still depend on a few critical JavaScript-based ESLint plugins, Oxlint has [JS plugin support](https://oxc.rs/docs/guide/usage/linter/js-plugins) that can help you keep those plugins running while you complete the migration. From 10f0bc95f60b71a65ff118780809bd4dd3343236 Mon Sep 17 00:00:00 2001 From: MK Date: Thu, 5 Nov 2026 15:12:44 +0800 Subject: [PATCH 02/21] perf(lint): use tsrs as the experimental default backend --- bench/tsrs-lint.md | 2 +- bench/tsrs-lint.ts | 18 +++- crates/vp_global_cli/src/commands/version.rs | 43 ++++++++- docs/guide/check.md | 4 +- docs/guide/lint.md | 14 +-- packages/cli/package.json | 3 +- .../cli/src/__tests__/resolve-lint.spec.ts | 27 +++--- packages/cli/src/__tests__/toolchain.spec.ts | 2 +- packages/cli/src/__tests__/versions.spec.ts | 2 +- packages/cli/src/resolve-lint.ts | 13 +-- ...{tsgolint-path.ts => lint-backend-path.ts} | 38 ++++---- packages/cli/src/version.ts | 2 +- packages/cli/toolchain.config.json | 20 ++-- pnpm-lock.yaml | 94 ++++++++++++++++++- pnpm-workspace.yaml | 3 + 15 files changed, 215 insertions(+), 70 deletions(-) rename packages/cli/src/utils/{tsgolint-path.ts => lint-backend-path.ts} (50%) diff --git a/bench/tsrs-lint.md b/bench/tsrs-lint.md index 9c643ebf3d..6543ba2418 100644 --- a/bench/tsrs-lint.md +++ b/bench/tsrs-lint.md @@ -15,7 +15,7 @@ The table shows the median of ten runs per backend after one warm-up each. The r ## Reproduce -Start in a built Vite+ checkout. Install the experimental backend under the ignored `tmp` directory to keep it separate from workspace dependencies. +Start in a built Vite+ checkout. The runner explicitly selects the `oxlint-tsgolint` development dependency for the Go baseline, even when the CLI defaults to tsrs. Install the candidate backend under the ignored `tmp` directory to compare a specific version. ```sh npm install --prefix ./tmp/tsrs-lint --no-save --package-lock=false tsrs@0.21.0-ts7.1.0-beta diff --git a/bench/tsrs-lint.ts b/bench/tsrs-lint.ts index 7c9088d805..5dfb3a42da 100644 --- a/bench/tsrs-lint.ts +++ b/bench/tsrs-lint.ts @@ -2,13 +2,29 @@ // Additional arguments (e.g. --type-check or file paths) go to both vp lint runs. import assert from 'node:assert/strict'; import { spawnSync } from 'node:child_process'; +import { existsSync } from 'node:fs'; +import { createRequire } from 'node:module'; import { fileURLToPath } from 'node:url'; -const { OXLINT_TSGOLINT_PATH: tsrs, ...baselineEnv } = process.env; +const { OXLINT_TSGOLINT_PATH: tsrs, ...env } = process.env; if (!tsrs) { throw new Error('Set OXLINT_TSGOLINT_PATH to the tsrs-lint executable.'); } const vp = fileURLToPath(new URL('../packages/cli/bin/vp', import.meta.url)); +const require = createRequire(new URL('../packages/cli/package.json', import.meta.url)); +const tsgolint = + process.platform === 'win32' + ? ['tsgolint.exe', 'tsgolint.cmd'] + .map((bin) => + fileURLToPath(new URL(`../packages/cli/node_modules/.bin/${bin}`, import.meta.url)), + ) + .find(existsSync) + : require.resolve('oxlint-tsgolint/bin/tsgolint'); +assert.ok(tsgolint, 'Install workspace development dependencies to get the Go baseline.'); +const baselineEnv = { + ...env, + OXLINT_TSGOLINT_PATH: tsgolint, +}; const args = [ vp, 'lint', diff --git a/crates/vp_global_cli/src/commands/version.rs b/crates/vp_global_cli/src/commands/version.rs index c35976392e..6ecb78969b 100644 --- a/crates/vp_global_cli/src/commands/version.rs +++ b/crates/vp_global_cli/src/commands/version.rs @@ -123,6 +123,16 @@ fn resolve_tool_version( } } +fn tool_spec_for_manifest(tool: ToolSpec, manifest: Option<&vp_toolchain::Manifest>) -> ToolSpec { + if tool.id == "oxlint-tsgolint" + && manifest.is_some_and(|manifest| vp_toolchain::node_by_id(manifest, "tsrs").is_some()) + { + ToolSpec { id: "tsrs", package_name: "tsrs", bundled_version_key: None } + } else { + tool + } +} + fn print_stdout(message: &str) { vp_shared::output::print_and_flush(&mut io::stdout().lock(), message); } @@ -189,7 +199,8 @@ pub async fn execute(cwd: AbsolutePathBuf) -> Result { let tool_rows = TOOL_SPECS .iter() .map(|tool| { - let version = resolve_tool_version(local.as_ref(), manifest.as_ref(), *tool); + let tool = tool_spec_for_manifest(*tool, manifest.as_ref()); + let version = resolve_tool_version(local.as_ref(), manifest.as_ref(), tool); (tool.id, format_version(version)) }) .collect::>(); @@ -233,9 +244,12 @@ mod tests { #[cfg(unix)] use std::{fs, path::Path}; + use super::{ + TOOL_SPECS, detect_system_node_version, format_version, resolve_tool_version, + tool_spec_for_manifest, + }; #[cfg(unix)] - use super::{TOOL_SPECS, find_local_vite_plus, read_toolchain_manifest, resolve_tool_version}; - use super::{detect_system_node_version, format_version}; + use super::{find_local_vite_plus, read_toolchain_manifest}; #[cfg(unix)] fn symlink_dir(src: &Path, dst: &Path) { @@ -248,6 +262,29 @@ mod tests { assert_eq!(format_version(None), "Not found"); } + #[test] + fn reports_the_type_aware_backend_from_the_local_manifest() { + for (id, version) in [("tsrs", "0.21.0-ts7.1.0-beta"), ("oxlint-tsgolint", "7.0.2005")] { + let manifest = serde_json::from_value::(serde_json::json!({ + "schemaVersion": 1, + "nodes": [{ + "id": id, + "name": id, + "version": version, + "kind": "tool", + "delivery": ["dependency"], + "aliases": [] + }], + "edges": [] + })) + .unwrap(); + let tool = tool_spec_for_manifest(TOOL_SPECS[5], Some(&manifest)); + assert_eq!(tool.id, id); + assert_eq!(resolve_tool_version(None, Some(&manifest), tool).as_deref(), Some(version)); + } + assert_eq!(tool_spec_for_manifest(TOOL_SPECS[5], None).id, "oxlint-tsgolint"); + } + // The spawned `node` inherits this process's environment, so hold // temp_env's lock with PATH/VP_HOME re-pinned to their current values: // no concurrent with_vars scope can swap them mid-test and make a vp shim diff --git a/docs/guide/check.md b/docs/guide/check.md index a80ae27496..8c0778caf6 100644 --- a/docs/guide/check.md +++ b/docs/guide/check.md @@ -4,9 +4,9 @@ ## Overview -`vp check` is the default command for fast static checks in Vite+. It brings together formatting through [Oxfmt](https://oxc.rs/docs/guide/usage/formatter.html), linting through [Oxlint](https://oxc.rs/docs/guide/usage/linter.html), and TypeScript type checks through [tsgolint](https://github.com/oxc-project/tsgolint). By merging all of these tasks into a single command, `vp check` is faster than running formatting, linting, and type checking as separate tools in separate commands. +`vp check` is the default command for fast static checks in Vite+. It brings together formatting through [Oxfmt](https://oxc.rs/docs/guide/usage/formatter.html), linting through [Oxlint](https://oxc.rs/docs/guide/usage/linter.html), and TypeScript type checks through [tsrs](https://github.com/oxc-project/tsrs) in this experimental build. By merging all of these tasks into a single command, `vp check` is faster than running formatting, linting, and type checking as separate tools in separate commands. -When both `typeAware` and `typeCheck` are enabled in the `lint.options` block in `vite.config.ts`, `vp check` also runs TypeScript type checks through the Oxlint type-aware path powered by the TypeScript Go toolchain and [tsgolint](https://github.com/oxc-project/tsgolint). `vp create` and `vp migrate` enable both `typeAware` and `typeCheck` by default. +When both `typeAware` and `typeCheck` are enabled in the `lint.options` block in `vite.config.ts`, `vp check` also runs TypeScript type checks through the Oxlint type-aware path powered by `tsrs-lint`. `vp create` and `vp migrate` enable both `typeAware` and `typeCheck` by default. We recommend turning both `typeAware` and `typeCheck` on so `vp check` becomes the single command for static checks during development. diff --git a/docs/guide/lint.md b/docs/guide/lint.md index d2f7444545..69538b86d3 100644 --- a/docs/guide/lint.md +++ b/docs/guide/lint.md @@ -49,18 +49,20 @@ We recommend enabling both `typeAware` and `typeCheck` in the `lint` block: - `typeAware: true` enables rules that require TypeScript type information - `typeCheck: true` enables full type checking during linting -This path is powered by [tsgolint](https://github.com/oxc-project/tsgolint) on top of the TypeScript 7 (aka TypeScript Go) toolchain. It gives Oxlint access to type information and allows type checking directly via `vp lint` and `vp check`. +This experimental build uses [tsrs](https://github.com/oxc-project/tsrs), a Rust port of the TypeScript 7 compiler. Its `tsrs-lint` executable gives Oxlint access to type information and allows type checking directly via `vp lint` and `vp check`. -### Try the experimental Rust backend +### Select another backend -To try the Rust backend, install `tsrs` in your project and point Oxlint at the `tsrs-lint` executable. Run these commands from the project root. +The bundled `tsrs-lint` runs by default. To compare it with [tsgolint](https://github.com/oxc-project/tsgolint), install the Go backend and set `OXLINT_TSGOLINT_PATH`. Run these commands from the project root. ```sh -vp add -D tsrs -OXLINT_TSGOLINT_PATH="$PWD/node_modules/.bin/tsrs-lint" vp lint --type-aware --type-check +vp add -D oxlint-tsgolint +OXLINT_TSGOLINT_PATH="$PWD/node_modules/.bin/tsgolint" vp lint --type-aware --type-check ``` -The same environment variable works with `vp check` when type-aware linting is enabled. Omit it to use the bundled tsgolint again. +The same environment variable works with `vp check` when type-aware linting is enabled. Omit it to use the bundled tsrs again. You can also point it at another `tsrs-lint` installation. + +The pinned `tsrs` release does not provide a Windows ARM64 binary. This experimental replacement does not support that platform yet. ## JS Plugins diff --git a/packages/cli/package.json b/packages/cli/package.json index bbefe97f75..1f7950851e 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -364,7 +364,7 @@ "@vitest/utils": "catalog:", "oxfmt": "catalog:", "oxlint": "catalog:", - "oxlint-tsgolint": "catalog:", + "tsrs": "catalog:", "vite": "workspace:@voidzero-dev/vite-plus-core@*", "vitest": "catalog:" }, @@ -388,6 +388,7 @@ "lint-staged": "catalog:", "minimatch": "catalog:", "nanotar": "catalog:", + "oxlint-tsgolint": "catalog:", "picocolors": "catalog:", "pirates": "catalog:", "tinyglobby": "catalog:", diff --git a/packages/cli/src/__tests__/resolve-lint.spec.ts b/packages/cli/src/__tests__/resolve-lint.spec.ts index c8220e9200..c5734d7895 100644 --- a/packages/cli/src/__tests__/resolve-lint.spec.ts +++ b/packages/cli/src/__tests__/resolve-lint.spec.ts @@ -2,7 +2,7 @@ import { existsSync } from 'node:fs'; import { describe, expect, it } from 'vitest'; -import { lint, resolveWindowsTsgolintExecutable } from '../resolve-lint.js'; +import { lint, resolveWindowsLintBackendExecutable } from '../resolve-lint.js'; describe('resolve-lint', () => { it('should resolve binPath and OXLINT_TSGOLINT_PATH to existing files', async () => { @@ -14,28 +14,29 @@ describe('resolve-lint', () => { `oxlint binPath should point to an existing file, got: ${result.binPath}`, ).toBe(true); - const tsgolintPath = result.envs.OXLINT_TSGOLINT_PATH; - expect(tsgolintPath).toBeTruthy(); + const backendPath = result.envs.OXLINT_TSGOLINT_PATH; + expect(backendPath).toBeTruthy(); + expect(backendPath).toMatch(/[/\\]tsrs-lint(?:\.(?:cmd|exe))?$/); expect( - existsSync(tsgolintPath), - `OXLINT_TSGOLINT_PATH should point to an existing file, got: ${tsgolintPath}`, + existsSync(backendPath), + `OXLINT_TSGOLINT_PATH should point to an existing file, got: ${backendPath}`, ).toBe(true); }); - it('should keep the Windows tsgolint executable path absolute', () => { - const tsgolintPath = - 'C:\\repo\\node_modules\\.pnpm\\vite-plus@0.1.24\\node_modules\\vite-plus\\node_modules\\.bin\\tsgolint.cmd'; - const result = resolveWindowsTsgolintExecutable( + it('should keep the Windows tsrs-lint executable path absolute', () => { + const backendPath = + 'C:\\repo\\node_modules\\.pnpm\\vite-plus@0.1.24\\node_modules\\vite-plus\\node_modules\\.bin\\tsrs-lint.cmd'; + const result = resolveWindowsLintBackendExecutable( [ - 'C:\\repo\\node_modules\\.pnpm\\vite-plus@0.1.24\\node_modules\\vite-plus\\node_modules\\.bin\\tsgolint.exe', - tsgolintPath, + 'C:\\repo\\node_modules\\.pnpm\\vite-plus@0.1.24\\node_modules\\vite-plus\\node_modules\\.bin\\tsrs-lint.exe', + backendPath, ], { - exists: (path) => path === tsgolintPath, + exists: (path) => path === backendPath, }, ); - expect(result).toBe(tsgolintPath); + expect(result).toBe(backendPath); expect(result).not.toMatch(/^\.\\/); }); }); diff --git a/packages/cli/src/__tests__/toolchain.spec.ts b/packages/cli/src/__tests__/toolchain.spec.ts index 036d1e4a63..c1dc6ab535 100644 --- a/packages/cli/src/__tests__/toolchain.spec.ts +++ b/packages/cli/src/__tests__/toolchain.spec.ts @@ -32,7 +32,7 @@ describe('toolchain export', () => { 'vitest', 'oxlint', 'oxfmt', - 'oxlint-tsgolint', + 'tsrs', 'tsdown', 'vite-task', 'oxc', diff --git a/packages/cli/src/__tests__/versions.spec.ts b/packages/cli/src/__tests__/versions.spec.ts index 646a608397..223f70785c 100644 --- a/packages/cli/src/__tests__/versions.spec.ts +++ b/packages/cli/src/__tests__/versions.spec.ts @@ -115,7 +115,7 @@ describe('versions export', () => { expect(versions.vitest).toBeTypeOf('string'); expect(versions.oxlint).toBeTypeOf('string'); expect(versions.oxfmt).toBeTypeOf('string'); - expect(versions['oxlint-tsgolint']).toBeTypeOf('string'); + expect(versions.tsrs).toBeTypeOf('string'); }); }); }); diff --git a/packages/cli/src/resolve-lint.ts b/packages/cli/src/resolve-lint.ts index f552962c4b..9b0ef98d25 100644 --- a/packages/cli/src/resolve-lint.ts +++ b/packages/cli/src/resolve-lint.ts @@ -13,10 +13,10 @@ import { dirname, join } from 'node:path'; -import { CONFIG_METADATA_ENV, DEFAULT_ENVS, resolve } from './utils/constants.ts'; -import { resolveTsgolintExecutable } from './utils/tsgolint-path.ts'; +import { CONFIG_METADATA_ENV, DEFAULT_ENVS, resolve, resolveBundled } from './utils/constants.ts'; +import { resolveLintBackendExecutable } from './utils/lint-backend-path.ts'; -export { resolveWindowsTsgolintExecutable } from './utils/tsgolint-path.ts'; +export { resolveWindowsLintBackendExecutable } from './utils/lint-backend-path.ts'; /** * Resolves the oxlint binary path and environment variables. @@ -40,16 +40,17 @@ export async function lint(): Promise<{ const oxlintMainPath = resolve('oxlint'); const oxlintPackageRoot = dirname(dirname(oxlintMainPath)); const binPath = join(oxlintPackageRoot, 'bin', 'oxlint'); - const oxlintTsgolintPath = resolveTsgolintExecutable( - resolve('oxlint-tsgolint/bin/tsgolint'), + const tsrsLintPath = resolveLintBackendExecutable( + join(dirname(resolveBundled('tsrs/package.json')), 'bin', 'tsrs-lint'), import.meta.url, + 'tsrs-lint', ); const result = { binPath, // TODO: provide envs inference API envs: { ...DEFAULT_ENVS, - OXLINT_TSGOLINT_PATH: oxlintTsgolintPath, + OXLINT_TSGOLINT_PATH: tsrsLintPath, // oxlint loads vite.config.ts only to read the `lint` block, so skip the // user's Vite plugin factory (lazyPlugins) while it evaluates the config. [CONFIG_METADATA_ENV]: '1', diff --git a/packages/cli/src/utils/tsgolint-path.ts b/packages/cli/src/utils/lint-backend-path.ts similarity index 50% rename from packages/cli/src/utils/tsgolint-path.ts rename to packages/cli/src/utils/lint-backend-path.ts index e98f1d41ca..5b7ac60571 100644 --- a/packages/cli/src/utils/tsgolint-path.ts +++ b/packages/cli/src/utils/lint-backend-path.ts @@ -2,54 +2,58 @@ import { existsSync, realpathSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; -export function resolveWindowsTsgolintExecutable( +export function resolveWindowsLintBackendExecutable( pathCandidates: string[], options: { exists: (path: string) => boolean; getRealpathCandidates?: () => string[]; }, ): string { - let oxlintTsgolintPath = pathCandidates.find((p) => options.exists(p)) ?? ''; - if (!oxlintTsgolintPath && options.getRealpathCandidates) { + let backendPath = pathCandidates.find((p) => options.exists(p)) ?? ''; + if (!backendPath && options.getRealpathCandidates) { try { - oxlintTsgolintPath = options.getRealpathCandidates().find((p) => options.exists(p)) ?? ''; + backendPath = options.getRealpathCandidates().find((p) => options.exists(p)) ?? ''; } catch { // realpath failed, fall through to default } } - if (!oxlintTsgolintPath) { + if (!backendPath) { throw new Error( - 'Unable to resolve oxlint-tsgolint executable, tried:\n' + + 'Unable to resolve type-aware lint executable, tried:\n' + pathCandidates.map((path) => `- ${path}`).join('\n'), ); } - return oxlintTsgolintPath; + return backendPath; } -export function resolveTsgolintExecutable(tsgolintBinPath: string, scriptUrl: string): string { +export function resolveLintBackendExecutable( + backendBinPath: string, + scriptUrl: string, + executableName: string, +): string { if (process.platform !== 'win32') { - return tsgolintBinPath; + return backendBinPath; } // On Windows, try .exe first (bun creates .exe), then .cmd (npm/pnpm/yarn create .cmd) const scriptDir = dirname(fileURLToPath(scriptUrl)); const localBinDir = join(scriptDir, '..', 'node_modules', '.bin'); - const oxlintTsgolintPackagePath = dirname(dirname(tsgolintBinPath)); - const projectBinDir = join(oxlintTsgolintPackagePath, '..', '.bin'); + const backendPackagePath = dirname(dirname(backendBinPath)); + const projectBinDir = join(backendPackagePath, '..', '.bin'); const pathCandidates = [ - join(localBinDir, 'tsgolint.exe'), - join(localBinDir, 'tsgolint.cmd'), - join(projectBinDir, 'tsgolint.exe'), - join(projectBinDir, 'tsgolint.cmd'), + join(localBinDir, `${executableName}.exe`), + join(localBinDir, `${executableName}.cmd`), + join(projectBinDir, `${executableName}.exe`), + join(projectBinDir, `${executableName}.cmd`), ]; - return resolveWindowsTsgolintExecutable(pathCandidates, { + return resolveWindowsLintBackendExecutable(pathCandidates, { exists: existsSync, // Bun stores packages in .bun/ cache dirs where the symlinked paths above won't match. getRealpathCandidates: () => { const realPkgDir = realpathSync(join(scriptDir, '..')); const realBinDir = join(dirname(realPkgDir), '.bin'); - return [join(realBinDir, 'tsgolint.exe'), join(realBinDir, 'tsgolint.cmd')]; + return [join(realBinDir, `${executableName}.exe`), join(realBinDir, `${executableName}.cmd`)]; }, }); } diff --git a/packages/cli/src/version.ts b/packages/cli/src/version.ts index 2a62af26b9..ac5d3f7f22 100644 --- a/packages/cli/src/version.ts +++ b/packages/cli/src/version.ts @@ -15,7 +15,7 @@ const TOOL_DISPLAY_ORDER = [ 'vitest', 'oxfmt', 'oxlint', - 'oxlint-tsgolint', + 'tsrs', 'tsdown', ] as const; diff --git a/packages/cli/toolchain.config.json b/packages/cli/toolchain.config.json index d71c335087..1a15f0148f 100644 --- a/packages/cli/toolchain.config.json +++ b/packages/cli/toolchain.config.json @@ -1,14 +1,6 @@ { "schemaVersion": 1, - "versionExportIds": [ - "vite", - "rolldown", - "vitest", - "oxfmt", - "oxlint", - "oxlint-tsgolint", - "tsdown" - ], + "versionExportIds": ["vite", "rolldown", "vitest", "oxfmt", "oxlint", "tsrs", "tsdown"], "nodes": [ { "id": "vite-plus", @@ -86,14 +78,14 @@ } }, { - "id": "oxlint-tsgolint", - "name": "oxlint-tsgolint", + "id": "tsrs", + "name": "tsrs", "kind": "tool", "delivery": ["dependency"], - "aliases": ["tsgolint"], + "aliases": ["tsrs-lint", "tsgolint", "oxlint-tsgolint"], "versionSource": { "type": "npm-dependency", - "package": "oxlint-tsgolint" + "package": "tsrs" } }, { @@ -203,7 +195,7 @@ }, { "from": "vite-plus", - "to": "oxlint-tsgolint", + "to": "tsrs", "relationship": "depends-on" }, { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 024acffdc9..19bdb38cfa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -344,6 +344,9 @@ catalogs: tsdown: specifier: ^0.23.4 version: 0.23.4 + tsrs: + specifier: '=0.21.0-ts7.1.0-beta' + version: 0.21.0-ts7.1.0-beta typescript: specifier: ~7.0.2 version: 7.0.2 @@ -463,12 +466,12 @@ importers: oxlint: specifier: 'catalog:' version: 1.93.0(oxlint-tsgolint@7.0.2006)(vite-plus@packages+cli) - oxlint-tsgolint: - specifier: 'catalog:' - version: 7.0.2006 playwright: specifier: '*' version: 1.62.1 + tsrs: + specifier: 'catalog:' + version: 0.21.0-ts7.1.0-beta vite: specifier: workspace:@voidzero-dev/vite-plus-core@* version: link:../core @@ -533,6 +536,9 @@ importers: nanotar: specifier: 'catalog:' version: 0.3.0 + oxlint-tsgolint: + specifier: 'catalog:' + version: 7.0.2005 picocolors: specifier: 'catalog:' version: 1.1.1 @@ -3576,6 +3582,52 @@ packages: peerDependencies: tsdown: 0.23.4 + '@tsrs/darwin-arm64@0.21.0-ts7.1.0-beta': + resolution: {integrity: sha512-qBHxa+LmrNu/XYZjqLXV4qQPsHzxSI+dQTISxXuxyBaxtJVDwCwGAq9rVHKgaUX3U0bsyb8FyC1JIZFu4+EqZQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [darwin] + + '@tsrs/darwin-x64@0.21.0-ts7.1.0-beta': + resolution: {integrity: sha512-7WUGrCYqz94dRpSTungOh90LL/5NhhnLQrMQqRYvmirHcp1sRlcVziJ2w+cynKwQ81/I2UajsNiKho/YSObcyA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [darwin] + + '@tsrs/linux-arm64-gnu@0.21.0-ts7.1.0-beta': + resolution: {integrity: sha512-bNWkz4lu+bOzl+sl9btE4pQ0RbLkv0Zm4FE2oKvBrt56vCnl7khcAcsqpSxBXApOkp5OwvyyY+XuOGuROBt7yg==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@tsrs/linux-arm64-musl@0.21.0-ts7.1.0-beta': + resolution: {integrity: sha512-TwvKTiOD8Fwb3HB+CrhnpYgKf7He1BobtjefTja0iGAzDtfaiGwoF06zLUdmey27kmCmxjBPybS45VRyZBkQnQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@tsrs/linux-x64-gnu@0.21.0-ts7.1.0-beta': + resolution: {integrity: sha512-0OAYWOp33xHJbY/zwowbq4IKg1aCzD98sb2ksZb/1um2l28HYhJyhMEEsNczLpUFipQDE+zVBNkFoBML/KRNvA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@tsrs/linux-x64-musl@0.21.0-ts7.1.0-beta': + resolution: {integrity: sha512-OtYmWa1tzjSt/bXxotPBrPrzlfZqz+azkkfotmuYMQfxiOgmRRm7cl022iV4lE21EOUicqp0NAnuDGDLfDlNBg==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@tsrs/win32-x64@0.21.0-ts7.1.0-beta': + resolution: {integrity: sha512-ITwz3wOx161DNwEC4qYrrRwWgQyZQR4Os/GA3g67dKml0CQ4LPWHXniCzXrMagwYyH2vBBhPYT+7o+bKDj0qtA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [win32] + '@tybys/wasm-util@0.10.3': resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} @@ -6666,6 +6718,11 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tsrs@0.21.0-ts7.1.0-beta: + resolution: {integrity: sha512-4aafVQhCMlJ9Qhjm3wT/a0o21HO7ajTDA6cSCnP4PVNU1fhQ2rTXLxJxEbhonz969Hh8s+dYOlFpaO4GoJMzDA==} + engines: {node: '>=16.20.0'} + hasBin: true + tsx@4.23.15: resolution: {integrity: sha512-Yiex1Ovn8z2xPpOWckIiysV1SSyRMY9BkLF++q0yKiDxCqRhosKfMg3janKkiLBwZ5c/YryloKwGZcrEmtwxKw==} engines: {node: '>=18.0.0'} @@ -8958,6 +9015,27 @@ snapshots: tsdown: 0.23.4(@arethetypeswrong/core@0.18.2)(@tsdown/css@0.23.4)(@tsdown/exe@0.23.4)(@typescript/native-preview@7.0.0-dev.20260707.2)(@vitejs/devtools@0.8.0(crossws@0.4.12(srvx@1.0.6))(srvx@1.0.6)(vite@packages+core))(@volar/typescript@2.4.28)(@vue/language-core@3.3.12)(oxc-resolver@11.24.2)(publint@0.3.24)(tsx@4.23.15)(typescript@7.0.2)(unplugin-unused@0.5.6)(unrun@0.3.1)(vue-tsc@3.3.12(typescript@7.0.2)) verkit: 0.5.0 + '@tsrs/darwin-arm64@0.21.0-ts7.1.0-beta': + optional: true + + '@tsrs/darwin-x64@0.21.0-ts7.1.0-beta': + optional: true + + '@tsrs/linux-arm64-gnu@0.21.0-ts7.1.0-beta': + optional: true + + '@tsrs/linux-arm64-musl@0.21.0-ts7.1.0-beta': + optional: true + + '@tsrs/linux-x64-gnu@0.21.0-ts7.1.0-beta': + optional: true + + '@tsrs/linux-x64-musl@0.21.0-ts7.1.0-beta': + optional: true + + '@tsrs/win32-x64@0.21.0-ts7.1.0-beta': + optional: true + '@tybys/wasm-util@0.10.3': dependencies: tslib: 2.8.1 @@ -12130,6 +12208,16 @@ snapshots: tslib@2.8.1: {} + tsrs@0.21.0-ts7.1.0-beta: + optionalDependencies: + '@tsrs/darwin-arm64': 0.21.0-ts7.1.0-beta + '@tsrs/darwin-x64': 0.21.0-ts7.1.0-beta + '@tsrs/linux-arm64-gnu': 0.21.0-ts7.1.0-beta + '@tsrs/linux-arm64-musl': 0.21.0-ts7.1.0-beta + '@tsrs/linux-x64-gnu': 0.21.0-ts7.1.0-beta + '@tsrs/linux-x64-musl': 0.21.0-ts7.1.0-beta + '@tsrs/win32-x64': 0.21.0-ts7.1.0-beta + tsx@4.23.15: dependencies: esbuild: 0.28.2 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index c9718053da..856e1c3b1b 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -67,6 +67,7 @@ catalog: source-map: 0.8.0 tinyglobby: ^0.2.15 tsdown: ^0.23.4 + tsrs: =0.21.0-ts7.1.0-beta typescript: ~7.0.2 valibot: 1.5.0 validate-npm-package-name: ^8.0.0 @@ -94,6 +95,7 @@ minimumReleaseAgeExclude: - '@oxlint/*' - '@rolldown/*' - '@tsdown/*' + - '@tsrs/*' - '@types/*' - '@vitejs/*' - '@vitest/*' @@ -119,6 +121,7 @@ minimumReleaseAgeExclude: - rolldown-plugin-dts - tinyexec - tsdown + - tsrs - tsx - unrun - verkit From 9d3f969b82eb4ee9afe398fdc76f7361d5710f3f Mon Sep 17 00:00:00 2001 From: MK Date: Thu, 5 Nov 2026 15:21:11 +0800 Subject: [PATCH 03/21] chore(deps): update tsrs to 0.22.0-ts7.1.0-beta --- pnpm-lock.yaml | 68 ++++++++++++++++++++++----------------------- pnpm-workspace.yaml | 2 +- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 19bdb38cfa..c0b747628f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -345,8 +345,8 @@ catalogs: specifier: ^0.23.4 version: 0.23.4 tsrs: - specifier: '=0.21.0-ts7.1.0-beta' - version: 0.21.0-ts7.1.0-beta + specifier: '=0.22.0-ts7.1.0-beta' + version: 0.22.0-ts7.1.0-beta typescript: specifier: ~7.0.2 version: 7.0.2 @@ -471,7 +471,7 @@ importers: version: 1.62.1 tsrs: specifier: 'catalog:' - version: 0.21.0-ts7.1.0-beta + version: 0.22.0-ts7.1.0-beta vite: specifier: workspace:@voidzero-dev/vite-plus-core@* version: link:../core @@ -3582,48 +3582,48 @@ packages: peerDependencies: tsdown: 0.23.4 - '@tsrs/darwin-arm64@0.21.0-ts7.1.0-beta': - resolution: {integrity: sha512-qBHxa+LmrNu/XYZjqLXV4qQPsHzxSI+dQTISxXuxyBaxtJVDwCwGAq9rVHKgaUX3U0bsyb8FyC1JIZFu4+EqZQ==} + '@tsrs/darwin-arm64@0.22.0-ts7.1.0-beta': + resolution: {integrity: sha512-mY0dMEtFxig4h5At3nByjZe8E4DTDPqqz73kkxQ6gqW5K9ym+u/VHQhtnpTF6ZOTkYyjP3bTaj5T8HcyoGVddA==} engines: {node: '>=16.20.0'} cpu: [arm64] os: [darwin] - '@tsrs/darwin-x64@0.21.0-ts7.1.0-beta': - resolution: {integrity: sha512-7WUGrCYqz94dRpSTungOh90LL/5NhhnLQrMQqRYvmirHcp1sRlcVziJ2w+cynKwQ81/I2UajsNiKho/YSObcyA==} + '@tsrs/darwin-x64@0.22.0-ts7.1.0-beta': + resolution: {integrity: sha512-dvsSnyU8rpI3OqTIH2xxjt9ThvZC52BsL6/LS69eWg7Ieq2oni1PnLels5gGTFqvHFoRbezi5Y0KY7PQ1OmK5w==} engines: {node: '>=16.20.0'} cpu: [x64] os: [darwin] - '@tsrs/linux-arm64-gnu@0.21.0-ts7.1.0-beta': - resolution: {integrity: sha512-bNWkz4lu+bOzl+sl9btE4pQ0RbLkv0Zm4FE2oKvBrt56vCnl7khcAcsqpSxBXApOkp5OwvyyY+XuOGuROBt7yg==} + '@tsrs/linux-arm64-gnu@0.22.0-ts7.1.0-beta': + resolution: {integrity: sha512-igg42LxSN/DHFqafvDqZW2JNBMH8WIwc1JE/IY4QUDOXNhZnUfDgEu9xo1QYwbgryeo5llmhRwM4CI/BQUWF1Q==} engines: {node: '>=16.20.0'} cpu: [arm64] os: [linux] libc: [glibc] - '@tsrs/linux-arm64-musl@0.21.0-ts7.1.0-beta': - resolution: {integrity: sha512-TwvKTiOD8Fwb3HB+CrhnpYgKf7He1BobtjefTja0iGAzDtfaiGwoF06zLUdmey27kmCmxjBPybS45VRyZBkQnQ==} + '@tsrs/linux-arm64-musl@0.22.0-ts7.1.0-beta': + resolution: {integrity: sha512-f0zCqMkxPof7QVwjdgA8Fb0n6BtX5MOdILH/xfS+He6gRLCoECky0ejbsgLWCpB06GMlsL1gCbDr0/tmc91APg==} engines: {node: '>=16.20.0'} cpu: [arm64] os: [linux] libc: [musl] - '@tsrs/linux-x64-gnu@0.21.0-ts7.1.0-beta': - resolution: {integrity: sha512-0OAYWOp33xHJbY/zwowbq4IKg1aCzD98sb2ksZb/1um2l28HYhJyhMEEsNczLpUFipQDE+zVBNkFoBML/KRNvA==} + '@tsrs/linux-x64-gnu@0.22.0-ts7.1.0-beta': + resolution: {integrity: sha512-/e511w7xXmxBf14paz8eMLUbmlIc33Sy1E00y1FqmnUK04FTotWl+80mNvIVX5NrlOOJgrh+B125t1CHWu/8vQ==} engines: {node: '>=16.20.0'} cpu: [x64] os: [linux] libc: [glibc] - '@tsrs/linux-x64-musl@0.21.0-ts7.1.0-beta': - resolution: {integrity: sha512-OtYmWa1tzjSt/bXxotPBrPrzlfZqz+azkkfotmuYMQfxiOgmRRm7cl022iV4lE21EOUicqp0NAnuDGDLfDlNBg==} + '@tsrs/linux-x64-musl@0.22.0-ts7.1.0-beta': + resolution: {integrity: sha512-B3aDOdS5+h77HcFep+KtOI+XmLm2G+FQPQC3KPSDjS/IG0tY9GPSc14mYnLwgO++dXluukO/oufk3J4Lo0CFjg==} engines: {node: '>=16.20.0'} cpu: [x64] os: [linux] libc: [musl] - '@tsrs/win32-x64@0.21.0-ts7.1.0-beta': - resolution: {integrity: sha512-ITwz3wOx161DNwEC4qYrrRwWgQyZQR4Os/GA3g67dKml0CQ4LPWHXniCzXrMagwYyH2vBBhPYT+7o+bKDj0qtA==} + '@tsrs/win32-x64@0.22.0-ts7.1.0-beta': + resolution: {integrity: sha512-cZ69w+wOECbU6YF5UWi15TK73eX1pgoMEHKSmUF4K8PbS7DPIh/nWMsdb3rM8Ee1SUZ9ONCx+S6myh64SRi3Bg==} engines: {node: '>=16.20.0'} cpu: [x64] os: [win32] @@ -6718,8 +6718,8 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - tsrs@0.21.0-ts7.1.0-beta: - resolution: {integrity: sha512-4aafVQhCMlJ9Qhjm3wT/a0o21HO7ajTDA6cSCnP4PVNU1fhQ2rTXLxJxEbhonz969Hh8s+dYOlFpaO4GoJMzDA==} + tsrs@0.22.0-ts7.1.0-beta: + resolution: {integrity: sha512-DEnEqQZRM3sHCV7MnQKVeTh61U2SX+0WksVlWp4eVayy0NffQ2oiCOjgH/Eye3sMPYkGwpM3tht3HtVI9NdURg==} engines: {node: '>=16.20.0'} hasBin: true @@ -9015,25 +9015,25 @@ snapshots: tsdown: 0.23.4(@arethetypeswrong/core@0.18.2)(@tsdown/css@0.23.4)(@tsdown/exe@0.23.4)(@typescript/native-preview@7.0.0-dev.20260707.2)(@vitejs/devtools@0.8.0(crossws@0.4.12(srvx@1.0.6))(srvx@1.0.6)(vite@packages+core))(@volar/typescript@2.4.28)(@vue/language-core@3.3.12)(oxc-resolver@11.24.2)(publint@0.3.24)(tsx@4.23.15)(typescript@7.0.2)(unplugin-unused@0.5.6)(unrun@0.3.1)(vue-tsc@3.3.12(typescript@7.0.2)) verkit: 0.5.0 - '@tsrs/darwin-arm64@0.21.0-ts7.1.0-beta': + '@tsrs/darwin-arm64@0.22.0-ts7.1.0-beta': optional: true - '@tsrs/darwin-x64@0.21.0-ts7.1.0-beta': + '@tsrs/darwin-x64@0.22.0-ts7.1.0-beta': optional: true - '@tsrs/linux-arm64-gnu@0.21.0-ts7.1.0-beta': + '@tsrs/linux-arm64-gnu@0.22.0-ts7.1.0-beta': optional: true - '@tsrs/linux-arm64-musl@0.21.0-ts7.1.0-beta': + '@tsrs/linux-arm64-musl@0.22.0-ts7.1.0-beta': optional: true - '@tsrs/linux-x64-gnu@0.21.0-ts7.1.0-beta': + '@tsrs/linux-x64-gnu@0.22.0-ts7.1.0-beta': optional: true - '@tsrs/linux-x64-musl@0.21.0-ts7.1.0-beta': + '@tsrs/linux-x64-musl@0.22.0-ts7.1.0-beta': optional: true - '@tsrs/win32-x64@0.21.0-ts7.1.0-beta': + '@tsrs/win32-x64@0.22.0-ts7.1.0-beta': optional: true '@tybys/wasm-util@0.10.3': @@ -12208,15 +12208,15 @@ snapshots: tslib@2.8.1: {} - tsrs@0.21.0-ts7.1.0-beta: + tsrs@0.22.0-ts7.1.0-beta: optionalDependencies: - '@tsrs/darwin-arm64': 0.21.0-ts7.1.0-beta - '@tsrs/darwin-x64': 0.21.0-ts7.1.0-beta - '@tsrs/linux-arm64-gnu': 0.21.0-ts7.1.0-beta - '@tsrs/linux-arm64-musl': 0.21.0-ts7.1.0-beta - '@tsrs/linux-x64-gnu': 0.21.0-ts7.1.0-beta - '@tsrs/linux-x64-musl': 0.21.0-ts7.1.0-beta - '@tsrs/win32-x64': 0.21.0-ts7.1.0-beta + '@tsrs/darwin-arm64': 0.22.0-ts7.1.0-beta + '@tsrs/darwin-x64': 0.22.0-ts7.1.0-beta + '@tsrs/linux-arm64-gnu': 0.22.0-ts7.1.0-beta + '@tsrs/linux-arm64-musl': 0.22.0-ts7.1.0-beta + '@tsrs/linux-x64-gnu': 0.22.0-ts7.1.0-beta + '@tsrs/linux-x64-musl': 0.22.0-ts7.1.0-beta + '@tsrs/win32-x64': 0.22.0-ts7.1.0-beta tsx@4.23.15: dependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 856e1c3b1b..5e57281df2 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -67,7 +67,7 @@ catalog: source-map: 0.8.0 tinyglobby: ^0.2.15 tsdown: ^0.23.4 - tsrs: =0.21.0-ts7.1.0-beta + tsrs: =0.22.0-ts7.1.0-beta typescript: ~7.0.2 valibot: 1.5.0 validate-npm-package-name: ^8.0.0 From 7f0991bd6af5b9062e2d3e3842de0c0d7ff094b4 Mon Sep 17 00:00:00 2001 From: MK Date: Thu, 5 Nov 2026 16:08:34 +0800 Subject: [PATCH 04/21] docs(rfc): describe the default tsrs backend --- rfcs/check-command.md | 148 +++++++++++++++++++++++--------------- rfcs/migration-command.md | 2 +- rfcs/toolchain-command.md | 67 ++++++++++------- 3 files changed, 133 insertions(+), 84 deletions(-) diff --git a/rfcs/check-command.md b/rfcs/check-command.md index dedcdf8c1b..69f83c3912 100644 --- a/rfcs/check-command.md +++ b/rfcs/check-command.md @@ -35,33 +35,42 @@ vp check # Auto-fix format and lint issues vp check --fix -vp check --fix --no-lint # Only fix formatting +vp check --fix --no-lint # Skip lint rules; configured type checks still run # Disable specific checks vp check --no-fmt vp check --no-lint -vp check --no-type-aware -vp check --no-type-check ``` ### Options -| Flag | Default | Description | -| ---------------------------------- | ------- | ------------------------------------------------------- | -| `--fix` | OFF | Auto-fix format and lint issues | -| `--fmt` / `--no-fmt` | ON | Run format check (`vp fmt --check`) | -| `--lint` / `--no-lint` | ON | Run lint check (`vp lint`) | -| `--type-aware` / `--no-type-aware` | ON | Enable type-aware lint rules (oxlint `--type-aware`) | -| `--type-check` / `--no-type-check` | ON | Enable TypeScript type checking (oxlint `--type-check`) | -| `--no-error-on-unmatched-pattern` | OFF | Do not exit with error when pattern is unmatched | +| Flag | Default | Description | +| --------------------------------- | ------- | --------------------------------------------------------- | +| `--fix` | OFF | Auto-fix formatting and lint issues | +| `--no-fmt` | OFF | Skip formatting | +| `--no-lint` | OFF | Skip lint rules; retain configured TypeScript diagnostics | +| `--quiet` | OFF | Report errors without warnings | +| `--deny-warnings` | OFF | Treat lint warnings as errors | +| `--no-error-on-unmatched-pattern` | OFF | Do not fail when no files match | -**Flag dependency:** `--type-check` requires `--type-aware` as a prerequisite. +Type-aware rules and TypeScript diagnostics follow `lint.options` in +`vite.config.ts`. `vp create` and `vp migrate` enable both settings by default: -- `--type-aware` enables lint rules that use type information (e.g., `no-floating-promises`) -- `--type-check` enables experimental TypeScript compiler-level type checking (requires type-aware) -- If `--no-type-aware` is set, `--type-check` is also implicitly disabled +```ts +export default { + lint: { + options: { + typeAware: true, + typeCheck: true, + }, + }, +}; +``` -Both are enabled by default in `vp check` to provide comprehensive static analysis. +`typeAware` enables rules that need type information, such as +`typescript/no-floating-promises`. `typeCheck` also enables TypeScript compiler +diagnostics. Set either option to `false` in the config to disable it. +`vp check` does not expose `--no-type-aware` or `--no-type-check` flags. ### File Path Arguments @@ -94,7 +103,7 @@ Commands run **sequentially** with fail-fast semantics: ``` 1. vp fmt --check (verify formatting, don't auto-fix) -2. vp lint --type-aware --type-check (lint + type checking) +2. vp lint (lint + configured type checking) ``` If any step fails, `vp check` exits immediately with a non-zero exit code. @@ -112,9 +121,9 @@ Output rules: - Do not print delegated commands such as `vp fmt --check` or `vp lint --type-aware --type-check` - Print one `pass:` line only after a phase completes successfully -- Mention type checks in the lint success line only when `--type-check` is enabled +- Mention type checks in the lint success line only when `lint.options.typeCheck` is enabled - On failure, print a human-readable `error:` line, then raw diagnostics, then a blank line and a final summary sentence -- Treat `vp check --no-fmt --no-lint` as an error instead of silent success +- With `--no-fmt --no-lint`, run only configured TypeScript diagnostics. Report an error if no checks remain enabled. Representative failure output: @@ -180,21 +189,54 @@ Commands::Check { args } => commands::delegate::execute(cwd, "check", &args).awa ### NAPI Binding -The `Check` variant is defined in `SynthesizableSubcommand` in `packages/cli/binding/src/cli.rs`. The check command's orchestration logic lives in its own module at `packages/cli/binding/src/check/`, following the same directory-per-command pattern as `exec/`: +The `Check` variant is defined in `SynthesizableSubcommand` in `packages/cli/binding/src/cli/types.rs`. The check command's orchestration logic lives in its own module at `packages/cli/binding/src/check/`, following the same directory-per-command pattern as `exec/`: - `check/mod.rs` — `execute_check()` orchestration (runs fmt + lint sequentially, handles `--fix` re-formatting) - `check/analysis.rs` — Output analysis types (`CheckSummary`, `LintMessageKind`, etc.), parsers, and formatting helpers -The check module reuses `SubcommandResolver` and `resolve_and_capture_output` from `cli.rs` to resolve and run the underlying fmt/lint commands. +The check module reuses `SubcommandResolver` and `resolve_and_capture_output` from `cli/resolver.rs` and `cli/execution.rs` to resolve and run the underlying fmt/lint commands. ### TypeScript Side -No new resolver needed — `vp check` reuses existing `resolve-lint.ts` and `resolve-fmt.ts`. +`vp check` reuses `resolve-lint.ts` and `resolve-fmt.ts`. + +### Experimental type-aware backend + +This draft replaces the bundled Go backend with `tsrs@0.22.0-ts7.1.0-beta`, +a Rust port of the TypeScript 7 compiler. `resolve-lint.ts` selects the bundled +`tsrs` package and passes its `bin/tsrs-lint` launcher to Oxlint through +`OXLINT_TSGOLINT_PATH`. The same resolver serves `vp lint` and `vp check`. +Users do not need to set an environment variable for the default backend. + +An explicit `OXLINT_TSGOLINT_PATH` takes precedence over the resolver default. +The environment variable keeps its existing name because Oxlint uses it to +select the type-aware backend. A project can still select Go explicitly: + +```sh +vp add -D oxlint-tsgolint +OXLINT_TSGOLINT_PATH="$PWD/node_modules/.bin/tsgolint" vp check +``` + +`tsrs` is a production dependency. `oxlint-tsgolint` remains a development +dependency for comparisons. The benchmark selects the Go executable explicitly, +so a change to the default cannot make it compare tsrs against itself. +See the [performance report](../bench/tsrs-lint.md) for elapsed time, peak memory, +and measurement conditions. + +The pinned tsrs release has no Windows ARM64 binary. This draft adds no automatic +Go fallback. A release for all supported Vite+ platforms needs an upstream +Windows ARM64 binary or an explicit fallback. Runtime smoke tests currently +cover macOS ARM64; other platforms still need runtime validation. + +The backend switch preserves the existing formatting phase, lint configuration, +and failure handling. Smoke fixtures compare diagnostics, suppression comments, +and automatic fixes against Go. These checks do not establish complete +compiler parity. ### Key Files 1. `crates/vp_global_cli/src/cli.rs` — `Check` command variant and routing -2. `packages/cli/binding/src/cli.rs` — `SynthesizableSubcommand::Check` definition, delegates to `check` module +2. `packages/cli/binding/src/cli/types.rs` — `SynthesizableSubcommand::Check` definition, delegates to `check` module 3. `packages/cli/binding/src/check/mod.rs` — Check command orchestration (`execute_check`) 4. `packages/cli/binding/src/check/analysis.rs` — Output parsing and analysis types @@ -203,13 +245,14 @@ No new resolver needed — `vp check` reuses existing `resolve-lint.ts` and `res ``` Run format, lint, and type checks -Usage: vp check [OPTIONS] +Usage: vp check [OPTIONS] [PATHS]... Options: - --fmt Run format check [default: true] - --lint Run lint check [default: true] - --type-aware Enable type-aware linting [default: true] - --type-check Enable TypeScript type checking [default: true] + --fix Auto-fix format and lint issues + --quiet Report errors without warnings + --deny-warnings Treat lint warnings as errors + --no-fmt Skip format check + --no-lint Skip lint rules; retain configured type checks --no-error-on-unmatched-pattern Do not exit with error when no files match -h, --help Print help ``` @@ -261,16 +304,15 @@ pass: Found no warnings or lint errors in 2 files (452ms, 16 threads) The check command's cache fingerprint includes: -- **Environment variable:** `OXLINT_TSGOLINT_PATH` (affects lint behavior) +- **Environment variable:** `OXLINT_TSGOLINT_PATH` (an explicit Go or tsrs backend override affects lint behavior) - **Input files:** Auto-tracked via fspy, excluding: - - `node_modules/.vite-temp/**` — config compilation cache (read+written by the vp CLI subprocess) - `node_modules/.vite/task-cache/**` — task runner state files that change after each run -These exclusions are defined by `check_cache_inputs()` in `cli.rs`. +These exclusions are defined by `check_cache_inputs()` in `packages/cli/binding/src/cli/resolver.rs`. ### How it differs from `vp fmt` / `vp lint` -When `vp fmt` or `vp lint` appear in task scripts, the command handler resolves them to their underlying binaries (e.g., `node path/to/oxfmt.mjs`). The `vp check` command is different — it runs as a full `vp check` subprocess because it's a composite command that orchestrates both fmt and lint internally. This means the `vp` CLI process itself is tracked by fspy, which is why the `.vite-temp` and `.vite/task-cache` exclusions are necessary. +When `vp fmt` or `vp lint` appear in task scripts, the command handler resolves them to their underlying binaries (e.g., `node path/to/oxfmt.mjs`). The `vp check` command is different — it runs as a full `vp check` subprocess because it's a composite command that orchestrates both fmt and lint internally. This means the `vp` CLI process itself is tracked by fspy, which is why the `.vite/task-cache` exclusions are necessary. ## Comparison with Other Tools @@ -281,30 +323,20 @@ When `vp fmt` or `vp lint` appear in task scripts, the command handler resolves | **`biome check`** | **Format + lint (closest analog)** | | `deno check` | Type checking only | -## Snap Tests +## Snapshot Tests -``` -packages/cli/snap-tests/check-basic/ - package.json - steps.json # { "steps": [{ "command": "vp check" }] } - src/index.ts # Clean file that passes all checks - snap.txt - -packages/cli/snap-tests/check-fmt-fail/ - package.json - steps.json # { "steps": [{ "command": "vp check" }] } - src/index.ts # Badly formatted file - snap.txt # Shows fmt --check failure, lint doesn't run (fail-fast) - -packages/cli/snap-tests/check-no-fmt/ - package.json - steps.json # { "steps": [{ "command": "vp check --no-fmt" }] } - snap.txt # Only lint runs - -packages/cli/snap-tests/check-cache-enabled/ - package.json # { "scripts": { "check": "vp check" } } - vite.config.ts # { run: { cache: true } } - steps.json # Runs vp run check twice, expects cache hit on second run - src/index.js - snap.txt -``` +CLI cases live in `crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/`. +Each case uses `snapshots.toml` and a recorded Markdown snapshot. + +| Fixture | Coverage | +| ------------------------- | ------------------------------------------------------ | +| `check_pass_typecheck` | Formatting, linting, and configured type checks | +| `check_fmt_fail` | Formatting failure stops the command | +| `check_no_fmt` | Linting without formatting | +| `check_no_lint_typecheck` | Type checks without lint rules | +| `check_oxlint_env` | An explicit backend path overrides the bundled default | +| `check_cache_enabled` | A second `vp run check` replays cached output | +| `oxlint_typeaware` | Type-aware task caching tracks imported type files | + +Use the [snapshot runner instructions](../crates/vp_cli_snapshots/tests/cli_snapshots/README.md) +to run or update these cases. diff --git a/rfcs/migration-command.md b/rfcs/migration-command.md index 2d82152eb7..82cec43d34 100644 --- a/rfcs/migration-command.md +++ b/rfcs/migration-command.md @@ -40,7 +40,7 @@ When transitioning to Vite+, projects typically use standalone tools like vite, - ✅ **Configuration files**: - .oxlintrc → vite.config.ts (lint section) - .oxfmtrc → vite.config.ts (format section) -- ✅ **tsconfig.json cleanup**: Removes deprecated `esModuleInterop: false` (causes oxlint tsgolint errors) +- ✅ **tsconfig.json cleanup**: Removes deprecated `esModuleInterop: false` (causes Oxlint type-aware backend errors) **What this command optionally migrates** (prompted): diff --git a/rfcs/toolchain-command.md b/rfcs/toolchain-command.md index 7a407cccbc..5f94f55812 100644 --- a/rfcs/toolchain-command.md +++ b/rfcs/toolchain-command.md @@ -81,7 +81,7 @@ compatibility: 1. Vite+ distribution packages, including `vite-plus` and `@voidzero-dev/vite-plus-core`. 2. User-facing tools invoked or composed by Vite+, including Vite, Rolldown, - Vitest, Oxlint, Oxfmt, oxlint-tsgolint, tsdown, and Vite Task. + Vitest, Oxlint, Oxfmt, tsrs, tsdown, and Vite Task. 3. Bundled or compiled engines whose versions affect tool behavior. Version 1 includes Oxc and Oxc Resolver. @@ -164,13 +164,13 @@ vite-plus@0.2.4 │ └── bundles tsdown@0.22.3 ├── depends on vitest@4.1.10 ├── depends on oxlint@1.72.0 -├── depends on oxlint-tsgolint@0.24.0 +├── depends on tsrs@0.22.0-ts7.1.0-beta ├── depends on oxfmt@0.57.0 └── compiles vite-task (built 2026-08-06T09:30:00Z, revision ) ``` -These versions show the repository state when this RFC was written. They are -not part of the command contract. +These versions illustrate the output format. They are not part of the command +contract. The tsrs row reflects the experimental default backend. The readable tree can repeat a shared node to show two relationships. JSON has one entry for each node ID. @@ -209,12 +209,19 @@ Filters match a node's: Initial aliases include: -| Query | Node | -| ---------------- | ------------------------------ | -| `vite-plus-core` | `@voidzero-dev/vite-plus-core` | -| `tsgolint` | `oxlint-tsgolint` | -| `vite-task` | Vite Task | -| `oxc-resolver` | Oxc Resolver | +| Query | Node | +| ----------------- | ------------------------------ | +| `vite-plus-core` | `@voidzero-dev/vite-plus-core` | +| `tsrs-lint` | `tsrs` | +| `tsgolint` | `tsrs` | +| `oxlint-tsgolint` | `tsrs` | +| `vite-task` | Vite Task | +| `oxc-resolver` | Oxc Resolver | + +The experimental manifest uses `tsrs` as the backend node ID and package name. +It retains `tsgolint` and `oxlint-tsgolint` as aliases for existing queries. +These aliases select the bundled tsrs node; they do not report a separate Go +dependency. Older manifests retain their original `oxlint-tsgolint` node. Package and tool names are case-sensitive. This matches npm and Cargo names. @@ -412,6 +419,16 @@ the local Vite+ release to use the command. To show the global release, run It reads tool rows from the manifest. Use `vp toolchain` to select tools and show relationships or engine details. +For this experimental backend, the local version output and `vite-plus/versions` +report the `tsrs` version. The generated export uses the `tsrs` key in place of +`oxlint-tsgolint`. The global version command selects the tsrs row when the local +manifest contains it. It retains the Go row for older local manifests. + +The manifest describes the bundled backend. An `OXLINT_TSGOLINT_PATH` override +does not change its nodes or the version exports. The manifest also does not +certify platform support: the pinned tsrs release lacks a Windows ARM64 binary. +See the [backend design](./check-command.md#experimental-type-aware-backend). + ## Relationship to `vp why` `vp why` sends the command to the detected package manager. It keeps the @@ -486,21 +503,21 @@ The documentation states that `vp why` is a package-manager operation. ### CLI snapshot tests -New cases belong in `crates/vite_cli_snapshots/tests/cli_snapshots/`: - -| Scenario | Expected coverage | -| --------------------------------- | -------------------------------------------------------- | -| Full local manifest | Complete tree and local source | -| `vp toolchain vite` | Core, Vite, Rolldown, Oxc, and Oxc Resolver chain | -| Multiple filters | Stable union of branches | -| Alias filter | `vite-plus-core`, `vite-task`, and `tsgolint` resolution | -| `--json` | Valid JSON without header, styling, or trailing text | -| No local package | Global source selection | -| `--global` inside a local project | Global source forced | -| Old local Vite+ package | Unknown-command failure from the local CLI | -| Unknown tool | Status 1 and `vp why` hint | -| `vp why vite` | Package-manager output followed by toolchain hint | -| `vp why vite --json` | Unmodified JSON package-manager output | +New cases belong in `crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/`: + +| Scenario | Expected coverage | +| --------------------------------- | -------------------------------------------------------------------------- | +| Full local manifest | Complete tree and local source | +| `vp toolchain vite` | Core, Vite, Rolldown, Oxc, and Oxc Resolver chain | +| Multiple filters | Stable union of branches | +| Alias filter | `vite-plus-core`, `vite-task`, `tsrs-lint`, and legacy Go alias resolution | +| `--json` | Valid JSON without header, styling, or trailing text | +| No local package | Global source selection | +| `--global` inside a local project | Global source forced | +| Old local Vite+ package | Unknown-command failure from the local CLI | +| Unknown tool | Status 1 and `vp why` hint | +| `vp why vite` | Package-manager output followed by toolchain hint | +| `vp why vite --json` | Unmodified JSON package-manager output | Release artifact tests load the same manifest with each platform binding. The tests compare native versions with the compiled release inputs. From 5f73f22d6437cdff325c5af4301d6dfcae2f95e7 Mon Sep 17 00:00:00 2001 From: MK Date: Thu, 5 Nov 2026 16:22:24 +0800 Subject: [PATCH 05/21] bench(lint): add reproducible Codespaces resource comparison --- bench/tsrs-lint-codespaces/README.md | 95 +++++++ bench/tsrs-lint-codespaces/inputs/bench.py | 236 ++++++++++++++++++ bench/tsrs-lint-codespaces/inputs/docs.py | 11 + bench/tsrs-lint-codespaces/inputs/docs.sh | 1 + bench/tsrs-lint-codespaces/inputs/run.sh | 1 + bench/tsrs-lint-codespaces/inputs/setup.py | 150 +++++++++++ bench/tsrs-lint-codespaces/inputs/setup.sh | 1 + bench/tsrs-lint-codespaces/inputs/validate.sh | 1 + .../inputs/verify-setup.py | 6 + .../inputs/verify-setup.sh | 1 + bench/tsrs-lint-codespaces/stages.json | 46 ++++ rfcs/check-command.md | 5 +- 12 files changed, 552 insertions(+), 2 deletions(-) create mode 100644 bench/tsrs-lint-codespaces/README.md create mode 100644 bench/tsrs-lint-codespaces/inputs/bench.py create mode 100644 bench/tsrs-lint-codespaces/inputs/docs.py create mode 100644 bench/tsrs-lint-codespaces/inputs/docs.sh create mode 100644 bench/tsrs-lint-codespaces/inputs/run.sh create mode 100644 bench/tsrs-lint-codespaces/inputs/setup.py create mode 100644 bench/tsrs-lint-codespaces/inputs/setup.sh create mode 100644 bench/tsrs-lint-codespaces/inputs/validate.sh create mode 100644 bench/tsrs-lint-codespaces/inputs/verify-setup.py create mode 100644 bench/tsrs-lint-codespaces/inputs/verify-setup.sh create mode 100644 bench/tsrs-lint-codespaces/stages.json diff --git a/bench/tsrs-lint-codespaces/README.md b/bench/tsrs-lint-codespaces/README.md new file mode 100644 index 0000000000..a43b01cc7c --- /dev/null +++ b/bench/tsrs-lint-codespaces/README.md @@ -0,0 +1,95 @@ +# Codespaces comparison of lint backends + +This recipe compares the explicit Go override with the default tsrs backend in +the same built CLI. It runs three workloads on both Vite+ and vinext: type-aware +rules, rules with TypeScript diagnostics, and the full `vp check` command. + +Use the [codespaces-run skill](https://github.com/Dunqing/skills/tree/main/skills/codespaces-run). +The recipe uses one `standardLinux32gb` instance with four CPUs and 16 GB of RAM. +The skill retains the environment and stops the instance after artifact collection. +Do not run other builds or benchmarks on the instance during measurement. + +## Source and setup + +The measured CLI source is `22b07bd150aa57b3e04cc52e0b81d43294ffe982`. +The RFC patch from `da260f86fce1d43810c62e4e6aad011066c88cb9` preserves the +documentation edits present when this comparison started. The vinext corpus is +`249088af307c0a852fa6dc59c08fc6ec80e1620e` from `fengmk2/vinext`. + +Setup installs Node.js `24.22.2` for Vite+ and `26.13.0` for vinext. It installs +the package managers declared by those revisions: pnpm `12.11.2` and `11.1.1`. +Both projects and Vite+'s separate documentation workspace use frozen lockfiles. +The Python environment pins `psutil==7.0.0`. +Node downloads are checked against the official SHA256 files. + +The recipe builds the PR's CLI JavaScript. It uses the published Vite+ `1.5.0` +native binding, core, and prompts packages, plus the matching Rolldown binding. +It does not compile a complete native release. The setup receipt records hashes +of the CLI entry point, native binding, Node executables, and benchmark config. + +The repository's Ubuntu devcontainer can lack an SSH server. If `gh codespace ssh` +reports that problem, open the existing Codespace's web terminal and install +`openssh-server` with `sudo apt-get update` and +`sudo apt-get install -y --no-install-recommends openssh-server`. +Keep the repository's devcontainer configuration unchanged. Setup installs +`python3-venv` only if Python cannot import `ensurepip`. + +## Run + +Run these commands from this checkout. The temporary input directory stays +outside the source tree so it cannot change the measured file set. + +```sh +tsrs_skill_tool="${CODEX_HOME:-$HOME/.codex}/skills/codespaces-run/scripts/sandbox.py" +tsrs_bench_inputs="$(mktemp -d)" +cp bench/tsrs-lint-codespaces/inputs/* "$tsrs_bench_inputs/" +git diff 22b07bd150aa57b3e04cc52e0b81d43294ffe982 da260f86fce1d43810c62e4e6aad011066c88cb9 \ + -- rfcs/check-command.md rfcs/toolchain-command.md rfcs/migration-command.md \ + > "$tsrs_bench_inputs/rfcs.patch" + +python3 "$tsrs_skill_tool" submit \ + --repo voidzero-dev/vite-plus \ + --git-url https://github.com/voidzero-dev/vite-plus.git \ + --git-commit 22b07bd150aa57b3e04cc52e0b81d43294ffe982 \ + --stages "$PWD/bench/tsrs-lint-codespaces/stages.json" \ + --inputs "$tsrs_bench_inputs" \ + --machine standardLinux32gb --cpus 4 --min-memory-mib 14000 \ + --timeout 7200 --profile default \ + --environment-key tsrs-022-linux-x64-node24.22.2-26.13.0-pnpm12.11.2-11.1.1 + +python3 "$tsrs_skill_tool" events JOB_ID --follow +``` + +Use the returned job ID in the last command. Check the final receipt for +`collected: true`, a shutdown timestamp, and no cleanup error. Logs, raw samples, +memory traces, and results are retained under the skill's local job directory. +If a later stage fails, use the skill's stage recovery procedure to retain a +verified setup. Do not start another job while the previous job holds the lease. + +## Method + +Validation runs each backend once for each workload before measurement. Every +run must exit successfully with no diagnostics and the expected file and rule +counts. Full checks must also report successful formatting. Memory runs check +the native executable identity to detect accidental comparisons of one backend +against itself. + +Timing uses ten pairs after one warm-up per backend. Each pair reverses the +previous pair's order. Memory uses a separate five-pair phase with its own +warm-ups. Each invocation starts a fresh CLI process. Installation, builds, +validation, and memory sampling are excluded from the timing table. + +The memory sampler records the simultaneous sum of resident set size (RSS) for +the CLI and its descendants. It sleeps for 20 ms between samples and records the +actual intervals. This includes Node, Oxlint, and the native type-aware backend. +Shared pages can count more than once, and sampling can miss short peaks. The +result is a sampled process-tree peak, not total allocation or container memory. + +Lint uses four threads. Vinext lint uses `OXLINT_TSGOLINT_CHECKER_SCHEDULING=sorted`, +as in the earlier comparison. Full checks use the machine's default thread count +and backend scheduling. The rules-only configuration disables `typeCheck` while +preserving the project's original lint rules and plugins. + +The summary reports medians and ranges. Warm-up samples have `pair=-1` and do +not contribute to the medians. Each full run produces 204 records, including +warm-ups, plus the separate 12-command validation phase. diff --git a/bench/tsrs-lint-codespaces/inputs/bench.py b/bench/tsrs-lint-codespaces/inputs/bench.py new file mode 100644 index 0000000000..bae07e9059 --- /dev/null +++ b/bench/tsrs-lint-codespaces/inputs/bench.py @@ -0,0 +1,236 @@ +import argparse +import csv +import hashlib +import json +import os +import pathlib +import platform +import re +import statistics +import subprocess +import tempfile +import time + +import psutil + +ENV = pathlib.Path(os.environ['JOB_ENV_DIR']) +OUT = pathlib.Path(os.environ['ARTIFACTS_DIR']) +CONFIG = json.loads((ENV / 'bench-config.json').read_text()) +BASE_ENV = {key: value for key, value in os.environ.items() + if not key.startswith(('OXLINT_TSGOLINT', 'TSRS_')) + and key not in ('NODE_OPTIONS', 'NODE_PATH', 'GITHUB_ACTIONS')} +EXPECTED = {} +RECORDS = [] +SCENARIOS = ('rules', 'full', 'check') +TIMING_PAIRS = 10 +MEMORY_PAIRS = 5 + + +def progress(phase, message, completed=0, total=0): + value = {'phase': phase, 'message': message, 'completed': completed, 'total': total} + subprocess.run(['python3', os.environ['JOB_WORKER'], 'progress', os.environ['JOB_DIR'], json.dumps(value)], check=True) + print(json.dumps(value), flush=True) + + +def environment(project, scenario, backend): + env = BASE_ENV | { + 'PATH': str(pathlib.Path(project['node']).parent) + ':' + BASE_ENV['PATH'], + 'VP_HOME': str(ENV / 'vp-home'), 'VP_SELF_SETUP_NO_MODIFY_PATH': '1', + } + if scenario != 'check' and project['scheduling']: + env['OXLINT_TSGOLINT_CHECKER_SCHEDULING'] = project['scheduling'] + if backend == 'go': + env['OXLINT_TSGOLINT_PATH'] = CONFIG['go'] + return env + + +def lint_config(project): + code = ''' +import { createRequire } from 'node:module'; +import { pathToFileURL } from 'node:url'; +const require = createRequire(process.argv[1]); +const { loadConfigFromFile } = await import(pathToFileURL(require.resolve('vite')).href); +const result = await loadConfigFromFile({ command: 'serve', mode: 'development' }, undefined, process.cwd(), 'silent'); +if (!result?.config?.lint) throw new Error('Missing lint configuration'); +process.stdout.write(JSON.stringify(result.config.lint)); +''' + cli_package = str(pathlib.Path(CONFIG['cli']).parent.parent / 'package.json') + env = environment(project, 'rules', 'rust') | {'VP_RESOLVING_CONFIG_METADATA': '1'} + output = subprocess.check_output([project['node'], '--input-type=module', '-e', code, cli_package], cwd=project['root'], env=env, text=True) + original = json.loads(output) + (OUT / (project['name'] + '-lint-config.json')).write_text(json.dumps(original, indent=2) + '\n') + config = json.loads(output) + config.setdefault('options', {})['typeAware'] = True + config['options']['typeCheck'] = False + return config + + +def signature(project, scenario, stdout, stderr): + if scenario == 'check': + combined = stdout + stderr + formatted = re.search(r'pass: All ([\d,]+) files are correctly formatted', combined) + linted = re.search(r'pass: Found no warnings, lint errors, or type errors in ([\d,]+) files', combined) + assert formatted and linted, combined + files = int(linted[1].replace(',', '')) + result = {'files': files, 'format_files': int(formatted[1].replace(',', ''))} + else: + data = json.loads(stdout) + assert not data['diagnostics'], data['diagnostics'][:5] + files = data['number_of_files'] + assert data['number_of_rules'] == project['rules'], data + result = {key: data[key] for key in ('number_of_files', 'number_of_rules', 'threads_count', 'diagnostics')} + assert files == project['files'], (project['name'], files, project['files']) + key = (project['name'], scenario) + if key in EXPECTED: + assert result == EXPECTED[key], (key, result, EXPECTED[key]) + else: + EXPECTED[key] = result + return result + + +def sample(child, start): + root = psutil.Process(child.pid) + seen = {child.pid: root.create_time()} + trace = [] + processes = {} + while child.poll() is None: + try: + for process in root.children(recursive=True): + seen[process.pid] = process.create_time() + except psutil.Error: + pass + alive = [] + for pid, born in list(seen.items()): + try: + process = psutil.Process(pid) + with process.oneshot(): + if process.create_time() != born or process.status() == psutil.STATUS_ZOMBIE: + continue + rss = process.memory_info().rss + exe = process.exe() + elapsed = time.perf_counter() - start + key = f'{pid}:{exe}' + record = processes.setdefault(key, {'pid': pid, 'exe': exe, 'first': elapsed, 'peak_rss': 0}) + record['last'] = elapsed + record['peak_rss'] = max(record['peak_rss'], rss) + alive.append({'pid': pid, 'exe': exe, 'rss': rss}) + except psutil.Error: + pass + trace.append({'seconds': time.perf_counter() - start, 'rss': sum(p['rss'] for p in alive), 'processes': alive}) + time.sleep(0.02) + if time.perf_counter() - start > 300: + child.kill() + raise TimeoutError('Benchmark command exceeded 300 seconds') + return trace, list(processes.values()) + + +def run(project, scenario, backend, pair, phase, config=None): + args = ['check'] if scenario == 'check' else ['lint', '--type-aware', '--threads', '4', '--format', 'json'] + if scenario == 'full': + args += ['--type-check'] + if config: + args += ['-c', str(config)] + command = [project['node'], CONFIG['cli'], *args] + directory = OUT / phase / project['name'] + directory.mkdir(parents=True, exist_ok=True) + stem = directory / f'{scenario}-{backend}-{pair:02d}' + with stem.with_suffix('.stdout').open('w') as stdout, stem.with_suffix('.stderr').open('w') as stderr: + start = time.perf_counter() + child = subprocess.Popen(command, cwd=project['root'], env=environment(project, scenario, backend), stdout=stdout, stderr=stderr) + trace, processes = sample(child, start) if phase in ('validate', 'memory') else ([], []) + # Blocking wait avoids the polling latency of wait(timeout=...). + # The Codespaces worker enforces the overall task deadline. + status = child.wait() + seconds = time.perf_counter() - start + stdout = stem.with_suffix('.stdout').read_text() + stderr = stem.with_suffix('.stderr').read_text() + assert status == 0, (project['name'], scenario, backend, status, stdout[-5000:], stderr[-5000:]) + result = signature(project, scenario, stdout, stderr) + record = {'project': project['name'], 'scenario': scenario, 'backend': backend, + 'phase': phase, 'pair': pair, 'seconds': seconds, 'exit': status, + 'peak_rss': max((x['rss'] for x in trace), default=None), 'signature': result} + if trace: + native = [p for p in processes if pathlib.Path(p['exe']).name in ('tsgolint', 'tsrs')] + assert len(native) == 1, native + assert pathlib.Path(native[0]['exe']).name == ('tsgolint' if backend == 'go' else 'tsrs') + intervals = [b['seconds'] - a['seconds'] for a, b in zip(trace, trace[1:])] + record.update({'native': native[0], 'median_sample_interval_ms': statistics.median(intervals) * 1000}) + stem.with_suffix('.trace.json').write_text(json.dumps(trace)) + stem.with_suffix('.processes.json').write_text(json.dumps(processes, indent=2)) + stem.with_suffix('.json').write_text(json.dumps(record, indent=2) + '\n') + RECORDS.append(record) + (OUT / 'samples.json').write_text(json.dumps(RECORDS, indent=2) + '\n') + print(project['name'], scenario, backend, phase, pair, f'{seconds:.3f}s', record['peak_rss'], flush=True) + return record + + +def measure(project, scenario, phase): + handle = None + if scenario == 'rules': + handle = tempfile.NamedTemporaryFile(mode='w', suffix='.json', prefix='.tsrs-bench-', dir=project['root']) + json.dump(lint_config(project), handle) + handle.flush() + try: + pairs = 0 if phase == 'validate' else TIMING_PAIRS if phase == 'time' else MEMORY_PAIRS + for pair in range(-1, pairs): + backends = ('go', 'rust') if pair < 0 or pair % 2 == 0 else ('rust', 'go') + for backend in backends: + run(project, scenario, backend, pair, phase, handle.name if handle else None) + finally: + if handle: + handle.close() + + +def metadata(): + info = {'vp_commit': CONFIG['vp_commit'], 'python': platform.python_version(), 'psutil': psutil.__version__, + 'platform': platform.platform(), 'cpu_affinity': psutil.Process().cpu_affinity(), + 'memory_bytes': psutil.virtual_memory().total, 'timing_pairs': TIMING_PAIRS, + 'memory_pairs': MEMORY_PAIRS, 'sample_sleep_ms': 20, 'projects': CONFIG['projects'], + 'versions': json.loads((ENV / 'setup-receipt.json').read_text())['versions']} + for name, args in [('cpu', ['lscpu']), ('git_diff', ['git', 'diff', '--stat']), ('source', ['git', 'rev-parse', 'HEAD'])]: + info[name] = subprocess.check_output(args, text=True) + info['node_versions'] = {p['name']: subprocess.check_output([p['node'], '--version'], text=True).strip() for p in CONFIG['projects']} + info['cpu_percent_before'] = psutil.cpu_percent(interval=1) + (OUT / 'metadata.json').write_text(json.dumps(info, indent=2) + '\n') + + +def summarize(): + rows = [] + for project in CONFIG['projects']: + for scenario in SCENARIOS: + row = {'project': project['name'], 'scenario': scenario} + for backend in ('go', 'rust'): + for phase, metric in [('time', 'seconds'), ('memory', 'peak_rss')]: + values = [r[metric] for r in RECORDS if r['project'] == project['name'] and r['scenario'] == scenario and r['backend'] == backend and r['phase'] == phase and r['pair'] >= 0] + row[f'{backend}_{metric}'] = statistics.median(values) + row[f'{backend}_{metric}_range'] = [min(values), max(values)] + row['time_saved_percent'] = 100 * (1 - row['rust_seconds'] / row['go_seconds']) + row['rss_saved_percent'] = 100 * (1 - row['rust_peak_rss'] / row['go_peak_rss']) + rows.append(row) + (OUT / 'summary.json').write_text(json.dumps(rows, indent=2) + '\n') + with (OUT / 'samples.csv').open('w') as file: + keys = ['project', 'scenario', 'backend', 'phase', 'pair', 'seconds', 'peak_rss', 'exit'] + writer = csv.DictWriter(file, fieldnames=keys, extrasaction='ignore') + writer.writeheader() + writer.writerows(RECORDS) + print('RESULTS', json.dumps(rows), flush=True) + + +parser = argparse.ArgumentParser() +parser.add_argument('--validate-only', action='store_true') +options = parser.parse_args() +metadata() +phases = ('validate',) if options.validate_only else ('time', 'memory') +total = len(phases) * len(CONFIG['projects']) * len(SCENARIOS) +completed = 0 +for phase in phases: + for project in CONFIG['projects']: + for scenario in SCENARIOS: + progress(phase, f"{project['name']}: {scenario}", completed, total) + measure(project, scenario, phase) + completed += 1 + progress(phase, f"Completed {project['name']}: {scenario}", completed, total) +if not options.validate_only: + summarize() +assert len(RECORDS) == (12 if options.validate_only else 204), len(RECORDS) +progress('validated' if options.validate_only else 'measured', 'All output assertions passed', total, total) diff --git a/bench/tsrs-lint-codespaces/inputs/docs.py b/bench/tsrs-lint-codespaces/inputs/docs.py new file mode 100644 index 0000000000..cc1cef3543 --- /dev/null +++ b/bench/tsrs-lint-codespaces/inputs/docs.py @@ -0,0 +1,11 @@ +import os +import pathlib +import subprocess + +root = pathlib.Path.cwd() +env_root = pathlib.Path(os.environ['JOB_ENV_DIR']) +env = os.environ | {'PATH': str(env_root / 'node-v24.22.2-linux-x64/bin') + ':' + os.environ['PATH']} +subprocess.run([ + env_root / 'pnpm12/node_modules/.bin/pnpm', 'install', '--frozen-lockfile', + '--ignore-scripts', '--store-dir', env_root / 'pnpm-store', +], cwd=root / 'docs', env=env, check=True) diff --git a/bench/tsrs-lint-codespaces/inputs/docs.sh b/bench/tsrs-lint-codespaces/inputs/docs.sh new file mode 100644 index 0000000000..0ff5cba507 --- /dev/null +++ b/bench/tsrs-lint-codespaces/inputs/docs.sh @@ -0,0 +1 @@ +python3 "$JOB_INPUT_DIR/docs.py" diff --git a/bench/tsrs-lint-codespaces/inputs/run.sh b/bench/tsrs-lint-codespaces/inputs/run.sh new file mode 100644 index 0000000000..68be2b7d7b --- /dev/null +++ b/bench/tsrs-lint-codespaces/inputs/run.sh @@ -0,0 +1 @@ +"$JOB_ENV_DIR/venv/bin/python" "$JOB_INPUT_DIR/bench.py" diff --git a/bench/tsrs-lint-codespaces/inputs/setup.py b/bench/tsrs-lint-codespaces/inputs/setup.py new file mode 100644 index 0000000000..32fa921a55 --- /dev/null +++ b/bench/tsrs-lint-codespaces/inputs/setup.py @@ -0,0 +1,150 @@ +import hashlib +import json +import os +import pathlib +import shutil +import subprocess +import urllib.request + +ROOT = pathlib.Path.cwd() +ENV = pathlib.Path(os.environ['JOB_ENV_DIR']) +ENV.mkdir(parents=True, exist_ok=True) +INPUT = pathlib.Path(os.environ['JOB_INPUT_DIR']) +OUT = pathlib.Path(os.environ['ARTIFACTS_DIR']) +VP_COMMIT = '22b07bd150aa57b3e04cc52e0b81d43294ffe982' +VINEXT_COMMIT = '249088af307c0a852fa6dc59c08fc6ec80e1620e' + + +def run(args, cwd=ROOT, env=None): + print('+', ' '.join(map(str, args)), flush=True) + subprocess.run(list(map(str, args)), cwd=cwd, env=env, check=True) + + +def progress(message): + run(['python3', os.environ['JOB_WORKER'], 'progress', os.environ['JOB_DIR'], + json.dumps({'phase': 'setup', 'message': message})]) + + +def fetch_git(url, revision, destination): + if not (destination / '.git').exists(): + destination.mkdir(parents=True, exist_ok=True) + run(['git', 'init', destination]) + run(['git', 'remote', 'add', 'origin', url], cwd=destination) + run(['git', 'fetch', '--depth', '1', 'origin', revision], cwd=destination) + run(['git', 'checkout', '--detach', 'FETCH_HEAD'], cwd=destination) + actual = subprocess.check_output(['git', 'rev-parse', 'HEAD'], cwd=destination, text=True).strip() + assert actual == revision, (destination, actual, revision) + + +def node(version): + target = ENV / f'node-v{version}-linux-x64' + if not (target / 'bin/node').exists(): + filename = target.name + '.tar.xz' + base = f'https://nodejs.org/dist/v{version}/' + with urllib.request.urlopen(base + 'SHASUMS256.txt') as response: + checksums = response.read().decode() + (OUT / f'node-{version}-SHASUMS256.txt').write_text(checksums) + expected = next(line.split()[0] for line in checksums.splitlines() if line.split()[-1] == filename) + archive = ENV / filename + urllib.request.urlretrieve(base + filename, archive) + assert hashlib.sha256(archive.read_bytes()).hexdigest() == expected + run(['tar', '-xJf', archive, '-C', ENV]) + assert subprocess.check_output([target / 'bin/node', '--version'], text=True).strip() == 'v' + version + return target + + +def npm(prefix, packages, runtime): + prefix.mkdir(parents=True, exist_ok=True) + env = os.environ | {'PATH': str(runtime / 'bin') + ':' + os.environ['PATH']} + run([runtime / 'bin/npm', 'install', '--prefix', prefix, '--no-audit', '--no-fund', + '--ignore-scripts', '--package-lock=false', *packages], env=env) + + +def copy_dir(source, destination): + if destination.is_symlink(): + destination.unlink() + shutil.copytree(source, destination, dirs_exist_ok=True) + + +progress('Installing pinned Node and package-manager versions') +assert subprocess.check_output(['git', 'rev-parse', 'HEAD'], text=True).strip() == VP_COMMIT +patch = INPUT / 'rfcs.patch' +if patch.stat().st_size: + reverse = subprocess.run(['git', 'apply', '--reverse', '--check', str(patch)], capture_output=True) + if reverse.returncode: + run(['git', 'apply', patch]) +node24 = node('24.22.2') +node26 = node('26.13.0') +npm(ENV / 'pnpm12', ['pnpm@12.11.2'], node24) +# pnpm 12 ships a shebang-less placeholder until its native installer runs. +run([node24 / 'bin/node', ENV / 'pnpm12/node_modules/pnpm/install.js']) +npm(ENV / 'pnpm11', ['pnpm@11.1.1'], node26) +for prefix, version in [('pnpm12', '12.11.2'), ('pnpm11', '11.1.1')]: + pm_env = os.environ | {'PATH': str(node26 / 'bin') + ':' + os.environ['PATH']} + assert subprocess.check_output([ENV / prefix / 'node_modules/.bin/pnpm', '--version'], cwd=ENV, env=pm_env, text=True).strip() == version +if not (ENV / 'venv/bin/python').exists(): + # The repository's minimal Ubuntu image can omit ensurepip. + check = subprocess.run(['python3', '-c', 'import ensurepip'], capture_output=True, text=True) + if check.returncode: + if 'No module named' not in check.stderr or 'ensurepip' not in check.stderr: + raise RuntimeError(check.stderr) + progress('Installing python3-venv because the container lacks ensurepip') + run(['sudo', 'apt-get', 'update']) + run(['sudo', 'env', 'DEBIAN_FRONTEND=noninteractive', 'apt-get', 'install', '-y', 'python3-venv']) + run(['python3', '-m', 'venv', ENV / 'venv']) +run([ENV / 'venv/bin/python', '-m', 'pip', 'install', 'psutil==7.0.0']) +base_env = os.environ | { + 'PATH': str(node24 / 'bin') + ':' + str(ENV / 'pnpm12/node_modules/.bin') + ':' + os.environ['PATH'], + 'VP_HOME': str(ENV / 'vp-home'), 'VP_SELF_SETUP_NO_MODIFY_PATH': '1', + 'CARGO_HOME': str(ENV / 'cargo'), 'CARGO_TARGET_DIR': str(ENV / 'cargo-target'), +} +progress('Fetching exact source dependencies and published native artifacts') +for name, spec in json.loads((ROOT / 'packages/tools/.upstream-versions.json').read_text()).items(): + fetch_git(spec['repo'], spec['hash'], ROOT / name) +run([node24 / 'bin/node', 'packages/tools/src/vendored-vitest.ts'], env=base_env) +npm(ENV / 'published', [ + 'vite-plus@1.5.0', '@voidzero-dev/vite-plus-core@1.5.0', + '@voidzero-dev/vite-plus-linux-x64-gnu@1.5.0', + '@voidzero-dev/vite-plus-prompts@1.5.0', 'rolldown@1.2.16', +], node24) +run([ENV / 'pnpm12/node_modules/.bin/pnpm', 'install', '--frozen-lockfile', '--ignore-scripts', + '--store-dir', ENV / 'pnpm-store'], env=base_env) +modules = ENV / 'published/node_modules' +for source, destination in [ + (modules / 'vite-plus/dist', ROOT / 'packages/cli/dist'), + (modules / '@voidzero-dev/vite-plus-core/dist', ROOT / 'packages/core/dist'), + (modules / '@voidzero-dev/vite-plus-prompts/dist', ROOT / 'packages/prompts/dist'), + (modules / 'rolldown/dist', ROOT / 'rolldown/packages/rolldown/dist'), + (modules / '@rolldown/binding-linux-x64-gnu', ROOT / 'rolldown/packages/rolldown/node_modules/@rolldown/binding-linux-x64-gnu'), + (modules / '@voidzero-dev/vite-plus-linux-x64-gnu', ROOT / 'packages/core/node_modules/@voidzero-dev/vite-plus-linux-x64-gnu'), +]: + copy_dir(source, destination) +native = modules / '@voidzero-dev/vite-plus-linux-x64-gnu/vite-plus.linux-x64-gnu.node' +shutil.copy2(native, ROOT / 'packages/cli/binding' / native.name) +progress('Building CLI JavaScript; using the published 1.5.0 Linux native binding and core') +run([ENV / 'pnpm12/node_modules/.bin/pnpm', '--filter', 'vite-plus', 'build-ts'], env=base_env) +progress('Preparing the pinned vinext benchmark corpus and generated types') +vinext = ENV / 'vinext' +fetch_git('https://github.com/fengmk2/vinext.git', VINEXT_COMMIT, vinext) +vinext_env = base_env | {'PATH': str(node26 / 'bin') + ':' + str(ENV / 'pnpm11/node_modules/.bin') + ':' + os.environ['PATH']} +run([ENV / 'pnpm11/node_modules/.bin/pnpm', 'install', '--frozen-lockfile', '--ignore-scripts', + '--store-dir', ENV / 'pnpm-store-vinext'], cwd=vinext, env=vinext_env) +run([ENV / 'pnpm11/node_modules/.bin/pnpm', 'run', 'postinstall'], cwd=vinext, env=vinext_env) +config = { + 'vp_commit': VP_COMMIT, 'cli': str(ROOT / 'packages/cli/bin/vp'), + 'go': str(ROOT / 'packages/cli/node_modules/.bin/tsgolint'), + 'tsrs': str(ROOT / 'packages/cli/node_modules/.bin/tsrs-lint'), + 'projects': [ + {'name': 'vite-plus', 'root': str(ROOT), 'node': str(node24 / 'bin/node'), 'files': 385, 'rules': 174, 'scheduling': None}, + {'name': 'vinext', 'root': str(vinext), 'node': str(node26 / 'bin/node'), 'files': 1828, 'rules': 151, 'scheduling': 'sorted', 'commit': VINEXT_COMMIT}, + ], +} +(ENV / 'bench-config.json').write_text(json.dumps(config, indent=2) + '\n') +versions = {} +for package in ['tsrs', 'oxlint', 'oxlint-tsgolint']: + versions[package] = json.loads((ROOT / 'packages/cli/node_modules' / package / 'package.json').read_text())['version'] +assert versions == {'tsrs': '0.22.0-ts7.1.0-beta', 'oxlint': '1.91.0', 'oxlint-tsgolint': '7.0.2005'}, versions +files = [ROOT / 'packages/cli/dist/bin.js', native, node24 / 'bin/node', node26 / 'bin/node', ENV / 'bench-config.json'] +receipt = {'versions': versions, 'hashes': {str(p): hashlib.sha256(p.read_bytes()).hexdigest() for p in files}} +(ENV / 'setup-receipt.json').write_text(json.dumps(receipt, indent=2) + '\n') +print('SETUP_COMPLETE', json.dumps(versions), flush=True) diff --git a/bench/tsrs-lint-codespaces/inputs/setup.sh b/bench/tsrs-lint-codespaces/inputs/setup.sh new file mode 100644 index 0000000000..b2ff6948e5 --- /dev/null +++ b/bench/tsrs-lint-codespaces/inputs/setup.sh @@ -0,0 +1 @@ +python3 "$JOB_INPUT_DIR/setup.py" diff --git a/bench/tsrs-lint-codespaces/inputs/validate.sh b/bench/tsrs-lint-codespaces/inputs/validate.sh new file mode 100644 index 0000000000..1f8e6ed6c9 --- /dev/null +++ b/bench/tsrs-lint-codespaces/inputs/validate.sh @@ -0,0 +1 @@ +"$JOB_ENV_DIR/venv/bin/python" "$JOB_INPUT_DIR/bench.py" --validate-only diff --git a/bench/tsrs-lint-codespaces/inputs/verify-setup.py b/bench/tsrs-lint-codespaces/inputs/verify-setup.py new file mode 100644 index 0000000000..e14726debc --- /dev/null +++ b/bench/tsrs-lint-codespaces/inputs/verify-setup.py @@ -0,0 +1,6 @@ +import hashlib,json,os,pathlib +root=pathlib.Path(os.environ['JOB_ENV_DIR']) +receipt=json.loads((root/'setup-receipt.json').read_text()) +for name,expected in receipt['hashes'].items(): + assert hashlib.sha256(pathlib.Path(name).read_bytes()).hexdigest()==expected,name +print('Setup artifacts verified') diff --git a/bench/tsrs-lint-codespaces/inputs/verify-setup.sh b/bench/tsrs-lint-codespaces/inputs/verify-setup.sh new file mode 100644 index 0000000000..3968785a51 --- /dev/null +++ b/bench/tsrs-lint-codespaces/inputs/verify-setup.sh @@ -0,0 +1 @@ +python3 "$JOB_INPUT_DIR/verify-setup.py" diff --git a/bench/tsrs-lint-codespaces/stages.json b/bench/tsrs-lint-codespaces/stages.json new file mode 100644 index 0000000000..5506f61409 --- /dev/null +++ b/bench/tsrs-lint-codespaces/stages.json @@ -0,0 +1,46 @@ +{ + "stages": [ + { + "name": "setup", + "script": "setup.sh", + "inputs": { + "workspace": [ + "pnpm-lock.yaml", + "pnpm-workspace.yaml", + "packages/cli/package.json", + "packages/tools/.upstream-versions.json" + ], + "inputs": ["setup.py", "verify-setup.py", "rfcs.patch"] + }, + "outputs": { + "env": ["setup-receipt.json", "bench-config.json"] + }, + "verify": "verify-setup.sh" + }, + { + "name": "docs", + "script": "docs.sh", + "inputs": { + "workspace": ["docs/package.json", "docs/pnpm-lock.yaml", "docs/pnpm-workspace.yaml"], + "inputs": ["docs.py"], + "env": ["setup-receipt.json"] + } + }, + { + "name": "validate", + "script": "validate.sh", + "inputs": { + "inputs": ["bench.py"], + "env": ["setup-receipt.json", "bench-config.json"] + } + }, + { + "name": "benchmark", + "script": "run.sh", + "inputs": { + "inputs": ["bench.py"], + "env": ["setup-receipt.json", "bench-config.json"] + } + } + ] +} diff --git a/rfcs/check-command.md b/rfcs/check-command.md index 69f83c3912..dff09b084b 100644 --- a/rfcs/check-command.md +++ b/rfcs/check-command.md @@ -225,8 +225,9 @@ and measurement conditions. The pinned tsrs release has no Windows ARM64 binary. This draft adds no automatic Go fallback. A release for all supported Vite+ platforms needs an upstream -Windows ARM64 binary or an explicit fallback. Runtime smoke tests currently -cover macOS ARM64; other platforms still need runtime validation. +Windows ARM64 binary or an explicit fallback. Runtime smoke tests cover macOS +ARM64. The Codespaces comparison also validates both backends on Linux x64 +with Vite+ and vinext. Other platforms still need runtime validation. The backend switch preserves the existing formatting phase, lint configuration, and failure handling. Smoke fixtures compare diagnostics, suppression comments, From 1c470ab4b0c682c1aa8bba6d5a21ee4fa0b20013 Mon Sep 17 00:00:00 2001 From: MK Date: Fri, 6 Nov 2026 00:09:53 +0800 Subject: [PATCH 06/21] bench(lint): report tsrs 0.22 time and memory results --- bench/tsrs-lint-codespaces/README.md | 16 ++ bench/tsrs-lint-codespaces/inputs/bench.py | 33 ++- .../results/main-metadata.json | 52 +++++ .../results/main-samples.csv | 205 ++++++++++++++++++ .../tsrs-lint-codespaces/results/summary.json | 98 +++++++++ .../results/timing-rerun-metadata.json | 36 +++ .../results/timing-rerun-samples.csv | 23 ++ bench/tsrs-lint.md | 107 +++++++-- 8 files changed, 544 insertions(+), 26 deletions(-) create mode 100644 bench/tsrs-lint-codespaces/results/main-metadata.json create mode 100644 bench/tsrs-lint-codespaces/results/main-samples.csv create mode 100644 bench/tsrs-lint-codespaces/results/summary.json create mode 100644 bench/tsrs-lint-codespaces/results/timing-rerun-metadata.json create mode 100644 bench/tsrs-lint-codespaces/results/timing-rerun-samples.csv diff --git a/bench/tsrs-lint-codespaces/README.md b/bench/tsrs-lint-codespaces/README.md index a43b01cc7c..6676e5c19d 100644 --- a/bench/tsrs-lint-codespaces/README.md +++ b/bench/tsrs-lint-codespaces/README.md @@ -8,6 +8,9 @@ Use the [codespaces-run skill](https://github.com/Dunqing/skills/tree/main/skill The recipe uses one `standardLinux32gb` instance with four CPUs and 16 GB of RAM. The skill retains the environment and stops the instance after artifact collection. Do not run other builds or benchmarks on the instance during measurement. +The CPU model can change when the same Codespace restarts. Inspect each job's +metadata before combining results. Keep both backends of a comparison in the +same job, and identify any hardware change in the report. ## Source and setup @@ -66,6 +69,19 @@ memory traces, and results are retained under the skill's local job directory. If a later stage fails, use the skill's stage recovery procedure to retain a verified setup. Do not start another job while the previous job holds the lease. +The runner also accepts `--project`, `--scenario`, and `--phase` filters. For a +focused repetition, submit a continuation after the prior job stops. Its script +can run the following commands with the recipe's input files: + +```sh +python3 "$JOB_INPUT_DIR/verify-setup.py" +"$JOB_ENV_DIR/venv/bin/python" "$JOB_INPUT_DIR/bench.py" \ + --project vinext --scenario full --phase time +``` + +This selection produces 22 timing records: ten pairs and two warm-ups. Keep the +new job's metadata and raw records when it replaces a previous timing set. + ## Method Validation runs each backend once for each workload before measurement. Every diff --git a/bench/tsrs-lint-codespaces/inputs/bench.py b/bench/tsrs-lint-codespaces/inputs/bench.py index bae07e9059..846ba77bc2 100644 --- a/bench/tsrs-lint-codespaces/inputs/bench.py +++ b/bench/tsrs-lint-codespaces/inputs/bench.py @@ -186,7 +186,8 @@ def metadata(): 'platform': platform.platform(), 'cpu_affinity': psutil.Process().cpu_affinity(), 'memory_bytes': psutil.virtual_memory().total, 'timing_pairs': TIMING_PAIRS, 'memory_pairs': MEMORY_PAIRS, 'sample_sleep_ms': 20, 'projects': CONFIG['projects'], - 'versions': json.loads((ENV / 'setup-receipt.json').read_text())['versions']} + 'versions': json.loads((ENV / 'setup-receipt.json').read_text())['versions'], + 'selection': vars(options)} for name, args in [('cpu', ['lscpu']), ('git_diff', ['git', 'diff', '--stat']), ('source', ['git', 'rev-parse', 'HEAD'])]: info[name] = subprocess.check_output(args, text=True) info['node_versions'] = {p['name']: subprocess.check_output([p['node'], '--version'], text=True).strip() for p in CONFIG['projects']} @@ -196,16 +197,20 @@ def metadata(): def summarize(): rows = [] - for project in CONFIG['projects']: - for scenario in SCENARIOS: + for project in selected_projects: + for scenario in selected_scenarios: row = {'project': project['name'], 'scenario': scenario} for backend in ('go', 'rust'): for phase, metric in [('time', 'seconds'), ('memory', 'peak_rss')]: + if phase not in phases: + continue values = [r[metric] for r in RECORDS if r['project'] == project['name'] and r['scenario'] == scenario and r['backend'] == backend and r['phase'] == phase and r['pair'] >= 0] row[f'{backend}_{metric}'] = statistics.median(values) row[f'{backend}_{metric}_range'] = [min(values), max(values)] - row['time_saved_percent'] = 100 * (1 - row['rust_seconds'] / row['go_seconds']) - row['rss_saved_percent'] = 100 * (1 - row['rust_peak_rss'] / row['go_peak_rss']) + if 'time' in phases: + row['time_saved_percent'] = 100 * (1 - row['rust_seconds'] / row['go_seconds']) + if 'memory' in phases: + row['rss_saved_percent'] = 100 * (1 - row['rust_peak_rss'] / row['go_peak_rss']) rows.append(row) (OUT / 'summary.json').write_text(json.dumps(rows, indent=2) + '\n') with (OUT / 'samples.csv').open('w') as file: @@ -218,19 +223,27 @@ def summarize(): parser = argparse.ArgumentParser() parser.add_argument('--validate-only', action='store_true') +parser.add_argument('--project', choices=[p['name'] for p in CONFIG['projects']]) +parser.add_argument('--scenario', choices=SCENARIOS) +parser.add_argument('--phase', choices=('time', 'memory')) options = parser.parse_args() +selected_projects = [p for p in CONFIG['projects'] if not options.project or p['name'] == options.project] +selected_scenarios = (options.scenario,) if options.scenario else SCENARIOS metadata() -phases = ('validate',) if options.validate_only else ('time', 'memory') -total = len(phases) * len(CONFIG['projects']) * len(SCENARIOS) +phases = ('validate',) if options.validate_only else (options.phase,) if options.phase else ('time', 'memory') +total = len(phases) * len(selected_projects) * len(selected_scenarios) completed = 0 for phase in phases: - for project in CONFIG['projects']: - for scenario in SCENARIOS: + for project in selected_projects: + for scenario in selected_scenarios: progress(phase, f"{project['name']}: {scenario}", completed, total) measure(project, scenario, phase) completed += 1 progress(phase, f"Completed {project['name']}: {scenario}", completed, total) if not options.validate_only: summarize() -assert len(RECORDS) == (12 if options.validate_only else 204), len(RECORDS) +expected_runs = len(selected_projects) * len(selected_scenarios) * sum( + 2 * (1 + (0 if phase == 'validate' else TIMING_PAIRS if phase == 'time' else MEMORY_PAIRS)) + for phase in phases) +assert len(RECORDS) == expected_runs, (len(RECORDS), expected_runs) progress('validated' if options.validate_only else 'measured', 'All output assertions passed', total, total) diff --git a/bench/tsrs-lint-codespaces/results/main-metadata.json b/bench/tsrs-lint-codespaces/results/main-metadata.json new file mode 100644 index 0000000000..84ee55482f --- /dev/null +++ b/bench/tsrs-lint-codespaces/results/main-metadata.json @@ -0,0 +1,52 @@ +{ + "vp_commit": "22b07bd150aa57b3e04cc52e0b81d43294ffe982", + "python": "3.10.12", + "psutil": "7.0.0", + "platform": "Linux-6.8.0-1064-azure-x86_64-with-glibc2.35", + "cpu_affinity": [0, 1, 2, 3], + "memory_bytes": 16770494464, + "timing_pairs": 10, + "memory_pairs": 5, + "sample_sleep_ms": 20, + "versions": { + "tsrs": "0.22.0-ts7.1.0-beta", + "oxlint": "1.91.0", + "oxlint-tsgolint": "7.0.2005" + }, + "node_versions": { + "vite-plus": "v24.22.2", + "vinext": "v26.13.0" + }, + "cpu_model": "AMD EPYC 9V74 80-Core Processor", + "rfcs_commit": "da260f86fce1d43810c62e4e6aad011066c88cb9", + "vinext_commit": "249088af307c0a852fa6dc59c08fc6ec80e1620e", + "job_id": "job-3dbd90be3d4f4aa3", + "validation_commands": 12, + "benchmark_records": 204, + "median_sampling_interval_ms": 21.781716749956104, + "sampling_interval_median_range_ms": [21.504334000383096, 24.079619999611168], + "full_check_signatures": { + "vite-plus": { + "files": 385, + "format_files": 632 + }, + "vinext": { + "files": 1828, + "format_files": 4673 + } + }, + "worker_result": { + "status": "succeeded", + "exit_code": 0, + "started_at": 1793866444.5705564, + "finished_at": 1793872607.508668 + }, + "excluded_timing_set": { + "project": "vinext", + "scenario": "full", + "phase": "time", + "reason": "Browser troubleshooting started editor extension activity during this timing set. A clean repetition replaces it." + }, + "harness_sha256": "5ae0087c81988829d74777717f4e06561aeb883c33017478f4ef33f2da85a42d", + "collected": true +} diff --git a/bench/tsrs-lint-codespaces/results/main-samples.csv b/bench/tsrs-lint-codespaces/results/main-samples.csv new file mode 100644 index 0000000000..394d9e3859 --- /dev/null +++ b/bench/tsrs-lint-codespaces/results/main-samples.csv @@ -0,0 +1,205 @@ +project,scenario,backend,phase,pair,seconds,peak_rss,exit +vite-plus,rules,go,time,-1,3.6880124389999764,,0 +vite-plus,rules,rust,time,-1,2.191973147999988,,0 +vite-plus,rules,go,time,0,3.713578874000177,,0 +vite-plus,rules,rust,time,0,2.2759946600001513,,0 +vite-plus,rules,rust,time,1,2.4920109750000847,,0 +vite-plus,rules,go,time,1,3.6422700799998893,,0 +vite-plus,rules,go,time,2,3.5738986520000253,,0 +vite-plus,rules,rust,time,2,2.242526223999903,,0 +vite-plus,rules,rust,time,3,2.223109660000091,,0 +vite-plus,rules,go,time,3,3.483162299000014,,0 +vite-plus,rules,go,time,4,3.7109736130000783,,0 +vite-plus,rules,rust,time,4,2.2263701169999877,,0 +vite-plus,rules,rust,time,5,2.2016010700001516,,0 +vite-plus,rules,go,time,5,3.9088208739999573,,0 +vite-plus,rules,go,time,6,3.736302202999923,,0 +vite-plus,rules,rust,time,6,2.187699441999939,,0 +vite-plus,rules,rust,time,7,2.1890677399999277,,0 +vite-plus,rules,go,time,7,3.5215749090000372,,0 +vite-plus,rules,go,time,8,3.7870844199999283,,0 +vite-plus,rules,rust,time,8,2.17189058200006,,0 +vite-plus,rules,rust,time,9,2.187169283000003,,0 +vite-plus,rules,go,time,9,3.839309356000058,,0 +vite-plus,full,go,time,-1,4.654679539999961,,0 +vite-plus,full,rust,time,-1,2.3562958489999346,,0 +vite-plus,full,go,time,0,4.566960510999934,,0 +vite-plus,full,rust,time,0,2.3931662680001864,,0 +vite-plus,full,rust,time,1,2.3698954330000106,,0 +vite-plus,full,go,time,1,4.634962337999923,,0 +vite-plus,full,go,time,2,4.632292471000028,,0 +vite-plus,full,rust,time,2,2.412981780000109,,0 +vite-plus,full,rust,time,3,2.430237735999981,,0 +vite-plus,full,go,time,3,4.53366976999996,,0 +vite-plus,full,go,time,4,4.307837832000132,,0 +vite-plus,full,rust,time,4,2.493581193999944,,0 +vite-plus,full,rust,time,5,2.3662074239998674,,0 +vite-plus,full,go,time,5,4.511783857999944,,0 +vite-plus,full,go,time,6,5.0457770020000225,,0 +vite-plus,full,rust,time,6,2.3568083630000274,,0 +vite-plus,full,rust,time,7,2.3296725099999094,,0 +vite-plus,full,go,time,7,4.698553215999937,,0 +vite-plus,full,go,time,8,4.591066730999955,,0 +vite-plus,full,rust,time,8,2.364594460999797,,0 +vite-plus,full,rust,time,9,2.456160010000076,,0 +vite-plus,full,go,time,9,4.855081535999943,,0 +vite-plus,check,go,time,-1,5.750060867999991,,0 +vite-plus,check,rust,time,-1,3.431430506999959,,0 +vite-plus,check,go,time,0,5.560635703999878,,0 +vite-plus,check,rust,time,0,3.5964835449999555,,0 +vite-plus,check,rust,time,1,3.4927115529999355,,0 +vite-plus,check,go,time,1,6.118377254000052,,0 +vite-plus,check,go,time,2,5.643318074000035,,0 +vite-plus,check,rust,time,2,3.5944409420001193,,0 +vite-plus,check,rust,time,3,3.4559823089998645,,0 +vite-plus,check,go,time,3,5.5692066710000745,,0 +vite-plus,check,go,time,4,5.942744820999906,,0 +vite-plus,check,rust,time,4,3.629871501000025,,0 +vite-plus,check,rust,time,5,3.5768438600000536,,0 +vite-plus,check,go,time,5,5.60959895100018,,0 +vite-plus,check,go,time,6,5.8363495700000385,,0 +vite-plus,check,rust,time,6,3.4873933159999524,,0 +vite-plus,check,rust,time,7,3.661024717999908,,0 +vite-plus,check,go,time,7,5.935824476999869,,0 +vite-plus,check,go,time,8,5.847994853000046,,0 +vite-plus,check,rust,time,8,3.4268209399999705,,0 +vite-plus,check,rust,time,9,3.530400184999962,,0 +vite-plus,check,go,time,9,6.113309405000109,,0 +vinext,rules,go,time,-1,52.948004241000035,,0 +vinext,rules,rust,time,-1,46.124062173000084,,0 +vinext,rules,go,time,0,52.21461774299996,,0 +vinext,rules,rust,time,0,46.608522696000136,,0 +vinext,rules,rust,time,1,46.39468327899999,,0 +vinext,rules,go,time,1,51.841172385999926,,0 +vinext,rules,go,time,2,52.348372229000006,,0 +vinext,rules,rust,time,2,43.62988959800009,,0 +vinext,rules,rust,time,3,44.699639799999886,,0 +vinext,rules,go,time,3,49.70923635600002,,0 +vinext,rules,go,time,4,53.68720330700012,,0 +vinext,rules,rust,time,4,43.043341374999954,,0 +vinext,rules,rust,time,5,46.44647001299995,,0 +vinext,rules,go,time,5,56.2561799030002,,0 +vinext,rules,go,time,6,55.63514441699999,,0 +vinext,rules,rust,time,6,42.57279852900001,,0 +vinext,rules,rust,time,7,45.58332635399984,,0 +vinext,rules,go,time,7,49.66620632200011,,0 +vinext,rules,go,time,8,51.518826092999916,,0 +vinext,rules,rust,time,8,45.142249451000225,,0 +vinext,rules,rust,time,9,46.268095335,,0 +vinext,rules,go,time,9,51.84008323399985,,0 +vinext,full,go,time,-1,54.97031217999984,,0 +vinext,full,rust,time,-1,49.50701832100003,,0 +vinext,full,go,time,0,54.54909113500025,,0 +vinext,full,rust,time,0,48.777478402999805,,0 +vinext,full,rust,time,1,50.565860768999755,,0 +vinext,full,go,time,1,54.497046376999606,,0 +vinext,full,go,time,2,55.068685018999986,,0 +vinext,full,rust,time,2,49.378554876999715,,0 +vinext,full,rust,time,3,46.8017662240004,,0 +vinext,full,go,time,3,54.68703606200006,,0 +vinext,full,go,time,4,56.505208954999944,,0 +vinext,full,rust,time,4,49.47823296100023,,0 +vinext,full,rust,time,5,46.74362833999976,,0 +vinext,full,go,time,5,55.63798557900009,,0 +vinext,full,go,time,6,54.669546999999966,,0 +vinext,full,rust,time,6,49.366139873000066,,0 +vinext,full,rust,time,7,48.29169880900008,,0 +vinext,full,go,time,7,60.90340383700004,,0 +vinext,full,go,time,8,58.05361229900018,,0 +vinext,full,rust,time,8,49.801143192999916,,0 +vinext,full,rust,time,9,48.88615762499967,,0 +vinext,full,go,time,9,56.70309664500019,,0 +vinext,check,go,time,-1,61.557492223999816,,0 +vinext,check,rust,time,-1,50.663133098000344,,0 +vinext,check,go,time,0,62.98640043899968,,0 +vinext,check,rust,time,0,48.491100012999595,,0 +vinext,check,rust,time,1,49.07317851800008,,0 +vinext,check,go,time,1,62.14544949099991,,0 +vinext,check,go,time,2,60.34417550099988,,0 +vinext,check,rust,time,2,50.885471571999915,,0 +vinext,check,rust,time,3,50.162232835000395,,0 +vinext,check,go,time,3,62.48697384699972,,0 +vinext,check,go,time,4,63.368093478000446,,0 +vinext,check,rust,time,4,50.91846492900004,,0 +vinext,check,rust,time,5,53.96345718400062,,0 +vinext,check,go,time,5,68.245969263,,0 +vinext,check,go,time,6,64.2242278700005,,0 +vinext,check,rust,time,6,52.53176643899951,,0 +vinext,check,rust,time,7,50.54870111699984,,0 +vinext,check,go,time,7,61.418192543999794,,0 +vinext,check,go,time,8,62.163307601999804,,0 +vinext,check,rust,time,8,47.784832813999856,,0 +vinext,check,rust,time,9,48.62056124399987,,0 +vinext,check,go,time,9,61.23984774499968,,0 +vite-plus,rules,go,memory,-1,3.940764267999839,1458286592,0 +vite-plus,rules,rust,memory,-1,2.329289043999779,1018667008,0 +vite-plus,rules,go,memory,0,3.857643793000534,1215549440,0 +vite-plus,rules,rust,memory,0,2.352922975000183,976420864,0 +vite-plus,rules,rust,memory,1,2.5503717050005434,1004322816,0 +vite-plus,rules,go,memory,1,3.7755272800004605,1337851904,0 +vite-plus,rules,go,memory,2,3.9427604910006266,1220345856,0 +vite-plus,rules,rust,memory,2,2.2911063749997993,1000955904,0 +vite-plus,rules,rust,memory,3,2.3516781999996965,998109184,0 +vite-plus,rules,go,memory,3,3.8952087559991924,1343397888,0 +vite-plus,rules,go,memory,4,3.78354734200002,1261670400,0 +vite-plus,rules,rust,memory,4,2.3114647920001516,964509696,0 +vite-plus,full,go,memory,-1,5.309363115000451,1652715520,0 +vite-plus,full,rust,memory,-1,2.525028889999703,1011937280,0 +vite-plus,full,go,memory,0,4.856635449999885,1443962880,0 +vite-plus,full,rust,memory,0,2.658091739000156,1061445632,0 +vite-plus,full,rust,memory,1,2.5839939859997685,1056391168,0 +vite-plus,full,go,memory,1,4.8830151010006375,1477206016,0 +vite-plus,full,go,memory,2,5.28511940000044,1383268352,0 +vite-plus,full,rust,memory,2,2.5511508569998114,1054629888,0 +vite-plus,full,rust,memory,3,2.5333675159999984,1069342720,0 +vite-plus,full,go,memory,3,5.299707000000126,1786830848,0 +vite-plus,full,go,memory,4,4.7314949120000165,1393152000,0 +vite-plus,full,rust,memory,4,2.576505080000061,1045348352,0 +vite-plus,check,go,memory,-1,5.992466878999949,1424461824,0 +vite-plus,check,rust,memory,-1,3.699139065000054,1118486528,0 +vite-plus,check,go,memory,0,5.935010570000486,1546260480,0 +vite-plus,check,rust,memory,0,3.6238119339996047,1078734848,0 +vite-plus,check,rust,memory,1,3.7297287820001657,1094008832,0 +vite-plus,check,go,memory,1,5.904669201999241,1786224640,0 +vite-plus,check,go,memory,2,6.3074621320001825,1534910464,0 +vite-plus,check,rust,memory,2,3.609159493000334,1111625728,0 +vite-plus,check,rust,memory,3,3.7218976559997827,1092087808,0 +vite-plus,check,go,memory,3,5.842184367999835,1543946240,0 +vite-plus,check,go,memory,4,6.1350453840004775,1427783680,0 +vite-plus,check,rust,memory,4,3.898626783000509,1090322432,0 +vinext,rules,go,memory,-1,54.02692431700052,4816293888,0 +vinext,rules,rust,memory,-1,46.12704977300018,3161968640,0 +vinext,rules,go,memory,0,53.78586441100015,4626669568,0 +vinext,rules,rust,memory,0,48.66075261300011,3163717632,0 +vinext,rules,rust,memory,1,48.704375655999684,3253788672,0 +vinext,rules,go,memory,1,53.11478282000007,4844425216,0 +vinext,rules,go,memory,2,50.72010573700027,5035069440,0 +vinext,rules,rust,memory,2,46.750776367999606,3171049472,0 +vinext,rules,rust,memory,3,43.352124744999855,3257614336,0 +vinext,rules,go,memory,3,55.42714346699995,4705017856,0 +vinext,rules,go,memory,4,50.09278948500014,4499263488,0 +vinext,rules,rust,memory,4,44.060889810000845,3327598592,0 +vinext,full,go,memory,-1,56.74663075999979,5482045440,0 +vinext,full,rust,memory,-1,49.11478131599961,3299753984,0 +vinext,full,go,memory,0,57.2608009810001,5504036864,0 +vinext,full,rust,memory,0,50.5766341210001,3399245824,0 +vinext,full,rust,memory,1,46.43882131400005,3354329088,0 +vinext,full,go,memory,1,56.82105735300047,5004701696,0 +vinext,full,go,memory,2,57.29950182000084,5265002496,0 +vinext,full,rust,memory,2,50.1835814720007,3529072640,0 +vinext,full,rust,memory,3,49.69551689400032,3426652160,0 +vinext,full,go,memory,3,57.23072275499999,4893655040,0 +vinext,full,go,memory,4,57.49116492400026,5388804096,0 +vinext,full,rust,memory,4,47.16479458400045,3357630464,0 +vinext,check,go,memory,-1,63.65577673000007,5786767360,0 +vinext,check,rust,memory,-1,50.898781433000295,3493175296,0 +vinext,check,go,memory,0,60.84386064899991,5591572480,0 +vinext,check,rust,memory,0,50.34470988399971,3572527104,0 +vinext,check,rust,memory,1,49.92382415800057,3534950400,0 +vinext,check,go,memory,1,61.628392914999495,6064586752,0 +vinext,check,go,memory,2,58.81841730999986,5774688256,0 +vinext,check,rust,memory,2,51.66727800700028,3535589376,0 +vinext,check,rust,memory,3,48.62187229600022,3533045760,0 +vinext,check,go,memory,3,63.150578589999895,6065008640,0 +vinext,check,go,memory,4,62.494274702999974,5812363264,0 +vinext,check,rust,memory,4,50.306470861999514,3523481600,0 diff --git a/bench/tsrs-lint-codespaces/results/summary.json b/bench/tsrs-lint-codespaces/results/summary.json new file mode 100644 index 0000000000..bdb9ccf395 --- /dev/null +++ b/bench/tsrs-lint-codespaces/results/summary.json @@ -0,0 +1,98 @@ +[ + { + "project": "vite-plus", + "scenario": "rules", + "go_seconds": 3.7122762435001277, + "go_seconds_range": [3.483162299000014, 3.9088208739999573], + "go_peak_rss": 1261670400, + "go_peak_rss_range": [1215549440, 1343397888], + "rust_seconds": 2.2123553650001213, + "rust_seconds_range": [2.17189058200006, 2.4920109750000847], + "rust_peak_rss": 998109184, + "rust_peak_rss_range": [964509696, 1004322816], + "time_saved_percent": 40.40434439991467, + "rss_saved_percent": 20.88986283580878, + "timing_job": "job-3dbd90be3d4f4aa3", + "memory_job": "job-3dbd90be3d4f4aa3" + }, + { + "project": "vite-plus", + "scenario": "full", + "go_seconds": 4.611679600999992, + "go_seconds_range": [4.307837832000132, 5.0457770020000225], + "go_peak_rss": 1443962880, + "go_peak_rss_range": [1383268352, 1786830848], + "rust_seconds": 2.3815308505000985, + "rust_seconds_range": [2.3296725099999094, 2.493581193999944], + "rust_peak_rss": 1056391168, + "rust_peak_rss_range": [1045348352, 1069342720], + "time_saved_percent": 48.35870969909336, + "rss_saved_percent": 26.8408362408873, + "timing_job": "job-3dbd90be3d4f4aa3", + "memory_job": "job-3dbd90be3d4f4aa3" + }, + { + "project": "vite-plus", + "scenario": "check", + "go_seconds": 5.842172211500042, + "go_seconds_range": [5.560635703999878, 6.118377254000052], + "go_peak_rss": 1543946240, + "go_peak_rss_range": [1427783680, 1786224640], + "rust_seconds": 3.553622022500008, + "rust_seconds_range": [3.4268209399999705, 3.661024717999908], + "rust_peak_rss": 1092087808, + "rust_peak_rss_range": [1078734848, 1111625728], + "time_saved_percent": 39.172932706350736, + "rss_saved_percent": 29.26646150580995, + "timing_job": "job-3dbd90be3d4f4aa3", + "memory_job": "job-3dbd90be3d4f4aa3" + }, + { + "project": "vinext", + "scenario": "rules", + "go_seconds": 52.027895064499944, + "go_seconds_range": [49.66620632200011, 56.2561799030002], + "go_peak_rss": 4705017856, + "go_peak_rss_range": [4499263488, 5035069440], + "rust_seconds": 45.36278790250003, + "rust_seconds_range": [42.57279852900001, 46.608522696000136], + "rust_peak_rss": 3253788672, + "rust_peak_rss_range": [3163717632, 3327598592], + "time_saved_percent": 12.810641587050675, + "rss_saved_percent": 30.84428642814485, + "timing_job": "job-3dbd90be3d4f4aa3", + "memory_job": "job-3dbd90be3d4f4aa3" + }, + { + "project": "vinext", + "scenario": "full", + "go_seconds": 54.19228503350001, + "go_seconds_range": [51.108869654000046, 55.4913207269999], + "go_peak_rss": 5265002496, + "go_peak_rss_range": [4893655040, 5504036864], + "rust_seconds": 44.79370169149996, + "rust_seconds_range": [42.42826828800003, 46.32845908899998], + "rust_peak_rss": 3399245824, + "rust_peak_rss_range": [3354329088, 3529072640], + "time_saved_percent": 17.343028322555753, + "rss_saved_percent": 35.436957027417904, + "timing_job": "job-4685d3c6e32f48bf", + "memory_job": "job-3dbd90be3d4f4aa3" + }, + { + "project": "vinext", + "scenario": "check", + "go_seconds": 62.32514072449976, + "go_seconds_range": [60.34417550099988, 68.245969263], + "go_peak_rss": 5812363264, + "go_peak_rss_range": [5591572480, 6065008640], + "rust_seconds": 50.355466976000116, + "rust_seconds_range": [47.784832813999856, 53.96345718400062], + "rust_peak_rss": 3534950400, + "rust_peak_rss_range": [3523481600, 3572527104], + "time_saved_percent": 19.205209341459884, + "rss_saved_percent": 39.18221832598796, + "timing_job": "job-3dbd90be3d4f4aa3", + "memory_job": "job-3dbd90be3d4f4aa3" + } +] diff --git a/bench/tsrs-lint-codespaces/results/timing-rerun-metadata.json b/bench/tsrs-lint-codespaces/results/timing-rerun-metadata.json new file mode 100644 index 0000000000..083a981844 --- /dev/null +++ b/bench/tsrs-lint-codespaces/results/timing-rerun-metadata.json @@ -0,0 +1,36 @@ +{ + "vp_commit": "22b07bd150aa57b3e04cc52e0b81d43294ffe982", + "python": "3.10.12", + "psutil": "7.0.0", + "platform": "Linux-6.8.0-1064-azure-x86_64-with-glibc2.35", + "cpu_affinity": [0, 1, 2, 3], + "memory_bytes": 16770494464, + "timing_pairs": 10, + "versions": { + "tsrs": "0.22.0-ts7.1.0-beta", + "oxlint": "1.91.0", + "oxlint-tsgolint": "7.0.2005" + }, + "node_versions": { + "vite-plus": "v24.22.2", + "vinext": "v26.13.0" + }, + "selection": { + "validate_only": false, + "project": "vinext", + "scenario": "full", + "phase": "time" + }, + "cpu_model": "AMD EPYC 7763 64-Core Processor", + "job_id": "job-4685d3c6e32f48bf", + "records": 22, + "worker_result": { + "status": "succeeded", + "exit_code": 0, + "started_at": 1793893477.6474826, + "finished_at": 1793894561.3346298 + }, + "harness_sha256": "734df51a34c3f202dad7e007648a1ca718daa3e9c2a26d5d0c9ba32a952992f8", + "collected": true, + "shutdown_confirmed_at": 1793894671.662227 +} diff --git a/bench/tsrs-lint-codespaces/results/timing-rerun-samples.csv b/bench/tsrs-lint-codespaces/results/timing-rerun-samples.csv new file mode 100644 index 0000000000..c82e7ce8c5 --- /dev/null +++ b/bench/tsrs-lint-codespaces/results/timing-rerun-samples.csv @@ -0,0 +1,23 @@ +project,scenario,backend,phase,pair,seconds,peak_rss,exit +vinext,full,go,time,-1,53.405041512000025,,0 +vinext,full,rust,time,-1,45.34521164800003,,0 +vinext,full,go,time,0,54.13607682600002,,0 +vinext,full,rust,time,0,45.48383163799997,,0 +vinext,full,rust,time,1,46.32845908899998,,0 +vinext,full,go,time,1,54.58117138299997,,0 +vinext,full,go,time,2,51.108869654000046,,0 +vinext,full,rust,time,2,42.42826828800003,,0 +vinext,full,rust,time,3,45.07651155499991,,0 +vinext,full,go,time,3,52.29078975899995,,0 +vinext,full,go,time,4,54.71693397199999,,0 +vinext,full,rust,time,4,44.51089182800001,,0 +vinext,full,rust,time,5,45.55147170500004,,0 +vinext,full,go,time,5,54.248493241000006,,0 +vinext,full,go,time,6,54.310657021,,0 +vinext,full,rust,time,6,42.593737007999835,,0 +vinext,full,rust,time,7,44.00615199700019,,0 +vinext,full,go,time,7,55.4913207269999,,0 +vinext,full,go,time,8,54.02196162600012,,0 +vinext,full,rust,time,8,43.040702052999904,,0 +vinext,full,rust,time,9,45.36723157000006,,0 +vinext,full,go,time,9,53.41371220799988,,0 diff --git a/bench/tsrs-lint.md b/bench/tsrs-lint.md index 6543ba2418..b91343953d 100644 --- a/bench/tsrs-lint.md +++ b/bench/tsrs-lint.md @@ -1,26 +1,101 @@ # Type-aware linting with tsrs-lint -On the Vite+ workspace at `a32a8cec2` with the benchmark script added, the experimental backend saved 0.51 seconds per lint run with full type checking. Both backends analyzed 385 files with 174 rules, reported zero diagnostics, and exited successfully on every run. +This comparison uses the default `tsrs@0.22.0-ts7.1.0-beta` integration and an explicit +`oxlint-tsgolint@7.0.2005` override. Both use Oxlint `1.91.0` and the same built CLI. -| `vp lint` workload | Bundled tsgolint | tsrs-lint | Wall time saved | -| ------------------------------------------- | ---------------: | --------: | --------------: | -| Type-aware rules | 1.089 s | 0.822 s | 24.5% | -| Type-aware rules and TypeScript diagnostics | 1.340 s | 0.830 s | 38.1% | +## Elapsed time -For the full `vp check` command, the median fell from 1.852 s to 1.335 s (27.9%). This command also checked formatting on 632 files and used its default 16 threads. +Each value is the median of ten fresh CLI processes after one warm-up per backend. +The runner alternates the order between pairs. Timing runs do not sample memory. -Measured on an Apple M4 Max with macOS 27.0.1, Node.js 24.22.2, Vite+ 1.5.0, Oxlint 1.91.0, oxlint-tsgolint 7.0.2005, and tsrs 0.21.0-ts7.1.0-beta. The benchmark used the published Vite+ 1.5.0 CLI artifacts and the workspace's locked dependencies. Each sample started a fresh local CLI process and included config loading, JS plugins, the backend launcher, and diagnostic reporting. The `vp lint` runs used four Oxlint threads. Both backends used their default settings. +| Project | Workload | Go (s) | tsrs (s) | Reduction | +| -------- | -------------------------------- | -----: | -------: | --------: | +| Vite+ | Type-aware rules | 3.712 | 2.212 | 40.4% | +| Vite+ | Rules and TypeScript diagnostics | 4.612 | 2.382 | 48.4% | +| Vite+ | Full `vp check` | 5.842 | 3.554 | 39.2% | +| vinext | Type-aware rules | 52.028 | 45.363 | 12.8% | +| vinext † | Rules and TypeScript diagnostics | 54.192 | 44.794 | 17.3% | +| vinext | Full `vp check` | 62.325 | 50.355 | 19.2% | -The table shows the median of ten runs per backend after one warm-up each. The runner reverses the backend order between pairs and compares the exit code, file and rule counts, and sorted JSON diagnostics for every run. In a separate failing fixture, both backends reported `typescript/no-floating-promises` and `TS2322` at the same locations through `vp lint` and `vp check`. +† The clean timing repetition used an EPYC 7763 after a Codespace restart. +All other timing rows used an EPYC 9V74. Both backends within each row used +the same CPU. Compare backend values within a row; the CPU change prevents +a strict comparison of absolute times across workloads. -## Reproduce +## Peak memory -Start in a built Vite+ checkout. The runner explicitly selects the `oxlint-tsgolint` development dependency for the Go baseline, even when the CLI defaults to tsrs. Install the candidate backend under the ignored `tmp` directory to compare a specific version. +Each value is the median of five separate runs after one warm-up per backend. +Memory is the sampled peak of the simultaneous sum of RSS for the CLI and its +descendants, including Node, Oxlint, and the native backend. One GiB is 1,073,741,824 bytes. -```sh -npm install --prefix ./tmp/tsrs-lint --no-save --package-lock=false tsrs@0.21.0-ts7.1.0-beta -export OXLINT_TSGOLINT_PATH="$PWD/tmp/tsrs-lint/node_modules/.bin/tsrs-lint" -node bench/tsrs-lint.ts --type-check -``` +| Project | Workload | Go (GiB) | tsrs (GiB) | Reduction | +| ------- | -------------------------------- | -------: | ---------: | --------: | +| Vite+ | Type-aware rules | 1.175 | 0.930 | 20.9% | +| Vite+ | Rules and TypeScript diagnostics | 1.345 | 0.984 | 26.8% | +| Vite+ | Full `vp check` | 1.438 | 1.017 | 29.3% | +| vinext | Type-aware rules | 4.382 | 3.030 | 30.8% | +| vinext | Rules and TypeScript diagnostics | 4.903 | 3.166 | 35.4% | +| vinext | Full `vp check` | 5.413 | 3.292 | 39.2% | -For the rules-only row, temporarily set `lint.options.typeCheck` to `false` in the root `vite.config.ts`, run `node bench/tsrs-lint.ts` without `--type-check`, then restore the setting. The benchmark always enables `--type-aware` and prints the individual samples in seconds. +The sampler sleeps for 20 ms. The median observed interval across runs was +21.8 ms. Shared pages can count more than once, +and sampling can miss short peaks. These values are process-tree RSS, not total +allocation or container memory. Per-case ranges are in the [summary](./tsrs-lint-codespaces/results/summary.json). + +## Conditions and interpretation + +The jobs used one GitHub Codespace with four vCPUs, 16 GB advertised RAM, and an +AMD EPYC 9V74 processor for the main run. The focused timing repetition used an +AMD EPYC 7763 after the instance restarted. Usable RAM was 15.62 GiB. +The container used Ubuntu 22.04 on Linux x64 with glibc. Vite+ used Node.js +`24.22.2`; vinext used Node.js `26.13.0`. + +The CLI JavaScript came from `22b07bd150aa57b3e04cc52e0b81d43294ffe982`. +Its native binding, core, and prompts artifacts came from the published Vite+ +`1.5.0` packages. The measured Vite+ source also includes the RFC patch at +`da260f86fce1d43810c62e4e6aad011066c88cb9`. Vinext is pinned to +`249088af307c0a852fa6dc59c08fc6ec80e1620e`, from the requested upgrade branch. +This is a comparison of backend routing in the built CLI, not a full native release build. + +Lint uses four threads. Vinext lint sets +`OXLINT_TSGOLINT_CHECKER_SCHEDULING=sorted`; Vite+ lint uses default scheduling. +Full checks use four threads and default backend scheduling. The rules-only +configuration disables TypeScript diagnostics while retaining the project's +original rules and plugins. Setup, builds, validation, and memory sampling are +excluded from the elapsed-time table. + +Both backends passed all 12 validation commands and every benchmark invocation. +Vite+ checked 385 source files with 174 rules and formatted 632 files. Vinext +checked 1,828 source files with 151 rules and formatted 4,673 files. These clean +corpora do not establish complete diagnostic or compiler parity. + +Vinext retains its original JS plugin rules, including +`vinext-utils/prefer-shared-utils`. The backend switch leaves that shared work +in place. This limits the elapsed-time improvement even though the memory +reduction is larger. The measurements do not include that rule's separate optimization. + +## Evidence and reproduction + +Use the [Codespaces recipe](./tsrs-lint-codespaces/README.md) to recreate the pinned +setup and all six comparisons. The recipe uses frozen lockfiles and retains +verified dependencies between jobs. + +The [main samples](./tsrs-lint-codespaces/results/main-samples.csv) contain all 204 +invocations, including warm-ups. A browser connection used to diagnose an SSH +outage started editor extension activity during vinext's type-checking timing +set. That entire timing set is excluded. The table instead uses the 22 records +from the [clean timing repetition](./tsrs-lint-codespaces/results/timing-rerun-samples.csv). +The repetition uses the same retained build and dependencies, with their hashes +checked before execution. All memory values come from the main job. + +Records with `pair=-1` are warm-ups and do not contribute to medians. For the +vinext `full`/`time` group, select the repetition CSV; select the main CSV for +all other groups. Take each backend's median `seconds` or `peak_rss` value and +calculate the reduction as `100 * (1 - rust / go)`. + +The [main metadata](./tsrs-lint-codespaces/results/main-metadata.json) and +[repetition metadata](./tsrs-lint-codespaces/results/timing-rerun-metadata.json) +record versions, hardware, source revisions, and successful worker results. +The main worker finished while the controller was disconnected; its complete +artifacts were recovered from the stopped Codespace. The jobs are `job-3dbd90be3d4f4aa3` and +`job-4685d3c6e32f48bf`. From a9912b4f8c5030bf5e20f1106d0e1c38011bd135 Mon Sep 17 00:00:00 2001 From: MK Date: Fri, 6 Nov 2026 00:29:35 +0800 Subject: [PATCH 07/21] test(cli): update snapshots for the tsrs backend --- .../snapshots/cli_helper_message.md | 14 +++++++------- .../snapshots/toolchain_filters.md | 2 +- .../snapshots/toolchain_full_global.md | 2 +- .../snapshots/toolchain_full_local.md | 2 +- .../snapshots/toolchain_old_local_routing.md | 4 ++-- .../snapshots/command_version_local_tools.md | 14 +++++++------- .../snapshots/command_version_no_side_effects.md | 14 +++++++------- .../snapshots/command_version_with_env.md | 14 +++++++------- 8 files changed, 33 insertions(+), 33 deletions(-) diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/cli_helper_message/snapshots/cli_helper_message.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/cli_helper_message/snapshots/cli_helper_message.md index 9fc35cf1f6..db73ff1f06 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/cli_helper_message/snapshots/cli_helper_message.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/cli_helper_message/snapshots/cli_helper_message.md @@ -77,13 +77,13 @@ Local vite-plus: vite-plus Tools: - vite - rolldown - vitest - oxfmt - oxlint - oxlint-tsgolint - tsdown + vite + rolldown + vitest + oxfmt + oxlint + tsrs + tsdown Environment: Package manager Not found diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_toolchain/snapshots/toolchain_filters.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_toolchain/snapshots/toolchain_filters.md index c189dfbc67..f674bbb61f 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_toolchain/snapshots/toolchain_filters.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_toolchain/snapshots/toolchain_filters.md @@ -40,6 +40,6 @@ Vite+ toolchain (local) vite-plus@ ├── depends on @voidzero-dev/vite-plus-core@ -├── depends on oxlint-tsgolint@ +├── depends on tsrs@ └── compiles vite-task (built , revision ) ``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_toolchain/snapshots/toolchain_full_global.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_toolchain/snapshots/toolchain_full_global.md index 209b74891e..860c74f1da 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_toolchain/snapshots/toolchain_full_global.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_toolchain/snapshots/toolchain_full_global.md @@ -19,7 +19,7 @@ vite-plus@ │ └── bundles tsdown@ ├── depends on vitest@ ├── depends on oxlint@ -├── depends on oxlint-tsgolint@ +├── depends on tsrs@ ├── depends on oxfmt@ ├── compiles vite-task (built , revision ) └── compiles oxc-node@ () diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_toolchain/snapshots/toolchain_full_local.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_toolchain/snapshots/toolchain_full_local.md index a983347b5a..e125bb8365 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_toolchain/snapshots/toolchain_full_local.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_toolchain/snapshots/toolchain_full_local.md @@ -19,7 +19,7 @@ vite-plus@ │ └── bundles tsdown@ ├── depends on vitest@ ├── depends on oxlint@ -├── depends on oxlint-tsgolint@ +├── depends on tsrs@ ├── depends on oxfmt@ ├── compiles vite-task (built , revision ) └── compiles oxc-node@ () diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_toolchain/snapshots/toolchain_old_local_routing.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_toolchain/snapshots/toolchain_old_local_routing.md index b98a86b6cb..727e750ae0 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_toolchain/snapshots/toolchain_old_local_routing.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_toolchain/snapshots/toolchain_old_local_routing.md @@ -48,7 +48,7 @@ vite-plus@ │ └── bundles tsdown@ ├── depends on vitest@ ├── depends on oxlint@ -├── depends on oxlint-tsgolint@ +├── depends on tsrs@ ├── depends on oxfmt@ ├── compiles vite-task (built , revision ) └── compiles oxc-node@ () @@ -85,7 +85,7 @@ vite-plus@ │ └── bundles tsdown@ ├── depends on vitest@ ├── depends on oxlint@ -├── depends on oxlint-tsgolint@ +├── depends on tsrs@ ├── depends on oxfmt@ ├── compiles vite-task (built , revision ) └── compiles oxc-node@ () diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_version_local_tools/snapshots/command_version_local_tools.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_version_local_tools/snapshots/command_version_local_tools.md index 3cd9eaedb6..bbb0a67908 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_version_local_tools/snapshots/command_version_local_tools.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_version_local_tools/snapshots/command_version_local_tools.md @@ -13,11 +13,11 @@ Local vite-plus: vite-plus Tools: - vite - rolldown - vitest - oxfmt - oxlint - oxlint-tsgolint - tsdown + vite + rolldown + vitest + oxfmt + oxlint + tsrs + tsdown ``` diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_version_no_side_effects/snapshots/command_version_no_side_effects.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_version_no_side_effects/snapshots/command_version_no_side_effects.md index 8b90bb1474..74521cd9d4 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_version_no_side_effects/snapshots/command_version_no_side_effects.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_version_no_side_effects/snapshots/command_version_no_side_effects.md @@ -13,13 +13,13 @@ Local vite-plus: vite-plus Tools: - vite - rolldown - vitest - oxfmt - oxlint - oxlint-tsgolint - tsdown + vite + rolldown + vitest + oxfmt + oxlint + tsrs + tsdown Environment: Package manager Not found diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_version_with_env/snapshots/command_version_with_env.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_version_with_env/snapshots/command_version_with_env.md index e31aa008c4..6794e89a27 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_version_with_env/snapshots/command_version_with_env.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_version_with_env/snapshots/command_version_with_env.md @@ -11,13 +11,13 @@ Local vite-plus: vite-plus Tools: - vite - rolldown - vitest - oxfmt - oxlint - oxlint-tsgolint - tsdown + vite + rolldown + vitest + oxfmt + oxlint + tsrs + tsdown Environment: Package manager pnpm From 191c963ad68920cfa3eb7a668fcf59cd76e91dd0 Mon Sep 17 00:00:00 2001 From: MK Date: Fri, 6 Nov 2026 09:51:13 +0800 Subject: [PATCH 08/21] docs(lint): remove backend switching instructions --- docs/guide/lint.md | 11 ----------- rfcs/check-command.md | 7 +------ 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/docs/guide/lint.md b/docs/guide/lint.md index 69538b86d3..6e37103417 100644 --- a/docs/guide/lint.md +++ b/docs/guide/lint.md @@ -51,17 +51,6 @@ We recommend enabling both `typeAware` and `typeCheck` in the `lint` block: This experimental build uses [tsrs](https://github.com/oxc-project/tsrs), a Rust port of the TypeScript 7 compiler. Its `tsrs-lint` executable gives Oxlint access to type information and allows type checking directly via `vp lint` and `vp check`. -### Select another backend - -The bundled `tsrs-lint` runs by default. To compare it with [tsgolint](https://github.com/oxc-project/tsgolint), install the Go backend and set `OXLINT_TSGOLINT_PATH`. Run these commands from the project root. - -```sh -vp add -D oxlint-tsgolint -OXLINT_TSGOLINT_PATH="$PWD/node_modules/.bin/tsgolint" vp lint --type-aware --type-check -``` - -The same environment variable works with `vp check` when type-aware linting is enabled. Omit it to use the bundled tsrs again. You can also point it at another `tsrs-lint` installation. - The pinned `tsrs` release does not provide a Windows ARM64 binary. This experimental replacement does not support that platform yet. ## JS Plugins diff --git a/rfcs/check-command.md b/rfcs/check-command.md index dff09b084b..90bb4f4c44 100644 --- a/rfcs/check-command.md +++ b/rfcs/check-command.md @@ -210,12 +210,7 @@ Users do not need to set an environment variable for the default backend. An explicit `OXLINT_TSGOLINT_PATH` takes precedence over the resolver default. The environment variable keeps its existing name because Oxlint uses it to -select the type-aware backend. A project can still select Go explicitly: - -```sh -vp add -D oxlint-tsgolint -OXLINT_TSGOLINT_PATH="$PWD/node_modules/.bin/tsgolint" vp check -``` +select the type-aware backend. `tsrs` is a production dependency. `oxlint-tsgolint` remains a development dependency for comparisons. The benchmark selects the Go executable explicitly, From 7e1c8dc8333fa65ac352c21400a5ac759921fed8 Mon Sep 17 00:00:00 2001 From: MK Date: Fri, 6 Nov 2026 09:58:50 +0800 Subject: [PATCH 09/21] chore(bench): keep Codespaces artifacts out of the PR --- bench/tsrs-lint-codespaces/README.md | 111 -------- bench/tsrs-lint-codespaces/inputs/bench.py | 249 ------------------ bench/tsrs-lint-codespaces/inputs/docs.py | 11 - bench/tsrs-lint-codespaces/inputs/docs.sh | 1 - bench/tsrs-lint-codespaces/inputs/run.sh | 1 - bench/tsrs-lint-codespaces/inputs/setup.py | 150 ----------- bench/tsrs-lint-codespaces/inputs/setup.sh | 1 - bench/tsrs-lint-codespaces/inputs/validate.sh | 1 - .../inputs/verify-setup.py | 6 - .../inputs/verify-setup.sh | 1 - .../results/main-metadata.json | 52 ---- .../results/main-samples.csv | 205 -------------- .../tsrs-lint-codespaces/results/summary.json | 98 ------- .../results/timing-rerun-metadata.json | 36 --- .../results/timing-rerun-samples.csv | 23 -- bench/tsrs-lint-codespaces/stages.json | 46 ---- bench/tsrs-lint.md | 27 +- 17 files changed, 7 insertions(+), 1012 deletions(-) delete mode 100644 bench/tsrs-lint-codespaces/README.md delete mode 100644 bench/tsrs-lint-codespaces/inputs/bench.py delete mode 100644 bench/tsrs-lint-codespaces/inputs/docs.py delete mode 100644 bench/tsrs-lint-codespaces/inputs/docs.sh delete mode 100644 bench/tsrs-lint-codespaces/inputs/run.sh delete mode 100644 bench/tsrs-lint-codespaces/inputs/setup.py delete mode 100644 bench/tsrs-lint-codespaces/inputs/setup.sh delete mode 100644 bench/tsrs-lint-codespaces/inputs/validate.sh delete mode 100644 bench/tsrs-lint-codespaces/inputs/verify-setup.py delete mode 100644 bench/tsrs-lint-codespaces/inputs/verify-setup.sh delete mode 100644 bench/tsrs-lint-codespaces/results/main-metadata.json delete mode 100644 bench/tsrs-lint-codespaces/results/main-samples.csv delete mode 100644 bench/tsrs-lint-codespaces/results/summary.json delete mode 100644 bench/tsrs-lint-codespaces/results/timing-rerun-metadata.json delete mode 100644 bench/tsrs-lint-codespaces/results/timing-rerun-samples.csv delete mode 100644 bench/tsrs-lint-codespaces/stages.json diff --git a/bench/tsrs-lint-codespaces/README.md b/bench/tsrs-lint-codespaces/README.md deleted file mode 100644 index 6676e5c19d..0000000000 --- a/bench/tsrs-lint-codespaces/README.md +++ /dev/null @@ -1,111 +0,0 @@ -# Codespaces comparison of lint backends - -This recipe compares the explicit Go override with the default tsrs backend in -the same built CLI. It runs three workloads on both Vite+ and vinext: type-aware -rules, rules with TypeScript diagnostics, and the full `vp check` command. - -Use the [codespaces-run skill](https://github.com/Dunqing/skills/tree/main/skills/codespaces-run). -The recipe uses one `standardLinux32gb` instance with four CPUs and 16 GB of RAM. -The skill retains the environment and stops the instance after artifact collection. -Do not run other builds or benchmarks on the instance during measurement. -The CPU model can change when the same Codespace restarts. Inspect each job's -metadata before combining results. Keep both backends of a comparison in the -same job, and identify any hardware change in the report. - -## Source and setup - -The measured CLI source is `22b07bd150aa57b3e04cc52e0b81d43294ffe982`. -The RFC patch from `da260f86fce1d43810c62e4e6aad011066c88cb9` preserves the -documentation edits present when this comparison started. The vinext corpus is -`249088af307c0a852fa6dc59c08fc6ec80e1620e` from `fengmk2/vinext`. - -Setup installs Node.js `24.22.2` for Vite+ and `26.13.0` for vinext. It installs -the package managers declared by those revisions: pnpm `12.11.2` and `11.1.1`. -Both projects and Vite+'s separate documentation workspace use frozen lockfiles. -The Python environment pins `psutil==7.0.0`. -Node downloads are checked against the official SHA256 files. - -The recipe builds the PR's CLI JavaScript. It uses the published Vite+ `1.5.0` -native binding, core, and prompts packages, plus the matching Rolldown binding. -It does not compile a complete native release. The setup receipt records hashes -of the CLI entry point, native binding, Node executables, and benchmark config. - -The repository's Ubuntu devcontainer can lack an SSH server. If `gh codespace ssh` -reports that problem, open the existing Codespace's web terminal and install -`openssh-server` with `sudo apt-get update` and -`sudo apt-get install -y --no-install-recommends openssh-server`. -Keep the repository's devcontainer configuration unchanged. Setup installs -`python3-venv` only if Python cannot import `ensurepip`. - -## Run - -Run these commands from this checkout. The temporary input directory stays -outside the source tree so it cannot change the measured file set. - -```sh -tsrs_skill_tool="${CODEX_HOME:-$HOME/.codex}/skills/codespaces-run/scripts/sandbox.py" -tsrs_bench_inputs="$(mktemp -d)" -cp bench/tsrs-lint-codespaces/inputs/* "$tsrs_bench_inputs/" -git diff 22b07bd150aa57b3e04cc52e0b81d43294ffe982 da260f86fce1d43810c62e4e6aad011066c88cb9 \ - -- rfcs/check-command.md rfcs/toolchain-command.md rfcs/migration-command.md \ - > "$tsrs_bench_inputs/rfcs.patch" - -python3 "$tsrs_skill_tool" submit \ - --repo voidzero-dev/vite-plus \ - --git-url https://github.com/voidzero-dev/vite-plus.git \ - --git-commit 22b07bd150aa57b3e04cc52e0b81d43294ffe982 \ - --stages "$PWD/bench/tsrs-lint-codespaces/stages.json" \ - --inputs "$tsrs_bench_inputs" \ - --machine standardLinux32gb --cpus 4 --min-memory-mib 14000 \ - --timeout 7200 --profile default \ - --environment-key tsrs-022-linux-x64-node24.22.2-26.13.0-pnpm12.11.2-11.1.1 - -python3 "$tsrs_skill_tool" events JOB_ID --follow -``` - -Use the returned job ID in the last command. Check the final receipt for -`collected: true`, a shutdown timestamp, and no cleanup error. Logs, raw samples, -memory traces, and results are retained under the skill's local job directory. -If a later stage fails, use the skill's stage recovery procedure to retain a -verified setup. Do not start another job while the previous job holds the lease. - -The runner also accepts `--project`, `--scenario`, and `--phase` filters. For a -focused repetition, submit a continuation after the prior job stops. Its script -can run the following commands with the recipe's input files: - -```sh -python3 "$JOB_INPUT_DIR/verify-setup.py" -"$JOB_ENV_DIR/venv/bin/python" "$JOB_INPUT_DIR/bench.py" \ - --project vinext --scenario full --phase time -``` - -This selection produces 22 timing records: ten pairs and two warm-ups. Keep the -new job's metadata and raw records when it replaces a previous timing set. - -## Method - -Validation runs each backend once for each workload before measurement. Every -run must exit successfully with no diagnostics and the expected file and rule -counts. Full checks must also report successful formatting. Memory runs check -the native executable identity to detect accidental comparisons of one backend -against itself. - -Timing uses ten pairs after one warm-up per backend. Each pair reverses the -previous pair's order. Memory uses a separate five-pair phase with its own -warm-ups. Each invocation starts a fresh CLI process. Installation, builds, -validation, and memory sampling are excluded from the timing table. - -The memory sampler records the simultaneous sum of resident set size (RSS) for -the CLI and its descendants. It sleeps for 20 ms between samples and records the -actual intervals. This includes Node, Oxlint, and the native type-aware backend. -Shared pages can count more than once, and sampling can miss short peaks. The -result is a sampled process-tree peak, not total allocation or container memory. - -Lint uses four threads. Vinext lint uses `OXLINT_TSGOLINT_CHECKER_SCHEDULING=sorted`, -as in the earlier comparison. Full checks use the machine's default thread count -and backend scheduling. The rules-only configuration disables `typeCheck` while -preserving the project's original lint rules and plugins. - -The summary reports medians and ranges. Warm-up samples have `pair=-1` and do -not contribute to the medians. Each full run produces 204 records, including -warm-ups, plus the separate 12-command validation phase. diff --git a/bench/tsrs-lint-codespaces/inputs/bench.py b/bench/tsrs-lint-codespaces/inputs/bench.py deleted file mode 100644 index 846ba77bc2..0000000000 --- a/bench/tsrs-lint-codespaces/inputs/bench.py +++ /dev/null @@ -1,249 +0,0 @@ -import argparse -import csv -import hashlib -import json -import os -import pathlib -import platform -import re -import statistics -import subprocess -import tempfile -import time - -import psutil - -ENV = pathlib.Path(os.environ['JOB_ENV_DIR']) -OUT = pathlib.Path(os.environ['ARTIFACTS_DIR']) -CONFIG = json.loads((ENV / 'bench-config.json').read_text()) -BASE_ENV = {key: value for key, value in os.environ.items() - if not key.startswith(('OXLINT_TSGOLINT', 'TSRS_')) - and key not in ('NODE_OPTIONS', 'NODE_PATH', 'GITHUB_ACTIONS')} -EXPECTED = {} -RECORDS = [] -SCENARIOS = ('rules', 'full', 'check') -TIMING_PAIRS = 10 -MEMORY_PAIRS = 5 - - -def progress(phase, message, completed=0, total=0): - value = {'phase': phase, 'message': message, 'completed': completed, 'total': total} - subprocess.run(['python3', os.environ['JOB_WORKER'], 'progress', os.environ['JOB_DIR'], json.dumps(value)], check=True) - print(json.dumps(value), flush=True) - - -def environment(project, scenario, backend): - env = BASE_ENV | { - 'PATH': str(pathlib.Path(project['node']).parent) + ':' + BASE_ENV['PATH'], - 'VP_HOME': str(ENV / 'vp-home'), 'VP_SELF_SETUP_NO_MODIFY_PATH': '1', - } - if scenario != 'check' and project['scheduling']: - env['OXLINT_TSGOLINT_CHECKER_SCHEDULING'] = project['scheduling'] - if backend == 'go': - env['OXLINT_TSGOLINT_PATH'] = CONFIG['go'] - return env - - -def lint_config(project): - code = ''' -import { createRequire } from 'node:module'; -import { pathToFileURL } from 'node:url'; -const require = createRequire(process.argv[1]); -const { loadConfigFromFile } = await import(pathToFileURL(require.resolve('vite')).href); -const result = await loadConfigFromFile({ command: 'serve', mode: 'development' }, undefined, process.cwd(), 'silent'); -if (!result?.config?.lint) throw new Error('Missing lint configuration'); -process.stdout.write(JSON.stringify(result.config.lint)); -''' - cli_package = str(pathlib.Path(CONFIG['cli']).parent.parent / 'package.json') - env = environment(project, 'rules', 'rust') | {'VP_RESOLVING_CONFIG_METADATA': '1'} - output = subprocess.check_output([project['node'], '--input-type=module', '-e', code, cli_package], cwd=project['root'], env=env, text=True) - original = json.loads(output) - (OUT / (project['name'] + '-lint-config.json')).write_text(json.dumps(original, indent=2) + '\n') - config = json.loads(output) - config.setdefault('options', {})['typeAware'] = True - config['options']['typeCheck'] = False - return config - - -def signature(project, scenario, stdout, stderr): - if scenario == 'check': - combined = stdout + stderr - formatted = re.search(r'pass: All ([\d,]+) files are correctly formatted', combined) - linted = re.search(r'pass: Found no warnings, lint errors, or type errors in ([\d,]+) files', combined) - assert formatted and linted, combined - files = int(linted[1].replace(',', '')) - result = {'files': files, 'format_files': int(formatted[1].replace(',', ''))} - else: - data = json.loads(stdout) - assert not data['diagnostics'], data['diagnostics'][:5] - files = data['number_of_files'] - assert data['number_of_rules'] == project['rules'], data - result = {key: data[key] for key in ('number_of_files', 'number_of_rules', 'threads_count', 'diagnostics')} - assert files == project['files'], (project['name'], files, project['files']) - key = (project['name'], scenario) - if key in EXPECTED: - assert result == EXPECTED[key], (key, result, EXPECTED[key]) - else: - EXPECTED[key] = result - return result - - -def sample(child, start): - root = psutil.Process(child.pid) - seen = {child.pid: root.create_time()} - trace = [] - processes = {} - while child.poll() is None: - try: - for process in root.children(recursive=True): - seen[process.pid] = process.create_time() - except psutil.Error: - pass - alive = [] - for pid, born in list(seen.items()): - try: - process = psutil.Process(pid) - with process.oneshot(): - if process.create_time() != born or process.status() == psutil.STATUS_ZOMBIE: - continue - rss = process.memory_info().rss - exe = process.exe() - elapsed = time.perf_counter() - start - key = f'{pid}:{exe}' - record = processes.setdefault(key, {'pid': pid, 'exe': exe, 'first': elapsed, 'peak_rss': 0}) - record['last'] = elapsed - record['peak_rss'] = max(record['peak_rss'], rss) - alive.append({'pid': pid, 'exe': exe, 'rss': rss}) - except psutil.Error: - pass - trace.append({'seconds': time.perf_counter() - start, 'rss': sum(p['rss'] for p in alive), 'processes': alive}) - time.sleep(0.02) - if time.perf_counter() - start > 300: - child.kill() - raise TimeoutError('Benchmark command exceeded 300 seconds') - return trace, list(processes.values()) - - -def run(project, scenario, backend, pair, phase, config=None): - args = ['check'] if scenario == 'check' else ['lint', '--type-aware', '--threads', '4', '--format', 'json'] - if scenario == 'full': - args += ['--type-check'] - if config: - args += ['-c', str(config)] - command = [project['node'], CONFIG['cli'], *args] - directory = OUT / phase / project['name'] - directory.mkdir(parents=True, exist_ok=True) - stem = directory / f'{scenario}-{backend}-{pair:02d}' - with stem.with_suffix('.stdout').open('w') as stdout, stem.with_suffix('.stderr').open('w') as stderr: - start = time.perf_counter() - child = subprocess.Popen(command, cwd=project['root'], env=environment(project, scenario, backend), stdout=stdout, stderr=stderr) - trace, processes = sample(child, start) if phase in ('validate', 'memory') else ([], []) - # Blocking wait avoids the polling latency of wait(timeout=...). - # The Codespaces worker enforces the overall task deadline. - status = child.wait() - seconds = time.perf_counter() - start - stdout = stem.with_suffix('.stdout').read_text() - stderr = stem.with_suffix('.stderr').read_text() - assert status == 0, (project['name'], scenario, backend, status, stdout[-5000:], stderr[-5000:]) - result = signature(project, scenario, stdout, stderr) - record = {'project': project['name'], 'scenario': scenario, 'backend': backend, - 'phase': phase, 'pair': pair, 'seconds': seconds, 'exit': status, - 'peak_rss': max((x['rss'] for x in trace), default=None), 'signature': result} - if trace: - native = [p for p in processes if pathlib.Path(p['exe']).name in ('tsgolint', 'tsrs')] - assert len(native) == 1, native - assert pathlib.Path(native[0]['exe']).name == ('tsgolint' if backend == 'go' else 'tsrs') - intervals = [b['seconds'] - a['seconds'] for a, b in zip(trace, trace[1:])] - record.update({'native': native[0], 'median_sample_interval_ms': statistics.median(intervals) * 1000}) - stem.with_suffix('.trace.json').write_text(json.dumps(trace)) - stem.with_suffix('.processes.json').write_text(json.dumps(processes, indent=2)) - stem.with_suffix('.json').write_text(json.dumps(record, indent=2) + '\n') - RECORDS.append(record) - (OUT / 'samples.json').write_text(json.dumps(RECORDS, indent=2) + '\n') - print(project['name'], scenario, backend, phase, pair, f'{seconds:.3f}s', record['peak_rss'], flush=True) - return record - - -def measure(project, scenario, phase): - handle = None - if scenario == 'rules': - handle = tempfile.NamedTemporaryFile(mode='w', suffix='.json', prefix='.tsrs-bench-', dir=project['root']) - json.dump(lint_config(project), handle) - handle.flush() - try: - pairs = 0 if phase == 'validate' else TIMING_PAIRS if phase == 'time' else MEMORY_PAIRS - for pair in range(-1, pairs): - backends = ('go', 'rust') if pair < 0 or pair % 2 == 0 else ('rust', 'go') - for backend in backends: - run(project, scenario, backend, pair, phase, handle.name if handle else None) - finally: - if handle: - handle.close() - - -def metadata(): - info = {'vp_commit': CONFIG['vp_commit'], 'python': platform.python_version(), 'psutil': psutil.__version__, - 'platform': platform.platform(), 'cpu_affinity': psutil.Process().cpu_affinity(), - 'memory_bytes': psutil.virtual_memory().total, 'timing_pairs': TIMING_PAIRS, - 'memory_pairs': MEMORY_PAIRS, 'sample_sleep_ms': 20, 'projects': CONFIG['projects'], - 'versions': json.loads((ENV / 'setup-receipt.json').read_text())['versions'], - 'selection': vars(options)} - for name, args in [('cpu', ['lscpu']), ('git_diff', ['git', 'diff', '--stat']), ('source', ['git', 'rev-parse', 'HEAD'])]: - info[name] = subprocess.check_output(args, text=True) - info['node_versions'] = {p['name']: subprocess.check_output([p['node'], '--version'], text=True).strip() for p in CONFIG['projects']} - info['cpu_percent_before'] = psutil.cpu_percent(interval=1) - (OUT / 'metadata.json').write_text(json.dumps(info, indent=2) + '\n') - - -def summarize(): - rows = [] - for project in selected_projects: - for scenario in selected_scenarios: - row = {'project': project['name'], 'scenario': scenario} - for backend in ('go', 'rust'): - for phase, metric in [('time', 'seconds'), ('memory', 'peak_rss')]: - if phase not in phases: - continue - values = [r[metric] for r in RECORDS if r['project'] == project['name'] and r['scenario'] == scenario and r['backend'] == backend and r['phase'] == phase and r['pair'] >= 0] - row[f'{backend}_{metric}'] = statistics.median(values) - row[f'{backend}_{metric}_range'] = [min(values), max(values)] - if 'time' in phases: - row['time_saved_percent'] = 100 * (1 - row['rust_seconds'] / row['go_seconds']) - if 'memory' in phases: - row['rss_saved_percent'] = 100 * (1 - row['rust_peak_rss'] / row['go_peak_rss']) - rows.append(row) - (OUT / 'summary.json').write_text(json.dumps(rows, indent=2) + '\n') - with (OUT / 'samples.csv').open('w') as file: - keys = ['project', 'scenario', 'backend', 'phase', 'pair', 'seconds', 'peak_rss', 'exit'] - writer = csv.DictWriter(file, fieldnames=keys, extrasaction='ignore') - writer.writeheader() - writer.writerows(RECORDS) - print('RESULTS', json.dumps(rows), flush=True) - - -parser = argparse.ArgumentParser() -parser.add_argument('--validate-only', action='store_true') -parser.add_argument('--project', choices=[p['name'] for p in CONFIG['projects']]) -parser.add_argument('--scenario', choices=SCENARIOS) -parser.add_argument('--phase', choices=('time', 'memory')) -options = parser.parse_args() -selected_projects = [p for p in CONFIG['projects'] if not options.project or p['name'] == options.project] -selected_scenarios = (options.scenario,) if options.scenario else SCENARIOS -metadata() -phases = ('validate',) if options.validate_only else (options.phase,) if options.phase else ('time', 'memory') -total = len(phases) * len(selected_projects) * len(selected_scenarios) -completed = 0 -for phase in phases: - for project in selected_projects: - for scenario in selected_scenarios: - progress(phase, f"{project['name']}: {scenario}", completed, total) - measure(project, scenario, phase) - completed += 1 - progress(phase, f"Completed {project['name']}: {scenario}", completed, total) -if not options.validate_only: - summarize() -expected_runs = len(selected_projects) * len(selected_scenarios) * sum( - 2 * (1 + (0 if phase == 'validate' else TIMING_PAIRS if phase == 'time' else MEMORY_PAIRS)) - for phase in phases) -assert len(RECORDS) == expected_runs, (len(RECORDS), expected_runs) -progress('validated' if options.validate_only else 'measured', 'All output assertions passed', total, total) diff --git a/bench/tsrs-lint-codespaces/inputs/docs.py b/bench/tsrs-lint-codespaces/inputs/docs.py deleted file mode 100644 index cc1cef3543..0000000000 --- a/bench/tsrs-lint-codespaces/inputs/docs.py +++ /dev/null @@ -1,11 +0,0 @@ -import os -import pathlib -import subprocess - -root = pathlib.Path.cwd() -env_root = pathlib.Path(os.environ['JOB_ENV_DIR']) -env = os.environ | {'PATH': str(env_root / 'node-v24.22.2-linux-x64/bin') + ':' + os.environ['PATH']} -subprocess.run([ - env_root / 'pnpm12/node_modules/.bin/pnpm', 'install', '--frozen-lockfile', - '--ignore-scripts', '--store-dir', env_root / 'pnpm-store', -], cwd=root / 'docs', env=env, check=True) diff --git a/bench/tsrs-lint-codespaces/inputs/docs.sh b/bench/tsrs-lint-codespaces/inputs/docs.sh deleted file mode 100644 index 0ff5cba507..0000000000 --- a/bench/tsrs-lint-codespaces/inputs/docs.sh +++ /dev/null @@ -1 +0,0 @@ -python3 "$JOB_INPUT_DIR/docs.py" diff --git a/bench/tsrs-lint-codespaces/inputs/run.sh b/bench/tsrs-lint-codespaces/inputs/run.sh deleted file mode 100644 index 68be2b7d7b..0000000000 --- a/bench/tsrs-lint-codespaces/inputs/run.sh +++ /dev/null @@ -1 +0,0 @@ -"$JOB_ENV_DIR/venv/bin/python" "$JOB_INPUT_DIR/bench.py" diff --git a/bench/tsrs-lint-codespaces/inputs/setup.py b/bench/tsrs-lint-codespaces/inputs/setup.py deleted file mode 100644 index 32fa921a55..0000000000 --- a/bench/tsrs-lint-codespaces/inputs/setup.py +++ /dev/null @@ -1,150 +0,0 @@ -import hashlib -import json -import os -import pathlib -import shutil -import subprocess -import urllib.request - -ROOT = pathlib.Path.cwd() -ENV = pathlib.Path(os.environ['JOB_ENV_DIR']) -ENV.mkdir(parents=True, exist_ok=True) -INPUT = pathlib.Path(os.environ['JOB_INPUT_DIR']) -OUT = pathlib.Path(os.environ['ARTIFACTS_DIR']) -VP_COMMIT = '22b07bd150aa57b3e04cc52e0b81d43294ffe982' -VINEXT_COMMIT = '249088af307c0a852fa6dc59c08fc6ec80e1620e' - - -def run(args, cwd=ROOT, env=None): - print('+', ' '.join(map(str, args)), flush=True) - subprocess.run(list(map(str, args)), cwd=cwd, env=env, check=True) - - -def progress(message): - run(['python3', os.environ['JOB_WORKER'], 'progress', os.environ['JOB_DIR'], - json.dumps({'phase': 'setup', 'message': message})]) - - -def fetch_git(url, revision, destination): - if not (destination / '.git').exists(): - destination.mkdir(parents=True, exist_ok=True) - run(['git', 'init', destination]) - run(['git', 'remote', 'add', 'origin', url], cwd=destination) - run(['git', 'fetch', '--depth', '1', 'origin', revision], cwd=destination) - run(['git', 'checkout', '--detach', 'FETCH_HEAD'], cwd=destination) - actual = subprocess.check_output(['git', 'rev-parse', 'HEAD'], cwd=destination, text=True).strip() - assert actual == revision, (destination, actual, revision) - - -def node(version): - target = ENV / f'node-v{version}-linux-x64' - if not (target / 'bin/node').exists(): - filename = target.name + '.tar.xz' - base = f'https://nodejs.org/dist/v{version}/' - with urllib.request.urlopen(base + 'SHASUMS256.txt') as response: - checksums = response.read().decode() - (OUT / f'node-{version}-SHASUMS256.txt').write_text(checksums) - expected = next(line.split()[0] for line in checksums.splitlines() if line.split()[-1] == filename) - archive = ENV / filename - urllib.request.urlretrieve(base + filename, archive) - assert hashlib.sha256(archive.read_bytes()).hexdigest() == expected - run(['tar', '-xJf', archive, '-C', ENV]) - assert subprocess.check_output([target / 'bin/node', '--version'], text=True).strip() == 'v' + version - return target - - -def npm(prefix, packages, runtime): - prefix.mkdir(parents=True, exist_ok=True) - env = os.environ | {'PATH': str(runtime / 'bin') + ':' + os.environ['PATH']} - run([runtime / 'bin/npm', 'install', '--prefix', prefix, '--no-audit', '--no-fund', - '--ignore-scripts', '--package-lock=false', *packages], env=env) - - -def copy_dir(source, destination): - if destination.is_symlink(): - destination.unlink() - shutil.copytree(source, destination, dirs_exist_ok=True) - - -progress('Installing pinned Node and package-manager versions') -assert subprocess.check_output(['git', 'rev-parse', 'HEAD'], text=True).strip() == VP_COMMIT -patch = INPUT / 'rfcs.patch' -if patch.stat().st_size: - reverse = subprocess.run(['git', 'apply', '--reverse', '--check', str(patch)], capture_output=True) - if reverse.returncode: - run(['git', 'apply', patch]) -node24 = node('24.22.2') -node26 = node('26.13.0') -npm(ENV / 'pnpm12', ['pnpm@12.11.2'], node24) -# pnpm 12 ships a shebang-less placeholder until its native installer runs. -run([node24 / 'bin/node', ENV / 'pnpm12/node_modules/pnpm/install.js']) -npm(ENV / 'pnpm11', ['pnpm@11.1.1'], node26) -for prefix, version in [('pnpm12', '12.11.2'), ('pnpm11', '11.1.1')]: - pm_env = os.environ | {'PATH': str(node26 / 'bin') + ':' + os.environ['PATH']} - assert subprocess.check_output([ENV / prefix / 'node_modules/.bin/pnpm', '--version'], cwd=ENV, env=pm_env, text=True).strip() == version -if not (ENV / 'venv/bin/python').exists(): - # The repository's minimal Ubuntu image can omit ensurepip. - check = subprocess.run(['python3', '-c', 'import ensurepip'], capture_output=True, text=True) - if check.returncode: - if 'No module named' not in check.stderr or 'ensurepip' not in check.stderr: - raise RuntimeError(check.stderr) - progress('Installing python3-venv because the container lacks ensurepip') - run(['sudo', 'apt-get', 'update']) - run(['sudo', 'env', 'DEBIAN_FRONTEND=noninteractive', 'apt-get', 'install', '-y', 'python3-venv']) - run(['python3', '-m', 'venv', ENV / 'venv']) -run([ENV / 'venv/bin/python', '-m', 'pip', 'install', 'psutil==7.0.0']) -base_env = os.environ | { - 'PATH': str(node24 / 'bin') + ':' + str(ENV / 'pnpm12/node_modules/.bin') + ':' + os.environ['PATH'], - 'VP_HOME': str(ENV / 'vp-home'), 'VP_SELF_SETUP_NO_MODIFY_PATH': '1', - 'CARGO_HOME': str(ENV / 'cargo'), 'CARGO_TARGET_DIR': str(ENV / 'cargo-target'), -} -progress('Fetching exact source dependencies and published native artifacts') -for name, spec in json.loads((ROOT / 'packages/tools/.upstream-versions.json').read_text()).items(): - fetch_git(spec['repo'], spec['hash'], ROOT / name) -run([node24 / 'bin/node', 'packages/tools/src/vendored-vitest.ts'], env=base_env) -npm(ENV / 'published', [ - 'vite-plus@1.5.0', '@voidzero-dev/vite-plus-core@1.5.0', - '@voidzero-dev/vite-plus-linux-x64-gnu@1.5.0', - '@voidzero-dev/vite-plus-prompts@1.5.0', 'rolldown@1.2.16', -], node24) -run([ENV / 'pnpm12/node_modules/.bin/pnpm', 'install', '--frozen-lockfile', '--ignore-scripts', - '--store-dir', ENV / 'pnpm-store'], env=base_env) -modules = ENV / 'published/node_modules' -for source, destination in [ - (modules / 'vite-plus/dist', ROOT / 'packages/cli/dist'), - (modules / '@voidzero-dev/vite-plus-core/dist', ROOT / 'packages/core/dist'), - (modules / '@voidzero-dev/vite-plus-prompts/dist', ROOT / 'packages/prompts/dist'), - (modules / 'rolldown/dist', ROOT / 'rolldown/packages/rolldown/dist'), - (modules / '@rolldown/binding-linux-x64-gnu', ROOT / 'rolldown/packages/rolldown/node_modules/@rolldown/binding-linux-x64-gnu'), - (modules / '@voidzero-dev/vite-plus-linux-x64-gnu', ROOT / 'packages/core/node_modules/@voidzero-dev/vite-plus-linux-x64-gnu'), -]: - copy_dir(source, destination) -native = modules / '@voidzero-dev/vite-plus-linux-x64-gnu/vite-plus.linux-x64-gnu.node' -shutil.copy2(native, ROOT / 'packages/cli/binding' / native.name) -progress('Building CLI JavaScript; using the published 1.5.0 Linux native binding and core') -run([ENV / 'pnpm12/node_modules/.bin/pnpm', '--filter', 'vite-plus', 'build-ts'], env=base_env) -progress('Preparing the pinned vinext benchmark corpus and generated types') -vinext = ENV / 'vinext' -fetch_git('https://github.com/fengmk2/vinext.git', VINEXT_COMMIT, vinext) -vinext_env = base_env | {'PATH': str(node26 / 'bin') + ':' + str(ENV / 'pnpm11/node_modules/.bin') + ':' + os.environ['PATH']} -run([ENV / 'pnpm11/node_modules/.bin/pnpm', 'install', '--frozen-lockfile', '--ignore-scripts', - '--store-dir', ENV / 'pnpm-store-vinext'], cwd=vinext, env=vinext_env) -run([ENV / 'pnpm11/node_modules/.bin/pnpm', 'run', 'postinstall'], cwd=vinext, env=vinext_env) -config = { - 'vp_commit': VP_COMMIT, 'cli': str(ROOT / 'packages/cli/bin/vp'), - 'go': str(ROOT / 'packages/cli/node_modules/.bin/tsgolint'), - 'tsrs': str(ROOT / 'packages/cli/node_modules/.bin/tsrs-lint'), - 'projects': [ - {'name': 'vite-plus', 'root': str(ROOT), 'node': str(node24 / 'bin/node'), 'files': 385, 'rules': 174, 'scheduling': None}, - {'name': 'vinext', 'root': str(vinext), 'node': str(node26 / 'bin/node'), 'files': 1828, 'rules': 151, 'scheduling': 'sorted', 'commit': VINEXT_COMMIT}, - ], -} -(ENV / 'bench-config.json').write_text(json.dumps(config, indent=2) + '\n') -versions = {} -for package in ['tsrs', 'oxlint', 'oxlint-tsgolint']: - versions[package] = json.loads((ROOT / 'packages/cli/node_modules' / package / 'package.json').read_text())['version'] -assert versions == {'tsrs': '0.22.0-ts7.1.0-beta', 'oxlint': '1.91.0', 'oxlint-tsgolint': '7.0.2005'}, versions -files = [ROOT / 'packages/cli/dist/bin.js', native, node24 / 'bin/node', node26 / 'bin/node', ENV / 'bench-config.json'] -receipt = {'versions': versions, 'hashes': {str(p): hashlib.sha256(p.read_bytes()).hexdigest() for p in files}} -(ENV / 'setup-receipt.json').write_text(json.dumps(receipt, indent=2) + '\n') -print('SETUP_COMPLETE', json.dumps(versions), flush=True) diff --git a/bench/tsrs-lint-codespaces/inputs/setup.sh b/bench/tsrs-lint-codespaces/inputs/setup.sh deleted file mode 100644 index b2ff6948e5..0000000000 --- a/bench/tsrs-lint-codespaces/inputs/setup.sh +++ /dev/null @@ -1 +0,0 @@ -python3 "$JOB_INPUT_DIR/setup.py" diff --git a/bench/tsrs-lint-codespaces/inputs/validate.sh b/bench/tsrs-lint-codespaces/inputs/validate.sh deleted file mode 100644 index 1f8e6ed6c9..0000000000 --- a/bench/tsrs-lint-codespaces/inputs/validate.sh +++ /dev/null @@ -1 +0,0 @@ -"$JOB_ENV_DIR/venv/bin/python" "$JOB_INPUT_DIR/bench.py" --validate-only diff --git a/bench/tsrs-lint-codespaces/inputs/verify-setup.py b/bench/tsrs-lint-codespaces/inputs/verify-setup.py deleted file mode 100644 index e14726debc..0000000000 --- a/bench/tsrs-lint-codespaces/inputs/verify-setup.py +++ /dev/null @@ -1,6 +0,0 @@ -import hashlib,json,os,pathlib -root=pathlib.Path(os.environ['JOB_ENV_DIR']) -receipt=json.loads((root/'setup-receipt.json').read_text()) -for name,expected in receipt['hashes'].items(): - assert hashlib.sha256(pathlib.Path(name).read_bytes()).hexdigest()==expected,name -print('Setup artifacts verified') diff --git a/bench/tsrs-lint-codespaces/inputs/verify-setup.sh b/bench/tsrs-lint-codespaces/inputs/verify-setup.sh deleted file mode 100644 index 3968785a51..0000000000 --- a/bench/tsrs-lint-codespaces/inputs/verify-setup.sh +++ /dev/null @@ -1 +0,0 @@ -python3 "$JOB_INPUT_DIR/verify-setup.py" diff --git a/bench/tsrs-lint-codespaces/results/main-metadata.json b/bench/tsrs-lint-codespaces/results/main-metadata.json deleted file mode 100644 index 84ee55482f..0000000000 --- a/bench/tsrs-lint-codespaces/results/main-metadata.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "vp_commit": "22b07bd150aa57b3e04cc52e0b81d43294ffe982", - "python": "3.10.12", - "psutil": "7.0.0", - "platform": "Linux-6.8.0-1064-azure-x86_64-with-glibc2.35", - "cpu_affinity": [0, 1, 2, 3], - "memory_bytes": 16770494464, - "timing_pairs": 10, - "memory_pairs": 5, - "sample_sleep_ms": 20, - "versions": { - "tsrs": "0.22.0-ts7.1.0-beta", - "oxlint": "1.91.0", - "oxlint-tsgolint": "7.0.2005" - }, - "node_versions": { - "vite-plus": "v24.22.2", - "vinext": "v26.13.0" - }, - "cpu_model": "AMD EPYC 9V74 80-Core Processor", - "rfcs_commit": "da260f86fce1d43810c62e4e6aad011066c88cb9", - "vinext_commit": "249088af307c0a852fa6dc59c08fc6ec80e1620e", - "job_id": "job-3dbd90be3d4f4aa3", - "validation_commands": 12, - "benchmark_records": 204, - "median_sampling_interval_ms": 21.781716749956104, - "sampling_interval_median_range_ms": [21.504334000383096, 24.079619999611168], - "full_check_signatures": { - "vite-plus": { - "files": 385, - "format_files": 632 - }, - "vinext": { - "files": 1828, - "format_files": 4673 - } - }, - "worker_result": { - "status": "succeeded", - "exit_code": 0, - "started_at": 1793866444.5705564, - "finished_at": 1793872607.508668 - }, - "excluded_timing_set": { - "project": "vinext", - "scenario": "full", - "phase": "time", - "reason": "Browser troubleshooting started editor extension activity during this timing set. A clean repetition replaces it." - }, - "harness_sha256": "5ae0087c81988829d74777717f4e06561aeb883c33017478f4ef33f2da85a42d", - "collected": true -} diff --git a/bench/tsrs-lint-codespaces/results/main-samples.csv b/bench/tsrs-lint-codespaces/results/main-samples.csv deleted file mode 100644 index 394d9e3859..0000000000 --- a/bench/tsrs-lint-codespaces/results/main-samples.csv +++ /dev/null @@ -1,205 +0,0 @@ -project,scenario,backend,phase,pair,seconds,peak_rss,exit -vite-plus,rules,go,time,-1,3.6880124389999764,,0 -vite-plus,rules,rust,time,-1,2.191973147999988,,0 -vite-plus,rules,go,time,0,3.713578874000177,,0 -vite-plus,rules,rust,time,0,2.2759946600001513,,0 -vite-plus,rules,rust,time,1,2.4920109750000847,,0 -vite-plus,rules,go,time,1,3.6422700799998893,,0 -vite-plus,rules,go,time,2,3.5738986520000253,,0 -vite-plus,rules,rust,time,2,2.242526223999903,,0 -vite-plus,rules,rust,time,3,2.223109660000091,,0 -vite-plus,rules,go,time,3,3.483162299000014,,0 -vite-plus,rules,go,time,4,3.7109736130000783,,0 -vite-plus,rules,rust,time,4,2.2263701169999877,,0 -vite-plus,rules,rust,time,5,2.2016010700001516,,0 -vite-plus,rules,go,time,5,3.9088208739999573,,0 -vite-plus,rules,go,time,6,3.736302202999923,,0 -vite-plus,rules,rust,time,6,2.187699441999939,,0 -vite-plus,rules,rust,time,7,2.1890677399999277,,0 -vite-plus,rules,go,time,7,3.5215749090000372,,0 -vite-plus,rules,go,time,8,3.7870844199999283,,0 -vite-plus,rules,rust,time,8,2.17189058200006,,0 -vite-plus,rules,rust,time,9,2.187169283000003,,0 -vite-plus,rules,go,time,9,3.839309356000058,,0 -vite-plus,full,go,time,-1,4.654679539999961,,0 -vite-plus,full,rust,time,-1,2.3562958489999346,,0 -vite-plus,full,go,time,0,4.566960510999934,,0 -vite-plus,full,rust,time,0,2.3931662680001864,,0 -vite-plus,full,rust,time,1,2.3698954330000106,,0 -vite-plus,full,go,time,1,4.634962337999923,,0 -vite-plus,full,go,time,2,4.632292471000028,,0 -vite-plus,full,rust,time,2,2.412981780000109,,0 -vite-plus,full,rust,time,3,2.430237735999981,,0 -vite-plus,full,go,time,3,4.53366976999996,,0 -vite-plus,full,go,time,4,4.307837832000132,,0 -vite-plus,full,rust,time,4,2.493581193999944,,0 -vite-plus,full,rust,time,5,2.3662074239998674,,0 -vite-plus,full,go,time,5,4.511783857999944,,0 -vite-plus,full,go,time,6,5.0457770020000225,,0 -vite-plus,full,rust,time,6,2.3568083630000274,,0 -vite-plus,full,rust,time,7,2.3296725099999094,,0 -vite-plus,full,go,time,7,4.698553215999937,,0 -vite-plus,full,go,time,8,4.591066730999955,,0 -vite-plus,full,rust,time,8,2.364594460999797,,0 -vite-plus,full,rust,time,9,2.456160010000076,,0 -vite-plus,full,go,time,9,4.855081535999943,,0 -vite-plus,check,go,time,-1,5.750060867999991,,0 -vite-plus,check,rust,time,-1,3.431430506999959,,0 -vite-plus,check,go,time,0,5.560635703999878,,0 -vite-plus,check,rust,time,0,3.5964835449999555,,0 -vite-plus,check,rust,time,1,3.4927115529999355,,0 -vite-plus,check,go,time,1,6.118377254000052,,0 -vite-plus,check,go,time,2,5.643318074000035,,0 -vite-plus,check,rust,time,2,3.5944409420001193,,0 -vite-plus,check,rust,time,3,3.4559823089998645,,0 -vite-plus,check,go,time,3,5.5692066710000745,,0 -vite-plus,check,go,time,4,5.942744820999906,,0 -vite-plus,check,rust,time,4,3.629871501000025,,0 -vite-plus,check,rust,time,5,3.5768438600000536,,0 -vite-plus,check,go,time,5,5.60959895100018,,0 -vite-plus,check,go,time,6,5.8363495700000385,,0 -vite-plus,check,rust,time,6,3.4873933159999524,,0 -vite-plus,check,rust,time,7,3.661024717999908,,0 -vite-plus,check,go,time,7,5.935824476999869,,0 -vite-plus,check,go,time,8,5.847994853000046,,0 -vite-plus,check,rust,time,8,3.4268209399999705,,0 -vite-plus,check,rust,time,9,3.530400184999962,,0 -vite-plus,check,go,time,9,6.113309405000109,,0 -vinext,rules,go,time,-1,52.948004241000035,,0 -vinext,rules,rust,time,-1,46.124062173000084,,0 -vinext,rules,go,time,0,52.21461774299996,,0 -vinext,rules,rust,time,0,46.608522696000136,,0 -vinext,rules,rust,time,1,46.39468327899999,,0 -vinext,rules,go,time,1,51.841172385999926,,0 -vinext,rules,go,time,2,52.348372229000006,,0 -vinext,rules,rust,time,2,43.62988959800009,,0 -vinext,rules,rust,time,3,44.699639799999886,,0 -vinext,rules,go,time,3,49.70923635600002,,0 -vinext,rules,go,time,4,53.68720330700012,,0 -vinext,rules,rust,time,4,43.043341374999954,,0 -vinext,rules,rust,time,5,46.44647001299995,,0 -vinext,rules,go,time,5,56.2561799030002,,0 -vinext,rules,go,time,6,55.63514441699999,,0 -vinext,rules,rust,time,6,42.57279852900001,,0 -vinext,rules,rust,time,7,45.58332635399984,,0 -vinext,rules,go,time,7,49.66620632200011,,0 -vinext,rules,go,time,8,51.518826092999916,,0 -vinext,rules,rust,time,8,45.142249451000225,,0 -vinext,rules,rust,time,9,46.268095335,,0 -vinext,rules,go,time,9,51.84008323399985,,0 -vinext,full,go,time,-1,54.97031217999984,,0 -vinext,full,rust,time,-1,49.50701832100003,,0 -vinext,full,go,time,0,54.54909113500025,,0 -vinext,full,rust,time,0,48.777478402999805,,0 -vinext,full,rust,time,1,50.565860768999755,,0 -vinext,full,go,time,1,54.497046376999606,,0 -vinext,full,go,time,2,55.068685018999986,,0 -vinext,full,rust,time,2,49.378554876999715,,0 -vinext,full,rust,time,3,46.8017662240004,,0 -vinext,full,go,time,3,54.68703606200006,,0 -vinext,full,go,time,4,56.505208954999944,,0 -vinext,full,rust,time,4,49.47823296100023,,0 -vinext,full,rust,time,5,46.74362833999976,,0 -vinext,full,go,time,5,55.63798557900009,,0 -vinext,full,go,time,6,54.669546999999966,,0 -vinext,full,rust,time,6,49.366139873000066,,0 -vinext,full,rust,time,7,48.29169880900008,,0 -vinext,full,go,time,7,60.90340383700004,,0 -vinext,full,go,time,8,58.05361229900018,,0 -vinext,full,rust,time,8,49.801143192999916,,0 -vinext,full,rust,time,9,48.88615762499967,,0 -vinext,full,go,time,9,56.70309664500019,,0 -vinext,check,go,time,-1,61.557492223999816,,0 -vinext,check,rust,time,-1,50.663133098000344,,0 -vinext,check,go,time,0,62.98640043899968,,0 -vinext,check,rust,time,0,48.491100012999595,,0 -vinext,check,rust,time,1,49.07317851800008,,0 -vinext,check,go,time,1,62.14544949099991,,0 -vinext,check,go,time,2,60.34417550099988,,0 -vinext,check,rust,time,2,50.885471571999915,,0 -vinext,check,rust,time,3,50.162232835000395,,0 -vinext,check,go,time,3,62.48697384699972,,0 -vinext,check,go,time,4,63.368093478000446,,0 -vinext,check,rust,time,4,50.91846492900004,,0 -vinext,check,rust,time,5,53.96345718400062,,0 -vinext,check,go,time,5,68.245969263,,0 -vinext,check,go,time,6,64.2242278700005,,0 -vinext,check,rust,time,6,52.53176643899951,,0 -vinext,check,rust,time,7,50.54870111699984,,0 -vinext,check,go,time,7,61.418192543999794,,0 -vinext,check,go,time,8,62.163307601999804,,0 -vinext,check,rust,time,8,47.784832813999856,,0 -vinext,check,rust,time,9,48.62056124399987,,0 -vinext,check,go,time,9,61.23984774499968,,0 -vite-plus,rules,go,memory,-1,3.940764267999839,1458286592,0 -vite-plus,rules,rust,memory,-1,2.329289043999779,1018667008,0 -vite-plus,rules,go,memory,0,3.857643793000534,1215549440,0 -vite-plus,rules,rust,memory,0,2.352922975000183,976420864,0 -vite-plus,rules,rust,memory,1,2.5503717050005434,1004322816,0 -vite-plus,rules,go,memory,1,3.7755272800004605,1337851904,0 -vite-plus,rules,go,memory,2,3.9427604910006266,1220345856,0 -vite-plus,rules,rust,memory,2,2.2911063749997993,1000955904,0 -vite-plus,rules,rust,memory,3,2.3516781999996965,998109184,0 -vite-plus,rules,go,memory,3,3.8952087559991924,1343397888,0 -vite-plus,rules,go,memory,4,3.78354734200002,1261670400,0 -vite-plus,rules,rust,memory,4,2.3114647920001516,964509696,0 -vite-plus,full,go,memory,-1,5.309363115000451,1652715520,0 -vite-plus,full,rust,memory,-1,2.525028889999703,1011937280,0 -vite-plus,full,go,memory,0,4.856635449999885,1443962880,0 -vite-plus,full,rust,memory,0,2.658091739000156,1061445632,0 -vite-plus,full,rust,memory,1,2.5839939859997685,1056391168,0 -vite-plus,full,go,memory,1,4.8830151010006375,1477206016,0 -vite-plus,full,go,memory,2,5.28511940000044,1383268352,0 -vite-plus,full,rust,memory,2,2.5511508569998114,1054629888,0 -vite-plus,full,rust,memory,3,2.5333675159999984,1069342720,0 -vite-plus,full,go,memory,3,5.299707000000126,1786830848,0 -vite-plus,full,go,memory,4,4.7314949120000165,1393152000,0 -vite-plus,full,rust,memory,4,2.576505080000061,1045348352,0 -vite-plus,check,go,memory,-1,5.992466878999949,1424461824,0 -vite-plus,check,rust,memory,-1,3.699139065000054,1118486528,0 -vite-plus,check,go,memory,0,5.935010570000486,1546260480,0 -vite-plus,check,rust,memory,0,3.6238119339996047,1078734848,0 -vite-plus,check,rust,memory,1,3.7297287820001657,1094008832,0 -vite-plus,check,go,memory,1,5.904669201999241,1786224640,0 -vite-plus,check,go,memory,2,6.3074621320001825,1534910464,0 -vite-plus,check,rust,memory,2,3.609159493000334,1111625728,0 -vite-plus,check,rust,memory,3,3.7218976559997827,1092087808,0 -vite-plus,check,go,memory,3,5.842184367999835,1543946240,0 -vite-plus,check,go,memory,4,6.1350453840004775,1427783680,0 -vite-plus,check,rust,memory,4,3.898626783000509,1090322432,0 -vinext,rules,go,memory,-1,54.02692431700052,4816293888,0 -vinext,rules,rust,memory,-1,46.12704977300018,3161968640,0 -vinext,rules,go,memory,0,53.78586441100015,4626669568,0 -vinext,rules,rust,memory,0,48.66075261300011,3163717632,0 -vinext,rules,rust,memory,1,48.704375655999684,3253788672,0 -vinext,rules,go,memory,1,53.11478282000007,4844425216,0 -vinext,rules,go,memory,2,50.72010573700027,5035069440,0 -vinext,rules,rust,memory,2,46.750776367999606,3171049472,0 -vinext,rules,rust,memory,3,43.352124744999855,3257614336,0 -vinext,rules,go,memory,3,55.42714346699995,4705017856,0 -vinext,rules,go,memory,4,50.09278948500014,4499263488,0 -vinext,rules,rust,memory,4,44.060889810000845,3327598592,0 -vinext,full,go,memory,-1,56.74663075999979,5482045440,0 -vinext,full,rust,memory,-1,49.11478131599961,3299753984,0 -vinext,full,go,memory,0,57.2608009810001,5504036864,0 -vinext,full,rust,memory,0,50.5766341210001,3399245824,0 -vinext,full,rust,memory,1,46.43882131400005,3354329088,0 -vinext,full,go,memory,1,56.82105735300047,5004701696,0 -vinext,full,go,memory,2,57.29950182000084,5265002496,0 -vinext,full,rust,memory,2,50.1835814720007,3529072640,0 -vinext,full,rust,memory,3,49.69551689400032,3426652160,0 -vinext,full,go,memory,3,57.23072275499999,4893655040,0 -vinext,full,go,memory,4,57.49116492400026,5388804096,0 -vinext,full,rust,memory,4,47.16479458400045,3357630464,0 -vinext,check,go,memory,-1,63.65577673000007,5786767360,0 -vinext,check,rust,memory,-1,50.898781433000295,3493175296,0 -vinext,check,go,memory,0,60.84386064899991,5591572480,0 -vinext,check,rust,memory,0,50.34470988399971,3572527104,0 -vinext,check,rust,memory,1,49.92382415800057,3534950400,0 -vinext,check,go,memory,1,61.628392914999495,6064586752,0 -vinext,check,go,memory,2,58.81841730999986,5774688256,0 -vinext,check,rust,memory,2,51.66727800700028,3535589376,0 -vinext,check,rust,memory,3,48.62187229600022,3533045760,0 -vinext,check,go,memory,3,63.150578589999895,6065008640,0 -vinext,check,go,memory,4,62.494274702999974,5812363264,0 -vinext,check,rust,memory,4,50.306470861999514,3523481600,0 diff --git a/bench/tsrs-lint-codespaces/results/summary.json b/bench/tsrs-lint-codespaces/results/summary.json deleted file mode 100644 index bdb9ccf395..0000000000 --- a/bench/tsrs-lint-codespaces/results/summary.json +++ /dev/null @@ -1,98 +0,0 @@ -[ - { - "project": "vite-plus", - "scenario": "rules", - "go_seconds": 3.7122762435001277, - "go_seconds_range": [3.483162299000014, 3.9088208739999573], - "go_peak_rss": 1261670400, - "go_peak_rss_range": [1215549440, 1343397888], - "rust_seconds": 2.2123553650001213, - "rust_seconds_range": [2.17189058200006, 2.4920109750000847], - "rust_peak_rss": 998109184, - "rust_peak_rss_range": [964509696, 1004322816], - "time_saved_percent": 40.40434439991467, - "rss_saved_percent": 20.88986283580878, - "timing_job": "job-3dbd90be3d4f4aa3", - "memory_job": "job-3dbd90be3d4f4aa3" - }, - { - "project": "vite-plus", - "scenario": "full", - "go_seconds": 4.611679600999992, - "go_seconds_range": [4.307837832000132, 5.0457770020000225], - "go_peak_rss": 1443962880, - "go_peak_rss_range": [1383268352, 1786830848], - "rust_seconds": 2.3815308505000985, - "rust_seconds_range": [2.3296725099999094, 2.493581193999944], - "rust_peak_rss": 1056391168, - "rust_peak_rss_range": [1045348352, 1069342720], - "time_saved_percent": 48.35870969909336, - "rss_saved_percent": 26.8408362408873, - "timing_job": "job-3dbd90be3d4f4aa3", - "memory_job": "job-3dbd90be3d4f4aa3" - }, - { - "project": "vite-plus", - "scenario": "check", - "go_seconds": 5.842172211500042, - "go_seconds_range": [5.560635703999878, 6.118377254000052], - "go_peak_rss": 1543946240, - "go_peak_rss_range": [1427783680, 1786224640], - "rust_seconds": 3.553622022500008, - "rust_seconds_range": [3.4268209399999705, 3.661024717999908], - "rust_peak_rss": 1092087808, - "rust_peak_rss_range": [1078734848, 1111625728], - "time_saved_percent": 39.172932706350736, - "rss_saved_percent": 29.26646150580995, - "timing_job": "job-3dbd90be3d4f4aa3", - "memory_job": "job-3dbd90be3d4f4aa3" - }, - { - "project": "vinext", - "scenario": "rules", - "go_seconds": 52.027895064499944, - "go_seconds_range": [49.66620632200011, 56.2561799030002], - "go_peak_rss": 4705017856, - "go_peak_rss_range": [4499263488, 5035069440], - "rust_seconds": 45.36278790250003, - "rust_seconds_range": [42.57279852900001, 46.608522696000136], - "rust_peak_rss": 3253788672, - "rust_peak_rss_range": [3163717632, 3327598592], - "time_saved_percent": 12.810641587050675, - "rss_saved_percent": 30.84428642814485, - "timing_job": "job-3dbd90be3d4f4aa3", - "memory_job": "job-3dbd90be3d4f4aa3" - }, - { - "project": "vinext", - "scenario": "full", - "go_seconds": 54.19228503350001, - "go_seconds_range": [51.108869654000046, 55.4913207269999], - "go_peak_rss": 5265002496, - "go_peak_rss_range": [4893655040, 5504036864], - "rust_seconds": 44.79370169149996, - "rust_seconds_range": [42.42826828800003, 46.32845908899998], - "rust_peak_rss": 3399245824, - "rust_peak_rss_range": [3354329088, 3529072640], - "time_saved_percent": 17.343028322555753, - "rss_saved_percent": 35.436957027417904, - "timing_job": "job-4685d3c6e32f48bf", - "memory_job": "job-3dbd90be3d4f4aa3" - }, - { - "project": "vinext", - "scenario": "check", - "go_seconds": 62.32514072449976, - "go_seconds_range": [60.34417550099988, 68.245969263], - "go_peak_rss": 5812363264, - "go_peak_rss_range": [5591572480, 6065008640], - "rust_seconds": 50.355466976000116, - "rust_seconds_range": [47.784832813999856, 53.96345718400062], - "rust_peak_rss": 3534950400, - "rust_peak_rss_range": [3523481600, 3572527104], - "time_saved_percent": 19.205209341459884, - "rss_saved_percent": 39.18221832598796, - "timing_job": "job-3dbd90be3d4f4aa3", - "memory_job": "job-3dbd90be3d4f4aa3" - } -] diff --git a/bench/tsrs-lint-codespaces/results/timing-rerun-metadata.json b/bench/tsrs-lint-codespaces/results/timing-rerun-metadata.json deleted file mode 100644 index 083a981844..0000000000 --- a/bench/tsrs-lint-codespaces/results/timing-rerun-metadata.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "vp_commit": "22b07bd150aa57b3e04cc52e0b81d43294ffe982", - "python": "3.10.12", - "psutil": "7.0.0", - "platform": "Linux-6.8.0-1064-azure-x86_64-with-glibc2.35", - "cpu_affinity": [0, 1, 2, 3], - "memory_bytes": 16770494464, - "timing_pairs": 10, - "versions": { - "tsrs": "0.22.0-ts7.1.0-beta", - "oxlint": "1.91.0", - "oxlint-tsgolint": "7.0.2005" - }, - "node_versions": { - "vite-plus": "v24.22.2", - "vinext": "v26.13.0" - }, - "selection": { - "validate_only": false, - "project": "vinext", - "scenario": "full", - "phase": "time" - }, - "cpu_model": "AMD EPYC 7763 64-Core Processor", - "job_id": "job-4685d3c6e32f48bf", - "records": 22, - "worker_result": { - "status": "succeeded", - "exit_code": 0, - "started_at": 1793893477.6474826, - "finished_at": 1793894561.3346298 - }, - "harness_sha256": "734df51a34c3f202dad7e007648a1ca718daa3e9c2a26d5d0c9ba32a952992f8", - "collected": true, - "shutdown_confirmed_at": 1793894671.662227 -} diff --git a/bench/tsrs-lint-codespaces/results/timing-rerun-samples.csv b/bench/tsrs-lint-codespaces/results/timing-rerun-samples.csv deleted file mode 100644 index c82e7ce8c5..0000000000 --- a/bench/tsrs-lint-codespaces/results/timing-rerun-samples.csv +++ /dev/null @@ -1,23 +0,0 @@ -project,scenario,backend,phase,pair,seconds,peak_rss,exit -vinext,full,go,time,-1,53.405041512000025,,0 -vinext,full,rust,time,-1,45.34521164800003,,0 -vinext,full,go,time,0,54.13607682600002,,0 -vinext,full,rust,time,0,45.48383163799997,,0 -vinext,full,rust,time,1,46.32845908899998,,0 -vinext,full,go,time,1,54.58117138299997,,0 -vinext,full,go,time,2,51.108869654000046,,0 -vinext,full,rust,time,2,42.42826828800003,,0 -vinext,full,rust,time,3,45.07651155499991,,0 -vinext,full,go,time,3,52.29078975899995,,0 -vinext,full,go,time,4,54.71693397199999,,0 -vinext,full,rust,time,4,44.51089182800001,,0 -vinext,full,rust,time,5,45.55147170500004,,0 -vinext,full,go,time,5,54.248493241000006,,0 -vinext,full,go,time,6,54.310657021,,0 -vinext,full,rust,time,6,42.593737007999835,,0 -vinext,full,rust,time,7,44.00615199700019,,0 -vinext,full,go,time,7,55.4913207269999,,0 -vinext,full,go,time,8,54.02196162600012,,0 -vinext,full,rust,time,8,43.040702052999904,,0 -vinext,full,rust,time,9,45.36723157000006,,0 -vinext,full,go,time,9,53.41371220799988,,0 diff --git a/bench/tsrs-lint-codespaces/stages.json b/bench/tsrs-lint-codespaces/stages.json deleted file mode 100644 index 5506f61409..0000000000 --- a/bench/tsrs-lint-codespaces/stages.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "stages": [ - { - "name": "setup", - "script": "setup.sh", - "inputs": { - "workspace": [ - "pnpm-lock.yaml", - "pnpm-workspace.yaml", - "packages/cli/package.json", - "packages/tools/.upstream-versions.json" - ], - "inputs": ["setup.py", "verify-setup.py", "rfcs.patch"] - }, - "outputs": { - "env": ["setup-receipt.json", "bench-config.json"] - }, - "verify": "verify-setup.sh" - }, - { - "name": "docs", - "script": "docs.sh", - "inputs": { - "workspace": ["docs/package.json", "docs/pnpm-lock.yaml", "docs/pnpm-workspace.yaml"], - "inputs": ["docs.py"], - "env": ["setup-receipt.json"] - } - }, - { - "name": "validate", - "script": "validate.sh", - "inputs": { - "inputs": ["bench.py"], - "env": ["setup-receipt.json", "bench-config.json"] - } - }, - { - "name": "benchmark", - "script": "run.sh", - "inputs": { - "inputs": ["bench.py"], - "env": ["setup-receipt.json", "bench-config.json"] - } - } - ] -} diff --git a/bench/tsrs-lint.md b/bench/tsrs-lint.md index b91343953d..9ef24c0174 100644 --- a/bench/tsrs-lint.md +++ b/bench/tsrs-lint.md @@ -40,7 +40,7 @@ descendants, including Node, Oxlint, and the native backend. One GiB is 1,073,74 The sampler sleeps for 20 ms. The median observed interval across runs was 21.8 ms. Shared pages can count more than once, and sampling can miss short peaks. These values are process-tree RSS, not total -allocation or container memory. Per-case ranges are in the [summary](./tsrs-lint-codespaces/results/summary.json). +allocation or container memory. ## Conditions and interpretation @@ -74,28 +74,15 @@ Vinext retains its original JS plugin rules, including in place. This limits the elapsed-time improvement even though the memory reduction is larger. The measurements do not include that rule's separate optimization. -## Evidence and reproduction +## Run selection -Use the [Codespaces recipe](./tsrs-lint-codespaces/README.md) to recreate the pinned -setup and all six comparisons. The recipe uses frozen lockfiles and retains -verified dependencies between jobs. - -The [main samples](./tsrs-lint-codespaces/results/main-samples.csv) contain all 204 -invocations, including warm-ups. A browser connection used to diagnose an SSH +The main job completed 204 invocations, including warm-ups. +A browser connection used to diagnose an SSH outage started editor extension activity during vinext's type-checking timing set. That entire timing set is excluded. The table instead uses the 22 records -from the [clean timing repetition](./tsrs-lint-codespaces/results/timing-rerun-samples.csv). +from a clean timing repetition. The repetition uses the same retained build and dependencies, with their hashes checked before execution. All memory values come from the main job. -Records with `pair=-1` are warm-ups and do not contribute to medians. For the -vinext `full`/`time` group, select the repetition CSV; select the main CSV for -all other groups. Take each backend's median `seconds` or `peak_rss` value and -calculate the reduction as `100 * (1 - rust / go)`. - -The [main metadata](./tsrs-lint-codespaces/results/main-metadata.json) and -[repetition metadata](./tsrs-lint-codespaces/results/timing-rerun-metadata.json) -record versions, hardware, source revisions, and successful worker results. -The main worker finished while the controller was disconnected; its complete -artifacts were recovered from the stopped Codespace. The jobs are `job-3dbd90be3d4f4aa3` and -`job-4685d3c6e32f48bf`. +Warm-up runs do not contribute to medians. Each reduction is calculated as +`100 * (1 - rust / go)` from the corresponding backend medians. From 86abf39b2646d8b310694dd73e533652f19d710b Mon Sep 17 00:00:00 2001 From: MK Date: Fri, 6 Nov 2026 15:55:51 +0800 Subject: [PATCH 10/21] bench(lint): compare installed dependency disk usage --- bench/tsrs-lint.md | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/bench/tsrs-lint.md b/bench/tsrs-lint.md index 9ef24c0174..2c7652aafc 100644 --- a/bench/tsrs-lint.md +++ b/bench/tsrs-lint.md @@ -42,6 +42,44 @@ The sampler sleeps for 20 ms. The median observed interval across runs was and sampling can miss short peaks. These values are process-tree RSS, not total allocation or container memory. +## Installed disk footprint + +Clean production installations on Linux x64 with glibc compare published `1.5.0` +with preview `0.0.0-commit.4fcf99edbe42fe3305dcd41dc884a4c4b7f5fd45`. +The preview binary-size comment measures individual executables and native bindings; +it does not measure the installed dependency tree. + +| Scope | Go (MiB) | tsrs (MiB) | Increase (MiB) | Increase | +| -------------------------------- | -------: | ---------: | -------------: | -------: | +| Complete `node_modules`, on disk | 132.93 | 150.87 | 17.94 | 13.5% | +| Complete `node_modules`, files | 123.39 | 139.27 | 15.88 | 12.9% | +| Backend packages, on disk | 21.63 | 39.69 | 18.06 | 83.5% | + +One MiB is 1,048,576 bytes. On-disk values use `du -s -B1 node_modules` and +include directory allocation. File sizes sum regular-file lengths without following +symlinks or counting an inode twice. The filesystem uses 4 KiB blocks. +Stores, download caches, Node.js, pnpm, and the global `vp` executable are excluded. + +The job used Node.js `24.22.2`, pnpm `10.34.7`, and each version's published +`vp-install-lock.yaml`. It reproduced the global installer's manifest and frozen +installation flags. Separate stores and `--config.package-import-method=copy` +prevent shared hard links from affecting the standalone footprint. Each installation +contained 80 packages. Version output and type-aware linting with TypeScript +diagnostics passed for both installations. The preview contained no `oxlint-tsgolint`. + +The backend totals include `oxlint-tsgolint` and `@oxlint-tsgolint/linux-x64`, or +`tsrs` and `@tsrs/linux-x64-gnu`. The Rust distribution includes a larger executable, +TypeScript library declarations, JavaScript APIs, and source maps. Its package +footprint increases despite the runtime and memory improvements. The total +installation comparison also includes other release-to-preview changes; +the backend row separates the backend packages from those changes. + +The measurement ran on 2026-11-06 in Codespaces job `job-2a30c8ec3fb84524`. +Scripts, raw package inventories, lockfiles, and logs remain outside the repository. +The lockfile SHA256 values are +`eb06955c3e76589da771286d3bfc6e6ea940c1a09ac64f597d2c72eb94403ad1` for `1.5.0` +and `cf8fe673142ace0abaa362552bff78281f3b8f00e01bc13fa0eb93c5b1344683` for the preview. + ## Conditions and interpretation The jobs used one GitHub Codespace with four vCPUs, 16 GB advertised RAM, and an From f18b5495e694bdc426f802be199aca5a165bc4a2 Mon Sep 17 00:00:00 2001 From: MK Date: Fri, 6 Nov 2026 16:50:11 +0800 Subject: [PATCH 11/21] docs(lint): record ecosystem compatibility regressions --- bench/tsrs-lint-ecosystem.md | 212 +++++++++++++++++++++++++++++++++++ bench/tsrs-lint.md | 4 + 2 files changed, 216 insertions(+) create mode 100644 bench/tsrs-lint-ecosystem.md diff --git a/bench/tsrs-lint-ecosystem.md b/bench/tsrs-lint-ecosystem.md new file mode 100644 index 0000000000..af9d8129ac --- /dev/null +++ b/bench/tsrs-lint-ecosystem.md @@ -0,0 +1,212 @@ +# Ecosystem correctness comparison + +The default `tsrs@0.22.0-ts7.1.0-beta` switch is not diagnostically equivalent to +`oxlint-tsgolint@7.0.2005`. The representative sweep found new type-check errors in +Hono and Vue Core, and a DOM-library compatibility change in OneSignal. These +findings prevent a regression-free release claim. + +On 2026-11-06, a Linux x64/glibc Codespace ran 141 commands across 14 pinned forks +from the [ecosystem catalog](https://github.com/vite-plus-ecosystem-ci/.github). +The explicit type-check workload covered 2,289 files. No command timed out or +terminated by a signal. Seven repositories had identical normalized diagnostics; +seven had differences. This covers the selected repositories, not all 83 catalog entries. + +## Controls and method + +The comparison used three toolchains: + +| Control | Published Vite+ package | Native lint backend | +| ----------------------- | ------------------------------------------------------- | -------------------------- | +| Stable | `1.5.0` | `oxlint-tsgolint@7.0.2005` | +| Preview with Go control | `0.0.0-commit.4fcf99edbe42fe3305dcd41dc884a4c4b7f5fd45` | `oxlint-tsgolint@7.0.2005` | +| Default preview | Same preview | `tsrs@0.22.0-ts7.1.0-beta` | + +All controls used Oxlint `1.91.0`. Stable and preview with Go produced identical +normalized lint and TypeScript diagnostics in every selected repository. This +control separates backend differences from other preview changes. + +Each project retained its original frozen lockfile, application dependencies, and +source configuration. The runner temporarily linked the published CLI, Vite, +Oxlint, and Oxfmt into workspace importers, then restored those links. Tracked +source files remained unchanged. The setup used each project's pinned package +manager, with lifecycle scripts disabled. Node.js was `24.22.2`, except +`eslint-plugin-oxlint` and `react-data-grid`, which used `26.13.0` to meet their engine requirements. + +Each control ran these commands: + +```sh +vp lint --format json --threads 4 +vp lint --type-aware --type-check --format json --threads 4 +vp check +``` + +Hono retained its lint scopes: `src`, `runtime-tests`, `build`, `perf-measures`, +`benchmarks`, and `adapters`. Hono and Vue Core do not enable type-aware linting in +their current configurations; the explicit flags add stress coverage. React Data +Grid enables type-aware rules but does not enable TypeScript diagnostics by default. + +The runner also used `vp run --no-cache` for five existing project tasks: +`oxc-walker`'s `test:types`, `urllib`'s `typecheck`, `media-processors`'s `typecheck`, +`react-data-grid`'s `typecheck`, and `vue-core`'s `check`. These tasks retain their +own compiler selection. A `tsc` task does not automatically become a `tsrs` task. + +The runner compared sorted JSON diagnostics, including codes, severity, messages, +files, spans, and help text. It normalized absolute project paths. Process traces +confirmed the expected native backend in all 42 explicit type-check runs. React +Data Grid and Vue Core required the matching optional `@vitest/browser-playwright@5.1.0` +peer for configuration imports. No browser installation was required. + +## Repository results + +The counts below include errors and warnings from the explicit type-check workload. +Equal counts alone do not establish equality; the comparison also checked diagnostic content. +All source links point to immutable commits in `vite-plus-ecosystem-ci`. + +| Repository / commit | Files | Go diagnostics | tsrs diagnostics | Comparison | +| -------------------------------------------------------------------------------------------------------------------------------------------------- | ----: | -------------: | ---------------: | --------------------------------------------- | +| [oxc-walker `9e520f25d`](https://github.com/vite-plus-ecosystem-ci/oxc-walker/tree/9e520f25d2bf5c3748f51d143c534fedff207147) | 12 | 0 | 0 | Identical diagnostics | +| [eslint-plugin-oxlint `ca4b10897`](https://github.com/vite-plus-ecosystem-ci/eslint-plugin-oxlint/tree/ca4b1089757245fa9a6a6ee46341d94a218d9c05) | 41 | 0 | 0 | Identical diagnostics | +| [solid-services `add4b036b`](https://github.com/vite-plus-ecosystem-ci/solid-services/tree/add4b036b72de7e6372b07faf0aed6b3f2e506d4) | 10 | 0 | 0 | Identical diagnostics | +| [pinia-shared-state `1b1ec6109`](https://github.com/vite-plus-ecosystem-ci/pinia-shared-state/tree/1b1ec6109b6f46079e4ddb5aeb9feff1d7188c24) | 6 | 0 | 0 | Identical diagnostics | +| [urllib `91f32d53f`](https://github.com/vite-plus-ecosystem-ci/urllib/tree/91f32d53faaae34a9f09f38e402e617420bc4d15) | 72 | 6 | 6 | Identical diagnostics | +| [anywidget `443d720b2`](https://github.com/vite-plus-ecosystem-ci/anywidget/tree/443d720b2233fff131a0f0a842b08744c8ffabd6) | 66 | 14 | 14 | Identical diagnostics | +| [css-modules-kit `634608fdd`](https://github.com/vite-plus-ecosystem-ci/css-modules-kit/tree/634608fdd401001d1fb05511a89177acf38b8111) | 131 | 0 | 0 | Identical diagnostics | +| [media-processors `79c87cd5c`](https://github.com/vite-plus-ecosystem-ci/media-processors/tree/79c87cd5c066a0a9abea979044c38c69603a85e8) | 22 | 101 | 91 | 10 fewer diagnostics | +| [web-app-template `f9a09f3d8`](https://github.com/vite-plus-ecosystem-ci/web-app-template/tree/f9a09f3d81002372e839247c6274af43177bbeca) | 8 | 3 | 1 | 2 fewer diagnostics | +| [OneSignal-Website-SDK `5375640c0`](https://github.com/vite-plus-ecosystem-ci/OneSignal-Website-SDK/tree/5375640c001a2dc462ed1cb938520edfb9a23d07) | 298 | 8 | 9 | New DOM-library requirement | +| [react-data-grid `384aedffc`](https://github.com/vite-plus-ecosystem-ci/react-data-grid/tree/384aedffc2cac013666b58b6787d888b9dde3a94) | 127 | 302 | 300 | 2 fewer diagnostics; message changes | +| [vue-clamp `6f0da3991`](https://github.com/vite-plus-ecosystem-ci/vue-clamp/tree/6f0da3991f804587ba402a7ca22aec4564eab938) | 134 | 5 | 1 | 4 fewer recursion errors | +| [hono `c91b60a25`](https://github.com/vite-plus-ecosystem-ci/hono/tree/c91b60a251186fb024557c6d55a11d8e388f179c) | 457 | 474 | 476 | 2 new contextual-typing errors; span changes | +| [vue-core `747e8d668`](https://github.com/vite-plus-ecosystem-ci/vue-core/tree/747e8d668c4a64ae6bd9dcc74c173364f1c1490b) | 905 | 547 | 546 | 7 new source errors; 8 fewer recursion errors | + +Default `vp lint` diagnostics matched in ten repositories. Its four differences +were OneSignal, media-processors, web-app-template, and vue-clamp. Vue Clamp's lint +exit changed from `1` to `0` after four `TS2321` errors disappeared; one warning remained. +The additional Hono, Vue Core, and React Data Grid differences appeared with explicit type-check flags. + +`vp check` passed in oxc-walker, eslint-plugin-oxlint, pinia-shared-state, and +css-modules-kit under all controls. The other ten failed under all controls. +Formatting stopped five of them before lint: solid-services, anywidget, +vue-clamp, Hono, and Vue Core. These failures do not demonstrate backend failure +or backend coverage. + +## Confirmed contextual-typing regression + +[Hono's `Style` implementation](https://github.com/vite-plus-ecosystem-ci/hono/blob/c91b60a251186fb024557c6d55a11d8e388f179c/src/helper/css/index.ts#L192) +produces two new `TS7031` errors for `children` and `nonce` with tsrs. This reduced +source reproduces the failure with strict type checking: + +```ts +interface StyleType { + (args?: { children?: Promise; nonce?: string }): string; +} +export const Style: StyleType = ({ children, nonce } = {}) => `${children}${nonce}`; +``` + +TypeScript `6.0.3`, TypeScript `7.0.2`, and both Go lint controls accept this source. +The default preview and standalone tsrs report both errors. An explicit parameter +type annotation makes all controls pass. Hono's production project also passes +`tsc -p tsconfig.build.json --noEmit` but fails the same check with standalone tsrs. +This confirms a compiler difference independent of Vite+ backend resolution. + +[Vue Core's `applyTextModel`](https://github.com/vite-plus-ecosystem-ci/vue-core/blob/747e8d668c4a64ae6bd9dcc74c173364f1c1490b/packages/runtime-vapor/src/directives/vModel.ts#L46) +has the same default-destructuring pattern. Its `trim`, `number`, and `lazy` bindings +produce three new `TS7031` errors. A four-line reduction with a mapped optional +parameter passes both TypeScript versions and both Go controls, but fails both tsrs paths. + +## Vue Core selector types + +[Vue Core's scoped-style transformer](https://github.com/vite-plus-ecosystem-ci/vue-core/blob/747e8d668c4a64ae6bd9dcc74c173364f1c1490b/packages/compiler-sfc/src/style/pluginScoped.ts#L107) +produces a new `TS5115` circular-instantiation error and two `TS7006` callback errors. +This smaller case uses the project's locked `postcss-selector-parser@7.1.6`: + +```ts +import type parser from 'postcss-selector-parser'; +export function inspect(selector: parser.Selector): void { + selector.each((n) => { + if (n.type === 'pseudo') { + n.nodes[0].each((ss) => { + void ss; + }); + } + }); +} +``` + +Both TypeScript versions and both Go controls accept the reduced source. The +default preview and standalone tsrs report `TS5115` and `TS7006`. The reduction +uses strict mode, bundler module resolution, and `skipLibCheck: true`. + +## Vue Core generic conditional return type + +[Vue Core's `toHandlerKey`](https://github.com/vite-plus-ecosystem-ci/vue-core/blob/747e8d668c4a64ae6bd9dcc74c173364f1c1490b/packages/shared/src/general.ts#L147) +produces a new `TS2322` when it assigns a generic function with a conditional +return type. Both an isolated copy of the original implementation and this +reduction reproduce the error: + +```ts +declare function cache string>(fn: T): T; +export const handler: (s: T) => T extends '' ? '' : `on${Capitalize}` = cache( + (s: T) => { + const value = s ? `on${s}` : ''; + return value as T extends '' ? '' : `on${Capitalize}`; + }, +); +``` + +Both TypeScript versions and both Go controls accept both cases. The default +preview and standalone tsrs reject both cases with `TS2322`. The reduction uses +the same strict compiler options as the selector case. + +## OneSignal DOM-library difference + +[OneSignal's notification mock](https://github.com/vite-plus-ecosystem-ci/OneSignal-Website-SDK/blob/5375640c001a2dc462ed1cb938520edfb9a23d07/__test__/support/environment/TestEnvironmentHelpers.ts#L46) +produces a new `TS2741`: the constructor lacks `Notification.maxActions`. The tsrs +`lib.dom.d.ts` requires that static property. The tested TypeScript `6.0.3` and +`7.0.2` libraries do not contain it. + +```ts +type HasMaxActions = 'maxActions' extends keyof typeof Notification ? true : false; +export const hasMaxActions: HasMaxActions = false; +``` + +The TypeScript and Go controls accept this probe. Both tsrs paths report `TS2322`. +This establishes a library-contract difference; it does not establish that the +new DOM declaration is incorrect. The difference affects OneSignal's existing +configuration because it already enables TypeScript diagnostics. + +## Other differences and limits + +Media-processors, web-app-template, vue-clamp, and React Data Grid report fewer +recursive-comparison or configuration-compatibility errors with tsrs. Vue Core +also loses eight `TS2321` errors but adds seven production-source errors. Its total +therefore falls from 547 to 546 despite the new failures. Fewer diagnostics are +not classified as fixes without a separate correctness check. + +React Data Grid also changes expanded type text in twelve existing diagnostics. +Vue Core changes two existing messages, and Hono moves three existing `TS2875` +spans. These presentation changes are included in the comparison. + +Several errors are shared by both backends. They include urllib's six +`Http2Stream.respond` errors, anywidget's 14 type errors, missing generated +`video-track-processor` declarations in media-processors, and a missing translation +file in web-app-template. The broad Hono workload includes optional benchmark +and runtime fixtures. React Data Grid and Vue Core include test declarations +that conflict with the overlaid toolchain's Vitest types. + +Only oxc-walker's existing type-check task passed. The other four tasks failed +under every control, with matching TypeScript diagnostics after installation-path +normalization. Vue Core initially returned `2` and then `1` as its incremental +cache warmed; fresh build-info files produced exit `2` and identical output in +all three controls. No task compiler was replaced by the default lint backend. + +This method isolates the backend while preserving application dependencies. It +does not reproduce each repository's full build pipeline or a complete dependency +upgrade. Shared setup and source errors must not be counted as new tsrs regressions. +The reduced cases provide independent evidence for the new compiler failures. + +The primary sweep used Codespaces job `job-d8ac88c64fc64b11`. Final anywidget, +React Data Grid, and Vue Core results came from `job-f5f12efe4b514e83`, which also +reduced the Hono and OneSignal cases. Vue reductions used `job-cf53f53e0a2d4216` and `job-120ca876783b4213`. +The harness, frozen dependency receipts, process traces, compiler outputs, and +raw JSON remain outside this repository. diff --git a/bench/tsrs-lint.md b/bench/tsrs-lint.md index 2c7652aafc..8b5d3c3270 100644 --- a/bench/tsrs-lint.md +++ b/bench/tsrs-lint.md @@ -3,6 +3,10 @@ This comparison uses the default `tsrs@0.22.0-ts7.1.0-beta` integration and an explicit `oxlint-tsgolint@7.0.2005` override. Both use Oxlint `1.91.0` and the same built CLI. +The separate [ecosystem correctness comparison](./tsrs-lint-ecosystem.md) found +compiler regressions and library differences in representative forks. The performance +results below do not establish diagnostic equivalence. + ## Elapsed time Each value is the median of ten fresh CLI processes after one warm-up per backend. From 448556e018fb672bd9ed94872b17b7087460cffd Mon Sep 17 00:00:00 2001 From: MK Date: Sun, 8 Nov 2026 23:23:08 +0800 Subject: [PATCH 12/21] docs(lint): attribute compatibility changes to upstream TypeScript --- bench/tsrs-lint-ecosystem.md | 113 ++++++++++++++++++++++++++++++++--- bench/tsrs-lint.md | 3 +- 2 files changed, 106 insertions(+), 10 deletions(-) diff --git a/bench/tsrs-lint-ecosystem.md b/bench/tsrs-lint-ecosystem.md index af9d8129ac..4b085542cb 100644 --- a/bench/tsrs-lint-ecosystem.md +++ b/bench/tsrs-lint-ecosystem.md @@ -2,8 +2,10 @@ The default `tsrs@0.22.0-ts7.1.0-beta` switch is not diagnostically equivalent to `oxlint-tsgolint@7.0.2005`. The representative sweep found new type-check errors in -Hono and Vue Core, and a DOM-library compatibility change in OneSignal. These -findings prevent a regression-free release claim. +Hono and Vue Core, and a DOM-library compatibility change in OneSignal. The +exact-version audit below attributes all four reproduced categories to the pinned +TypeScript `7.1.0-beta` behavior. It found no Rust-port defect in these cases. +The default switch still changes diagnostics for projects that use the older backend. On 2026-11-06, a Linux x64/glibc Codespace ran 141 commands across 14 pinned forks from the [ecosystem catalog](https://github.com/vite-plus-ecosystem-ci/.github). @@ -11,6 +13,50 @@ The explicit type-check workload covered 2,289 files. No command timed out or terminated by a signal. Seven repositories had identical normalized diagnostics; seven had differences. This covers the selected repositories, not all 83 catalog entries. +## Exact-upstream attribution (2026-11-08) + +The original `6.0.3`, `7.0.2`, and `oxlint-tsgolint@7.0.2005` controls establish +upgrade compatibility, but cannot identify a Rust-port defect by themselves. +The [tsrs compatibility contract](https://github.com/oxc-project/tsrs/blob/625f918c85a3e12824ede39cd127d0f866b8d9c8/docs/COMPATIBILITY.md#conformance) +defines correctness against its exact upstream Go compiler, including upstream bugs. + +This audit compared the retained tsrs executable with published `typescript@7.1.0-beta`. +Both target `microsoft/TypeScript@3e9236361fa97a9e6c685f4833ac457ae2d8c50a`. +The reference wrapper and native package both identify that commit in `gitHead`. +The Rust source audit used release tag `v0.22.0`, commit +`625f918c85a3e12824ede39cd127d0f866b8d9c8`. + +All seven reduced cases produced identical diagnostic output and exit codes: + +| Reduced case | TypeScript `7.1.0-beta` | tsrs `0.22.0` | +| ----------------------------------------------- | ------------------------------- | ------------- | +| Hono default destructuring | Two `TS7031`, exit `2` | Identical | +| Hono explicit parameter annotation | No errors, exit `0` | Identical | +| Vue mapped-parameter destructuring | Three `TS7031`, exit `2` | Identical | +| Vue selector iteration | `TS5115` and `TS7006`, exit `2` | Identical | +| Vue conditional return, reduced | `TS2322`, exit `2` | Identical | +| Vue conditional return, original implementation | `TS2322`, exit `2` | Identical | +| DOM `maxActions` probe | `TS2322`, exit `2` | Identical | + +The four categories require different interpretations: + +| Category | Assessment | Follow-up | +| ----------------------- | ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | +| Default destructuring | Upstream TypeScript `7.1` false positive; tsrs matches it | Follow TypeScript #64974; an explicit parameter annotation passes | +| Conditional return type | Upstream `7.1` compatibility regression; no Rust divergence | Investigate the related conditional-type normalization fix in TypeScript #64307 | +| Selector recursion | Upstream `7.1` recursion restriction exposes the dependency's circular type construction | Rework or update the selector declarations; do not assume the older acceptance proves validity | +| Notification mock | Correct enforcement of the newer DOM declaration | Add the missing static member to the mock | + +Matching the pinned compiler does not mean every upstream diagnostic is desirable. +It means these reproductions do not justify changing the Rust port independently. +The new default also adopts a newer TypeScript version, so its compatibility cost +must be assessed separately from the implementation language. + +The 14 compiler invocations ran in Codespaces job `job-3aecfcc9bb6f49bb`. +The input files and compiler options were unchanged from the earlier reductions. +Raw output, executable/version evidence, package-lock hashes, and scripts remain +outside this repository. This follow-up did not repeat the full 14-repository sweep. + ## Controls and method The comparison used three toolchains: @@ -90,7 +136,7 @@ Formatting stopped five of them before lint: solid-services, anywidget, vue-clamp, Hono, and Vue Core. These failures do not demonstrate backend failure or backend coverage. -## Confirmed contextual-typing regression +## Upstream contextual-typing regression [Hono's `Style` implementation](https://github.com/vite-plus-ecosystem-ci/hono/blob/c91b60a251186fb024557c6d55a11d8e388f179c/src/helper/css/index.ts#L192) produces two new `TS7031` errors for `children` and `nonce` with tsrs. This reduced @@ -107,12 +153,21 @@ TypeScript `6.0.3`, TypeScript `7.0.2`, and both Go lint controls accept this so The default preview and standalone tsrs report both errors. An explicit parameter type annotation makes all controls pass. Hono's production project also passes `tsc -p tsconfig.build.json --noEmit` but fails the same check with standalone tsrs. -This confirms a compiler difference independent of Vite+ backend resolution. +This confirms a compiler-version difference independent of Vite+ backend resolution. [Vue Core's `applyTextModel`](https://github.com/vite-plus-ecosystem-ci/vue-core/blob/747e8d668c4a64ae6bd9dcc74c173364f1c1490b/packages/runtime-vapor/src/directives/vModel.ts#L46) has the same default-destructuring pattern. Its `trim`, `number`, and `lazy` bindings produce three new `TS7031` errors. A four-line reduction with a mapped optional -parameter passes both TypeScript versions and both Go controls, but fails both tsrs paths. +parameter passes TypeScript `6.0.3`, TypeScript `7.0.2`, and both Go controls, but fails both tsrs paths. + +The exact `7.1.0-beta` reference reports the same errors. Its +[`checkDeclarationInitializer` / `padObjectLiteralType`](https://github.com/microsoft/TypeScript/blob/3e9236361fa97a9e6c685f4833ac457ae2d8c50a/tsc/internal/checker/checker.go#L17205) +pads missing properties of the `{}` initializer and calls +`getTypeFromBindingElement` with `reportErrors: true`. The +[Rust port follows the same path](https://github.com/oxc-project/tsrs/blob/625f918c85a3e12824ede39cd127d0f866b8d9c8/crates/tsrs_checker/src/checker_08.rs#L2980). +That path can report implicit `any` despite an available contextual parameter type. +TypeScript tracks this as [bug #64974](https://github.com/microsoft/TypeScript/issues/64974). +The rejection is a false positive, while the tsrs implementation conforms to its reference. ## Vue Core selector types @@ -133,10 +188,25 @@ export function inspect(selector: parser.Selector): void { } ``` -Both TypeScript versions and both Go controls accept the reduced source. The +TypeScript `6.0.3`, TypeScript `7.0.2`, and both Go controls accept the reduced source. The default preview and standalone tsrs report `TS5115` and `TS7006`. The reduction uses strict mode, bundler module resolution, and `skipLibCheck: true`. +The dependency declares `Selector = _Selector`, where `_Selector` +extends `Container>`. `Node` itself includes `Selector`, and +`Diff` is a distributive conditional type. Resolving the base members can +therefore require comparing the type whose base members are still being resolved. +TypeScript [#64372](https://github.com/microsoft/TypeScript/pull/64372) deliberately +stopped allowing base type arguments to depend on partially resolved members. + +Both the [upstream instantiation guard](https://github.com/microsoft/TypeScript/blob/3e9236361fa97a9e6c685f4833ac457ae2d8c50a/tsc/internal/checker/checker.go#L22601) +and [Rust guard](https://github.com/oxc-project/tsrs/blob/625f918c85a3e12824ede39cd127d0f866b8d9c8/crates/tsrs_checker/src/checker_11.rs#L1319) +stop at depth `100` or `5,000,000` instantiations and report the circular type names. +The exact upstream compiler reports the same `Container` / `Diff` error and lost +callback type. This is a dependency compatibility problem under the newer rules, +not evidence of a Rust implementation error. The causal link to the stricter +inheritance rule follows from the source review; this audit did not bisect that upstream change. + ## Vue Core generic conditional return type [Vue Core's `toHandlerKey`](https://github.com/vite-plus-ecosystem-ci/vue-core/blob/747e8d668c4a64ae6bd9dcc74c173364f1c1490b/packages/shared/src/general.ts#L147) @@ -154,10 +224,24 @@ export const handler: (s: T) => T extends '' ? '' : `on${Capit ); ``` -Both TypeScript versions and both Go controls accept both cases. The default +TypeScript `6.0.3`, TypeScript `7.0.2`, and both Go controls accept both cases. The default preview and standalone tsrs reject both cases with `TS2322`. The reduction uses the same strict compiler options as the selector case. +The exact `7.1.0-beta` compiler rejects both cases identically. The +[upstream conditional-type comparison](https://github.com/microsoft/TypeScript/blob/3e9236361fa97a9e6c685f4833ac457ae2d8c50a/tsc/internal/checker/relater.go#L3758) +and [Rust comparison](https://github.com/oxc-project/tsrs/blob/625f918c85a3e12824ede39cd127d0f866b8d9c8/crates/tsrs_checker/src/relater_2.rs#L2541) +compare the conditional types without normalizing both distributed type parameters. +This is consistent with the upstream asymmetry described in +[#64673](https://github.com/microsoft/TypeScript/issues/64673): instantiation can +normalize one side while the other retains a distributed parameter. + +The open [fix #64307](https://github.com/microsoft/TypeScript/pull/64307) normalizes +both sides and is relevant to this failure. That root-cause connection is an +inference from the source and diagnostic chain. The patch was not applied or +validated against this case here. The exact-reference failure is sufficient to +rule out a Rust-only divergence for this reproduction. + ## OneSignal DOM-library difference [OneSignal's notification mock](https://github.com/vite-plus-ecosystem-ci/OneSignal-Website-SDK/blob/5375640c001a2dc462ed1cb938520edfb9a23d07/__test__/support/environment/TestEnvironmentHelpers.ts#L46) @@ -170,11 +254,20 @@ type HasMaxActions = 'maxActions' extends keyof typeof Notification ? true : fal export const hasMaxActions: HasMaxActions = false; ``` -The TypeScript and Go controls accept this probe. Both tsrs paths report `TS2322`. +The TypeScript `6.0.3` / `7.0.2` and Go lint controls accept this probe. Both tsrs paths report `TS2322`. This establishes a library-contract difference; it does not establish that the new DOM declaration is incorrect. The difference affects OneSignal's existing configuration because it already enables TypeScript diagnostics. +The tsrs and TypeScript `7.1.0-beta` `lib.dom.d.ts` files are byte-identical: +SHA256 `2f8c8d9e23d03e00c353412f5b2be3b1fd893290fc0976ad2229681bb7d46a35`. +The [Notifications standard](https://notifications.spec.whatwg.org/#dom-notification-maxactions) +also defines a static read-only `maxActions` member. Assigning `MockNotification` +to the constructor type now correctly requires that static member. Adding an +instance property would not satisfy it. A mock that supports no actions can +supply `static readonly maxActions = 0`. This is a mock adaptation to the updated +standard library, not a compiler false positive. + ## Other differences and limits Media-processors, web-app-template, vue-clamp, and React Data Grid report fewer @@ -203,7 +296,9 @@ all three controls. No task compiler was replaced by the default lint backend. This method isolates the backend while preserving application dependencies. It does not reproduce each repository's full build pipeline or a complete dependency upgrade. Shared setup and source errors must not be counted as new tsrs regressions. -The reduced cases provide independent evidence for the new compiler failures. +The reduced cases provide independent evidence for the compiler-version differences. +The exact-reference audit attributes the four reproduced categories; it does not +establish exact-upstream parity for every diagnostic in the full sweep. The primary sweep used Codespaces job `job-d8ac88c64fc64b11`. Final anywidget, React Data Grid, and Vue Core results came from `job-f5f12efe4b514e83`, which also diff --git a/bench/tsrs-lint.md b/bench/tsrs-lint.md index 8b5d3c3270..c9ba28b082 100644 --- a/bench/tsrs-lint.md +++ b/bench/tsrs-lint.md @@ -4,7 +4,8 @@ This comparison uses the default `tsrs@0.22.0-ts7.1.0-beta` integration and an e `oxlint-tsgolint@7.0.2005` override. Both use Oxlint `1.91.0` and the same built CLI. The separate [ecosystem correctness comparison](./tsrs-lint-ecosystem.md) found -compiler regressions and library differences in representative forks. The performance +upstream TypeScript `7.1` regressions and library differences in representative forks. +The exact-version audit found no Rust-port divergence in the reduced cases. The performance results below do not establish diagnostic equivalence. ## Elapsed time From 5f97ea799b7bf169427f19d3cac88f24485b4893 Mon Sep 17 00:00:00 2001 From: MK Date: Mon, 9 Nov 2026 14:13:32 +0800 Subject: [PATCH 13/21] chore(deps): update tsrs to 0.23.0-ts7.1.0-beta --- bench/tsrs-lint-ecosystem.md | 40 ++++++++++++++++++++- bench/tsrs-lint.md | 3 ++ pnpm-lock.yaml | 68 ++++++++++++++++++------------------ pnpm-workspace.yaml | 2 +- rfcs/check-command.md | 9 ++++- rfcs/toolchain-command.md | 2 +- 6 files changed, 86 insertions(+), 38 deletions(-) diff --git a/bench/tsrs-lint-ecosystem.md b/bench/tsrs-lint-ecosystem.md index 4b085542cb..1c54263050 100644 --- a/bench/tsrs-lint-ecosystem.md +++ b/bench/tsrs-lint-ecosystem.md @@ -1,6 +1,6 @@ # Ecosystem correctness comparison -The default `tsrs@0.22.0-ts7.1.0-beta` switch is not diagnostically equivalent to +The `tsrs@0.22.0-ts7.1.0-beta` experiment is not diagnostically equivalent to `oxlint-tsgolint@7.0.2005`. The representative sweep found new type-check errors in Hono and Vue Core, and a DOM-library compatibility change in OneSignal. The exact-version audit below attributes all four reproduced categories to the pinned @@ -13,6 +13,44 @@ The explicit type-check workload covered 2,289 files. No command timed out or terminated by a signal. Seven repositories had identical normalized diagnostics; seven had differences. This covers the selected repositories, not all 83 catalog entries. +## Version 0.23 upgrade check (2026-11-09) + +The current integration pins `tsrs@0.23.0-ts7.1.0-beta`. The full ecosystem sweep +and performance measurements below remain results for `0.22.0`. + +On macOS ARM64, all seven reduced cases retained identical diagnostics and exit +codes between `0.22.0` and `0.23.0`, both through `vp lint` and the standalone +compiler. The standalone results also matched `typescript@7.1.0-beta`. +The TypeScript source pin remains `3e9236361fa97a9e6c685f4833ac457ae2d8c50a`. +The four existing compatibility follow-ups remain open. + +The [0.23 release](https://github.com/oxc-project/tsrs/blob/v0.23.0/docs/releases/0.23.0.md) +expands `--type-check` to whole programs. A smoke fixture confirmed that selecting +a clean file now reports `TS2322` in an ignored file from the same program. +Type-aware linting without `--type-check` still passes that fixture. A separate +invalid declaration remains suppressed by `skipLibCheck: true`. + +The rebuilt CLI passed 3,403 unit tests, with one skipped. However, the full +repository `vp check` now fails with seven diagnostics. With the same source and +dependencies, `0.22.0` and `oxlint-tsgolint@7.0.2005` pass the lint/type-check phase: + +| Dependency | New diagnostics | +| ----------------------------------------------------- | -------------------------------------------------------------- | +| `@voidzero-dev/vitepress-theme@5.0.7` | One `TS2614` for the `Sponsors` export from a Vue file | +| `zod@4.6.5` | One `TS5115` for recursive JSON schema types | +| `webpack-virtual-modules@0.6.2` and `unplugin@2.3.11` | Five `TS2307` errors for missing optional bundler declarations | + +Standalone `typescript@7.1.0-beta -p tsconfig.json --pretty false` passes the +repository root configuration. These new whole-program diagnostics need a separate +scope and compiler-options investigation; the earlier seven-case parity result +does not establish their correctness. + +This check used the rebased branch on `main` at `92edd550c`, Node.js `24.22.2`, +Oxlint `1.91.0`, and fresh CLI JavaScript. The native binding and core artifacts +remain from published `1.5.0`; the JS build used published Rolldown `1.2.16`. +This is not a full native release build or a repeat of the 14-repository sweep. +Scripts, fixtures, and raw logs remain outside the repository. + ## Exact-upstream attribution (2026-11-08) The original `6.0.3`, `7.0.2`, and `oxlint-tsgolint@7.0.2005` controls establish diff --git a/bench/tsrs-lint.md b/bench/tsrs-lint.md index c9ba28b082..e66a0aacba 100644 --- a/bench/tsrs-lint.md +++ b/bench/tsrs-lint.md @@ -1,5 +1,8 @@ # Type-aware linting with tsrs-lint +The current integration pins `tsrs@0.23.0-ts7.1.0-beta`. The measurements below +remain results for `0.22.0`; they have not been repeated for `0.23.0`. + This comparison uses the default `tsrs@0.22.0-ts7.1.0-beta` integration and an explicit `oxlint-tsgolint@7.0.2005` override. Both use Oxlint `1.91.0` and the same built CLI. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c0b747628f..6e8a863c3b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -345,8 +345,8 @@ catalogs: specifier: ^0.23.4 version: 0.23.4 tsrs: - specifier: '=0.22.0-ts7.1.0-beta' - version: 0.22.0-ts7.1.0-beta + specifier: '=0.23.0-ts7.1.0-beta' + version: 0.23.0-ts7.1.0-beta typescript: specifier: ~7.0.2 version: 7.0.2 @@ -471,7 +471,7 @@ importers: version: 1.62.1 tsrs: specifier: 'catalog:' - version: 0.22.0-ts7.1.0-beta + version: 0.23.0-ts7.1.0-beta vite: specifier: workspace:@voidzero-dev/vite-plus-core@* version: link:../core @@ -3582,48 +3582,48 @@ packages: peerDependencies: tsdown: 0.23.4 - '@tsrs/darwin-arm64@0.22.0-ts7.1.0-beta': - resolution: {integrity: sha512-mY0dMEtFxig4h5At3nByjZe8E4DTDPqqz73kkxQ6gqW5K9ym+u/VHQhtnpTF6ZOTkYyjP3bTaj5T8HcyoGVddA==} + '@tsrs/darwin-arm64@0.23.0-ts7.1.0-beta': + resolution: {integrity: sha512-Bk1aFZRo4d4RKtOU8OEVLXWZ7E0JqnL8xKhOb+FuvRRb9Qxu5UIgD0W3xiGvixzZ99I7f8Bj/NCTN30xX7MkEA==} engines: {node: '>=16.20.0'} cpu: [arm64] os: [darwin] - '@tsrs/darwin-x64@0.22.0-ts7.1.0-beta': - resolution: {integrity: sha512-dvsSnyU8rpI3OqTIH2xxjt9ThvZC52BsL6/LS69eWg7Ieq2oni1PnLels5gGTFqvHFoRbezi5Y0KY7PQ1OmK5w==} + '@tsrs/darwin-x64@0.23.0-ts7.1.0-beta': + resolution: {integrity: sha512-asvC3hDKmRHjgfrr3o+bGVxhdPA5xfLCwcpfhIW4MCLcutBA8nltb4chtWRfEWsnKVimxtNvjnP0XldhgnJtkw==} engines: {node: '>=16.20.0'} cpu: [x64] os: [darwin] - '@tsrs/linux-arm64-gnu@0.22.0-ts7.1.0-beta': - resolution: {integrity: sha512-igg42LxSN/DHFqafvDqZW2JNBMH8WIwc1JE/IY4QUDOXNhZnUfDgEu9xo1QYwbgryeo5llmhRwM4CI/BQUWF1Q==} + '@tsrs/linux-arm64-gnu@0.23.0-ts7.1.0-beta': + resolution: {integrity: sha512-5Uhhvo7q5b5Xwv11uLCDcPBlzTvNcsDtog05eK5lsS8763BioGuKobYtmsBPQU6KyowzRv4GoocqSePd5yaEJw==} engines: {node: '>=16.20.0'} cpu: [arm64] os: [linux] libc: [glibc] - '@tsrs/linux-arm64-musl@0.22.0-ts7.1.0-beta': - resolution: {integrity: sha512-f0zCqMkxPof7QVwjdgA8Fb0n6BtX5MOdILH/xfS+He6gRLCoECky0ejbsgLWCpB06GMlsL1gCbDr0/tmc91APg==} + '@tsrs/linux-arm64-musl@0.23.0-ts7.1.0-beta': + resolution: {integrity: sha512-v5vZCEec88vACtPHRPKie9JziE+X77VCNj/dNhj/buGAJwUZW2MMaQjJxKiSY4VwVo302kRfKKATyPljD23U6Q==} engines: {node: '>=16.20.0'} cpu: [arm64] os: [linux] libc: [musl] - '@tsrs/linux-x64-gnu@0.22.0-ts7.1.0-beta': - resolution: {integrity: sha512-/e511w7xXmxBf14paz8eMLUbmlIc33Sy1E00y1FqmnUK04FTotWl+80mNvIVX5NrlOOJgrh+B125t1CHWu/8vQ==} + '@tsrs/linux-x64-gnu@0.23.0-ts7.1.0-beta': + resolution: {integrity: sha512-QJTJkk5XhkZSU/LBvjeFPpfRLDuICJbS9ij6q2rBy/Olw4T8WN7DGiE8ig687gI2dfnzlfScZsJYFtK2+CngnA==} engines: {node: '>=16.20.0'} cpu: [x64] os: [linux] libc: [glibc] - '@tsrs/linux-x64-musl@0.22.0-ts7.1.0-beta': - resolution: {integrity: sha512-B3aDOdS5+h77HcFep+KtOI+XmLm2G+FQPQC3KPSDjS/IG0tY9GPSc14mYnLwgO++dXluukO/oufk3J4Lo0CFjg==} + '@tsrs/linux-x64-musl@0.23.0-ts7.1.0-beta': + resolution: {integrity: sha512-cOW2i9W+ybOABvrmJpd8fI+Cbm5ej0unCSYBRyFAE0FLnQyuQT0+7vdNkrJn4QjaK62OS99vknW56/DfCcRrMA==} engines: {node: '>=16.20.0'} cpu: [x64] os: [linux] libc: [musl] - '@tsrs/win32-x64@0.22.0-ts7.1.0-beta': - resolution: {integrity: sha512-cZ69w+wOECbU6YF5UWi15TK73eX1pgoMEHKSmUF4K8PbS7DPIh/nWMsdb3rM8Ee1SUZ9ONCx+S6myh64SRi3Bg==} + '@tsrs/win32-x64@0.23.0-ts7.1.0-beta': + resolution: {integrity: sha512-Af9+bfGuTHtp41AOkWMmkQlWYPYvp3hvdw6dCRI6IlLdy4/5AqaJaFjDoCUTLJMojNjJjC4Ux/GhE2+CRoIomg==} engines: {node: '>=16.20.0'} cpu: [x64] os: [win32] @@ -6718,8 +6718,8 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - tsrs@0.22.0-ts7.1.0-beta: - resolution: {integrity: sha512-DEnEqQZRM3sHCV7MnQKVeTh61U2SX+0WksVlWp4eVayy0NffQ2oiCOjgH/Eye3sMPYkGwpM3tht3HtVI9NdURg==} + tsrs@0.23.0-ts7.1.0-beta: + resolution: {integrity: sha512-A7UU5bUA8caDkidkMfrfeF4etU92ODZxasCqIb1eaJ36+BAZgyZ6Znq7NVu/c1HFG57E6la/TeofpZhPdgmLBw==} engines: {node: '>=16.20.0'} hasBin: true @@ -9015,25 +9015,25 @@ snapshots: tsdown: 0.23.4(@arethetypeswrong/core@0.18.2)(@tsdown/css@0.23.4)(@tsdown/exe@0.23.4)(@typescript/native-preview@7.0.0-dev.20260707.2)(@vitejs/devtools@0.8.0(crossws@0.4.12(srvx@1.0.6))(srvx@1.0.6)(vite@packages+core))(@volar/typescript@2.4.28)(@vue/language-core@3.3.12)(oxc-resolver@11.24.2)(publint@0.3.24)(tsx@4.23.15)(typescript@7.0.2)(unplugin-unused@0.5.6)(unrun@0.3.1)(vue-tsc@3.3.12(typescript@7.0.2)) verkit: 0.5.0 - '@tsrs/darwin-arm64@0.22.0-ts7.1.0-beta': + '@tsrs/darwin-arm64@0.23.0-ts7.1.0-beta': optional: true - '@tsrs/darwin-x64@0.22.0-ts7.1.0-beta': + '@tsrs/darwin-x64@0.23.0-ts7.1.0-beta': optional: true - '@tsrs/linux-arm64-gnu@0.22.0-ts7.1.0-beta': + '@tsrs/linux-arm64-gnu@0.23.0-ts7.1.0-beta': optional: true - '@tsrs/linux-arm64-musl@0.22.0-ts7.1.0-beta': + '@tsrs/linux-arm64-musl@0.23.0-ts7.1.0-beta': optional: true - '@tsrs/linux-x64-gnu@0.22.0-ts7.1.0-beta': + '@tsrs/linux-x64-gnu@0.23.0-ts7.1.0-beta': optional: true - '@tsrs/linux-x64-musl@0.22.0-ts7.1.0-beta': + '@tsrs/linux-x64-musl@0.23.0-ts7.1.0-beta': optional: true - '@tsrs/win32-x64@0.22.0-ts7.1.0-beta': + '@tsrs/win32-x64@0.23.0-ts7.1.0-beta': optional: true '@tybys/wasm-util@0.10.3': @@ -12208,15 +12208,15 @@ snapshots: tslib@2.8.1: {} - tsrs@0.22.0-ts7.1.0-beta: + tsrs@0.23.0-ts7.1.0-beta: optionalDependencies: - '@tsrs/darwin-arm64': 0.22.0-ts7.1.0-beta - '@tsrs/darwin-x64': 0.22.0-ts7.1.0-beta - '@tsrs/linux-arm64-gnu': 0.22.0-ts7.1.0-beta - '@tsrs/linux-arm64-musl': 0.22.0-ts7.1.0-beta - '@tsrs/linux-x64-gnu': 0.22.0-ts7.1.0-beta - '@tsrs/linux-x64-musl': 0.22.0-ts7.1.0-beta - '@tsrs/win32-x64': 0.22.0-ts7.1.0-beta + '@tsrs/darwin-arm64': 0.23.0-ts7.1.0-beta + '@tsrs/darwin-x64': 0.23.0-ts7.1.0-beta + '@tsrs/linux-arm64-gnu': 0.23.0-ts7.1.0-beta + '@tsrs/linux-arm64-musl': 0.23.0-ts7.1.0-beta + '@tsrs/linux-x64-gnu': 0.23.0-ts7.1.0-beta + '@tsrs/linux-x64-musl': 0.23.0-ts7.1.0-beta + '@tsrs/win32-x64': 0.23.0-ts7.1.0-beta tsx@4.23.15: dependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 5e57281df2..81ed62ee6a 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -67,7 +67,7 @@ catalog: source-map: 0.8.0 tinyglobby: ^0.2.15 tsdown: ^0.23.4 - tsrs: =0.22.0-ts7.1.0-beta + tsrs: =0.23.0-ts7.1.0-beta typescript: ~7.0.2 valibot: 1.5.0 validate-npm-package-name: ^8.0.0 diff --git a/rfcs/check-command.md b/rfcs/check-command.md index 90bb4f4c44..1ba9f35196 100644 --- a/rfcs/check-command.md +++ b/rfcs/check-command.md @@ -202,12 +202,19 @@ The check module reuses `SubcommandResolver` and `resolve_and_capture_output` fr ### Experimental type-aware backend -This draft replaces the bundled Go backend with `tsrs@0.22.0-ts7.1.0-beta`, +This draft replaces the bundled Go backend with `tsrs@0.23.0-ts7.1.0-beta`, a Rust port of the TypeScript 7 compiler. `resolve-lint.ts` selects the bundled `tsrs` package and passes its `bin/tsrs-lint` launcher to Oxlint through `OXLINT_TSGOLINT_PATH`. The same resolver serves `vp lint` and `vp check`. Users do not need to set an environment variable for the default backend. +With TypeScript diagnostics enabled, this release checks the whole program for +each tsconfig selected by the lint input files. Unselected or ignored program +files can therefore report type errors. Oxlint ignores still control lint rules, +and `skipLibCheck` controls declaration checking. +Selected files outside the discovered tsconfigs use an inferred program with +fallback compiler options. + An explicit `OXLINT_TSGOLINT_PATH` takes precedence over the resolver default. The environment variable keeps its existing name because Oxlint uses it to select the type-aware backend. diff --git a/rfcs/toolchain-command.md b/rfcs/toolchain-command.md index 5f94f55812..ab11711110 100644 --- a/rfcs/toolchain-command.md +++ b/rfcs/toolchain-command.md @@ -164,7 +164,7 @@ vite-plus@0.2.4 │ └── bundles tsdown@0.22.3 ├── depends on vitest@4.1.10 ├── depends on oxlint@1.72.0 -├── depends on tsrs@0.22.0-ts7.1.0-beta +├── depends on tsrs@0.23.0-ts7.1.0-beta ├── depends on oxfmt@0.57.0 └── compiles vite-task (built 2026-08-06T09:30:00Z, revision ) ``` From 9f8d5f5e76687357819bc9af3569aea804c50bf7 Mon Sep 17 00:00:00 2001 From: MK Date: Mon, 9 Nov 2026 14:40:23 +0800 Subject: [PATCH 14/21] fix(check): align nested projects with whole-program checks --- bench/tsrs-lint-ecosystem.md | 29 ++++++++++++++----- docs/.vitepress/env.d.ts | 11 +++++++ .../cli/templates/generator/tsconfig.json | 1 + packages/cli/templates/monorepo/tsconfig.json | 3 +- 4 files changed, 35 insertions(+), 9 deletions(-) diff --git a/bench/tsrs-lint-ecosystem.md b/bench/tsrs-lint-ecosystem.md index 1c54263050..fbef47fe23 100644 --- a/bench/tsrs-lint-ecosystem.md +++ b/bench/tsrs-lint-ecosystem.md @@ -16,7 +16,7 @@ seven had differences. This covers the selected repositories, not all 83 catalog ## Version 0.23 upgrade check (2026-11-09) The current integration pins `tsrs@0.23.0-ts7.1.0-beta`. The full ecosystem sweep -and performance measurements below remain results for `0.22.0`. +and performance measurements remain results for `0.22.0`. On macOS ARM64, all seven reduced cases retained identical diagnostics and exit codes between `0.22.0` and `0.23.0`, both through `vp lint` and the standalone @@ -30,9 +30,9 @@ a clean file now reports `TS2322` in an ignored file from the same program. Type-aware linting without `--type-check` still passes that fixture. A separate invalid declaration remains suppressed by `skipLibCheck: true`. -The rebuilt CLI passed 3,403 unit tests, with one skipped. However, the full -repository `vp check` now fails with seven diagnostics. With the same source and -dependencies, `0.22.0` and `oxlint-tsgolint@7.0.2005` pass the lint/type-check phase: +Whole-program checking initially exposed seven errors in three nested project +configs. With those original configs, `0.22.0` and `oxlint-tsgolint@7.0.2005` pass +the lint/type-check phase because they do not report these dependency errors: | Dependency | New diagnostics | | ----------------------------------------------------- | -------------------------------------------------------------- | @@ -40,10 +40,23 @@ dependencies, `0.22.0` and `oxlint-tsgolint@7.0.2005` pass the lint/type-check p | `zod@4.6.5` | One `TS5115` for recursive JSON schema types | | `webpack-virtual-modules@0.6.2` and `unplugin@2.3.11` | Five `TS2307` errors for missing optional bundler declarations | -Standalone `typescript@7.1.0-beta -p tsconfig.json --pretty false` passes the -repository root configuration. These new whole-program diagnostics need a separate -scope and compiler-options investigation; the earlier seven-case parity result -does not establish their correctness. +Standalone `typescript@7.1.0-beta` reproduces all seven errors when invoked against +the same nested configs: `packages/cli/templates/generator/tsconfig.json`, +`packages/cli/templates/monorepo/tsconfig.json`, and `docs/.vitepress/tsconfig.json`. +The root-only compiler check uses different file selection and compiler options; +its success did not establish a tsrs defect. + +Both starter templates now set `skipLibCheck: true`, consistent with the repository +root, so they do not check third-party declarations. The docs shim now declares +the named `Sponsors` type exported by `VPSponsors.vue`. It reuses the theme's +`SponsorTier` fields with an optional `tier`, matching the component's interface. + +After these corrections, `vp check` and all three nested TypeScript checks pass. +Negative probes still reject source type errors and invalid sponsor fields, and +unrelated Vue components do not acquire the named export. The rebuilt CLI's +3,403 unit tests also pass, with one skipped. These seven errors required project +configuration and Vue declaration fixes; the four earlier compiler-version +compatibility follow-ups remain open. This check used the rebased branch on `main` at `92edd550c`, Node.js `24.22.2`, Oxlint `1.91.0`, and fresh CLI JavaScript. The native binding and core artifacts diff --git a/docs/.vitepress/env.d.ts b/docs/.vitepress/env.d.ts index 23ce8f7581..514d964043 100644 --- a/docs/.vitepress/env.d.ts +++ b/docs/.vitepress/env.d.ts @@ -8,6 +8,17 @@ declare const __DOCS_INSTALL_SH_URL__: string; // oxlint-disable-next-line no-underscore-dangle declare const __DOCS_INSTALL_PS1_URL__: string; +// The theme re-exports this named type from an SFC, which plain TypeScript cannot read. +// Keep this specific shim before the generic Vue module declaration. +declare module '*VPSponsors.vue' { + import type { SponsorTier } from '@voidzero-dev/vitepress-theme/src/types/theme-config'; + import type { DefineComponent } from 'vue'; + + export type Sponsors = Omit & { tier?: string }; + const component: DefineComponent<{}, {}, unknown>; + export default component; +} + // Vue SFC module declaration declare module '*.vue' { import type { DefineComponent } from 'vue'; diff --git a/packages/cli/templates/generator/tsconfig.json b/packages/cli/templates/generator/tsconfig.json index f7d37cbfe8..65f1bd5511 100644 --- a/packages/cli/templates/generator/tsconfig.json +++ b/packages/cli/templates/generator/tsconfig.json @@ -5,6 +5,7 @@ "moduleResolution": "nodenext", "noEmit": true, "resolveJsonModule": true, + "skipLibCheck": true, "types": ["node"] } } diff --git a/packages/cli/templates/monorepo/tsconfig.json b/packages/cli/templates/monorepo/tsconfig.json index c785f304a8..94031829b7 100644 --- a/packages/cli/templates/monorepo/tsconfig.json +++ b/packages/cli/templates/monorepo/tsconfig.json @@ -4,6 +4,7 @@ "module": "nodenext", "moduleResolution": "nodenext", "allowImportingTsExtensions": true, - "esModuleInterop": true + "esModuleInterop": true, + "skipLibCheck": true } } From 525951d89cd7f2e3d1c7d796939e0cedd9b4d33f Mon Sep 17 00:00:00 2001 From: MK Date: Wed, 11 Nov 2026 11:16:35 +0800 Subject: [PATCH 15/21] docs(rfc): describe the tsrs-lint backend replacement --- rfcs/check-command.md | 3 ++ rfcs/tsrs-lint-backend.md | 64 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 rfcs/tsrs-lint-backend.md diff --git a/rfcs/check-command.md b/rfcs/check-command.md index 1ba9f35196..34b84b4072 100644 --- a/rfcs/check-command.md +++ b/rfcs/check-command.md @@ -202,6 +202,9 @@ The check module reuses `SubcommandResolver` and `resolve_and_capture_output` fr ### Experimental type-aware backend +See the [backend replacement RFC](./tsrs-lint-backend.md) for the rationale, +compatibility considerations, and release conditions. + This draft replaces the bundled Go backend with `tsrs@0.23.0-ts7.1.0-beta`, a Rust port of the TypeScript 7 compiler. `resolve-lint.ts` selects the bundled `tsrs` package and passes its `bin/tsrs-lint` launcher to Oxlint through diff --git a/rfcs/tsrs-lint-backend.md b/rfcs/tsrs-lint-backend.md new file mode 100644 index 0000000000..262fe55ff5 --- /dev/null +++ b/rfcs/tsrs-lint-backend.md @@ -0,0 +1,64 @@ +# RFC: Replace tsgolint with tsrs-lint + +- Status: Proposed; experimental default in this draft +- Related: [check command](./check-command.md), [toolchain metadata](./toolchain-command.md) + +## Motivation + +Use `tsrs-lint`, the Rust implementation of the type-aware backend, to reduce +lint time and peak memory. The [Codespaces comparison](../bench/tsrs-lint.md) +shows improvements with `tsrs@0.22.0-ts7.1.0-beta`, at the cost of a larger +installed dependency tree. Those measurements do not describe the current +`0.23.0` integration and must be repeated before release. + +## Design + +Replace the production dependency on `oxlint-tsgolint` with an exact `tsrs` pin, +currently `0.23.0-ts7.1.0-beta`. Keep the Go package as a development dependency +for comparisons. Oxlint remains the linter frontend and delegates type-aware +rules and optional TypeScript diagnostics to the bundled `tsrs-lint` executable. + +The local `vite-plus` package owns this integration. Its +[`resolve-lint.ts`](../packages/cli/src/resolve-lint.ts) resolves the bundled +launcher and supplies it to Oxlint through the existing internal +`OXLINT_TSGOLINT_PATH` interface. Both `vp lint` and `vp check` use this resolver; +the global CLI is not required. Users keep the existing commands and +`lint.options.typeAware` / `lint.options.typeCheck` configuration. + +Version output, `vite-plus/versions`, and toolchain metadata identify `tsrs`. +The global version command remains compatible with older Go-backed installations. +Ordinary project tasks that invoke `tsc` keep their selected compiler. + +## Compatibility + +The pinned release targets TypeScript `7.1.0-beta`. Changing the backend also +changes the compiler version, so identical diagnostics are not guaranteed. +The [ecosystem report](../bench/tsrs-lint-ecosystem.md) records the four known +compatibility categories and their upstream attribution. Matching the pinned +TypeScript compiler does not make every new diagnostic desirable. + +With type checking enabled, `0.23.0` checks whole programs for the selected +tsconfigs. Unselected and ignored program files can report type errors; lint +ignore patterns still control lint rules. Files outside discovered configs use +an inferred program. This broader scope can increase the work per invocation. + +The backend respects each project's compiler options. `skipLibCheck` controls +declaration checking; it is not a requirement for using tsrs. This draft aligns +the two starter templates with the repository's existing setting and supplies +the docs theme's missing Vue type declaration. Existing projects need their own +configuration review. + +## Release conditions + +- Repeat elapsed-time, memory, and installed-size comparisons with the release + candidate. Keep historical measurements labeled with their original versions. +- Validate lint rules, fixes, suppression comments, and optional type checking + on representative ecosystem projects. Track compiler-version changes separately + from Rust implementation defects. +- Validate all supported runtime targets. The pinned release has no Windows + ARM64 binary, and this draft implements no automatic Go fallback. Resolve that + platform gap before a release that supports all Vite+ targets. + +If the candidate fails these conditions, retain the Go backend for the release +or revert the bundled-backend change. Backend selection remains a toolchain +decision rather than an additional user setup step. From 4a6298976211e88598ae788aa48ac89e9bfca3fd Mon Sep 17 00:00:00 2001 From: MK Date: Wed, 11 Nov 2026 11:27:52 +0800 Subject: [PATCH 16/21] chore(deps): update tsrs to 0.24.0-ts7.1.0-beta --- bench/tsrs-lint-ecosystem.md | 25 ++++++++++++- bench/tsrs-lint.md | 4 +-- pnpm-lock.yaml | 70 ++++++++++++++++++------------------ pnpm-workspace.yaml | 2 +- rfcs/check-command.md | 2 +- rfcs/toolchain-command.md | 2 +- rfcs/tsrs-lint-backend.md | 13 +++---- 7 files changed, 71 insertions(+), 47 deletions(-) diff --git a/bench/tsrs-lint-ecosystem.md b/bench/tsrs-lint-ecosystem.md index fbef47fe23..2d4cf11583 100644 --- a/bench/tsrs-lint-ecosystem.md +++ b/bench/tsrs-lint-ecosystem.md @@ -13,9 +13,32 @@ The explicit type-check workload covered 2,289 files. No command timed out or terminated by a signal. Seven repositories had identical normalized diagnostics; seven had differences. This covers the selected repositories, not all 83 catalog entries. +## Version 0.24 upgrade check (2026-11-11) + +The current integration pins `tsrs@0.24.0-ts7.1.0-beta`. The full ecosystem sweep +and performance measurements remain results for `0.22.0`. + +On macOS ARM64, all seven reduced cases retained identical diagnostics and exit +codes between `0.23.0` and `0.24.0`, both through `vp lint` and the standalone +compiler. The standalone results also matched `typescript@7.1.0-beta`. +The TypeScript source pin is unchanged, and the four compatibility follow-ups +remain open. + +The [0.24 release](https://github.com/oxc-project/tsrs/blob/v0.24.0/docs/releases/0.24.0.md) +checks whole programs before running lint rules. The existing smoke fixture still +reports the ignored program file's `TS2322` only when type checking is enabled. +Its invalid declaration remains suppressed by `skipLibCheck: true`. + +After rebasing onto `main` at `9f44ce760`, `vp check` passes, and the unit suite +passes 3,422 tests with one skipped. This check used Node.js `24.22.2`, Oxlint +`1.93.0`, and fresh CLI JavaScript built with published Rolldown `1.2.18`. +The native binding and core artifacts remain from published `1.5.0`. +This is not a full native release build or a repeat of the 14-repository sweep. +Scripts, fixtures, and raw logs remain outside the repository. + ## Version 0.23 upgrade check (2026-11-09) -The current integration pins `tsrs@0.23.0-ts7.1.0-beta`. The full ecosystem sweep +This upgrade pinned `tsrs@0.23.0-ts7.1.0-beta`. The full ecosystem sweep and performance measurements remain results for `0.22.0`. On macOS ARM64, all seven reduced cases retained identical diagnostics and exit diff --git a/bench/tsrs-lint.md b/bench/tsrs-lint.md index e66a0aacba..7af495fc69 100644 --- a/bench/tsrs-lint.md +++ b/bench/tsrs-lint.md @@ -1,7 +1,7 @@ # Type-aware linting with tsrs-lint -The current integration pins `tsrs@0.23.0-ts7.1.0-beta`. The measurements below -remain results for `0.22.0`; they have not been repeated for `0.23.0`. +The current integration pins `tsrs@0.24.0-ts7.1.0-beta`. The measurements below +remain results for `0.22.0`; they have not been repeated for `0.24.0`. This comparison uses the default `tsrs@0.22.0-ts7.1.0-beta` integration and an explicit `oxlint-tsgolint@7.0.2005` override. Both use Oxlint `1.91.0` and the same built CLI. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6e8a863c3b..43946a0ca8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -345,8 +345,8 @@ catalogs: specifier: ^0.23.4 version: 0.23.4 tsrs: - specifier: '=0.23.0-ts7.1.0-beta' - version: 0.23.0-ts7.1.0-beta + specifier: '=0.24.0-ts7.1.0-beta' + version: 0.24.0-ts7.1.0-beta typescript: specifier: ~7.0.2 version: 7.0.2 @@ -471,7 +471,7 @@ importers: version: 1.62.1 tsrs: specifier: 'catalog:' - version: 0.23.0-ts7.1.0-beta + version: 0.24.0-ts7.1.0-beta vite: specifier: workspace:@voidzero-dev/vite-plus-core@* version: link:../core @@ -538,7 +538,7 @@ importers: version: 0.3.0 oxlint-tsgolint: specifier: 'catalog:' - version: 7.0.2005 + version: 7.0.2006 picocolors: specifier: 'catalog:' version: 1.1.1 @@ -3582,48 +3582,48 @@ packages: peerDependencies: tsdown: 0.23.4 - '@tsrs/darwin-arm64@0.23.0-ts7.1.0-beta': - resolution: {integrity: sha512-Bk1aFZRo4d4RKtOU8OEVLXWZ7E0JqnL8xKhOb+FuvRRb9Qxu5UIgD0W3xiGvixzZ99I7f8Bj/NCTN30xX7MkEA==} + '@tsrs/darwin-arm64@0.24.0-ts7.1.0-beta': + resolution: {integrity: sha512-GxGPRieCGIXevJgGlbne9pRqkfAN+VmBQvBHWWcF8rXLNX9yCxYo78/a7Auqjf723b4eFTlM5mgkclUit/Fu9w==} engines: {node: '>=16.20.0'} cpu: [arm64] os: [darwin] - '@tsrs/darwin-x64@0.23.0-ts7.1.0-beta': - resolution: {integrity: sha512-asvC3hDKmRHjgfrr3o+bGVxhdPA5xfLCwcpfhIW4MCLcutBA8nltb4chtWRfEWsnKVimxtNvjnP0XldhgnJtkw==} + '@tsrs/darwin-x64@0.24.0-ts7.1.0-beta': + resolution: {integrity: sha512-TrXdOyEq++blyYp6KphoHGmsGxAn1flYzjm0peLEI3c88z1UQa+pKkcuPgZ6FOxTW6fSaT9572n21kz+0bWi/g==} engines: {node: '>=16.20.0'} cpu: [x64] os: [darwin] - '@tsrs/linux-arm64-gnu@0.23.0-ts7.1.0-beta': - resolution: {integrity: sha512-5Uhhvo7q5b5Xwv11uLCDcPBlzTvNcsDtog05eK5lsS8763BioGuKobYtmsBPQU6KyowzRv4GoocqSePd5yaEJw==} + '@tsrs/linux-arm64-gnu@0.24.0-ts7.1.0-beta': + resolution: {integrity: sha512-Ph5NK0vnaR4gnBW2LgrZsUPx4mKF+9tCQUYZFOHmny6SMojSsm/5GCiBu0sHZEZhmyIqq8UbineUEA7T0QImnw==} engines: {node: '>=16.20.0'} cpu: [arm64] os: [linux] libc: [glibc] - '@tsrs/linux-arm64-musl@0.23.0-ts7.1.0-beta': - resolution: {integrity: sha512-v5vZCEec88vACtPHRPKie9JziE+X77VCNj/dNhj/buGAJwUZW2MMaQjJxKiSY4VwVo302kRfKKATyPljD23U6Q==} + '@tsrs/linux-arm64-musl@0.24.0-ts7.1.0-beta': + resolution: {integrity: sha512-vjgx8wGuAVSOPLlb2eVQpQVolImZa7vsH/1kvYOaokcP/KZXVUmHzgroxK0t9gxJ0SXvT4VKNjWZ+t+4tDNu1A==} engines: {node: '>=16.20.0'} cpu: [arm64] os: [linux] libc: [musl] - '@tsrs/linux-x64-gnu@0.23.0-ts7.1.0-beta': - resolution: {integrity: sha512-QJTJkk5XhkZSU/LBvjeFPpfRLDuICJbS9ij6q2rBy/Olw4T8WN7DGiE8ig687gI2dfnzlfScZsJYFtK2+CngnA==} + '@tsrs/linux-x64-gnu@0.24.0-ts7.1.0-beta': + resolution: {integrity: sha512-tj1SNWBnJoBUzU2zrD18tUu3h6o36FXQGPpmIXsADHSfU5H9pVQdK/QzPIO76rb72P7foXHluE+xGTXXf8nNaA==} engines: {node: '>=16.20.0'} cpu: [x64] os: [linux] libc: [glibc] - '@tsrs/linux-x64-musl@0.23.0-ts7.1.0-beta': - resolution: {integrity: sha512-cOW2i9W+ybOABvrmJpd8fI+Cbm5ej0unCSYBRyFAE0FLnQyuQT0+7vdNkrJn4QjaK62OS99vknW56/DfCcRrMA==} + '@tsrs/linux-x64-musl@0.24.0-ts7.1.0-beta': + resolution: {integrity: sha512-sizu0EdGw9ITqEaZKg60TDhDk5PXT9RzVkCdesBjk/BZhS0BVU2cRhvoRBmDbk+2vCUgW14j+Otg3tl+002qNQ==} engines: {node: '>=16.20.0'} cpu: [x64] os: [linux] libc: [musl] - '@tsrs/win32-x64@0.23.0-ts7.1.0-beta': - resolution: {integrity: sha512-Af9+bfGuTHtp41AOkWMmkQlWYPYvp3hvdw6dCRI6IlLdy4/5AqaJaFjDoCUTLJMojNjJjC4Ux/GhE2+CRoIomg==} + '@tsrs/win32-x64@0.24.0-ts7.1.0-beta': + resolution: {integrity: sha512-WAbzgd5JBOauQ/oL6DTdGEh09tM/q8tlGAcRORskhPK7eIQVVa8Js2pc7DVW0XokWTSADeD9v6HKtU2Ge0r/fQ==} engines: {node: '>=16.20.0'} cpu: [x64] os: [win32] @@ -6718,8 +6718,8 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - tsrs@0.23.0-ts7.1.0-beta: - resolution: {integrity: sha512-A7UU5bUA8caDkidkMfrfeF4etU92ODZxasCqIb1eaJ36+BAZgyZ6Znq7NVu/c1HFG57E6la/TeofpZhPdgmLBw==} + tsrs@0.24.0-ts7.1.0-beta: + resolution: {integrity: sha512-Iy7q93qfRHcI0Y31IKx6fCsDQCsSjtrT01PIgOVolBXPJTMdm84oH1pEYyAMPQ/SnRqWDSWI8wEUHbv6e9TmHA==} engines: {node: '>=16.20.0'} hasBin: true @@ -9015,25 +9015,25 @@ snapshots: tsdown: 0.23.4(@arethetypeswrong/core@0.18.2)(@tsdown/css@0.23.4)(@tsdown/exe@0.23.4)(@typescript/native-preview@7.0.0-dev.20260707.2)(@vitejs/devtools@0.8.0(crossws@0.4.12(srvx@1.0.6))(srvx@1.0.6)(vite@packages+core))(@volar/typescript@2.4.28)(@vue/language-core@3.3.12)(oxc-resolver@11.24.2)(publint@0.3.24)(tsx@4.23.15)(typescript@7.0.2)(unplugin-unused@0.5.6)(unrun@0.3.1)(vue-tsc@3.3.12(typescript@7.0.2)) verkit: 0.5.0 - '@tsrs/darwin-arm64@0.23.0-ts7.1.0-beta': + '@tsrs/darwin-arm64@0.24.0-ts7.1.0-beta': optional: true - '@tsrs/darwin-x64@0.23.0-ts7.1.0-beta': + '@tsrs/darwin-x64@0.24.0-ts7.1.0-beta': optional: true - '@tsrs/linux-arm64-gnu@0.23.0-ts7.1.0-beta': + '@tsrs/linux-arm64-gnu@0.24.0-ts7.1.0-beta': optional: true - '@tsrs/linux-arm64-musl@0.23.0-ts7.1.0-beta': + '@tsrs/linux-arm64-musl@0.24.0-ts7.1.0-beta': optional: true - '@tsrs/linux-x64-gnu@0.23.0-ts7.1.0-beta': + '@tsrs/linux-x64-gnu@0.24.0-ts7.1.0-beta': optional: true - '@tsrs/linux-x64-musl@0.23.0-ts7.1.0-beta': + '@tsrs/linux-x64-musl@0.24.0-ts7.1.0-beta': optional: true - '@tsrs/win32-x64@0.23.0-ts7.1.0-beta': + '@tsrs/win32-x64@0.24.0-ts7.1.0-beta': optional: true '@tybys/wasm-util@0.10.3': @@ -12208,15 +12208,15 @@ snapshots: tslib@2.8.1: {} - tsrs@0.23.0-ts7.1.0-beta: + tsrs@0.24.0-ts7.1.0-beta: optionalDependencies: - '@tsrs/darwin-arm64': 0.23.0-ts7.1.0-beta - '@tsrs/darwin-x64': 0.23.0-ts7.1.0-beta - '@tsrs/linux-arm64-gnu': 0.23.0-ts7.1.0-beta - '@tsrs/linux-arm64-musl': 0.23.0-ts7.1.0-beta - '@tsrs/linux-x64-gnu': 0.23.0-ts7.1.0-beta - '@tsrs/linux-x64-musl': 0.23.0-ts7.1.0-beta - '@tsrs/win32-x64': 0.23.0-ts7.1.0-beta + '@tsrs/darwin-arm64': 0.24.0-ts7.1.0-beta + '@tsrs/darwin-x64': 0.24.0-ts7.1.0-beta + '@tsrs/linux-arm64-gnu': 0.24.0-ts7.1.0-beta + '@tsrs/linux-arm64-musl': 0.24.0-ts7.1.0-beta + '@tsrs/linux-x64-gnu': 0.24.0-ts7.1.0-beta + '@tsrs/linux-x64-musl': 0.24.0-ts7.1.0-beta + '@tsrs/win32-x64': 0.24.0-ts7.1.0-beta tsx@4.23.15: dependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 81ed62ee6a..abdc4f7f07 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -67,7 +67,7 @@ catalog: source-map: 0.8.0 tinyglobby: ^0.2.15 tsdown: ^0.23.4 - tsrs: =0.23.0-ts7.1.0-beta + tsrs: =0.24.0-ts7.1.0-beta typescript: ~7.0.2 valibot: 1.5.0 validate-npm-package-name: ^8.0.0 diff --git a/rfcs/check-command.md b/rfcs/check-command.md index 34b84b4072..555ecd37a4 100644 --- a/rfcs/check-command.md +++ b/rfcs/check-command.md @@ -205,7 +205,7 @@ The check module reuses `SubcommandResolver` and `resolve_and_capture_output` fr See the [backend replacement RFC](./tsrs-lint-backend.md) for the rationale, compatibility considerations, and release conditions. -This draft replaces the bundled Go backend with `tsrs@0.23.0-ts7.1.0-beta`, +This draft replaces the bundled Go backend with `tsrs@0.24.0-ts7.1.0-beta`, a Rust port of the TypeScript 7 compiler. `resolve-lint.ts` selects the bundled `tsrs` package and passes its `bin/tsrs-lint` launcher to Oxlint through `OXLINT_TSGOLINT_PATH`. The same resolver serves `vp lint` and `vp check`. diff --git a/rfcs/toolchain-command.md b/rfcs/toolchain-command.md index ab11711110..1df6ae6d7c 100644 --- a/rfcs/toolchain-command.md +++ b/rfcs/toolchain-command.md @@ -164,7 +164,7 @@ vite-plus@0.2.4 │ └── bundles tsdown@0.22.3 ├── depends on vitest@4.1.10 ├── depends on oxlint@1.72.0 -├── depends on tsrs@0.23.0-ts7.1.0-beta +├── depends on tsrs@0.24.0-ts7.1.0-beta ├── depends on oxfmt@0.57.0 └── compiles vite-task (built 2026-08-06T09:30:00Z, revision ) ``` diff --git a/rfcs/tsrs-lint-backend.md b/rfcs/tsrs-lint-backend.md index 262fe55ff5..038be21934 100644 --- a/rfcs/tsrs-lint-backend.md +++ b/rfcs/tsrs-lint-backend.md @@ -9,12 +9,12 @@ Use `tsrs-lint`, the Rust implementation of the type-aware backend, to reduce lint time and peak memory. The [Codespaces comparison](../bench/tsrs-lint.md) shows improvements with `tsrs@0.22.0-ts7.1.0-beta`, at the cost of a larger installed dependency tree. Those measurements do not describe the current -`0.23.0` integration and must be repeated before release. +`0.24.0` integration and must be repeated before release. ## Design Replace the production dependency on `oxlint-tsgolint` with an exact `tsrs` pin, -currently `0.23.0-ts7.1.0-beta`. Keep the Go package as a development dependency +currently `0.24.0-ts7.1.0-beta`. Keep the Go package as a development dependency for comparisons. Oxlint remains the linter frontend and delegates type-aware rules and optional TypeScript diagnostics to the bundled `tsrs-lint` executable. @@ -37,10 +37,11 @@ The [ecosystem report](../bench/tsrs-lint-ecosystem.md) records the four known compatibility categories and their upstream attribution. Matching the pinned TypeScript compiler does not make every new diagnostic desirable. -With type checking enabled, `0.23.0` checks whole programs for the selected -tsconfigs. Unselected and ignored program files can report type errors; lint -ignore patterns still control lint rules. Files outside discovered configs use -an inferred program. This broader scope can increase the work per invocation. +With type checking enabled, `0.24.0` checks whole programs for the selected +tsconfigs before running lint rules. Unselected and ignored program files can +report type errors; lint ignore patterns still control lint rules. Files outside +discovered configs use an inferred program. This broader scope can increase the +work per invocation. The backend respects each project's compiler options. `skipLibCheck` controls declaration checking; it is not a requirement for using tsrs. This draft aligns From 8144c2940880fb8428db06f9616c59159e661758 Mon Sep 17 00:00:00 2001 From: MK Date: Wed, 11 Nov 2026 11:30:52 +0800 Subject: [PATCH 17/21] docs(rfc): compare lint backend dependencies --- rfcs/tsrs-lint-backend.md | 42 +++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/rfcs/tsrs-lint-backend.md b/rfcs/tsrs-lint-backend.md index 038be21934..13eea48cc2 100644 --- a/rfcs/tsrs-lint-backend.md +++ b/rfcs/tsrs-lint-backend.md @@ -3,14 +3,6 @@ - Status: Proposed; experimental default in this draft - Related: [check command](./check-command.md), [toolchain metadata](./toolchain-command.md) -## Motivation - -Use `tsrs-lint`, the Rust implementation of the type-aware backend, to reduce -lint time and peak memory. The [Codespaces comparison](../bench/tsrs-lint.md) -shows improvements with `tsrs@0.22.0-ts7.1.0-beta`, at the cost of a larger -installed dependency tree. Those measurements do not describe the current -`0.24.0` integration and must be repeated before release. - ## Design Replace the production dependency on `oxlint-tsgolint` with an exact `tsrs` pin, @@ -29,6 +21,40 @@ Version output, `vite-plus/versions`, and toolchain metadata identify `tsrs`. The global version command remains compatible with older Go-backed installations. Ordinary project tasks that invoke `tsc` keep their selected compiler. +## Dependency changes + +Compare the package declarations on `main` at `9f44ce760` with this draft: + +| Dependency | Before | After | +| ------------------------- | ------------------------------------------- | -------------------------------------------------------------------------------- | +| Linter frontend | `oxlint@1.93.0` | Unchanged | +| Production backend | `oxlint-tsgolint@7.0.2006` | `tsrs@0.24.0-ts7.1.0-beta` | +| Development-only backend | None | `oxlint-tsgolint@7.0.2006` for comparisons | +| Backend platform packages | `@oxlint-tsgolint/*`: six optional packages | `@tsrs/*`: seven optional packages | +| Backend package contents | Launcher and Go executable | Launchers, Rust executable, TypeScript library declarations, and JavaScript APIs | + +Both backends use platform packages for their native executables. tsrs has +separate Linux glibc and musl packages, but no Windows ARM64 package. A normal +production installation selects the matching platform package. Repository +development installs retain both backends for comparisons. + +Oxlint declares `oxlint-tsgolint` as an optional peer dependency; it does not +require the Go backend in production. The existing project `typescript` +dependency is unchanged. tsrs supplies its own compiler and library declarations +for type-aware linting and optional type checking. + +The [existing disk comparison](../bench/tsrs-lint.md#installed-disk-footprint) +measured these Linux x64/glibc production installations: + +| On-disk scope | Vite+ `1.5.0`, `oxlint-tsgolint@7.0.2005` | Preview, `tsrs@0.22.0-ts7.1.0-beta` | Increase | +| ----------------------- | ----------------------------------------- | ----------------------------------- | ----------------- | +| Complete `node_modules` | 132.93 MiB | 150.87 MiB | 17.94 MiB (13.5%) | +| Backend packages | 21.63 MiB | 39.69 MiB | 18.06 MiB (83.5%) | + +The complete installation includes other release-to-preview dependency changes; +the backend row isolates the backend packages. These are historical measurements, +not sizes for the current `0.24.0` integration. Repeat them before release. + ## Compatibility The pinned release targets TypeScript `7.1.0-beta`. Changing the backend also From e1f3c8edf095c693e9a99f31b61a032e5c5e25ff Mon Sep 17 00:00:00 2001 From: MK Date: Wed, 11 Nov 2026 13:34:28 +0800 Subject: [PATCH 18/21] docs(rfc): explain the default tsrs user experience --- rfcs/tsrs-lint-backend.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/rfcs/tsrs-lint-backend.md b/rfcs/tsrs-lint-backend.md index 13eea48cc2..c3e829ab41 100644 --- a/rfcs/tsrs-lint-backend.md +++ b/rfcs/tsrs-lint-backend.md @@ -3,6 +3,16 @@ - Status: Proposed; experimental default in this draft - Related: [check command](./check-command.md), [toolchain metadata](./toolchain-command.md) +## User experience + +Once this change ships, users get tsrs by upgrading `vite-plus`. Existing +`vp lint` and `vp check` commands automatically use the bundled backend for +type-aware linting and type checking. Projects that enable these checks can +benefit from tsrs performance improvements without additional setup. + +Users keep their existing commands and configuration. No separate installation +of `tsrs` is required. + ## Design Replace the production dependency on `oxlint-tsgolint` with an exact `tsrs` pin, From 13f57cca7106c5ec86b9159e7db1db96761124b4 Mon Sep 17 00:00:00 2001 From: MK Date: Wed, 11 Nov 2026 14:51:50 +0800 Subject: [PATCH 19/21] docs(rfc): simplify the tsrs proposal --- rfcs/tsrs-lint-backend.md | 113 +++++++++----------------------------- 1 file changed, 27 insertions(+), 86 deletions(-) diff --git a/rfcs/tsrs-lint-backend.md b/rfcs/tsrs-lint-backend.md index c3e829ab41..40ec37467f 100644 --- a/rfcs/tsrs-lint-backend.md +++ b/rfcs/tsrs-lint-backend.md @@ -1,101 +1,42 @@ # RFC: Replace tsgolint with tsrs-lint -- Status: Proposed; experimental default in this draft -- Related: [check command](./check-command.md), [toolchain metadata](./toolchain-command.md) +Status: Proposed; experimental default in this draft. ## User experience -Once this change ships, users get tsrs by upgrading `vite-plus`. Existing -`vp lint` and `vp check` commands automatically use the bundled backend for -type-aware linting and type checking. Projects that enable these checks can -benefit from tsrs performance improvements without additional setup. - -Users keep their existing commands and configuration. No separate installation -of `tsrs` is required. +Upgrading `vite-plus` makes tsrs the default backend for type-aware linting and +type checking in `vp lint` and `vp check`. Projects with these checks enabled +can benefit from tsrs performance improvements without additional setup. +Users keep their existing commands and configuration; no separate `tsrs` +installation is required. ## Design -Replace the production dependency on `oxlint-tsgolint` with an exact `tsrs` pin, -currently `0.24.0-ts7.1.0-beta`. Keep the Go package as a development dependency -for comparisons. Oxlint remains the linter frontend and delegates type-aware -rules and optional TypeScript diagnostics to the bundled `tsrs-lint` executable. - -The local `vite-plus` package owns this integration. Its -[`resolve-lint.ts`](../packages/cli/src/resolve-lint.ts) resolves the bundled -launcher and supplies it to Oxlint through the existing internal -`OXLINT_TSGOLINT_PATH` interface. Both `vp lint` and `vp check` use this resolver; -the global CLI is not required. Users keep the existing commands and -`lint.options.typeAware` / `lint.options.typeCheck` configuration. - -Version output, `vite-plus/versions`, and toolchain metadata identify `tsrs`. -The global version command remains compatible with older Go-backed installations. -Ordinary project tasks that invoke `tsc` keep their selected compiler. +Oxlint remains the linter frontend. The local `vite-plus` package bundles +`tsrs-lint` and selects it through the shared +[`resolve-lint.ts`](../packages/cli/src/resolve-lint.ts) resolver. Version output +and toolchain metadata identify `tsrs`. ## Dependency changes -Compare the package declarations on `main` at `9f44ce760` with this draft: - -| Dependency | Before | After | -| ------------------------- | ------------------------------------------- | -------------------------------------------------------------------------------- | -| Linter frontend | `oxlint@1.93.0` | Unchanged | -| Production backend | `oxlint-tsgolint@7.0.2006` | `tsrs@0.24.0-ts7.1.0-beta` | -| Development-only backend | None | `oxlint-tsgolint@7.0.2006` for comparisons | -| Backend platform packages | `@oxlint-tsgolint/*`: six optional packages | `@tsrs/*`: seven optional packages | -| Backend package contents | Launcher and Go executable | Launchers, Rust executable, TypeScript library declarations, and JavaScript APIs | - -Both backends use platform packages for their native executables. tsrs has -separate Linux glibc and musl packages, but no Windows ARM64 package. A normal -production installation selects the matching platform package. Repository -development installs retain both backends for comparisons. - -Oxlint declares `oxlint-tsgolint` as an optional peer dependency; it does not -require the Go backend in production. The existing project `typescript` -dependency is unchanged. tsrs supplies its own compiler and library declarations -for type-aware linting and optional type checking. - -The [existing disk comparison](../bench/tsrs-lint.md#installed-disk-footprint) -measured these Linux x64/glibc production installations: - -| On-disk scope | Vite+ `1.5.0`, `oxlint-tsgolint@7.0.2005` | Preview, `tsrs@0.22.0-ts7.1.0-beta` | Increase | -| ----------------------- | ----------------------------------------- | ----------------------------------- | ----------------- | -| Complete `node_modules` | 132.93 MiB | 150.87 MiB | 17.94 MiB (13.5%) | -| Backend packages | 21.63 MiB | 39.69 MiB | 18.06 MiB (83.5%) | - -The complete installation includes other release-to-preview dependency changes; -the backend row isolates the backend packages. These are historical measurements, -not sizes for the current `0.24.0` integration. Repeat them before release. - -## Compatibility - -The pinned release targets TypeScript `7.1.0-beta`. Changing the backend also -changes the compiler version, so identical diagnostics are not guaranteed. -The [ecosystem report](../bench/tsrs-lint-ecosystem.md) records the four known -compatibility categories and their upstream attribution. Matching the pinned -TypeScript compiler does not make every new diagnostic desirable. - -With type checking enabled, `0.24.0` checks whole programs for the selected -tsconfigs before running lint rules. Unselected and ignored program files can -report type errors; lint ignore patterns still control lint rules. Files outside -discovered configs use an inferred program. This broader scope can increase the -work per invocation. +| Dependency | Before | After | +| ------------------------- | ------------------------------------------- | ------------------------------------------ | +| Linter frontend | `oxlint@1.93.0` | Unchanged | +| Production backend | `oxlint-tsgolint@7.0.2006` | `tsrs@0.24.0-ts7.1.0-beta` | +| Development-only backend | None | `oxlint-tsgolint@7.0.2006` for comparisons | +| Backend platform packages | `@oxlint-tsgolint/*`: six optional packages | `@tsrs/*`: seven optional packages | -The backend respects each project's compiler options. `skipLibCheck` controls -declaration checking; it is not a requirement for using tsrs. This draft aligns -the two starter templates with the repository's existing setting and supplies -the docs theme's missing Vue type declaration. Existing projects need their own -configuration review. +Production installs use tsrs; repository development retains Go for comparisons. +The project's `typescript` dependency and standalone `tsc` tasks are unchanged. -## Release conditions +## Compatibility and release considerations -- Repeat elapsed-time, memory, and installed-size comparisons with the release - candidate. Keep historical measurements labeled with their original versions. -- Validate lint rules, fixes, suppression comments, and optional type checking - on representative ecosystem projects. Track compiler-version changes separately - from Rust implementation defects. -- Validate all supported runtime targets. The pinned release has no Windows - ARM64 binary, and this draft implements no automatic Go fallback. Resolve that - platform gap before a release that supports all Vite+ targets. +- tsrs targets TypeScript `7.1.0-beta`, so compiler diagnostics can change. +- With `lint.options.typeCheck` enabled, tsrs checks whole TypeScript programs, + including files excluded by lint ignore patterns. Existing projects can + report additional errors. +- Windows ARM64 remains a release blocker: tsrs has no binary for that target, + and this integration has no automatic Go fallback. -If the candidate fails these conditions, retain the Go backend for the release -or revert the bundled-backend change. Backend selection remains a toolchain -decision rather than an additional user setup step. +The [benchmark report](../bench/tsrs-lint.md) contains historical timing, memory, +and disk-usage measurements. Refresh them for the release candidate. From 8e76399da0512e2b5fcc6bb0774d4e9cbc967015 Mon Sep 17 00:00:00 2001 From: MK Date: Wed, 11 Nov 2026 16:29:46 +0800 Subject: [PATCH 20/21] test(lint): update inherited type-check snapshots --- .../migration_oxlint_extends/snapshots.toml | 4 ++-- .../migration_oxlint_extends_type_options.md | 20 +++++++++++++++---- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxlint_extends/snapshots.toml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxlint_extends/snapshots.toml index 762a835758..81ebc4143c 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxlint_extends/snapshots.toml +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxlint_extends/snapshots.toml @@ -38,11 +38,11 @@ steps = [ { argv = ["vpt", "write-file", "type-error.ts", "export const value: string = 1;\n"], snapshot = false }, { argv = ["vpt", "write-file", "type-aware.ts", "Promise.resolve(1);\n"], snapshot = false }, { argv = ["vp", "lint", "--threads=1", "-c", ".oxlintrc.json", "type-error.ts"], comment = "Inherited typeCheck reports the original TypeScript diagnostic", continue-on-failure = true }, - { argv = ["vp", "lint", "--threads=1", "-c", ".oxlintrc.json", "type-aware.ts"], comment = "Inherited typeAware enables no-floating-promises", continue-on-failure = true }, + { argv = ["vp", "lint", "--threads=1", "-c", ".oxlintrc.json", "type-aware.ts"], comment = "Inherited options enable no-floating-promises and report type errors outside the lint input", continue-on-failure = true }, { argv = ["vp", "migrate", "--no-interactive", "--no-agent", "--no-editor", "--no-hooks"] }, { argv = ["vpt", "print-file", "vite.config.ts"], comment = "Local options stay empty so inherited settings remain live" }, { argv = ["vp", "lint", "--threads=1", "type-error.ts"], comment = "Imported typeCheck produces the same diagnostic and exit code", continue-on-failure = true }, - { argv = ["vp", "lint", "--threads=1", "type-aware.ts"], comment = "Imported typeAware produces the same diagnostic and exit code", continue-on-failure = true }, + { argv = ["vp", "lint", "--threads=1", "type-aware.ts"], comment = "Imported options preserve both diagnostics and the exit code", continue-on-failure = true }, { argv = ["vpt", "replace-file-content", "shared/base.ts", "typeAware: true", "typeAware: false"] }, { argv = ["vpt", "replace-file-content", "shared/base.ts", "typeCheck: true", "typeCheck: false"] }, { argv = ["vp", "lint", "--threads=1", "type-error.ts"], comment = "Disabling the shared options removes the TypeScript diagnostic" }, diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxlint_extends/snapshots/migration_oxlint_extends_type_options.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxlint_extends/snapshots/migration_oxlint_extends_type_options.md index dd4f60cfa4..6fe47a0368 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxlint_extends/snapshots/migration_oxlint_extends_type_options.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxlint_extends/snapshots/migration_oxlint_extends_type_options.md @@ -37,7 +37,7 @@ Finished in on 1 file with rules using threads. ## `vp lint --threads=1 -c .oxlintrc.json type-aware.ts` -Inherited typeAware enables no-floating-promises +Inherited options enable no-floating-promises and report type errors outside the lint input **Exit code:** 1 @@ -51,7 +51,13 @@ Inherited typeAware enables no-floating-promises ╰──── help: The promise must end with a call to .catch, or end with a call to .then with a rejection handler, or be explicitly marked as ignored with the `void` operator. -Found 0 warnings and 1 error. + × typescript(TS2322): Type 'number' is not assignable to type 'string'. + ╭─[type-error.ts:1:14] + 1 │ export const value: string = 1; + · ───── + ╰──── + +Found 0 warnings and 2 errors. Finished in on 1 file with rules using threads. ``` @@ -114,7 +120,7 @@ Finished in on 1 file with rules using threads. ## `vp lint --threads=1 type-aware.ts` -Imported typeAware produces the same diagnostic and exit code +Imported options preserve both diagnostics and the exit code **Exit code:** 1 @@ -128,7 +134,13 @@ Imported typeAware produces the same diagnostic and exit code ╰──── help: The promise must end with a call to .catch, or end with a call to .then with a rejection handler, or be explicitly marked as ignored with the `void` operator. -Found 0 warnings and 1 error. + × typescript(TS2322): Type 'number' is not assignable to type 'string'. + ╭─[type-error.ts:1:14] + 1 │ export const value: string = 1; + · ───── + ╰──── + +Found 0 warnings and 2 errors. Finished in on 1 file with rules using threads. ``` From 73e295379a546abcf760d856df26db3264ff89b9 Mon Sep 17 00:00:00 2001 From: MK Date: Wed, 11 Nov 2026 17:20:20 +0800 Subject: [PATCH 21/21] docs(rfc): design native declarations for vp pack --- rfcs/tsdown-native-declarations.md | 172 +++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 rfcs/tsdown-native-declarations.md diff --git a/rfcs/tsdown-native-declarations.md b/rfcs/tsdown-native-declarations.md new file mode 100644 index 0000000000..2b7f5c95d1 --- /dev/null +++ b/rfcs/tsdown-native-declarations.md @@ -0,0 +1,172 @@ +# RFC: Native declaration generation for `vp pack` + +Status: Proposed. Researched on 2026-11-11 against [PR #3352](https://github.com/voidzero-dev/vite-plus/pull/3352), head `8e76399da0512e2b5fcc6bb0774d4e9cbc967015`. + +## Decision + +Use the `tsrs` dependency introduced by #3352 to generate declarations for projects that do not install `typescript`. Select `dts.generator: 'tsrs'` through a Vite+ adapter. Keep the project's existing compiler when it supplies TypeScript 5, 6, or 7, and honor an explicit generator. Preserve Oxc generation for isolated declarations. + +The latest declaration plugin supports this integration. Its automatic selection does not select `tsrs`, even when that package is installed. Upgrading dependencies alone therefore does not provide the proposed default. The `tsrs` generator uses the TypeScript 7.1-compatible native API; `generator: 'tsgo'` remains a separate choice that loads the Microsoft `typescript` package. [Generator implementation](https://github.com/sxzz/rolldown-plugin-dts/blob/v0.30.1/src/options.ts), [native API loading](https://github.com/sxzz/rolldown-plugin-dts/blob/v0.30.1/src/tsgo.ts). + +This makes a project-level `typescript` dependency unnecessary for supported `vp check` and `vp pack` workflows. It does not replace `tsc` tasks, the editor's compiler, Vue/Volar integrations, or dependencies that use the classic TypeScript API. Application type packages such as `@types/node` remain project dependencies when needed. Existing projects keep `typescript` unless a separate dependency audit establishes that they can remove it. + +Ship the native default first in the experimental release built on #3352. Promote it after the declaration, packaging, and platform gates below pass. TypeScript 7.1 is still a beta, and upstream marks the `tsrs` generator as highly experimental. + +## Version baseline + +These are researched versions, not floating release requirements. Refresh the matrix before implementation and pin the versions used for release qualification. + +| Component | PR #3352 baseline | Proposed target | +| --- | --- | --- | +| `tsdown`, `@tsdown/css`, `@tsdown/exe` | `0.23.4` | `0.23.7`, kept in lockstep | +| Declaration plugin bundled through tsdown | `rolldown-plugin-dts@0.28.8` | `0.30.1` | +| Core's direct declaration-plugin development dependency | Catalog `^0.28.0` | Align with the bundled `0.30.1` | +| Native compiler supplied by Vite+ | `tsrs@0.24.0-ts7.1.0-beta`, used by lint/check | Same exact package, also used by pack | +| Microsoft compiler used as a parity reference | Project/repository compiler varies | Exact `typescript@7.1.0-beta` matching tsrs's source pin | +| Bundled Rolldown | `1.2.18` | A validated `1.3.x` integration; tsdown `0.23.7` requires `~1.3.0` | + +The npm registry currently labels TypeScript `7.0.2` as `latest`, `7.1.0-beta` as `beta`, and `7.1.0-dev.20261111.1` as `next`. The design targets the beta API already supported by the pinned tsrs release, not a presumed stable TypeScript 7.1 release. [TypeScript registry metadata](https://registry.npmjs.org/typescript), [tsrs release and upstream pin](https://github.com/oxc-project/tsrs/blob/v0.24.0/docs/releases/0.24.0.md). + +Two integration constraints precede the dependency update: + +- Core redirects tsdown's Rolldown imports to its shared bundle. Updating tsdown does not install an independent runtime Rolldown for pack. Qualify a Vite version with the selected Rolldown `1.3.x` before changing that shared bundle. If the combination is not ready, defer the tsdown upgrade. Do not force the current bundle past its supported range. [Core import rewriting](../packages/core/build-support/rewrite-imports.ts), [Rolldown 1.3 release warning](https://github.com/rolldown/rolldown/releases/tag/v1.3.0). +- Both target packages require Node `^22.18.0 || ^24.11.0 || >=26.0.0`. The PR's local CLI already declares this range; core still advertises Node 20 and a broader `>=24.11.0` range. Align core's metadata and document the change for direct core consumers. `vp migrate` can report incompatible runtime pins, but cannot make these dependencies run on Node 20. [tsdown manifest](https://github.com/rolldown/tsdown/blob/v0.23.7/package.json), [plugin manifest](https://github.com/sxzz/rolldown-plugin-dts/blob/v0.30.1/package.json). + +## User behavior + +A plain TypeScript library with a supported tsconfig needs only its normal `vite-plus` installation: + +```ts +import { defineConfig } from 'vite-plus'; + +export default defineConfig({ + pack: { + entry: ['src/index.ts'], + dts: true, + }, +}); +``` + +If the project does not provide `typescript`, `vp pack` selects bundled tsrs. If it already provides TypeScript 5/6, the same configuration keeps the classic compiler. An existing project can select bundled tsrs without removing its other compiler: + +```ts +pack: { + dts: { generator: 'tsrs' }, +} +``` + +Keep declaration enablement separate from compiler selection. Preserve `dts: false`, CLI overrides, executable defaults, and tsdown's inference from `types`, `typings`, `exports.types`, and `compilerOptions.declaration`. Installing tsrs must not start generating declarations for projects that previously disabled them. Apply the adapter after tsdown has resolved this enablement, including array/workspace configurations and watch reloads. [tsdown configuration resolution](https://github.com/rolldown/tsdown/blob/v0.23.7/src/config/options.ts). + +Keep `dts.typecheck` at its upstream default, `false`. Declaration emit can still fail on emit diagnostics; a complete type check remains an explicit choice. Keep #3352's existing `lint.options.typeCheck` behavior. This upgrade does not turn on additional checking for all pack builds. + +### Generator selection + +Apply these rules in order for each enabled declaration build. Resolve project packages from the package that owns the build, with workspace inheritance bounded by the workspace root. + +| Condition | Behavior | +| --- | --- | +| Explicit generator string or custom `Generator` object | Preserve it and its options. Validate requirements; do not replace it after an error. | +| Implicit generator with Vue/Volar, `resolver: 'tsc'`, or a requested classic-only capability | Use a compatible project TypeScript 5/6 compiler. If unavailable, report the specific requirement and migration action. | +| `isolatedDeclarations: true`, without conflicting requirements | Keep Oxc. `typecheck: true` needs a checking generator; report the conflict instead of ignoring it. | +| Project TypeScript 5/6 | Keep `tsc` and the selected project version. | +| Project TypeScript exposes the required declaration API | Use `tsgo` with that package's module URL. | +| Project TypeScript 7.0 | Retain the deprecated CLI backend that plugin `0.30.1` still supplies. Report its migration path; do not assume later plugin minors retain it. | +| No project TypeScript, supported tsconfig and native options | Use bundled `tsrs`, with its module URL supplied by core. | +| Unsupported or broken compiler installation/configuration | Fail before emission with a focused diagnostic. Do not reinterpret a broken installation as an absent compiler. | + +An explicit `tsgo` selection continues to require a compatible Microsoft compiler. An explicit `tsrs.moduleUrl` or native executable path remains an advanced override. A `tsgo` options object alone is not a new generator selector; preserve upstream semantics and migrate old boolean selectors through the existing migration rules. + +Native eligibility requires a real tsconfig, a supported platform, and the required `async`/`fs` exports and `Program.prototype.getDeclarationEmit` capability. Check capabilities as well as versions. Classify unsupported options before creating build outputs. Do not retry a failing type check, compiler crash, or declaration emit through a different compiler: that can change published types and conceal errors. + +### Compatibility limits + +The native generators currently read program options from the on-disk tsconfig. They do not apply `dts.compilerOptions` or `dts.tsconfigRaw` to the compiler program. Declaration maps also require `compilerOptions.declarationMap` in that file. Preserve an existing classic compiler when these overrides matter; reject an incompatible explicit native selection with a migration diagnostic instead of silently ignoring settings. [Native option definitions](https://github.com/sxzz/rolldown-plugin-dts/blob/v0.30.1/src/options.ts). + +Retain TypeScript 5/6 for Vue/Volar and classic API consumers. Treat non-Volar custom languages as explicit native configurations that require `vfs` and filename mapping. Preserve `runExternalCode: false` by default. Content mappers require an explicit opt-in and cannot combine with native VFS in the current plugin. TypeScript 7.1's new API does not make old compiler API plugins interchangeable. [Plugin integrations](https://github.com/sxzz/rolldown-plugin-dts/blob/v0.30.1/README.md), [TypeScript 7.1 API](https://devblogs.microsoft.com/typescript/announcing-typescript-7-1-beta/). + +Native support makes the pinned compiler's language and library features available. It does not change `target`, `lib`, module resolution, runtime polyfills, or editor settings. Check syntax that also passes through Rolldown/Oxc, including import attributes, through the full pack pipeline before advertising support. A library that uses newer declaration syntax may need a higher minimum TypeScript version for its consumers; migration cannot downlevel arbitrary public types. + +## Packaging and implementation + +Keep ownership in the local packages. The global CLI continues to delegate to the project's `vite-plus`; it does not provide the compiler. + +1. Add the same exact `tsrs` production dependency to core that #3352 adds to the CLI. Keep the CLI's direct dependency for lint resolution. Package managers can deduplicate matching versions, but neither package may rely on hoisting to the other. Avoid shipping a second Go compiler as a default fallback. +2. Keep `tsrs` and its API subpaths external when rebuilding tsdown. Resolve the default `tsrs.moduleUrl` from core's own dependency. Preserve tsrs's SDK, platform binary, and adjacent standard libraries. The lint executable path is not a declaration API module URL. +3. Update `mergePackageJson()` so upstream peer merging cannot replace the direct tsrs dependency with an optional peer. Keep `typescript` and the framework peers optional. Include TypeScript 7.1 prereleases in the effective peer range: tsdown's broad stable range alone does not admit `7.1.0-beta`. +4. Audit the declaration plugin's new `yuku` dependency and its native packages. The existing build externalizes `yuku-parser@0.19.0`; plugin `0.30.1` uses `yuku@^0.27.0`. Replace the old packaging assumption with the resolved dependency graph and verify native loading from the published tarball. [Plugin dependencies](https://registry.npmjs.org/rolldown-plugin-dts/0.30.1). +5. Put compiler selection in one core pack adapter, used by CLI resolution and public `build`/`resolveUserConfig` entry points. Preserve the remaining `vite-plus/pack` exports and custom generators. Hook after effective package/tsconfig discovery and before declaration-plugin construction, rather than setting a generator once in `pack-bin.ts` before defaults are known. +6. Resolve explicitly selected project TypeScript from the owning package. The plugin's classic compiler loader currently has a module-global cache. Scope compiler contexts by resolved compiler URL and project, or obtain upstream provider injection before claiming mixed-version programmatic builds work. Do not change process-wide module resolution to switch compilers. [Classic compiler loading](https://github.com/sxzz/rolldown-plugin-dts/blob/v0.30.1/src/require.ts). +7. Preserve the declaration externalization rules for `vite-plus`, Vitest, PostCSS, and Lightning CSS, including when declarations are enabled by inference. Keep public `vite-plus/test*` specifiers in output. Verify ESM/CJS filenames, exports, maps, and ambient declarations against the existing pack fixtures. +8. Handle the expected native-backend notice without failing a successful build. `vp pack` defaults to `failOnWarn: true`; the plugin's experimental warning currently sets exit status 1 even when emit succeeds. Give that notice a structured upstream code and report it once as informational in the experimental Vite+ release. If a temporary adapter is needed, match only the pinned, known notice. Preserve failures for other warnings, including ignored compiler settings and missing maps. + +Prefer an upstream integration hook for the adapter. If a local bundle transform is necessary, anchor it to the pinned source shape, assert the expected replacement count, and cover both CLI and programmatic calls. Do not add another public `native: true` switch alongside `dts.generator`. + +Expose the declaration plugin version, bundled tsrs version, and TypeScript source version through toolchain metadata. Debug pack output should include the selected generator, compiler location, and selection reason. Check and pack can use different compilers when a project retains TypeScript; report those identities without claiming diagnostic equivalence. + +## `vp migrate` + +Extend the existing pack migration in [`crates/vp_migration/src/pack_config.rs`](../crates/vp_migration/src/pack_config.rs) and its TypeScript orchestration. Run it for existing Vite+ projects without requiring `--full`, including workspace packages and retained `tsdown.config.*` imports. Read the installed source toolchain before the final install, as the current versioned migration does. + +Separate schema updates from compiler adoption. Apply static, behavior-preserving schema changes on the normal upgrade path. Keep TypeScript 5/6/7 selection and dependencies unless a recognized obsolete native configuration requires a change. Unknown source versions take the conservative path; do not assume a fresh project. + +| Existing configuration | Automatic action | Boundary | +| --- | --- | --- | +| Top-level `dts.vue`, `build`, `parallel`, `eager`, `newContext` | Move them under `dts.tsc` and retain classic compiler selection where required. | Preserve explicit nested values; conflicting values or dynamic objects require a finding. | +| Top-level `dts.incremental` | Move `true`; for `false`, preserve the old behavior of following tsconfig rather than writing the new disabling override. | Conflicts with `dts.tsc.incremental` need review. Do not implement this as a blind key rename. | +| `dts.emitDtsOnly` / `dts.dtsInput` | Normalize equivalent static settings to `dts.dtsOnly`. | Conflicting aliases, or combination with `dts.entry`, need a resolved entry plan before editing. | +| `dts.entry` whose matches are already build entries | Keep the filter; preserve its old base directory when different from the build's `cwd`. | For static workspace paths, rewrite patterns relative to the resolved declaration cwd. Do not change compiler-config discovery by changing `dts.cwd` without checking it. | +| `dts.entry` that used to add declaration-only files | Add a separate declaration-only pack configuration with those inputs, and retain the original JS entry set. | Preserve names, output paths, maps, cleaning, and package exports. Dynamic globs/layouts or output collisions require review; never add JS outputs just to recover `.d.ts` files. | +| Explicit `tsgo` backed only by `@typescript/native-preview` | For supported plain TS projects with no custom executable/options, select bundled `tsrs`; preserve the other project compiler and type-check settings. | Run native eligibility checks. Keep the original config and dependencies when checks fail; report the blocker. | +| TypeScript 7.0 automatic/explicit `tsgo` | Keep the working `0.30.1` CLI bridge and identify it in the migration report. | Do not force a project-wide beta compiler upgrade. Native adoption remains an explicit `generator: 'tsrs'` change. | +| Custom `tsgo.path` | Report the changed contract: on 7.1 it selects an API server executable. | Do not copy a CLI binary path into `tsrs.path` or assume API compatibility. | +| Native compiler overrides or declaration maps configured only in `dts` | Create a project-owned pack tsconfig extending the original; set equivalent compiler options there and point only the affected pack config at it. | Move only static values, preserve effective `include`/`exclude`/`files`, references, relative paths, and project ownership. Keep shared configs unchanged. | + +The `dts.entry` and alias changes come from [tsdown 0.23.7](https://github.com/rolldown/tsdown/releases/tag/v0.23.7). The native-preview removal, executable-path change, and native map behavior come from [plugin 0.29](https://github.com/sxzz/rolldown-plugin-dts/releases/tag/v0.29.0). Preserve deprecated options through the compatibility window where the target plugin still accepts them; use migration to remove warnings and handle semantic changes. + +For a declaration-only split, generate a second `pack` array item using `dts: { dtsOnly: true }`, with no `dts.entry`. Run both through tsdown's existing shared cleanup in `buildWithConfigs`, and avoid competing export writes. Verify that watch cleanup retains each sibling's outputs. Only automate the split when the migrator can preserve the output contract. Otherwise report the exact missing declaration entries and retain the source configuration for review. [Shared cleanup implementation](https://github.com/rolldown/tsdown/blob/v0.23.7/src/build.ts). + +For a native-preview migration, remove that dependency only after checking scripts, imports, CI, shared workspace consumers, and package-manager catalogs. Retain standalone `tsgo` tasks that still use it. Do not add `typescript` to the generic bundled-package removal list, alias `typescript` to `tsrs`, or replace `tsc -b`/`tsc --emitDeclarationOnly` with `vp check`. + +Preserve comments, JSONC, workspace ownership, and explicit values. Use static AST transforms; do not evaluate a config to decide what to rewrite. Spreads, computed keys, conflicting aliases, unresolved package versions, and third-party config files produce actionable findings. Each finding names the file, unsupported setting, and next action. Running migration twice must produce no second diff. Stop a dependent rewrite if its prerequisite fails; do not leave a partially converted native configuration. + +Treat a configuration that the target toolchain cannot run as an upgrade blocker, not a successful migration with a warning. Identify it before the final dependency install and retain the previous toolchain when possible. If the user has already upgraded, report the incomplete migration and the supported rollback or manual edit. Keep harmless deprecations separate from these blockers in the migration report. + +For TypeScript 7 incompatibilities such as removed compiler options, retain the classic generator where possible. Report source-level type errors and public declaration incompatibilities for review. Do not weaken checking with `skipLibCheck`, `noCheck`, or disabled rules to make migration appear successful. + +## Release gates + +| Area | Required evidence | +| --- | --- | +| No project compiler | Published CLI/core tarballs emit inferred declarations with no `typescript`, native-preview, or project `tsrs`; the native standard libraries resolve. | +| Existing compilers | TypeScript 5.9, 6.0, 7.0, and 7.1 beta retain the intended generator. Explicit strings, custom generators, missing/corrupt compilers, and Oxc conflicts produce the documented outcomes. | +| Packaging | npm, pnpm with `hoist: false`, Yarn PnP, and Bun; workspace and standalone installs; local CLI with no global `vp`; public config types with declaration checking enabled. | +| Declarations | Compare old/new exports, `.d.ts`/`.d.mts`/`.d.cts`, maps, JSDoc, JS inputs, referenced projects, entry filters, and retained public imports. Compile representative consumer fixtures with the library's supported TS5/6/7 versions. | +| Native parity | Compare tsrs with exact `typescript@7.1.0-beta` using identical options and inputs. Compare older compilers separately to identify version changes rather than mislabeling them as Rust defects. Assert real process exit codes, including the default `failOnWarn` behavior. | +| Configuration | Arrays, callbacks, shared configs, `cwd`, disabled/inferred dts, CLI flags, inline compiler overrides, no tsconfig, Vue/Volar, VFS, and content-mapper opt-in. | +| Lifecycle | Watch edit/add/delete/config changes, failed rebuild recovery, cancellation, multiple projects/compiler versions, API cleanup, and no stale declarations or leftover native processes. | +| Migration | Existing Vite+ without `--full`, all supported package managers, catalogs, retained consumers, source-version detection, static rewrites, manual findings, and idempotence. | +| Platforms | macOS x64/ARM64, Linux x64/ARM64 glibc/musl, Windows x64; Windows ARM64 remains a blocker inherited from #3352 until tsrs ships and passes native validation. | + +Use existing [`command_pack_dts_compat`](../crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_pack_dts_compat/snapshots.toml), pack package-manager/watch fixtures, [`pack.spec.ts`](../packages/cli/src/__tests__/pack.spec.ts), and migration tests. Add fixtures to the snapshot runner with the local registry. The native-preview fixture currently proves a supported path and must become an explicit migration regression rather than disappear. + +Measure cold emit, warm rebuilds, and peak memory on the same projects and compiler settings. Lint benchmarks from #3352 do not establish declaration performance or correctness. Keep tsrs's reference defaults; do not enable its fast preset as part of this change. + +## Delivery order + +1. Qualify the Vite/Rolldown combination and update tsdown, its extensions, declaration plugin, native dependencies, and core engine/peer metadata. Retain supported legacy compiler paths. +2. Add the shared pack adapter, native dependency resolution, toolchain reporting, and the no-TypeScript path. Keep this in the experimental release until its gates pass. +3. Add the versioned migration transformations and manual findings before releasing the upgrade to existing projects. Include the native-preview and `dts.entry` transitions. +4. Update pack documentation and plain TypeScript templates after validation. Omit TypeScript from new templates only when their scripts, editor setup, and framework dependencies permit it. Retain it in templates that need the classic API. + +Keep these as small PRs stacked on #3352. Do not make a stable release until Windows ARM64 and the shared Rolldown integration have a supported outcome. The fallback release plan is to retain the previous qualified pack toolchain while completing these prerequisites, rather than introduce an untested compiler fallback. + +## Design probe + +On macOS ARM64 with Node `24.22.2`, an isolated npm installation of `tsdown@0.23.7`, `rolldown-plugin-dts@0.30.1`, and `tsrs@0.24.0-ts7.1.0-beta` confirmed these behaviors: + +- `typescript` could not resolve, and upstream `dts: true` failed with the missing-TypeScript diagnostic. +- Explicit `generator: 'tsrs'` emitted `export declare const answer: number;` without installing TypeScript. +- Explicit `typecheck: true` rejected a string assigned to that numeric export. +- With `failOnWarn: true`, emit completed and `build()` resolved, but the process exited with status 1 because of the experimental-backend warning. The adapter must handle this before the native default can ship. + +The probe used published packages from npm because the question depends on their shipped native API. Installation disabled lifecycle scripts and automatic peer installation. This checks the upstream integration premise only; it does not validate the proposed Vite+ adapter, migration, cross-platform support, or release performance.