diff --git a/.github/renovate.json b/.github/renovate.json index ea6ef3018b..7baa60cb64 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -96,6 +96,7 @@ "oxlint", "oxlint-tsgolint", "tsdown", + "tsrs", "vite", "vitest", "vitest-dev", diff --git a/.github/scripts/__tests__/upgrade-deps.spec.ts b/.github/scripts/__tests__/upgrade-deps.spec.ts index 08ab6d0a5f..bf68c15892 100644 --- a/.github/scripts/__tests__/upgrade-deps.spec.ts +++ b/.github/scripts/__tests__/upgrade-deps.spec.ts @@ -123,6 +123,9 @@ test.each([false, true])( if (url === 'https://registry.npmjs.org/oxlint-tsgolint/latest') { return Response.json({ version: '7.0.2004' }); } + if (url === 'https://registry.npmjs.org/tsrs/latest') { + return Response.json({ version: '0.26.0-ts7.1.0-beta' }); + } if ( url === 'https://registry.npmjs.org/oxc-minify/latest' || url === 'https://registry.npmjs.org/oxc-transform/latest' @@ -173,6 +176,7 @@ test.each([false, true])( expect(readFileSync(workspacePath, 'utf8')).toContain("\n '@oxlint/plugins': =1.88.0\n"); expect(readFileSync(workspacePath, 'utf8')).toContain('\n oxlint: =1.88.0\n'); expect(readFileSync(workspacePath, 'utf8')).toContain('\n oxlint-tsgolint: =7.0.2004\n'); + expect(readFileSync(workspacePath, 'utf8')).toContain('\n tsrs: =0.26.0-ts7.1.0-beta\n'); expect(readFileSync(nodeVersionPath, 'utf8')).toBe('24.10.2\n'); expect(readFileSync(installerPath, 'utf8')).toContain( 'const PINNED_PNPM_VERSION: &str = "10.40.2";', @@ -201,6 +205,7 @@ test.each([false, true])( expect(versions['@oxlint/plugins'].new).toBe('1.88.0'); expect(versions['@oxlint/plugins'].new).toBe(versions.oxlint.new); expect(versions['oxlint-tsgolint'].new).toBe('7.0.2004'); + expect(versions.tsrs.new).toBe('0.26.0-ts7.1.0-beta'); expect(versions).not.toHaveProperty('oxc-minify'); expect(versions).not.toHaveProperty('oxc-transform'); expect(versions['oxc-node']).toEqual( diff --git a/.github/scripts/upgrade-deps.ts b/.github/scripts/upgrade-deps.ts index 06a0302111..9ca8fb16a9 100644 --- a/.github/scripts/upgrade-deps.ts +++ b/.github/scripts/upgrade-deps.ts @@ -66,6 +66,7 @@ type PnpmWorkspaceVersions = { oxfmt: string; oxlint: string; oxlintTsgolint: string; + tsrs: string; oxcProjectRuntime: string; oxcProjectTypes: string; oxcParser: string; @@ -369,6 +370,12 @@ async function updatePnpmWorkspace(versions: PnpmWorkspaceVersions): Promise { oxfmtVersion, oxlintVersion, oxlintTsgolintVersion, + tsrsVersion, oxcProjectRuntimeVersion, oxcProjectTypesVersion, oxcParserVersion, @@ -582,6 +590,7 @@ async function upgradeDependencies(): Promise { getLatestNpmVersion('oxfmt'), getLatestNpmVersion('oxlint'), getLatestNpmVersion('oxlint-tsgolint'), + getLatestNpmVersion('tsrs'), getLatestNpmVersion('@oxc-project/runtime'), getLatestNpmVersion('@oxc-project/types'), getLatestNpmVersion('oxc-parser'), @@ -599,6 +608,7 @@ async function upgradeDependencies(): Promise { console.log(`oxlint: ${oxlintVersion}`); console.log(`@oxlint/plugins (from oxlint): ${oxlintVersion}`); console.log(`oxlint-tsgolint: ${oxlintTsgolintVersion}`); + console.log(`tsrs: ${tsrsVersion}`); console.log(`@oxc-project/runtime: ${oxcProjectRuntimeVersion}`); console.log(`@oxc-project/types: ${oxcProjectTypesVersion}`); console.log(`oxc-parser: ${oxcParserVersion}`); @@ -621,6 +631,7 @@ async function upgradeDependencies(): Promise { oxfmt: oxfmtVersion, oxlint: oxlintVersion, oxlintTsgolint: oxlintTsgolintVersion, + tsrs: tsrsVersion, oxcProjectRuntime: oxcProjectRuntimeVersion, oxcProjectTypes: oxcProjectTypesVersion, oxcParser: oxcParserVersion, diff --git a/bench/tsrs-lint-ecosystem.md b/bench/tsrs-lint-ecosystem.md new file mode 100644 index 0000000000..7ea37392b1 --- /dev/null +++ b/bench/tsrs-lint-ecosystem.md @@ -0,0 +1,295 @@ +# Ecosystem correctness comparison + +On 2026-11-12, the local comparison covered **all 83 catalog forks plus vinext** with +the latest preview built from PR #3352 at `25a7c006af2db834bfc66f9689b02de1060f580f`. +The 740 commands found six repositories where a command passes with Go and fails +with tsrs. Four of those repositories also change from pass to fail for `vp check`. +The full results and exact fork commits are listed below. + +This is a correctness comparison on macOS ARM64. Two repository batches ran +concurrently; their elapsed times are not performance or memory measurements. + +## Tested toolchain + +| Component | Version / source | +| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Published preview | `0.0.0-commit.25a7c006af2db834bfc66f9689b02de1060f580f` | +| Preview build | [Build run](https://github.com/voidzero-dev/vite-plus/actions/runs/43190612524), [publication run](https://github.com/voidzero-dev/vite-plus/actions/runs/43194215156) | +| Oxlint / Oxfmt | `1.93.0` / `0.78.0` | +| Go control | `oxlint-tsgolint@7.0.2006` | +| Default backend | `tsrs@0.25.0-ts7.1.0-beta` | +| TypeScript reference | `7.1.0-beta`, commit `3e9236361fa97a9e6c685f4833ac457ae2d8c50a` | +| Machine | Apple M4 Max, 48 GiB RAM, macOS `27.0.1`, ARM64 | +| Node.js | `24.22.2` or `26.13.0`, according to the project engine requirement | +| Fork catalog | [83 entries at `5a74ee011`](https://github.com/vite-plus-ecosystem-ci/.github/blob/5a74ee011971dfcb4545b08b740a4d713dc90290/ecosystem.json), plus the vinext fork | + +Both controls use the same published preview, application commit, installed +dependencies, generated files, and command flags. Only the lint backend changes. +The fork commits and dependency trees are retained from the November 11 local run +to isolate the preview update. The current catalog has the same 83 repository names. + +## Exit-status summary + +| Workload | Both pass | Both fail | Go passes, tsrs fails | Go fails, tsrs passes | +| ----------------------------------- | --------: | --------: | --------------------: | --------------------: | +| `vp lint` | 46 | 33 | 5 | 0 | +| `vp lint --type-aware` | 32 | 47 | 5 | 0 | +| `vp lint --type-aware --type-check` | 17 | 61 | 6 | 0 | +| `vp check` | 28 | 52 | 4 | 0 | +| Declared project type-check task | 15 | 19 | 0 | 0 | + +`Both fail` includes configuration, formatting, dependency, and source errors. It does not identify a backend regression. + +## New command failures + +These are compiler diagnostics. All five default-lint cases already enable +`typeCheck` in their configuration. `yielded-auth` passes default lint with both +backends and fails only when the command explicitly enables type checking. + +| Repository | First failing workload | Errors, Go / tsrs | Cause and follow-up | +| --------------- | ---------------------- | ----------------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| css-modules-kit | Default lint | 0 / 3 | `TS2307` in three CSS-module fixture imports. Lint ignore patterns omit the fixtures, but whole-program checking still includes them. Review the fixture declarations or their TypeScript project boundaries. | +| elysia-clerk | Default lint | 0 / 4 | Two `TS2868` errors for `Bun` and two `TS2339` errors for `ImportMeta.dir` in the ignored `dev` directory. Add the intended Bun types or exclude this separate development program. | +| nostalgist | Default lint | 0 / 1 | `TS5115` in `zod@4.6.5` declarations. Follow up on the circular type construction with the dependency. | +| ramen-api | Default lint | 0 / 118 | Generated Cloudflare Worker declarations conflict with Node globals, plus Hono and Node declaration errors. Align the runtime declaration sets and review the affected dependency types. | +| vinext | Default lint | 0 / 17 | The ignored Sentry fixture reports 11 `TS2578`, four `TS2339`, one `TS7006`, and one `TS7016`. Exclude the separately checked fixture from the root TypeScript project. | +| yielded-auth | Explicit type check | 0 / 5 | Five `TS2339` errors in the docs dependency `starlight-links-validator`. Review its declared API types. | + +`vp check` changes from pass to fail in css-modules-kit, nostalgist, ramen-api, and +vinext. Elysia-clerk stops at formatting with both controls. + +All 148 errors above match the exact upstream TypeScript reference at the same +file, line, column, and diagnostic code. Standalone tsrs and TypeScript also produce +identical error headers for all 8 selected programs. Vinext's standalone root +check covers more files than lint and reports 241 errors with either compiler; +these include all 17 new lint errors. + +These six checks found no tsrs implementation divergence from its pinned compiler. +They still identify upgrade compatibility work: whole-program type checking can +report errors in lint-ignored fixtures and imported declarations. The additional +diagnostics in already-failing projects, listed below, have not all received this +independent compiler audit. + +## npmx.dev and vinext + +For the pinned npmx.dev fork, default lint passes with both backends. `vp check` +stops on the same eight formatting files. Explicit type checking reports 260 errors +with Go and 273 with tsrs; both fail. Its `test:types` task also fails under both +controls with two Vite/Vitest configuration-type errors. + +The tested vinext input is the unmodified catalog-org fork at `42d940ece`. +It does not contain the `tsconfig.exclude` fix in +[the separate vinext draft PR](https://github.com/fengmk2/vinext/pull/40). +That PR's [successful Check run](https://github.com/fengmk2/vinext/actions/runs/43164787804/job/131004010311) +uses a different commit and earlier preview; it is separate evidence from this sweep. + +## Complete fork results + +Each exit-code pair is **Go / tsrs**: `0` means success; `1` or `2` means failure. All source links pin the tested commit. +The error column counts error-severity diagnostics in the explicit type-check workload; warnings are excluded. Equal counts do not establish identical diagnostics. +`—` means that no suitable declared type-check task was found. Tasks retain their own compiler selection; switching the lint backend does not replace `tsc`, `vue-tsc`, or `svelte-check`. + +| Repository / commit | Lint | Type-aware lint | Explicit type check | Type-check errors | `vp check` | Project task / exits | +| ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | --------------- | ------------------- | ----------------: | ---------- | -------------------- | +| [agentsview `4a9d932b2`](https://github.com/vite-plus-ecosystem-ci/agentsview/tree/4a9d932b2b41ee6ee8d30e215b99b258c61c3163) | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 0 | `check` 0 / 0 | +| [anywidget `35160ac08`](https://github.com/vite-plus-ecosystem-ci/anywidget/tree/35160ac08e75918faee008a3b8996ab2a0a16a46) | 1 / 1 | 1 / 1 | 1 / 1 | 14 / 14 | 1 / 1 | — | +| [arrow-hero `e6dedbefb`](https://github.com/vite-plus-ecosystem-ci/arrow-hero/tree/e6dedbefb94f9e93b3a8c8d9479a94b2b0c0d45a) | 0 / 0 | 1 / 1 | 1 / 1 | 6 / 6 | 0 / 0 | — | +| [better-ajv-errors `03ce620b1`](https://github.com/vite-plus-ecosystem-ci/better-ajv-errors/tree/03ce620b155357aa2968459bf754949b7e07e3e5) | 1 / 1 | 1 / 1 | 1 / 1 | 1 / 1 | 1 / 1 | — | +| [bippy `fbafc407c`](https://github.com/vite-plus-ecosystem-ci/bippy/tree/fbafc407ca31ad82f391404c6c6539008f9e0d79) | 0 / 0 | 0 / 0 | 1 / 1 | 257 / 257 | 0 / 0 | `typecheck` 0 / 0 | +| [brazilian-utils-javascript `0f0f9a153`](https://github.com/vite-plus-ecosystem-ci/brazilian-utils-javascript/tree/0f0f9a1539d7263a08e245e9b03f8f7dffc7cd9e) | 1 / 1 | 1 / 1 | 1 / 1 | Config blocked | 1 / 1 | — | +| [cloudflare-cf `e2c720597`](https://github.com/vite-plus-ecosystem-ci/cloudflare-cf/tree/e2c720597887fb742e52cbe79132e4bdfec92f16) | 0 / 0 | 0 / 0 | 1 / 1 | 41 / 41 | 0 / 0 | `check:type` 1 / 1 | +| [cloudflare-os `1541cc07f`](https://github.com/vite-plus-ecosystem-ci/cloudflare-os/tree/1541cc07fc47c1beafeb98cd9927bb3f112d523f) | 0 / 0 | 1 / 1 | 1 / 1 | 8112 / 8119 | 0 / 0 | — | +| [codiff `2167e44b3`](https://github.com/vite-plus-ecosystem-ci/codiff/tree/2167e44b3a362480d8fca47a7fefcf98f8a24a27) | 1 / 1 | 1 / 1 | 1 / 1 | 53 / 344 | 1 / 1 | — | +| [Cookies-EU-banner `1193c4c41`](https://github.com/vite-plus-ecosystem-ci/Cookies-EU-banner/tree/1193c4c41a91f26d722c680564bc9f0af05903ed) | 1 / 1 | 1 / 1 | 1 / 1 | 6 / 6 | 1 / 1 | — | +| [critical `03897c82e`](https://github.com/vite-plus-ecosystem-ci/critical/tree/03897c82e255416ca7104774a9d19142bd1e2744) | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 0 | — | +| [crust `f007d2d0d`](https://github.com/vite-plus-ecosystem-ci/crust/tree/f007d2d0d3d6ca030a39cb3d1d978c707115b01d) | 1 / 1 | 1 / 1 | 1 / 1 | 1173 / 1173 | 1 / 1 | `check:types` 1 / 1 | +| [cs-demo-manager `164e48745`](https://github.com/vite-plus-ecosystem-ci/cs-demo-manager/tree/164e487451ed24478a02c6225f558cf0268efa65) | 1 / 1 | 1 / 1 | 1 / 1 | 3 / 3 | 1 / 1 | `compile` 0 / 0 | +| [css-modules-kit `1dec592b9`](https://github.com/vite-plus-ecosystem-ci/css-modules-kit/tree/1dec592b930c6b65cf15169dc85cbabdd44ef64f) | 0 / 1 | 0 / 1 | 0 / 1 | 0 / 3 | 0 / 1 | — | +| [delta-comic `d2b007579`](https://github.com/vite-plus-ecosystem-ci/delta-comic/tree/d2b007579b703f9d1561e4bd1b39cd7e7c17b6ab) | 0 / 0 | 1 / 1 | 1 / 1 | 288 / 288 | 1 / 1 | `typecheck` 1 / 1 | +| [digital-signature `2d6e49c24`](https://github.com/vite-plus-ecosystem-ci/digital-signature/tree/2d6e49c24448fca9929bfa1973c29bca09bebdc8) | 0 / 0 | 0 / 0 | 1 / 1 | 1 / 1 | 0 / 0 | `check` 0 / 0 | +| [docx-editor `2457e2765`](https://github.com/vite-plus-ecosystem-ci/docx-editor/tree/2457e2765d76e231cff7c87a2b2993cb6071e668) | 1 / 1 | 1 / 1 | 1 / 1 | 2580 / 2555 | 1 / 1 | `type-check` 2 / 2 | +| [dotabod-backend `65d80c3ef`](https://github.com/vite-plus-ecosystem-ci/dotabod-backend/tree/65d80c3ef8168150f5a32c8586ec136a2d877e01) | 1 / 1 | 1 / 1 | 1 / 1 | 5379 / 5381 | 1 / 1 | `typecheck` 1 / 1 | +| [dotabod-frontend `e06184067`](https://github.com/vite-plus-ecosystem-ci/dotabod-frontend/tree/e06184067abf0f97c8f74abf6e992638bc50b90d) | 1 / 1 | 1 / 1 | 1 / 1 | 6774 / 6774 | 1 / 1 | `typecheck` 0 / 0 | +| [electron-vite-template `4b1f4d64b`](https://github.com/vite-plus-ecosystem-ci/electron-vite-template/tree/4b1f4d64b36bab216391668efc24f12598553628) | 0 / 0 | 0 / 0 | 1 / 1 | 7 / 7 | 1 / 1 | — | +| [elysia-clerk `6e42ab83b`](https://github.com/vite-plus-ecosystem-ci/elysia-clerk/tree/6e42ab83b4fd8401f5ddb13ae206cbf4887df469) | 0 / 1 | 0 / 1 | 0 / 1 | 0 / 4 | 1 / 1 | — | +| [eslint-plugin-oxlint `4eab8b2d8`](https://github.com/vite-plus-ecosystem-ci/eslint-plugin-oxlint/tree/4eab8b2d87d80c508247209fde18e749160bf840) | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 0 | — | +| [f3-app `b03939e5c`](https://github.com/vite-plus-ecosystem-ci/f3-app/tree/b03939e5c9ac280dcc96454a69c950dec9bcd243) | 1 / 1 | 1 / 1 | 1 / 1 | 9 / 11 | 1 / 1 | — | +| [faasjs `813638437`](https://github.com/vite-plus-ecosystem-ci/faasjs/tree/813638437070cd8f30bec0b93d2c730903db6579) | 1 / 1 | 1 / 1 | 1 / 1 | 78 / 78 | 1 / 1 | — | +| [fate `97a730445`](https://github.com/vite-plus-ecosystem-ci/fate/tree/97a73044585c748b5a23be8f363ba6a6bb606295) | 1 / 1 | 1 / 1 | 1 / 1 | 100 / 100 | 1 / 1 | — | +| [gitify `3a843faed`](https://github.com/vite-plus-ecosystem-ci/gitify/tree/3a843faedca74e64dfadb82cfc8355c0f58a22ed) | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 0 | — | +| [graph-explorer `7a696be53`](https://github.com/vite-plus-ecosystem-ci/graph-explorer/tree/7a696be53875c4241826b3e55ce1e0b4d721ce62) | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 0 | — | +| [hono `61f7c59e5`](https://github.com/vite-plus-ecosystem-ci/hono/tree/61f7c59e50244ab4d2dd83c151928cf7ea448510) | 0 / 0 | 1 / 1 | 1 / 1 | 419 / 421 | 1 / 1 | — | +| [Ivy-Framework `f6b1d065f`](https://github.com/vite-plus-ecosystem-ci/Ivy-Framework/tree/f6b1d065f725d4fc27ec99b69e773750920f4145) | 0 / 0 | 0 / 0 | 1 / 1 | 1 / 1 | 0 / 0 | — | +| [kaneo `ea9eb6d3d`](https://github.com/vite-plus-ecosystem-ci/kaneo/tree/ea9eb6d3d66cb4b216ee9f4314f089ea331b224b) | 0 / 0 | 1 / 1 | 1 / 1 | 1640 / 1640 | 0 / 0 | `typecheck` 1 / 1 | +| [local-studio `f65c8af78`](https://github.com/vite-plus-ecosystem-ci/local-studio/tree/f65c8af78582f741b62546c93d7c66439f0cd5b1) | 1 / 1 | 1 / 1 | 1 / 1 | 34 / 34 | 1 / 1 | `typecheck` 0 / 0 | +| [matterviz `91ce15d7b`](https://github.com/vite-plus-ecosystem-ci/matterviz/tree/91ce15d7b032252ee0939d04148d09e881f1a8b4) | 1 / 1 | 1 / 1 | 1 / 1 | 71 / 80 | 2 / 2 | `typecheck` 1 / 1 | +| [mcp-omnisearch `03bb264b8`](https://github.com/vite-plus-ecosystem-ci/mcp-omnisearch/tree/03bb264b8edd72dac96f45ffdfe582682eca96ee) | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 0 | — | +| [media-processors `79c87cd5c`](https://github.com/vite-plus-ecosystem-ci/media-processors/tree/79c87cd5c066a0a9abea979044c38c69603a85e8) | 1 / 1 | 1 / 1 | 1 / 1 | 91 / 91 | 1 / 1 | `typecheck` 1 / 1 | +| [museeks `7163f6019`](https://github.com/vite-plus-ecosystem-ci/museeks/tree/7163f6019a3184043c06352caeec0ed1bf6ccff5) | 1 / 1 | 1 / 1 | 1 / 1 | 73 / 73 | 1 / 1 | `test:typings` 2 / 2 | +| [nostalgist `436e23e4c`](https://github.com/vite-plus-ecosystem-ci/nostalgist/tree/436e23e4cac64e61e71825bcb1c001cf3728b736) | 0 / 1 | 0 / 1 | 0 / 1 | 0 / 1 | 0 / 1 | — | +| [notero `011be2ca9`](https://github.com/vite-plus-ecosystem-ci/notero/tree/011be2ca9ddc6b34326f7e5ce75089266cbf94bc) | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 0 | 1 / 1 | `typecheck` 0 / 0 | +| [npmx.dev `ef6105e1e`](https://github.com/vite-plus-ecosystem-ci/npmx.dev/tree/ef6105e1e11d93b81f51c78c6ccb8b35fe8c19fe) | 0 / 0 | 1 / 1 | 1 / 1 | 260 / 273 | 1 / 1 | `test:types` 2 / 2 | +| [nshipster.com `ccdd0b5dc`](https://github.com/vite-plus-ecosystem-ci/nshipster.com/tree/ccdd0b5dc49100bdb2fce2fbfbdd12cff4414d8f) | 1 / 1 | 1 / 1 | 1 / 1 | 36 / 36 | 1 / 1 | `check` 1 / 1 | +| [ohbug `261f2807c`](https://github.com/vite-plus-ecosystem-ci/ohbug/tree/261f2807cfe94475bc5094a75a5a2e874dafa715) | 1 / 1 | 1 / 1 | 1 / 1 | 16 / 16 | 1 / 1 | — | +| [OneSignal-Website-SDK `836367e7f`](https://github.com/vite-plus-ecosystem-ci/OneSignal-Website-SDK/tree/836367e7fccf5420d4ef31cf750d21fdad76277a) | 1 / 1 | 1 / 1 | 1 / 1 | 1 / 2 | 1 / 1 | — | +| [oxc-walker `d35924c04`](https://github.com/vite-plus-ecosystem-ci/oxc-walker/tree/d35924c04f3cbd48ae53ba20f28933b508cfd244) | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 0 | `test:types` 0 / 0 | +| [oxlint-tailwindcss `7477ab484`](https://github.com/vite-plus-ecosystem-ci/oxlint-tailwindcss/tree/7477ab4849330e46802e8753af2cbb6968c58f3d) | 0 / 0 | 0 / 0 | 1 / 1 | 704 / 704 | 1 / 1 | `typecheck` 0 / 0 | +| [pinia-shared-state `1b1ec6109`](https://github.com/vite-plus-ecosystem-ci/pinia-shared-state/tree/1b1ec6109b6f46079e4ddb5aeb9feff1d7188c24) | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 0 | — | +| [pocket-id `8b5784206`](https://github.com/vite-plus-ecosystem-ci/pocket-id/tree/8b5784206b886f484caa98dd306f179b3a128b73) | 1 / 1 | 1 / 1 | 1 / 1 | 19 / 150 | 2 / 2 | — | +| [raindrop-mcp `20161f2ef`](https://github.com/vite-plus-ecosystem-ci/raindrop-mcp/tree/20161f2efeb38311002e5bed0b0ba320800a02b3) | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 0 | `type-check` 0 / 0 | +| [ramen-api `9e31be0d3`](https://github.com/vite-plus-ecosystem-ci/ramen-api/tree/9e31be0d3410948f6ee086c068c692d087ee07c7) | 0 / 1 | 0 / 1 | 0 / 1 | 0 / 118 | 0 / 1 | — | +| [react-data-grid `6f6469d88`](https://github.com/vite-plus-ecosystem-ci/react-data-grid/tree/6f6469d88dac7d7263862ef2d449b7b06d98e1f7) | 0 / 0 | 0 / 0 | 1 / 1 | 623 / 622 | 0 / 0 | `typecheck` 2 / 2 | +| [react-hookz-web `35944fbc4`](https://github.com/vite-plus-ecosystem-ci/react-hookz-web/tree/35944fbc4c8d5ab6c05a1c7f0b21a58b75d78fd6) | 1 / 1 | 1 / 1 | 1 / 1 | 22 / 22 | 1 / 1 | `typecheck` 0 / 0 | +| [react-native-onesignal `3fde3921d`](https://github.com/vite-plus-ecosystem-ci/react-native-onesignal/tree/3fde3921d22a030109987b615938ad8b53538586) | 1 / 1 | 1 / 1 | 1 / 1 | 2 / 2 | 1 / 1 | — | +| [react-useanimations `f7af6f53f`](https://github.com/vite-plus-ecosystem-ci/react-useanimations/tree/f7af6f53f6f1a97486ad95756b281a34e9f7c670) | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 0 | — | +| [redis-me `2db9d859b`](https://github.com/vite-plus-ecosystem-ci/redis-me/tree/2db9d859ba0a4c6fe2868c7774560f96f321ae90) | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 0 | — | +| [regle `c2668d6f0`](https://github.com/vite-plus-ecosystem-ci/regle/tree/c2668d6f02ec414bdf01609f323fbdbd6036c838) | 0 / 0 | 1 / 1 | 1 / 1 | 1165 / 1218 | 1 / 1 | `typecheck` 1 / 1 | +| [remdx `4da4d8f94`](https://github.com/vite-plus-ecosystem-ci/remdx/tree/4da4d8f945cb63056efc9a5cb94b32bf2b0b2777) | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 0 | 1 / 1 | — | +| [saasyland.com `ca0fd03bf`](https://github.com/vite-plus-ecosystem-ci/saasyland.com/tree/ca0fd03bf6a754b224ff5c2bc75e3cc4132f1079) | 1 / 1 | 1 / 1 | 1 / 1 | 164 / 164 | 1 / 1 | — | +| [sandbox-sdk `61cac81b5`](https://github.com/vite-plus-ecosystem-ci/sandbox-sdk/tree/61cac81b53334b314e4a4daf04486b9cee9b65c2) | 1 / 1 | 1 / 1 | 1 / 1 | 34 / 50 | 1 / 1 | — | +| [sigle `31767a4d8`](https://github.com/vite-plus-ecosystem-ci/sigle/tree/31767a4d8f16e8448f88c3ff9b3d68efb47541c8) | 1 / 1 | 1 / 1 | 1 / 1 | Config blocked | 1 / 1 | — | +| [solid-services `add4b036b`](https://github.com/vite-plus-ecosystem-ci/solid-services/tree/add4b036b72de7e6372b07faf0aed6b3f2e506d4) | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 0 | 1 / 1 | — | +| [soybean-admin `d2dfefd75`](https://github.com/vite-plus-ecosystem-ci/soybean-admin/tree/d2dfefd757e37d8d6077ea1cea6181fb628d6097) | 0 / 0 | 1 / 1 | 1 / 1 | 117 / 117 | 0 / 0 | `typecheck` 0 / 0 | +| [soybean-admin-antd `02a002d12`](https://github.com/vite-plus-ecosystem-ci/soybean-admin-antd/tree/02a002d120a51bdc3c255b0c6582b00a8bcdddf0) | 0 / 0 | 1 / 1 | 1 / 1 | 130 / 130 | 1 / 1 | `typecheck` 0 / 0 | +| [spark-ui `0436591bc`](https://github.com/vite-plus-ecosystem-ci/spark-ui/tree/0436591bc916bd17e7c5df28c1f58eb55066a3cb) | 1 / 1 | 1 / 1 | 1 / 1 | 6 / 4 | 1 / 1 | `typecheck` 1 / 1 | +| [svelte-meta-tags `57339bc3f`](https://github.com/vite-plus-ecosystem-ci/svelte-meta-tags/tree/57339bc3feefc1035914245dcbc37b08fb131680) | 0 / 0 | 1 / 1 | 1 / 1 | 4 / 4 | 2 / 2 | — | +| [svelte-multiselect `29a64aa5c`](https://github.com/vite-plus-ecosystem-ci/svelte-multiselect/tree/29a64aa5cd8e18867fee9b469882b1a0715f8b5a) | 1 / 1 | 1 / 1 | 1 / 1 | 81 / 84 | 2 / 2 | — | +| [sveltekit-embed `e7818c891`](https://github.com/vite-plus-ecosystem-ci/sveltekit-embed/tree/e7818c891e584070f5b00072e3bb85c070f71a6c) | 1 / 1 | 1 / 1 | 1 / 1 | 3 / 3 | 2 / 2 | — | +| [tiptap `228df820d`](https://github.com/vite-plus-ecosystem-ci/tiptap/tree/228df820d22a807310f7dcd47eb145557027c8e5) | 0 / 0 | 1 / 1 | 1 / 1 | 57 / 57 | 0 / 0 | — | +| [trpc-nuxt `6b2b2ba86`](https://github.com/vite-plus-ecosystem-ci/trpc-nuxt/tree/6b2b2ba86e6b15adb7d5c88a785adba1f050cac5) | 1 / 1 | 1 / 1 | 1 / 1 | 13 / 20 | 1 / 1 | — | +| [typed-xlsx `91ea9a725`](https://github.com/vite-plus-ecosystem-ci/typed-xlsx/tree/91ea9a725631f8d7ec8b6ff70d55fac14bf45dbb) | 0 / 0 | 1 / 1 | 1 / 1 | 39 / 39 | 1 / 1 | `typecheck` 1 / 1 | +| [unplugin-turbo-console `ea1986d13`](https://github.com/vite-plus-ecosystem-ci/unplugin-turbo-console/tree/ea1986d1329a7c1a059e3ae0731384ecfbd9dba1) | 0 / 0 | 1 / 1 | 1 / 1 | 41 / 44 | 0 / 0 | — | +| [urllib `7aba43082`](https://github.com/vite-plus-ecosystem-ci/urllib/tree/7aba43082d8fce3d967e1855ae326627a9ce139c) | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 0 | `typecheck` 0 / 0 | +| [v-offline `d0ed7de37`](https://github.com/vite-plus-ecosystem-ci/v-offline/tree/d0ed7de3707f5c43ed5f0b066e22f3347eea413e) | 0 / 0 | 0 / 0 | 1 / 1 | 31 / 29 | 0 / 0 | `typecheck` 0 / 0 | +| [varlet `4f56984db`](https://github.com/vite-plus-ecosystem-ci/varlet/tree/4f56984dbf90036ebdea4167217ff7659f2e4c9f) | 0 / 0 | 0 / 0 | 1 / 1 | 274 / 282 | 0 / 0 | `compile` 1 / 1 | +| [vean-ui `7c5493801`](https://github.com/vite-plus-ecosystem-ci/vean-ui/tree/7c5493801fe91cd2df33dec910bca3436d85a3df) | 0 / 0 | 1 / 1 | 1 / 1 | 2611 / 2605 | 0 / 0 | `typecheck` 1 / 1 | +| [vibe-dashboard `9b0ec30a3`](https://github.com/vite-plus-ecosystem-ci/vibe-dashboard/tree/9b0ec30a39b9d77a44656a7cdb520c15c224b3e1) | 1 / 1 | 1 / 1 | 1 / 1 | 1 / 1 | 1 / 1 | — | +| [vinext `42d940ece`](https://github.com/vite-plus-ecosystem-ci/vinext/tree/42d940eceb8f8199195d374711a14498b248dd61) | 0 / 1 | 0 / 1 | 0 / 1 | 0 / 17 | 0 / 1 | — | +| [vitepress-carbon `b9c0ecf06`](https://github.com/vite-plus-ecosystem-ci/vitepress-carbon/tree/b9c0ecf0685603ee7fae0776b479724539818f89) | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 0 | 1 / 1 | — | +| [vitepress-plugin-group-icons `602761b05`](https://github.com/vite-plus-ecosystem-ci/vitepress-plugin-group-icons/tree/602761b05108e4b4df2da9f69e08a406cd6540f0) | 0 / 0 | 0 / 0 | 1 / 1 | 3 / 3 | 0 / 0 | — | +| [VRM-Addon-for-Blender `f886e7fe2`](https://github.com/vite-plus-ecosystem-ci/VRM-Addon-for-Blender/tree/f886e7fe283f08035273f5406fadebea6db64d60) | 0 / 0 | 0 / 0 | 1 / 1 | 6 / 6 | 0 / 0 | — | +| [vue-clamp `6f0da3991`](https://github.com/vite-plus-ecosystem-ci/vue-clamp/tree/6f0da3991f804587ba402a7ca22aec4564eab938) | 0 / 0 | 0 / 0 | 0 / 0 | 0 / 0 | 1 / 1 | — | +| [vue-core `8f946dc4a`](https://github.com/vite-plus-ecosystem-ci/vue-core/tree/8f946dc4a2f0bb515a1a8070388b9b865a2a2b9d) | 0 / 0 | 0 / 0 | 1 / 1 | 541 / 551 | 1 / 1 | `check` 2 / 2 | +| [vue-devtools `bb6f283f9`](https://github.com/vite-plus-ecosystem-ci/vue-devtools/tree/bb6f283f9eab90bbfbe06376ac92fcc116d43b52) | 1 / 1 | 1 / 1 | 1 / 1 | 70 / 70 | 1 / 1 | `typecheck` 2 / 2 | +| [vueleaflet `254f84e5f`](https://github.com/vite-plus-ecosystem-ci/vueleaflet/tree/254f84e5f8660e914ecbd1f8a0d0763709edf514) | 0 / 0 | 0 / 0 | 1 / 1 | 18 / 18 | 1 / 1 | — | +| [web-app-template `7cc5e4e15`](https://github.com/vite-plus-ecosystem-ci/web-app-template/tree/7cc5e4e152429100f8dc230b2fbe9e516f6a1c8c) | 1 / 1 | 1 / 1 | 1 / 1 | 1 / 1 | 1 / 1 | — | +| [yaak `f7402f5f4`](https://github.com/vite-plus-ecosystem-ci/yaak/tree/f7402f5f4f9752c4722a478cdb51dd54ad87cc34) | 0 / 0 | 0 / 0 | 1 / 1 | 327 / 342 | 1 / 1 | — | +| [yielded-auth `788239010`](https://github.com/vite-plus-ecosystem-ci/yielded-auth/tree/788239010e5f806d28f7fb3151e9b37c1e59a39c) | 0 / 0 | 0 / 0 | 0 / 1 | 0 / 5 | 1 / 1 | — | + +## Diagnostic changes in already-failing projects + +The following projects already fail explicit type checking with Go and have additional error diagnostics with tsrs. Counts compare file, code, severity, first-label position, and message; a changed message or position can count as one addition and one removal. These differences have not all been independently attributed to the upstream compiler. + +| Repository | Added errors | Removed errors | Added diagnostic codes | +| ---------------------- | -----------: | -------------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| cloudflare-os | 380 | 373 | `TS2339` × 22, `TS2345` × 2, `TS2550` × 38, `TS2740` × 2, `TS5115` × 2, `TS7031` × 8, `TS7053` × 1, `no-unnecessary-type-assertion` × 114, `no-unsafe-type-assertion` × 191 | +| codiff | 291 | 0 | `TS18046` × 53, `TS18047` × 2, `TS18048` × 1, `TS2304` × 4, `TS2322` × 15, `TS2339` × 51, `TS2345` × 26, `TS2352` × 1, `TS2493` × 2, `TS2538` × 4, `TS2540` × 1, `TS2551` × 1, `TS2554` × 2, `TS2698` × 3, `TS2739` × 1, `TS2741` × 7, `TS2790` × 1, `TS6133` × 4, `TS7005` × 13, `TS7006` × 86, `TS7016` × 1, `TS7022` × 2, `TS7034` × 9, `TS7053` × 1 | +| delta-comic | 1 | 1 | `TS2345` × 1 | +| docx-editor | 31 | 56 | `TS2322` × 7, `TS2339` × 2, `TS2345` × 11, `TS2352` × 1, `TS2551` × 2, `TS2741` × 2, `TS7031` × 4, `TS7053` × 2 | +| dotabod-backend | 5 | 3 | `TS2345` × 1, `TS5115` × 2, `no-unnecessary-type-assertion` × 1, `no-unsafe-type-assertion` × 1 | +| dotabod-frontend | 8 | 8 | `no-unsafe-argument` × 5, `no-unsafe-type-assertion` × 3 | +| f3-app | 2 | 0 | `TS2344` × 1, `TS2538` × 1 | +| faasjs | 1 | 1 | `TS2322` × 1 | +| fate | 1 | 1 | `TS2345` × 1 | +| hono | 5 | 3 | `TS2875` × 3, `TS7031` × 2 | +| kaneo | 18 | 18 | `TS2322` × 10, `TS2339` × 4, `TS2345` × 3, `TS2352` × 1 | +| local-studio | 1 | 1 | `TS2741` × 1 | +| matterviz | 9 | 0 | `TS7031` × 9 | +| npmx.dev | 13 | 0 | `TS2322` × 2, `TS2339` × 5, `TS2552` × 1, `TS2595` × 4, `TS7006` × 1 | +| OneSignal-Website-SDK | 1 | 0 | `TS2741` × 1 | +| pocket-id | 131 | 0 | `TS2304` × 131 | +| react-data-grid | 1 | 2 | `TS2307` × 1 | +| regle | 53 | 0 | `TS2307` × 27, `TS2344` × 10, `TS2552` × 1, `TS2664` × 1, `TS7006` × 6, `TS7031` × 8 | +| sandbox-sdk | 16 | 0 | `TS18046` × 8, `TS2307` × 8 | +| soybean-admin | 1 | 1 | `no-unnecessary-type-assertion` × 1 | +| soybean-admin-antd | 1 | 1 | `no-unnecessary-type-assertion` × 1 | +| svelte-multiselect | 3 | 0 | `TS18046` × 3 | +| tiptap | 3 | 3 | `TS2339` × 2, `TS2741` × 1 | +| trpc-nuxt | 7 | 0 | `TS2307` × 6, `TS2339` × 1 | +| unplugin-turbo-console | 3 | 0 | `TS2307` × 1, `TS2552` × 1, `TS7016` × 1 | +| varlet | 9 | 1 | `TS2307` × 4, `TS2322` × 1, `TS7026` × 4 | +| vean-ui | 41 | 47 | `TS2322` × 1, `no-unnecessary-type-assertion` × 28, `no-unsafe-type-assertion` × 12 | +| vue-core | 12 | 2 | `TS2305` × 3, `TS2322` × 2, `TS2353` × 1, `TS5115` × 1, `TS7006` × 2, `TS7031` × 3 | +| yaak | 16 | 1 | `TS1479` × 1, `TS2300` × 14, `TS2345` × 1 | + +## Method and coverage limits + +Each control runs these four workloads. The lint commands also use +`--format json --threads 4` for structured comparison: + +```sh +vp lint +vp lint --type-aware +vp lint --type-aware --type-check +vp check +``` + +Available declared type-check or compile tasks run through `vp run --no-cache` in +34 repositories. Compiler build-info files outside `node_modules` are cleared +before each task control and restored afterwards. Task names and results appear +in the full table. Type-aware lint alone can still type-check when the project +configuration already enables `typeCheck`. + +Process traces observe the expected backend launchers or native processes in both +explicit type-check controls for 82 repositories. Native processes are sampled +under both controls for 80. In three control runs across svelte-meta-tags and +sveltekit-embed, sampling captures only the launcher. The following two shared +configuration failures prevent backend coverage: + +- **brazilian-utils-javascript:** the configuration imports + `vite-plus/test/browser-webdriverio`, which this preview does not export. +- **sigle:** its `effecttsgo` plugin requires `@effect/tsgo` binary patching. + The pinned `@effect/tsgo@0.52.0` supports Oxlint through `1.87.0` and tsgolint + through `7.0.2004`. Both unpatched controls reject the plugin with the tested + `1.93.0` / `7.0.2006` versions. The patched Effect integration remains unverified. + +Formatting stops `vp check` before lint in 30 Go-control runs. The independent +lint and explicit type-check workloads provide coverage for those projects. +No retained command times out or exits from a signal. Every fork remains at its +recorded commit with unchanged tracked sources and lockfile hashes. + +The setup preserves these details: + +- Installs use committed lockfiles where available. Matterviz and + svelte-multiselect have no committed lockfile; both controls reuse the same + one-time installation. Required generators run separately during preparation. +- The preview is linked into workspace importers and nested pnpm peer importers. + CLI and native executable hashes match across the isolated preview copies. +- Notero retains its original `@types/node@25.6.0` peer. Nostalgist reuses its + installed `@types/chai@5.2.3` to avoid introducing duplicate Chai declarations. +- Temporary `vite-plus` dependency declarations are needed for dotabod-backend, + dotabod-frontend, and oxlint-tailwindcss. The two Dotabod projects also need + temporary Vite wrappers around their existing standalone lint/format configs. + All changes are restored. Their custom baseline-filtering scripts are outside + these four primary workloads. +- Hono keeps its six configured lint scopes. Nostalgist uses `src` and `tests`; + docx-editor uses `packages`, `shared`, `scripts`, `apps/cli`, `apps/mcp`, and + `apps/vscode-ext`; digital-signature uses `*.ts`, `*.js`, and `src/`. + Dotabod-frontend disables nested config and ignores `supabase/functions/**`. + Vinext uses sorted checker scheduling under both controls. + +This local sweep does not cover other platforms or every application build and +test. Raw commands, output, dependency receipts, backend traces, diagnostic +differences, and compiler audits are retained outside this repository. + +## Separate Ecosystem CI results + +The PR-head [Ecosystem CI run](https://github.com/voidzero-dev/vite-plus/actions/runs/43187905687) +passes all **51 project/platform jobs across 31 repositories**. This is the +repository's configured Linux/Windows matrix, with its own inputs and workloads. +It does not establish that all 83 catalog forks pass `vp check`. + +The five earlier trust-policy failures now pass: dify on Linux, videojs-v10 on +Linux and Windows, and nuxt-devtools on Linux and Windows. Migration retains +`trustPolicy: no-downgrade` and adds only the reviewed +`tsrs@0.25.0-ts7.1.0-beta` exception. + +## Earlier reduced-case findings + +The [earlier report](https://github.com/voidzero-dev/vite-plus/blob/25a7c006af2db834bfc66f9689b02de1060f580f/bench/tsrs-lint-ecosystem.md#exact-upstream-attribution-2026-11-08) +preserves the original 14-repository experiment and seven reduced reproductions. +It attributes four categories to the pinned TypeScript `7.1` behavior: +default-destructuring contextual typing, conditional return types, selector-type +recursion, and the DOM `Notification.maxActions` requirement. Those historical +compiler audits are distinct from this complete `0.25.0` preview sweep and its +six pass-to-fail cases. diff --git a/bench/tsrs-lint.md b/bench/tsrs-lint.md new file mode 100644 index 0000000000..1cc4d0ea33 --- /dev/null +++ b/bench/tsrs-lint.md @@ -0,0 +1,127 @@ +# Type-aware linting with tsrs-lint + +The timing and memory results below were measured locally on **2026-11-13** with +the latest published [PR #3352 preview](https://github.com/voidzero-dev/vite-plus/actions/runs/43194215156), +`0.0.0-commit.25a7c006af2db834bfc66f9689b02de1060f580f`. +Both controls use the same published CLI and Oxlint `1.93.0`: the Go control uses +`oxlint-tsgolint@7.0.2006`, and the default preview uses `tsrs@0.25.0-ts7.1.0-beta`. +The subsequent source commit `68eb84008` changes only reports. + +All 12 validation commands and 204 benchmark invocations pass. The backends have +matching checked-file counts and no diagnostics on these two inputs. The separate +[full ecosystem comparison](./tsrs-lint-ecosystem.md) documents compatibility +differences on other projects; these performance results do not establish compiler parity. + +## Elapsed time + +Each cell shows the median of **10 runs**, followed by the interquartile range +(25th–75th percentile) in brackets. One warm-up per backend is excluded. +Each run starts a fresh CLI process. Backend order alternates between pairs. +Timing runs do not sample memory, and all benchmark commands run sequentially. + +| Project | Workload | Go (s) | tsrs (s) | Reduction | +| ------- | -------------------------------- | ------------------: | ------------------: | --------: | +| Vite+ | Type-aware rules | 1.518 [1.489–1.654] | 1.371 [1.255–1.399] | 9.7% | +| Vite+ | Rules and TypeScript diagnostics | 1.861 [1.820–1.928] | 1.410 [1.316–1.504] | 24.2% | +| Vite+ | Full `vp check` | 2.112 [2.102–2.243] | 1.674 [1.646–1.795] | 20.8% | +| vinext | Type-aware rules | 2.677 [2.616–2.791] | 1.572 [1.533–1.656] | 41.3% | +| vinext | Rules and TypeScript diagnostics | 3.364 [3.296–3.454] | 1.695 [1.635–1.728] | 49.6% | +| vinext | Full `vp check` | 4.680 [4.563–4.731] | 2.178 [2.095–2.184] | 53.4% | + +## Peak memory + +Each cell shows the median of **5 separate runs** and its interquartile range. +One warm-up per backend is excluded. Memory is the sampled peak of the simultaneous +sum of RSS for the CLI and its descendants, including Node, Oxlint, and the native +backend. The sampler itself is excluded. One GiB is 1,073,741,824 bytes. + +| Project | Workload | Go (GiB) | tsrs (GiB) | Reduction | +| ------- | -------------------------------- | ------------------: | ------------------: | --------: | +| Vite+ | Type-aware rules | 1.103 [1.047–1.153] | 0.633 [0.601–0.647] | 42.6% | +| Vite+ | Rules and TypeScript diagnostics | 1.276 [1.258–1.462] | 0.651 [0.644–0.670] | 49.0% | +| Vite+ | Full `vp check` | 1.325 [1.269–1.332] | 0.706 [0.687–0.715] | 46.7% | +| vinext | Type-aware rules | 3.427 [3.407–3.632] | 1.852 [1.836–1.889] | 46.0% | +| vinext | Rules and TypeScript diagnostics | 3.694 [3.662–3.904] | 1.967 [1.956–1.977] | 46.8% | +| vinext | Full `vp check` | 5.014 [4.756–5.391] | 2.102 [2.090–2.102] | 58.1% | + +The sampler sleeps for 20 ms; the median observed interval is 38.8 ms +(the median of the measured runs' sampling intervals). Shared pages can count more +than once, and sampling can miss short peaks. These are process-tree RSS values, +not total allocations or macOS physical-footprint measurements. + +## Inputs and method + +| Input | Pin / condition | +| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | +| Machine | Apple M4 Max, 16 CPU cores, 48 GiB RAM, macOS `27.0.1`, ARM64 | +| Vite+ source | [`68eb84008`](https://github.com/voidzero-dev/vite-plus/tree/68eb84008fd27a72a5e9426ad64636432385b0e1), Node.js `24.22.2` | +| vinext source | [`7f1feb134`](https://github.com/fengmk2/vinext/tree/7f1feb134255f9e816e1b03760b97c10148eed3c), Node.js `26.13.0` | +| Lint threads | `4` for both lint workloads | +| Full check threads | Default `16`, confirmed in format and lint output | +| Repetitions per project/workload/backend | 10 timing runs, 5 memory runs, and one warm-up before each set | + +The three workloads are: + +```sh +vp lint --type-aware --threads 4 --format json -c +vp lint --type-aware --type-check --threads 4 --format json +vp check +``` + +The temporary rules-only JSON contains the project's `lint` section with +`options.typeCheck` set to `false`; all rules, plugins, and ignore patterns +are retained. Vinext lint uses sorted checker scheduling under both controls. +Full checks and Vite+ lint use default checker scheduling. + +Both controls use the same source and installed dependencies. Vite+ retains its +own workspace dependency links; the measured CLI, Oxlint, and native backends +come from the published preview. Vinext links the preview CLI and core into its +workspace and pnpm peer importers. These temporary links are restored afterwards. +All commands use isolated temporary `VP_HOME` directories and disable the Node +compile cache. Setup, configuration extraction, and output validation are excluded +from the timed intervals; CLI startup and command execution are included. + +Native executable paths are verified in every validation and memory run. Package +versions, executable hashes, source commits, and lockfile hashes are recorded. +Tracked project sources and lockfiles are unchanged after measurement. + +| Project | Linted files | Rules | Formatted files in `vp check` | +| ------- | -----------: | ----: | ----------------------------: | +| Vite+ | 387 | 174 | 675 | +| vinext | 1,842 | 151 | 4,755 | + +## Interpretation and reproducibility + +This is a local desktop measurement with warm filesystem caches, not a cold-cache +or isolated-server result. CPU use measured for 0.5 seconds before each timed run +has a median of 12.4% across the machine (range 7.2%–23.9%). +The CPU frequency is not fixed. Compare the paired backends within each row; +the interquartile ranges describe sample spread, not confidence intervals. + +The vinext input contains both the Sentry fixture exclusion from its draft PR +and the upstream [`prefer-shared-utils` optimization](https://github.com/cloudflare/vinext/commit/5a6cc15f39ca7e93bb1892efdcf2d3d01aa1b562). +Its original plugin rules remain enabled. These source inputs, the hardware, +thread counts, and tool versions differ from the earlier Codespaces measurements. +Changes in absolute times or reduction percentages cannot be attributed solely +to the tsrs version update. + +All measured samples are retained; there is no outlier filtering. Reductions use +`100 * (1 - tsrs median / Go median)`. Raw samples, commands, memory traces, +dependency receipts, and the local runner remain outside the repository. + +## Historical installed disk footprint + +The following disk measurements retain the **2026-11-06 Linux x64** comparison +of published `1.5.0` and the `0.22.0` preview. They are not measurements of the +current macOS preview. The [original report](https://github.com/voidzero-dev/vite-plus/blob/68eb84008fd27a72a5e9426ad64636432385b0e1/bench/tsrs-lint.md#installed-disk-footprint) +contains the installation method, package inventory scope, and lockfile hashes. + +| Scope | Go (MiB) | tsrs (MiB) | Increase | +| --------------------------------------------- | -------: | ---------: | -------: | +| Complete `node_modules`, on disk | 132.93 | 150.87 | 13.5% | +| Complete `node_modules`, regular-file lengths | 123.39 | 139.27 | 12.9% | +| Backend packages, on disk | 21.63 | 39.69 | 83.5% | + +One MiB is 1,048,576 bytes. The total installation comparison includes other +release-to-preview dependency changes; the backend row isolates the backend +packages. These disk figures do not describe runtime memory use. diff --git a/bench/tsrs-lint.ts b/bench/tsrs-lint.ts new file mode 100644 index 0000000000..2b234afe16 --- /dev/null +++ b/bench/tsrs-lint.ts @@ -0,0 +1,95 @@ +// 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 { existsSync } from 'node:fs'; +import { createRequire } from 'node:module'; +import { fileURLToPath } from 'node:url'; + +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(import.meta.url); +const tsgolint = + process.platform === 'win32' + ? ['tsgolint.exe', 'tsgolint.cmd'] + .map((bin) => fileURLToPath(new URL(`../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', + '--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/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 diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_monorepo_pnpm/pnpm-workspace.yaml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_monorepo_pnpm/pnpm-workspace.yaml index b8b7c1b4f9..9705df5dbb 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_monorepo_pnpm/pnpm-workspace.yaml +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_monorepo_pnpm/pnpm-workspace.yaml @@ -11,3 +11,7 @@ catalog: oxlint-tsgolint: ^1.0.0 minimumReleaseAge: 1440 +trustPolicy: no-downgrade +trustPolicyExclude: + # Keep the project's existing exception. + - testnpm2@1.0.0 diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_monorepo_pnpm/snapshots/migration_monorepo_pnpm.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_monorepo_pnpm/snapshots/migration_monorepo_pnpm.md index 9b74a136c7..f795cd2d28 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_monorepo_pnpm/snapshots/migration_monorepo_pnpm.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_monorepo_pnpm/snapshots/migration_monorepo_pnpm.md @@ -130,6 +130,13 @@ catalog: vite-plus: minimumReleaseAge: 1440 +trustPolicy: no-downgrade +trustPolicyExclude: + # Keep the project's existing exception. + - testnpm2@1.0.0 + # Reviewed OIDC release; the bootstrap placeholder used staged publishing. + # https://github.com/oxc-project/tsrs/issues/945 + - tsrs@0.25.0-ts7.1.0-beta overrides: vite@*: 'catalog:' vitest@*: 'catalog:' @@ -147,6 +154,8 @@ minimumReleaseAgeExclude: - '@oxlint/*' - oxlint-tsgolint - '@oxlint-tsgolint/*' + - tsrs + - '@tsrs/*' - oxfmt - '@oxfmt/*' - vitest diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxlint_extends/snapshots.toml b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxlint_extends/snapshots.toml index 762a835758..81ebc4143c 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxlint_extends/snapshots.toml +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxlint_extends/snapshots.toml @@ -38,11 +38,11 @@ steps = [ { argv = ["vpt", "write-file", "type-error.ts", "export const value: string = 1;\n"], snapshot = false }, { argv = ["vpt", "write-file", "type-aware.ts", "Promise.resolve(1);\n"], snapshot = false }, { argv = ["vp", "lint", "--threads=1", "-c", ".oxlintrc.json", "type-error.ts"], comment = "Inherited typeCheck reports the original TypeScript diagnostic", continue-on-failure = true }, - { argv = ["vp", "lint", "--threads=1", "-c", ".oxlintrc.json", "type-aware.ts"], comment = "Inherited typeAware enables no-floating-promises", continue-on-failure = true }, + { argv = ["vp", "lint", "--threads=1", "-c", ".oxlintrc.json", "type-aware.ts"], comment = "Inherited options enable no-floating-promises and report type errors outside the lint input", continue-on-failure = true }, { argv = ["vp", "migrate", "--no-interactive", "--no-agent", "--no-editor", "--no-hooks"] }, { argv = ["vpt", "print-file", "vite.config.ts"], comment = "Local options stay empty so inherited settings remain live" }, { argv = ["vp", "lint", "--threads=1", "type-error.ts"], comment = "Imported typeCheck produces the same diagnostic and exit code", continue-on-failure = true }, - { argv = ["vp", "lint", "--threads=1", "type-aware.ts"], comment = "Imported typeAware produces the same diagnostic and exit code", continue-on-failure = true }, + { argv = ["vp", "lint", "--threads=1", "type-aware.ts"], comment = "Imported options preserve both diagnostics and the exit code", continue-on-failure = true }, { argv = ["vpt", "replace-file-content", "shared/base.ts", "typeAware: true", "typeAware: false"] }, { argv = ["vpt", "replace-file-content", "shared/base.ts", "typeCheck: true", "typeCheck: false"] }, { argv = ["vp", "lint", "--threads=1", "type-error.ts"], comment = "Disabling the shared options removes the TypeScript diagnostic" }, diff --git a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxlint_extends/snapshots/migration_oxlint_extends_type_options.md b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxlint_extends/snapshots/migration_oxlint_extends_type_options.md index dd4f60cfa4..6fe47a0368 100644 --- a/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxlint_extends/snapshots/migration_oxlint_extends_type_options.md +++ b/crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/migration_oxlint_extends/snapshots/migration_oxlint_extends_type_options.md @@ -37,7 +37,7 @@ Finished in on 1 file with rules using threads. ## `vp lint --threads=1 -c .oxlintrc.json type-aware.ts` -Inherited typeAware enables no-floating-promises +Inherited options enable no-floating-promises and report type errors outside the lint input **Exit code:** 1 @@ -51,7 +51,13 @@ Inherited typeAware enables no-floating-promises ╰──── help: The promise must end with a call to .catch, or end with a call to .then with a rejection handler, or be explicitly marked as ignored with the `void` operator. -Found 0 warnings and 1 error. + × typescript(TS2322): Type 'number' is not assignable to type 'string'. + ╭─[type-error.ts:1:14] + 1 │ export const value: string = 1; + · ───── + ╰──── + +Found 0 warnings and 2 errors. Finished in on 1 file with rules using threads. ``` @@ -114,7 +120,7 @@ Finished in on 1 file with rules using threads. ## `vp lint --threads=1 type-aware.ts` -Imported typeAware produces the same diagnostic and exit code +Imported options preserve both diagnostics and the exit code **Exit code:** 1 @@ -128,7 +134,13 @@ Imported typeAware produces the same diagnostic and exit code ╰──── help: The promise must end with a call to .catch, or end with a call to .then with a rejection handler, or be explicitly marked as ignored with the `void` operator. -Found 0 warnings and 1 error. + × typescript(TS2322): Type 'number' is not assignable to type 'string'. + ╭─[type-error.ts:1:14] + 1 │ export const value: string = 1; + · ───── + ╰──── + +Found 0 warnings and 2 errors. Finished in on 1 file with rules using threads. ``` 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/.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/docs/guide/check.md b/docs/guide/check.md index 02ea53d077..da68affd38 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). 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 211008821e..08fa54e07a 100644 --- a/docs/guide/lint.md +++ b/docs/guide/lint.md @@ -49,7 +49,9 @@ 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`. +Vite+ 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`. Upgrading Vite+ enables this backend without a separate installation or configuration change. + +With `typeCheck: true`, compiler checks cover the TypeScript programs selected by the lint input files. Lint ignore patterns only exclude lint rules; use `include` and `exclude` in `tsconfig.json` to set the compiler scope. Files imported by the program can still be checked even when they match `exclude`. ## JS Plugins diff --git a/docs/guide/migrate-rules.md b/docs/guide/migrate-rules.md index 3101d3f5dd..a883f113fb 100644 --- a/docs/guide/migrate-rules.md +++ b/docs/guide/migrate-rules.md @@ -381,6 +381,12 @@ Migration converts the Volta `volta.node` pin to `.node-version`, because Vite+ **Other rules.** +- If `pnpm-workspace.yaml` enables `trustPolicy: no-downgrade`, migration adds + `tsrs@0.25.0-ts7.1.0-beta` to `trustPolicyExclude`. This reviewed release uses + trusted publishing with provenance, but pnpm ranks its staged bootstrap + release higher ([upstream issue](https://github.com/oxc-project/tsrs/issues/945)). + Existing exceptions are preserved. Other packages and future tsrs versions + remain subject to the policy. Run `vp migrate` if this policy blocks an upgrade. - Packages that use `vite-plus` without a direct `vite` declaration keep that layout (see [Vite and Overrides](#vite-and-overrides)). - Unrelated selector-shaped and object-valued overrides are preserved. diff --git a/package.json b/package.json index fd4ce34eb3..ccba8f90ed 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "@types/node": "catalog:", "@typescript/native-preview": "catalog:", "@voidzero-dev/vite-plus-tools": "workspace:*", + "oxlint-tsgolint": "catalog:", "playwright": "catalog:", "typescript": "catalog:", "vite": "catalog:", diff --git a/packages/cli/package.json b/packages/cli/package.json index bbefe97f75..4110dfb271 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:" }, 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/init-config.ts b/packages/cli/src/init-config.ts index d687ff70f9..eae56746a3 100644 --- a/packages/cli/src/init-config.ts +++ b/packages/cli/src/init-config.ts @@ -224,7 +224,7 @@ export async function applyToolInitConfigToViteConfig( const lintInitConfigPath = path.join(projectPath, '.vite-plus-lint-init.oxlintrc.json'); const { fixBaseUrlInTsconfig, hasBaseUrlInTsconfig } = await import('./utils/tsconfig.ts'); await fixBaseUrlInTsconfig(projectPath); - // Skip typeAware/typeCheck when tsconfig still has baseUrl (unsupported by tsgolint) + // Skip typeAware/typeCheck when tsconfig still has baseUrl (unsupported by TypeScript 7) const hasBaseUrl = hasBaseUrlInTsconfig(projectPath); const initConfig = createDefaultVitePlusLintConfig({ includeTypeAwareDefaults: !hasBaseUrl, diff --git a/packages/cli/src/migration/__tests__/migrator.spec.ts b/packages/cli/src/migration/__tests__/migrator.spec.ts index 58bebeebd0..97fa64b689 100644 --- a/packages/cli/src/migration/__tests__/migrator.spec.ts +++ b/packages/cli/src/migration/__tests__/migrator.spec.ts @@ -4480,6 +4480,8 @@ describe('ensureVitePlusBootstrap', () => { // Vite+ pins a possibly-fresh vitest; its family must bypass the gate. expect(workspace.minimumReleaseAgeExclude).toContain('vitest'); expect(workspace.minimumReleaseAgeExclude).toContain('@vitest/*'); + expect(workspace.minimumReleaseAgeExclude).toContain('tsrs'); + expect(workspace.minimumReleaseAgeExclude).toContain('@tsrs/*'); // The user's own entry and the vite-plus/ox families are preserved. expect(workspace.minimumReleaseAgeExclude).toContain('my-internal-pkg'); expect(workspace.minimumReleaseAgeExclude).toContain('vite-plus'); @@ -4515,6 +4517,77 @@ describe('ensureVitePlusBootstrap', () => { expect(workspace.minimumReleaseAgeExclude).toBeUndefined(); }); + it('reconciles the reviewed tsrs exception without disabling pnpm trust checks', () => { + fs.writeFileSync( + path.join(tmpDir, 'package.json'), + JSON.stringify({ + name: 'test', + devDependencies: { 'vite-plus': 'latest' }, + packageManager: 'pnpm@11.22.0', + }), + ); + const workspaceInfo = makeWorkspaceInfo(tmpDir, PackageManager.pnpm); + ensureVitePlusBootstrap(workspaceInfo); + expect(detectVitePlusBootstrapPending(tmpDir, PackageManager.pnpm)).toBe(false); + + const workspacePath = path.join(tmpDir, 'pnpm-workspace.yaml'); + fs.appendFileSync( + workspacePath, + '\ntrustPolicy: no-downgrade\ntrustPolicyExclude:\n # Keep the project exception.\n - example@1.0.0\n', + ); + expect(detectVitePlusBootstrapPending(tmpDir, PackageManager.pnpm)).toBe(true); + expect(ensureVitePlusBootstrap(workspaceInfo).changed).toBe(true); + + const workspace = readYamlObject(workspacePath); + expect(workspace.trustPolicy).toBe('no-downgrade'); + expect(workspace.trustPolicyExclude).toEqual(['example@1.0.0', 'tsrs@0.25.0-ts7.1.0-beta']); + expect(workspace.trustLockfile).toBeUndefined(); + const after = fs.readFileSync(workspacePath, 'utf-8'); + expect(after).toContain('# Keep the project exception.'); + expect(after).toContain('https://github.com/oxc-project/tsrs/issues/945'); + expect(detectVitePlusBootstrapPending(tmpDir, PackageManager.pnpm)).toBe(false); + expect(ensureVitePlusBootstrap(workspaceInfo).changed).toBe(false); + expect(fs.readFileSync(workspacePath, 'utf-8')).toBe(after); + }); + + it.each(['', 'trustPolicy: off\n'])( + 'leaves trust exceptions unchanged without no-downgrade: %j', + (policy) => { + fs.writeFileSync( + path.join(tmpDir, 'package.json'), + JSON.stringify({ name: 'test', devDependencies: { 'vite-plus': 'latest' } }), + ); + const workspacePath = path.join(tmpDir, 'pnpm-workspace.yaml'); + fs.writeFileSync(workspacePath, `${policy}trustPolicyExclude:\n - example@1.0.0\n`); + + ensureVitePlusBootstrap(makeWorkspaceInfo(tmpDir, PackageManager.pnpm)); + + const workspace = readYamlObject(workspacePath); + expect(workspace.trustPolicyExclude).toEqual(['example@1.0.0']); + expect(workspace.trustPolicy).toBe(policy ? 'off' : undefined); + }, + ); + + it.each(['tsrs', 'tsrs@0.25.0-ts7.1.0-beta'])( + 'preserves an existing tsrs trust exception: %s', + (exception) => { + fs.writeFileSync( + path.join(tmpDir, 'package.json'), + JSON.stringify({ name: 'test', devDependencies: { 'vite-plus': 'latest' } }), + ); + const workspacePath = path.join(tmpDir, 'pnpm-workspace.yaml'); + fs.writeFileSync( + workspacePath, + `trustPolicy: no-downgrade\ntrustPolicyExclude:\n - ${exception}\n`, + ); + + ensureVitePlusBootstrap(makeWorkspaceInfo(tmpDir, PackageManager.pnpm)); + + expect(readYamlObject(workspacePath).trustPolicyExclude).toEqual([exception]); + expect(detectVitePlusBootstrapPending(tmpDir, PackageManager.pnpm)).toBe(false); + }, + ); + it('merges the vitest family into an existing yarn npmPreapprovedPackages list', () => { fs.writeFileSync( path.join(tmpDir, 'package.json'), diff --git a/packages/cli/src/migration/migrator/catalog.ts b/packages/cli/src/migration/migrator/catalog.ts index 76c5e8b932..1e76446ffd 100644 --- a/packages/cli/src/migration/migrator/catalog.ts +++ b/packages/cli/src/migration/migrator/catalog.ts @@ -69,11 +69,24 @@ const PNPM_MINIMUM_RELEASE_AGE_EXCLUDES = [ '@oxlint/*', 'oxlint-tsgolint', '@oxlint-tsgolint/*', + 'tsrs', + '@tsrs/*', 'oxfmt', '@oxfmt/*', ...VITEST_AGE_GATE_EXEMPT_PACKAGES, ] as const; +// The bootstrap placeholder used staged publishing; the real release uses +// GitHub trusted publishing with provenance. pnpm ranks the former higher. +// Keep this reviewed exception version-specific: dependency updates must not +// automatically exempt future releases or the @tsrs/* platform packages. +// https://github.com/oxc-project/tsrs/issues/945 +const PNPM_TSRS_TRUST_POLICY_EXCLUDE = 'tsrs@0.25.0-ts7.1.0-beta'; + +function hasTsrsTrustPolicyException(excludes: readonly unknown[]): boolean { + return excludes.includes('tsrs') || excludes.includes(PNPM_TSRS_TRUST_POLICY_EXCLUDE); +} + // Whether `version` is at least `minVersion`. A non-coercible version passes // only when it is one of the given dist-tags, which always point at a current // (feature-capable) release. @@ -452,6 +465,22 @@ export function rewritePnpmWorkspaceYaml( } doc.setIn(['minimumReleaseAgeExclude'], minimumReleaseAgeExclude); } + + if (doc.get('trustPolicy') === 'no-downgrade') { + const node = doc.get('trustPolicyExclude'); + if (node != null && !(node instanceof YAMLSeq)) { + throw new Error('pnpm-workspace.yaml: trustPolicyExclude must be a sequence'); + } + const excludes = node ?? new YAMLSeq>(); + if (!hasTsrsTrustPolicyException(excludes.toJSON())) { + const entry = scalarString(PNPM_TSRS_TRUST_POLICY_EXCLUDE); + entry.commentBefore = + ' Reviewed OIDC release; the bootstrap placeholder used staged publishing.\n' + + ' https://github.com/oxc-project/tsrs/issues/945'; + excludes.add(entry); + doc.set('trustPolicyExclude', excludes); + } + } }); } @@ -1406,6 +1435,25 @@ export function pnpmWorkspaceMinimumReleaseAgeExemptionsPending(projectPath: str return PNPM_MINIMUM_RELEASE_AGE_EXCLUDES.some((exclude) => !existing.has(exclude)); } +// Reconcile the exception even when the project already uses Vite+ and its +// other migration settings are current. Mirror the writer's policy gate. +export function pnpmWorkspaceTsrsTrustPolicyExceptionPending(projectPath: string): boolean { + const workspacePath = path.join(projectPath, 'pnpm-workspace.yaml'); + if (!fs.existsSync(workspacePath)) { + return false; + } + const doc = readYamlFile(workspacePath) as { + trustPolicy?: unknown; + trustPolicyExclude?: unknown; + } | null; + return ( + doc?.trustPolicy === 'no-downgrade' && + !hasTsrsTrustPolicyException( + Array.isArray(doc.trustPolicyExclude) ? doc.trustPolicyExclude : [], + ) + ); +} + export function readPnpmWorkspacePeerDependencyRules( projectPath: string, ): { allowAny?: string[]; allowedVersions?: Record } | undefined { diff --git a/packages/cli/src/migration/migrator/vite-config.ts b/packages/cli/src/migration/migrator/vite-config.ts index 96e6e6d46f..0cb6cc12ed 100644 --- a/packages/cli/src/migration/migrator/vite-config.ts +++ b/packages/cli/src/migration/migrator/vite-config.ts @@ -418,7 +418,7 @@ function mergeOxlintConfigFile( Object.assign(inheritedOptions, layer.options); } oxlintJson.options ||= {}; - // Skip typeAware/typeCheck when tsconfig.json has baseUrl (unsupported by tsgolint) + // Skip typeAware/typeCheck when tsconfig.json has baseUrl (unsupported by TypeScript 7) if (!hasBaseUrlInTsconfig(projectPath)) { if (inheritedOptions.typeAware === undefined) { oxlintJson.options.typeAware = true; diff --git a/packages/cli/src/migration/migrator/vite-plus-bootstrap.ts b/packages/cli/src/migration/migrator/vite-plus-bootstrap.ts index 5a4197d669..14def720cb 100644 --- a/packages/cli/src/migration/migrator/vite-plus-bootstrap.ts +++ b/packages/cli/src/migration/migrator/vite-plus-bootstrap.ts @@ -34,6 +34,7 @@ import { supportsCatalog, bunSupportsCatalog, pnpmWorkspaceMinimumReleaseAgeExemptionsPending, + pnpmWorkspaceTsrsTrustPolicyExceptionPending, projectUsesVitestDirectly, pruneLegacyWrapperAliases, readBunCatalogDependencyResolver, @@ -789,7 +790,8 @@ export function detectVitePlusBootstrapPending( readPnpmWorkspacePeerDependencyRules(projectPath), usesVitest, ) || - pnpmWorkspaceMinimumReleaseAgeExemptionsPending(projectPath) + pnpmWorkspaceMinimumReleaseAgeExemptionsPending(projectPath) || + pnpmWorkspaceTsrsTrustPolicyExceptionPending(projectPath) ); } @@ -1194,6 +1196,7 @@ export function ensureVitePlusBootstrap( ecosystemCatalogReferencesPending || !pnpmWorkspaceExoticSubdepsSettingSatisfied(projectPath) || pnpmWorkspaceMinimumReleaseAgeExemptionsPending(projectPath) || + pnpmWorkspaceTsrsTrustPolicyExceptionPending(projectPath) || workspaceCatalogVitePlusDependencyPending( projectPath, workspaceInfo.packages, 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/utils/tsconfig.ts b/packages/cli/src/utils/tsconfig.ts index 6da34c4b73..dfca62356b 100644 --- a/packages/cli/src/utils/tsconfig.ts +++ b/packages/cli/src/utils/tsconfig.ts @@ -14,7 +14,7 @@ export type BaseUrlFixStatus = 'not-needed' | 'fixed' | 'declined' | 'failed'; /** * Check if tsconfig.json has compilerOptions.baseUrl set. - * oxlint's TypeScript checker (tsgolint) does not support baseUrl, + * Oxlint's TypeScript 7 backend does not support baseUrl, * so typeAware/typeCheck must be disabled when it is present. */ export function hasBaseUrlInTsconfigFile(filePath: string): boolean { 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/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 } } 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 023b9c79c7..3665918e1c 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.25.0-ts7.1.0-beta' + version: 0.25.0-ts7.1.0-beta typescript: specifier: ~7.0.2 version: 7.0.2 @@ -409,6 +412,9 @@ importers: '@voidzero-dev/vite-plus-tools': specifier: workspace:* version: link:packages/tools + oxlint-tsgolint: + specifier: 'catalog:' + version: 7.0.2006 playwright: specifier: 'catalog:' version: 1.62.1 @@ -463,12 +469,12 @@ importers: oxlint: specifier: 'catalog:' version: 1.93.0(oxlint-tsgolint@7.0.2006)(vite-plus@packages+cli) - oxlint-tsgolint: - specifier: 'catalog:' - version: 7.0.2006 playwright: specifier: '*' version: 1.62.1 + tsrs: + specifier: 'catalog:' + version: 0.25.0-ts7.1.0-beta vite: specifier: workspace:@voidzero-dev/vite-plus-core@* version: link:../core @@ -3576,6 +3582,58 @@ packages: peerDependencies: tsdown: 0.23.4 + '@tsrs/darwin-arm64@0.25.0-ts7.1.0-beta': + resolution: {integrity: sha512-+pYoQ+AqpdUolO4kJBQlQmwxJOoc5INuusw/2AZnR4frfnyVGza+gEn0br8Z0LOk7nklQuILuMKH93BU3hpHvA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [darwin] + + '@tsrs/darwin-x64@0.25.0-ts7.1.0-beta': + resolution: {integrity: sha512-MnD5Bv48TdIcTpNavsg7PV6RUuwAKhozJ29CZDlKq6oZwGjGulcQLI3Sok6MvvCG7U6v4l9RONKO8BhNXCytvQ==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [darwin] + + '@tsrs/linux-arm64-gnu@0.25.0-ts7.1.0-beta': + resolution: {integrity: sha512-Mc/YuFQa7APDuX39UMr8NMQHKkCOmEx8SfUXVnew7eljJHKnUkpLHpoQFQA0aKnzIqDAxnNudXhIWB0DjZEX2A==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@tsrs/linux-arm64-musl@0.25.0-ts7.1.0-beta': + resolution: {integrity: sha512-ZTZxIv/W4qiqDBCXcDlr8ma76JCO0DQHbUukuhpSpKHz3E2m5FCwLcra/NTqAVniyLU6/RjIOCIcDRpVQvq6cQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@tsrs/linux-x64-gnu@0.25.0-ts7.1.0-beta': + resolution: {integrity: sha512-dGRvubxWQXNP3aow+RLgFAbW3pR4oQ+UPfwrSgdggKCKKL9sAPr8nFGYM50JWAiqGQYSwRJl48AzIC7bHuQ2cQ==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@tsrs/linux-x64-musl@0.25.0-ts7.1.0-beta': + resolution: {integrity: sha512-zm89HYpXAv36XuXrEsfxAEmGTehY5K0L27+tFmJzxX2CqLwki0AX4FGA78Fy97x8Yk8kOR8FJO2xTvSW9vXBIg==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@tsrs/win32-arm64@0.25.0-ts7.1.0-beta': + resolution: {integrity: sha512-fVqAnHvFD2vxIHxVJEeQq9W63QZUW6WLy1+qFVuMUdM5ViURhjzcuLgOx8EIGqp6qOqFDpuAlNPQmjUpTXYg2g==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [win32] + + '@tsrs/win32-x64@0.25.0-ts7.1.0-beta': + resolution: {integrity: sha512-BfRCSI5E0LQ3kim43dBqX3Dq3dmRUI2aMWLGcgUxbTEkyvsESX01tM7u31flzRRajw3zVZwVB95kh5xvUeIOkA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [win32] + '@tybys/wasm-util@0.10.3': resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} @@ -6663,6 +6721,11 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tsrs@0.25.0-ts7.1.0-beta: + resolution: {integrity: sha512-J7eAkkriIwHExX2TzwTW6flJ/fmg3U+FDQwmTUkvaYmo02iva69YPmxFaCXGYIlsED3l1Aa62hSsEDJewBrIKA==} + engines: {node: '>=16.20.0'} + hasBin: true + tsx@4.23.15: resolution: {integrity: sha512-Yiex1Ovn8z2xPpOWckIiysV1SSyRMY9BkLF++q0yKiDxCqRhosKfMg3janKkiLBwZ5c/YryloKwGZcrEmtwxKw==} engines: {node: '>=18.0.0'} @@ -8955,6 +9018,30 @@ 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.2(crossws@0.4.17(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.25.0-ts7.1.0-beta': + optional: true + + '@tsrs/darwin-x64@0.25.0-ts7.1.0-beta': + optional: true + + '@tsrs/linux-arm64-gnu@0.25.0-ts7.1.0-beta': + optional: true + + '@tsrs/linux-arm64-musl@0.25.0-ts7.1.0-beta': + optional: true + + '@tsrs/linux-x64-gnu@0.25.0-ts7.1.0-beta': + optional: true + + '@tsrs/linux-x64-musl@0.25.0-ts7.1.0-beta': + optional: true + + '@tsrs/win32-arm64@0.25.0-ts7.1.0-beta': + optional: true + + '@tsrs/win32-x64@0.25.0-ts7.1.0-beta': + optional: true + '@tybys/wasm-util@0.10.3': dependencies: tslib: 2.8.1 @@ -12125,6 +12212,17 @@ snapshots: tslib@2.8.1: {} + tsrs@0.25.0-ts7.1.0-beta: + optionalDependencies: + '@tsrs/darwin-arm64': 0.25.0-ts7.1.0-beta + '@tsrs/darwin-x64': 0.25.0-ts7.1.0-beta + '@tsrs/linux-arm64-gnu': 0.25.0-ts7.1.0-beta + '@tsrs/linux-arm64-musl': 0.25.0-ts7.1.0-beta + '@tsrs/linux-x64-gnu': 0.25.0-ts7.1.0-beta + '@tsrs/linux-x64-musl': 0.25.0-ts7.1.0-beta + '@tsrs/win32-arm64': 0.25.0-ts7.1.0-beta + '@tsrs/win32-x64': 0.25.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 a8d8f5840a..5e29670699 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.25.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 diff --git a/rfcs/check-command.md b/rfcs/check-command.md index dedcdf8c1b..3ee05cd5a2 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,57 @@ 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`. + +### Type-aware backend + +See the [backend replacement RFC](./tsrs-lint-backend.md) for the rationale, +dependency changes and compatibility considerations. + +Vite+ replaces the bundled Go backend with `tsrs@0.25.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. + +`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 provides native packages for all supported Vite+ +platforms, including Windows ARM64. No platform uses a Go fallback. + +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 +248,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 +307,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 +326,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..b0a90c3fa6 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.25.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 identifies the 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 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,15 @@ 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. +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. +See the [backend design](./check-command.md#type-aware-backend). + ## Relationship to `vp why` `vp why` sends the command to the detected package manager. It keeps the @@ -486,21 +502,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. diff --git a/rfcs/tsrs-lint-backend.md b/rfcs/tsrs-lint-backend.md new file mode 100644 index 0000000000..72f7900f85 --- /dev/null +++ b/rfcs/tsrs-lint-backend.md @@ -0,0 +1,48 @@ +# RFC: Replace tsgolint with tsrs-lint + +Status: Proposed for the next release. + +## User experience + +Upgrading `vite-plus` makes tsrs the default backend for type-aware linting and +type checking in `vp lint` and `vp check`. Projects with these checks enabled +can benefit from tsrs performance improvements without additional setup. +Users keep their existing commands and configuration; no separate `tsrs` +installation is required. + +## Design + +Oxlint remains the linter frontend. The local `vite-plus` package bundles +`tsrs-lint` and selects it through the shared +[`resolve-lint.ts`](../packages/cli/src/resolve-lint.ts) resolver. Version output +and toolchain metadata identify `tsrs`. + +## Dependency changes + +| Dependency | Before | After | +| ------------------------- | ------------------------------------------- | ------------------------------------------ | +| Linter frontend | `oxlint@1.93.0` | Unchanged | +| Production backend | `oxlint-tsgolint@7.0.2006` | `tsrs@0.25.0-ts7.1.0-beta` | +| Development-only backend | None | `oxlint-tsgolint@7.0.2006` for comparisons | +| Backend platform packages | `@oxlint-tsgolint/*`: six optional packages | `@tsrs/*`: eight optional packages | + +Production installs use tsrs. The root `package.json` retains `oxlint-tsgolint` +as a development dependency for `bench/tsrs-lint.ts`. +The project's `typescript` dependency and standalone `tsc` tasks are unchanged. + +## Compatibility and release considerations + +- tsrs targets TypeScript `7.1.0-beta`, so compiler diagnostics can change. +- With `lint.options.typeCheck` enabled, tsrs checks whole TypeScript programs, + including files excluded by lint ignore patterns. Existing projects can + report additional errors. Projects control compiler scope through `tsconfig.json`. +- tsrs `0.25.0` provides native packages for all Vite+ targets, including Windows ARM64. + The same backend runs on every supported platform. +- pnpm's `trustPolicy: no-downgrade` rejects tsrs because its bootstrap release + used staged publishing ([upstream issue](https://github.com/oxc-project/tsrs/issues/945)). + `vp migrate` adds the reviewed `tsrs@0.25.0-ts7.1.0-beta` to `trustPolicyExclude` + when that policy is enabled in `pnpm-workspace.yaml`. Other packages and future + tsrs versions remain subject to the policy. New versions require a separate review. + +The [benchmark report](../bench/tsrs-lint.md) contains historical timing, memory, +and disk-usage measurements. Refresh them for the release candidate.