From 714bef2b3181b65b483165347012aec96837582b Mon Sep 17 00:00:00 2001 From: MK Date: Wed, 4 Nov 2026 22:40:48 +0800 Subject: [PATCH 01/14] 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 e0ce0de920764170bea93fb6b742c7f5d306ee72 Mon Sep 17 00:00:00 2001 From: MK Date: Thu, 5 Nov 2026 15:12:44 +0800 Subject: [PATCH 02/14] 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 fac28b4936..f43ae90bd8 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 859f0da515..015a529c52 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 6b3d1ab020..03296ab3ab 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.91.0(oxlint-tsgolint@7.0.2005)(vite-plus@packages+cli) - oxlint-tsgolint: - specifier: 'catalog:' - version: 7.0.2005 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==} @@ -6614,6 +6666,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'} @@ -8897,6 +8954,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 @@ -12036,6 +12114,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 41ca2b670d..f1587b67bd 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 d9cd00422c1be31e71bca84278fff7713c744159 Mon Sep 17 00:00:00 2001 From: MK Date: Thu, 5 Nov 2026 15:21:11 +0800 Subject: [PATCH 03/14] 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 03296ab3ab..cbc4fe6eec 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] @@ -6666,8 +6666,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 @@ -8954,25 +8954,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': @@ -12114,15 +12114,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 f1587b67bd..dffb2065b4 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 6bb0e1011fad6300561468b3cdc665240509e827 Mon Sep 17 00:00:00 2001 From: MK Date: Thu, 5 Nov 2026 16:08:34 +0800 Subject: [PATCH 04/14] 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 74a3cca1b163fd491bc2ed0748af0b9bf4a62772 Mon Sep 17 00:00:00 2001 From: MK Date: Thu, 5 Nov 2026 16:22:24 +0800 Subject: [PATCH 05/14] 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 12ee93ece18c075aef70a5dcace3e3605765cc0e Mon Sep 17 00:00:00 2001 From: MK Date: Fri, 6 Nov 2026 00:09:53 +0800 Subject: [PATCH 06/14] 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 660db4ad5d999e40b71aa37842863b503cdd6f1c Mon Sep 17 00:00:00 2001 From: MK Date: Fri, 6 Nov 2026 00:29:35 +0800 Subject: [PATCH 07/14] 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 3c0e866629f0b9d91909d045401508ca6ed62d8a Mon Sep 17 00:00:00 2001 From: MK Date: Fri, 6 Nov 2026 09:51:13 +0800 Subject: [PATCH 08/14] 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 327482fb7799a103a44e1f7d7b4ff5c4c3a09f61 Mon Sep 17 00:00:00 2001 From: MK Date: Fri, 6 Nov 2026 09:58:50 +0800 Subject: [PATCH 09/14] 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 295947d15bb3806c22ec4f4ace0b78a258bd12c1 Mon Sep 17 00:00:00 2001 From: MK Date: Fri, 6 Nov 2026 15:55:51 +0800 Subject: [PATCH 10/14] 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 17fb8b32aa7cf75531dd0395808bec4ff2328ccc Mon Sep 17 00:00:00 2001 From: MK Date: Fri, 6 Nov 2026 16:50:11 +0800 Subject: [PATCH 11/14] 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 6328b1ba139901141c885f01cd001bc58cf9f11f Mon Sep 17 00:00:00 2001 From: MK Date: Sun, 8 Nov 2026 23:23:08 +0800 Subject: [PATCH 12/14] 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 aa41da4392e599431d511f5fb0125efcd52acda6 Mon Sep 17 00:00:00 2001 From: MK Date: Mon, 9 Nov 2026 14:13:32 +0800 Subject: [PATCH 13/14] 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 cbc4fe6eec..39bbda20f7 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] @@ -6666,8 +6666,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 @@ -8954,25 +8954,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': @@ -12114,15 +12114,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 dffb2065b4..eb6bb7ef23 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 1b9ba54cd95d8daf6e880343f4d6ccc2ad2d5226 Mon Sep 17 00:00:00 2001 From: MK Date: Mon, 9 Nov 2026 14:40:23 +0800 Subject: [PATCH 14/14] 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 } }