diff --git a/bench/tsrs-lint-ecosystem.md b/bench/tsrs-lint-ecosystem.md new file mode 100644 index 0000000000..2d4cf11583 --- /dev/null +++ b/bench/tsrs-lint-ecosystem.md @@ -0,0 +1,381 @@ +# Ecosystem correctness comparison + +The `tsrs@0.22.0-ts7.1.0-beta` experiment is not diagnostically equivalent to +`oxlint-tsgolint@7.0.2005`. The representative sweep found new type-check errors in +Hono and Vue Core, and a DOM-library compatibility change in OneSignal. The +exact-version audit below attributes all four reproduced categories to the pinned +TypeScript `7.1.0-beta` behavior. It found no Rust-port defect in these cases. +The default switch still changes diagnostics for projects that use the older backend. + +On 2026-11-06, a Linux x64/glibc Codespace ran 141 commands across 14 pinned forks +from the [ecosystem catalog](https://github.com/vite-plus-ecosystem-ci/.github). +The explicit type-check workload covered 2,289 files. No command timed out or +terminated by a signal. Seven repositories had identical normalized diagnostics; +seven had differences. This covers the selected repositories, not all 83 catalog entries. + +## Version 0.24 upgrade check (2026-11-11) + +The current integration pins `tsrs@0.24.0-ts7.1.0-beta`. The full ecosystem sweep +and performance measurements remain results for `0.22.0`. + +On macOS ARM64, all seven reduced cases retained identical diagnostics and exit +codes between `0.23.0` and `0.24.0`, both through `vp lint` and the standalone +compiler. The standalone results also matched `typescript@7.1.0-beta`. +The TypeScript source pin is unchanged, and the four compatibility follow-ups +remain open. + +The [0.24 release](https://github.com/oxc-project/tsrs/blob/v0.24.0/docs/releases/0.24.0.md) +checks whole programs before running lint rules. The existing smoke fixture still +reports the ignored program file's `TS2322` only when type checking is enabled. +Its invalid declaration remains suppressed by `skipLibCheck: true`. + +After rebasing onto `main` at `9f44ce760`, `vp check` passes, and the unit suite +passes 3,422 tests with one skipped. This check used Node.js `24.22.2`, Oxlint +`1.93.0`, and fresh CLI JavaScript built with published Rolldown `1.2.18`. +The native binding and core artifacts remain from published `1.5.0`. +This is not a full native release build or a repeat of the 14-repository sweep. +Scripts, fixtures, and raw logs remain outside the repository. + +## Version 0.23 upgrade check (2026-11-09) + +This upgrade pinned `tsrs@0.23.0-ts7.1.0-beta`. The full ecosystem sweep +and performance measurements remain results for `0.22.0`. + +On macOS ARM64, all seven reduced cases retained identical diagnostics and exit +codes between `0.22.0` and `0.23.0`, both through `vp lint` and the standalone +compiler. The standalone results also matched `typescript@7.1.0-beta`. +The TypeScript source pin remains `3e9236361fa97a9e6c685f4833ac457ae2d8c50a`. +The four existing compatibility follow-ups remain open. + +The [0.23 release](https://github.com/oxc-project/tsrs/blob/v0.23.0/docs/releases/0.23.0.md) +expands `--type-check` to whole programs. A smoke fixture confirmed that selecting +a clean file now reports `TS2322` in an ignored file from the same program. +Type-aware linting without `--type-check` still passes that fixture. A separate +invalid declaration remains suppressed by `skipLibCheck: true`. + +Whole-program checking initially exposed seven errors in three nested project +configs. With those original configs, `0.22.0` and `oxlint-tsgolint@7.0.2005` pass +the lint/type-check phase because they do not report these dependency errors: + +| Dependency | New diagnostics | +| ----------------------------------------------------- | -------------------------------------------------------------- | +| `@voidzero-dev/vitepress-theme@5.0.7` | One `TS2614` for the `Sponsors` export from a Vue file | +| `zod@4.6.5` | One `TS5115` for recursive JSON schema types | +| `webpack-virtual-modules@0.6.2` and `unplugin@2.3.11` | Five `TS2307` errors for missing optional bundler declarations | + +Standalone `typescript@7.1.0-beta` reproduces all seven errors when invoked against +the same nested configs: `packages/cli/templates/generator/tsconfig.json`, +`packages/cli/templates/monorepo/tsconfig.json`, and `docs/.vitepress/tsconfig.json`. +The root-only compiler check uses different file selection and compiler options; +its success did not establish a tsrs defect. + +Both starter templates now set `skipLibCheck: true`, consistent with the repository +root, so they do not check third-party declarations. The docs shim now declares +the named `Sponsors` type exported by `VPSponsors.vue`. It reuses the theme's +`SponsorTier` fields with an optional `tier`, matching the component's interface. + +After these corrections, `vp check` and all three nested TypeScript checks pass. +Negative probes still reject source type errors and invalid sponsor fields, and +unrelated Vue components do not acquire the named export. The rebuilt CLI's +3,403 unit tests also pass, with one skipped. These seven errors required project +configuration and Vue declaration fixes; the four earlier compiler-version +compatibility follow-ups remain open. + +This check used the rebased branch on `main` at `92edd550c`, Node.js `24.22.2`, +Oxlint `1.91.0`, and fresh CLI JavaScript. The native binding and core artifacts +remain from published `1.5.0`; the JS build used published Rolldown `1.2.16`. +This is not a full native release build or a repeat of the 14-repository sweep. +Scripts, fixtures, and raw logs remain outside the repository. + +## Exact-upstream attribution (2026-11-08) + +The original `6.0.3`, `7.0.2`, and `oxlint-tsgolint@7.0.2005` controls establish +upgrade compatibility, but cannot identify a Rust-port defect by themselves. +The [tsrs compatibility contract](https://github.com/oxc-project/tsrs/blob/625f918c85a3e12824ede39cd127d0f866b8d9c8/docs/COMPATIBILITY.md#conformance) +defines correctness against its exact upstream Go compiler, including upstream bugs. + +This audit compared the retained tsrs executable with published `typescript@7.1.0-beta`. +Both target `microsoft/TypeScript@3e9236361fa97a9e6c685f4833ac457ae2d8c50a`. +The reference wrapper and native package both identify that commit in `gitHead`. +The Rust source audit used release tag `v0.22.0`, commit +`625f918c85a3e12824ede39cd127d0f866b8d9c8`. + +All seven reduced cases produced identical diagnostic output and exit codes: + +| Reduced case | TypeScript `7.1.0-beta` | tsrs `0.22.0` | +| ----------------------------------------------- | ------------------------------- | ------------- | +| Hono default destructuring | Two `TS7031`, exit `2` | Identical | +| Hono explicit parameter annotation | No errors, exit `0` | Identical | +| Vue mapped-parameter destructuring | Three `TS7031`, exit `2` | Identical | +| Vue selector iteration | `TS5115` and `TS7006`, exit `2` | Identical | +| Vue conditional return, reduced | `TS2322`, exit `2` | Identical | +| Vue conditional return, original implementation | `TS2322`, exit `2` | Identical | +| DOM `maxActions` probe | `TS2322`, exit `2` | Identical | + +The four categories require different interpretations: + +| Category | Assessment | Follow-up | +| ----------------------- | ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | +| Default destructuring | Upstream TypeScript `7.1` false positive; tsrs matches it | Follow TypeScript #64974; an explicit parameter annotation passes | +| Conditional return type | Upstream `7.1` compatibility regression; no Rust divergence | Investigate the related conditional-type normalization fix in TypeScript #64307 | +| Selector recursion | Upstream `7.1` recursion restriction exposes the dependency's circular type construction | Rework or update the selector declarations; do not assume the older acceptance proves validity | +| Notification mock | Correct enforcement of the newer DOM declaration | Add the missing static member to the mock | + +Matching the pinned compiler does not mean every upstream diagnostic is desirable. +It means these reproductions do not justify changing the Rust port independently. +The new default also adopts a newer TypeScript version, so its compatibility cost +must be assessed separately from the implementation language. + +The 14 compiler invocations ran in Codespaces job `job-3aecfcc9bb6f49bb`. +The input files and compiler options were unchanged from the earlier reductions. +Raw output, executable/version evidence, package-lock hashes, and scripts remain +outside this repository. This follow-up did not repeat the full 14-repository sweep. + +## Controls and method + +The comparison used three toolchains: + +| Control | Published Vite+ package | Native lint backend | +| ----------------------- | ------------------------------------------------------- | -------------------------- | +| Stable | `1.5.0` | `oxlint-tsgolint@7.0.2005` | +| Preview with Go control | `0.0.0-commit.4fcf99edbe42fe3305dcd41dc884a4c4b7f5fd45` | `oxlint-tsgolint@7.0.2005` | +| Default preview | Same preview | `tsrs@0.22.0-ts7.1.0-beta` | + +All controls used Oxlint `1.91.0`. Stable and preview with Go produced identical +normalized lint and TypeScript diagnostics in every selected repository. This +control separates backend differences from other preview changes. + +Each project retained its original frozen lockfile, application dependencies, and +source configuration. The runner temporarily linked the published CLI, Vite, +Oxlint, and Oxfmt into workspace importers, then restored those links. Tracked +source files remained unchanged. The setup used each project's pinned package +manager, with lifecycle scripts disabled. Node.js was `24.22.2`, except +`eslint-plugin-oxlint` and `react-data-grid`, which used `26.13.0` to meet their engine requirements. + +Each control ran these commands: + +```sh +vp lint --format json --threads 4 +vp lint --type-aware --type-check --format json --threads 4 +vp check +``` + +Hono retained its lint scopes: `src`, `runtime-tests`, `build`, `perf-measures`, +`benchmarks`, and `adapters`. Hono and Vue Core do not enable type-aware linting in +their current configurations; the explicit flags add stress coverage. React Data +Grid enables type-aware rules but does not enable TypeScript diagnostics by default. + +The runner also used `vp run --no-cache` for five existing project tasks: +`oxc-walker`'s `test:types`, `urllib`'s `typecheck`, `media-processors`'s `typecheck`, +`react-data-grid`'s `typecheck`, and `vue-core`'s `check`. These tasks retain their +own compiler selection. A `tsc` task does not automatically become a `tsrs` task. + +The runner compared sorted JSON diagnostics, including codes, severity, messages, +files, spans, and help text. It normalized absolute project paths. Process traces +confirmed the expected native backend in all 42 explicit type-check runs. React +Data Grid and Vue Core required the matching optional `@vitest/browser-playwright@5.1.0` +peer for configuration imports. No browser installation was required. + +## Repository results + +The counts below include errors and warnings from the explicit type-check workload. +Equal counts alone do not establish equality; the comparison also checked diagnostic content. +All source links point to immutable commits in `vite-plus-ecosystem-ci`. + +| Repository / commit | Files | Go diagnostics | tsrs diagnostics | Comparison | +| -------------------------------------------------------------------------------------------------------------------------------------------------- | ----: | -------------: | ---------------: | --------------------------------------------- | +| [oxc-walker `9e520f25d`](https://github.com/vite-plus-ecosystem-ci/oxc-walker/tree/9e520f25d2bf5c3748f51d143c534fedff207147) | 12 | 0 | 0 | Identical diagnostics | +| [eslint-plugin-oxlint `ca4b10897`](https://github.com/vite-plus-ecosystem-ci/eslint-plugin-oxlint/tree/ca4b1089757245fa9a6a6ee46341d94a218d9c05) | 41 | 0 | 0 | Identical diagnostics | +| [solid-services `add4b036b`](https://github.com/vite-plus-ecosystem-ci/solid-services/tree/add4b036b72de7e6372b07faf0aed6b3f2e506d4) | 10 | 0 | 0 | Identical diagnostics | +| [pinia-shared-state `1b1ec6109`](https://github.com/vite-plus-ecosystem-ci/pinia-shared-state/tree/1b1ec6109b6f46079e4ddb5aeb9feff1d7188c24) | 6 | 0 | 0 | Identical diagnostics | +| [urllib `91f32d53f`](https://github.com/vite-plus-ecosystem-ci/urllib/tree/91f32d53faaae34a9f09f38e402e617420bc4d15) | 72 | 6 | 6 | Identical diagnostics | +| [anywidget `443d720b2`](https://github.com/vite-plus-ecosystem-ci/anywidget/tree/443d720b2233fff131a0f0a842b08744c8ffabd6) | 66 | 14 | 14 | Identical diagnostics | +| [css-modules-kit `634608fdd`](https://github.com/vite-plus-ecosystem-ci/css-modules-kit/tree/634608fdd401001d1fb05511a89177acf38b8111) | 131 | 0 | 0 | Identical diagnostics | +| [media-processors `79c87cd5c`](https://github.com/vite-plus-ecosystem-ci/media-processors/tree/79c87cd5c066a0a9abea979044c38c69603a85e8) | 22 | 101 | 91 | 10 fewer diagnostics | +| [web-app-template `f9a09f3d8`](https://github.com/vite-plus-ecosystem-ci/web-app-template/tree/f9a09f3d81002372e839247c6274af43177bbeca) | 8 | 3 | 1 | 2 fewer diagnostics | +| [OneSignal-Website-SDK `5375640c0`](https://github.com/vite-plus-ecosystem-ci/OneSignal-Website-SDK/tree/5375640c001a2dc462ed1cb938520edfb9a23d07) | 298 | 8 | 9 | New DOM-library requirement | +| [react-data-grid `384aedffc`](https://github.com/vite-plus-ecosystem-ci/react-data-grid/tree/384aedffc2cac013666b58b6787d888b9dde3a94) | 127 | 302 | 300 | 2 fewer diagnostics; message changes | +| [vue-clamp `6f0da3991`](https://github.com/vite-plus-ecosystem-ci/vue-clamp/tree/6f0da3991f804587ba402a7ca22aec4564eab938) | 134 | 5 | 1 | 4 fewer recursion errors | +| [hono `c91b60a25`](https://github.com/vite-plus-ecosystem-ci/hono/tree/c91b60a251186fb024557c6d55a11d8e388f179c) | 457 | 474 | 476 | 2 new contextual-typing errors; span changes | +| [vue-core `747e8d668`](https://github.com/vite-plus-ecosystem-ci/vue-core/tree/747e8d668c4a64ae6bd9dcc74c173364f1c1490b) | 905 | 547 | 546 | 7 new source errors; 8 fewer recursion errors | + +Default `vp lint` diagnostics matched in ten repositories. Its four differences +were OneSignal, media-processors, web-app-template, and vue-clamp. Vue Clamp's lint +exit changed from `1` to `0` after four `TS2321` errors disappeared; one warning remained. +The additional Hono, Vue Core, and React Data Grid differences appeared with explicit type-check flags. + +`vp check` passed in oxc-walker, eslint-plugin-oxlint, pinia-shared-state, and +css-modules-kit under all controls. The other ten failed under all controls. +Formatting stopped five of them before lint: solid-services, anywidget, +vue-clamp, Hono, and Vue Core. These failures do not demonstrate backend failure +or backend coverage. + +## Upstream contextual-typing regression + +[Hono's `Style` implementation](https://github.com/vite-plus-ecosystem-ci/hono/blob/c91b60a251186fb024557c6d55a11d8e388f179c/src/helper/css/index.ts#L192) +produces two new `TS7031` errors for `children` and `nonce` with tsrs. This reduced +source reproduces the failure with strict type checking: + +```ts +interface StyleType { + (args?: { children?: Promise; nonce?: string }): string; +} +export const Style: StyleType = ({ children, nonce } = {}) => `${children}${nonce}`; +``` + +TypeScript `6.0.3`, TypeScript `7.0.2`, and both Go lint controls accept this source. +The default preview and standalone tsrs report both errors. An explicit parameter +type annotation makes all controls pass. Hono's production project also passes +`tsc -p tsconfig.build.json --noEmit` but fails the same check with standalone tsrs. +This confirms a compiler-version difference independent of Vite+ backend resolution. + +[Vue Core's `applyTextModel`](https://github.com/vite-plus-ecosystem-ci/vue-core/blob/747e8d668c4a64ae6bd9dcc74c173364f1c1490b/packages/runtime-vapor/src/directives/vModel.ts#L46) +has the same default-destructuring pattern. Its `trim`, `number`, and `lazy` bindings +produce three new `TS7031` errors. A four-line reduction with a mapped optional +parameter passes TypeScript `6.0.3`, TypeScript `7.0.2`, and both Go controls, but fails both tsrs paths. + +The exact `7.1.0-beta` reference reports the same errors. Its +[`checkDeclarationInitializer` / `padObjectLiteralType`](https://github.com/microsoft/TypeScript/blob/3e9236361fa97a9e6c685f4833ac457ae2d8c50a/tsc/internal/checker/checker.go#L17205) +pads missing properties of the `{}` initializer and calls +`getTypeFromBindingElement` with `reportErrors: true`. The +[Rust port follows the same path](https://github.com/oxc-project/tsrs/blob/625f918c85a3e12824ede39cd127d0f866b8d9c8/crates/tsrs_checker/src/checker_08.rs#L2980). +That path can report implicit `any` despite an available contextual parameter type. +TypeScript tracks this as [bug #64974](https://github.com/microsoft/TypeScript/issues/64974). +The rejection is a false positive, while the tsrs implementation conforms to its reference. + +## Vue Core selector types + +[Vue Core's scoped-style transformer](https://github.com/vite-plus-ecosystem-ci/vue-core/blob/747e8d668c4a64ae6bd9dcc74c173364f1c1490b/packages/compiler-sfc/src/style/pluginScoped.ts#L107) +produces a new `TS5115` circular-instantiation error and two `TS7006` callback errors. +This smaller case uses the project's locked `postcss-selector-parser@7.1.6`: + +```ts +import type parser from 'postcss-selector-parser'; +export function inspect(selector: parser.Selector): void { + selector.each((n) => { + if (n.type === 'pseudo') { + n.nodes[0].each((ss) => { + void ss; + }); + } + }); +} +``` + +TypeScript `6.0.3`, TypeScript `7.0.2`, and both Go controls accept the reduced source. The +default preview and standalone tsrs report `TS5115` and `TS7006`. The reduction +uses strict mode, bundler module resolution, and `skipLibCheck: true`. + +The dependency declares `Selector = _Selector`, where `_Selector` +extends `Container>`. `Node` itself includes `Selector`, and +`Diff` is a distributive conditional type. Resolving the base members can +therefore require comparing the type whose base members are still being resolved. +TypeScript [#64372](https://github.com/microsoft/TypeScript/pull/64372) deliberately +stopped allowing base type arguments to depend on partially resolved members. + +Both the [upstream instantiation guard](https://github.com/microsoft/TypeScript/blob/3e9236361fa97a9e6c685f4833ac457ae2d8c50a/tsc/internal/checker/checker.go#L22601) +and [Rust guard](https://github.com/oxc-project/tsrs/blob/625f918c85a3e12824ede39cd127d0f866b8d9c8/crates/tsrs_checker/src/checker_11.rs#L1319) +stop at depth `100` or `5,000,000` instantiations and report the circular type names. +The exact upstream compiler reports the same `Container` / `Diff` error and lost +callback type. This is a dependency compatibility problem under the newer rules, +not evidence of a Rust implementation error. The causal link to the stricter +inheritance rule follows from the source review; this audit did not bisect that upstream change. + +## Vue Core generic conditional return type + +[Vue Core's `toHandlerKey`](https://github.com/vite-plus-ecosystem-ci/vue-core/blob/747e8d668c4a64ae6bd9dcc74c173364f1c1490b/packages/shared/src/general.ts#L147) +produces a new `TS2322` when it assigns a generic function with a conditional +return type. Both an isolated copy of the original implementation and this +reduction reproduce the error: + +```ts +declare function cache string>(fn: T): T; +export const handler: (s: T) => T extends '' ? '' : `on${Capitalize}` = cache( + (s: T) => { + const value = s ? `on${s}` : ''; + return value as T extends '' ? '' : `on${Capitalize}`; + }, +); +``` + +TypeScript `6.0.3`, TypeScript `7.0.2`, and both Go controls accept both cases. The default +preview and standalone tsrs reject both cases with `TS2322`. The reduction uses +the same strict compiler options as the selector case. + +The exact `7.1.0-beta` compiler rejects both cases identically. The +[upstream conditional-type comparison](https://github.com/microsoft/TypeScript/blob/3e9236361fa97a9e6c685f4833ac457ae2d8c50a/tsc/internal/checker/relater.go#L3758) +and [Rust comparison](https://github.com/oxc-project/tsrs/blob/625f918c85a3e12824ede39cd127d0f866b8d9c8/crates/tsrs_checker/src/relater_2.rs#L2541) +compare the conditional types without normalizing both distributed type parameters. +This is consistent with the upstream asymmetry described in +[#64673](https://github.com/microsoft/TypeScript/issues/64673): instantiation can +normalize one side while the other retains a distributed parameter. + +The open [fix #64307](https://github.com/microsoft/TypeScript/pull/64307) normalizes +both sides and is relevant to this failure. That root-cause connection is an +inference from the source and diagnostic chain. The patch was not applied or +validated against this case here. The exact-reference failure is sufficient to +rule out a Rust-only divergence for this reproduction. + +## OneSignal DOM-library difference + +[OneSignal's notification mock](https://github.com/vite-plus-ecosystem-ci/OneSignal-Website-SDK/blob/5375640c001a2dc462ed1cb938520edfb9a23d07/__test__/support/environment/TestEnvironmentHelpers.ts#L46) +produces a new `TS2741`: the constructor lacks `Notification.maxActions`. The tsrs +`lib.dom.d.ts` requires that static property. The tested TypeScript `6.0.3` and +`7.0.2` libraries do not contain it. + +```ts +type HasMaxActions = 'maxActions' extends keyof typeof Notification ? true : false; +export const hasMaxActions: HasMaxActions = false; +``` + +The TypeScript `6.0.3` / `7.0.2` and Go lint controls accept this probe. Both tsrs paths report `TS2322`. +This establishes a library-contract difference; it does not establish that the +new DOM declaration is incorrect. The difference affects OneSignal's existing +configuration because it already enables TypeScript diagnostics. + +The tsrs and TypeScript `7.1.0-beta` `lib.dom.d.ts` files are byte-identical: +SHA256 `2f8c8d9e23d03e00c353412f5b2be3b1fd893290fc0976ad2229681bb7d46a35`. +The [Notifications standard](https://notifications.spec.whatwg.org/#dom-notification-maxactions) +also defines a static read-only `maxActions` member. Assigning `MockNotification` +to the constructor type now correctly requires that static member. Adding an +instance property would not satisfy it. A mock that supports no actions can +supply `static readonly maxActions = 0`. This is a mock adaptation to the updated +standard library, not a compiler false positive. + +## Other differences and limits + +Media-processors, web-app-template, vue-clamp, and React Data Grid report fewer +recursive-comparison or configuration-compatibility errors with tsrs. Vue Core +also loses eight `TS2321` errors but adds seven production-source errors. Its total +therefore falls from 547 to 546 despite the new failures. Fewer diagnostics are +not classified as fixes without a separate correctness check. + +React Data Grid also changes expanded type text in twelve existing diagnostics. +Vue Core changes two existing messages, and Hono moves three existing `TS2875` +spans. These presentation changes are included in the comparison. + +Several errors are shared by both backends. They include urllib's six +`Http2Stream.respond` errors, anywidget's 14 type errors, missing generated +`video-track-processor` declarations in media-processors, and a missing translation +file in web-app-template. The broad Hono workload includes optional benchmark +and runtime fixtures. React Data Grid and Vue Core include test declarations +that conflict with the overlaid toolchain's Vitest types. + +Only oxc-walker's existing type-check task passed. The other four tasks failed +under every control, with matching TypeScript diagnostics after installation-path +normalization. Vue Core initially returned `2` and then `1` as its incremental +cache warmed; fresh build-info files produced exit `2` and identical output in +all three controls. No task compiler was replaced by the default lint backend. + +This method isolates the backend while preserving application dependencies. It +does not reproduce each repository's full build pipeline or a complete dependency +upgrade. Shared setup and source errors must not be counted as new tsrs regressions. +The reduced cases provide independent evidence for the compiler-version differences. +The exact-reference audit attributes the four reproduced categories; it does not +establish exact-upstream parity for every diagnostic in the full sweep. + +The primary sweep used Codespaces job `job-d8ac88c64fc64b11`. Final anywidget, +React Data Grid, and Vue Core results came from `job-f5f12efe4b514e83`, which also +reduced the Hono and OneSignal cases. Vue reductions used `job-cf53f53e0a2d4216` and `job-120ca876783b4213`. +The harness, frozen dependency receipts, process traces, compiler outputs, and +raw JSON remain outside this repository. diff --git a/bench/tsrs-lint.md b/bench/tsrs-lint.md new file mode 100644 index 0000000000..7af495fc69 --- /dev/null +++ b/bench/tsrs-lint.md @@ -0,0 +1,134 @@ +# Type-aware linting with tsrs-lint + +The current integration pins `tsrs@0.24.0-ts7.1.0-beta`. The measurements below +remain results for `0.22.0`; they have not been repeated for `0.24.0`. + +This comparison uses the default `tsrs@0.22.0-ts7.1.0-beta` integration and an explicit +`oxlint-tsgolint@7.0.2005` override. Both use Oxlint `1.91.0` and the same built CLI. + +The separate [ecosystem correctness comparison](./tsrs-lint-ecosystem.md) found +upstream TypeScript `7.1` regressions and library differences in representative forks. +The exact-version audit found no Rust-port divergence in the reduced cases. The performance +results below do not establish diagnostic equivalence. + +## Elapsed time + +Each value is the median of ten fresh CLI processes after one warm-up per backend. +The runner alternates the order between pairs. Timing runs do not sample memory. + +| Project | Workload | Go (s) | tsrs (s) | Reduction | +| -------- | -------------------------------- | -----: | -------: | --------: | +| Vite+ | Type-aware rules | 3.712 | 2.212 | 40.4% | +| Vite+ | Rules and TypeScript diagnostics | 4.612 | 2.382 | 48.4% | +| Vite+ | Full `vp check` | 5.842 | 3.554 | 39.2% | +| vinext | Type-aware rules | 52.028 | 45.363 | 12.8% | +| vinext † | Rules and TypeScript diagnostics | 54.192 | 44.794 | 17.3% | +| vinext | Full `vp check` | 62.325 | 50.355 | 19.2% | + +† The clean timing repetition used an EPYC 7763 after a Codespace restart. +All other timing rows used an EPYC 9V74. Both backends within each row used +the same CPU. Compare backend values within a row; the CPU change prevents +a strict comparison of absolute times across workloads. + +## Peak memory + +Each value is the median of five separate runs after one warm-up per backend. +Memory is the sampled peak of the simultaneous sum of RSS for the CLI and its +descendants, including Node, Oxlint, and the native backend. One GiB is 1,073,741,824 bytes. + +| Project | Workload | Go (GiB) | tsrs (GiB) | Reduction | +| ------- | -------------------------------- | -------: | ---------: | --------: | +| Vite+ | Type-aware rules | 1.175 | 0.930 | 20.9% | +| Vite+ | Rules and TypeScript diagnostics | 1.345 | 0.984 | 26.8% | +| Vite+ | Full `vp check` | 1.438 | 1.017 | 29.3% | +| vinext | Type-aware rules | 4.382 | 3.030 | 30.8% | +| vinext | Rules and TypeScript diagnostics | 4.903 | 3.166 | 35.4% | +| vinext | Full `vp check` | 5.413 | 3.292 | 39.2% | + +The sampler sleeps for 20 ms. The median observed interval across runs was +21.8 ms. Shared pages can count more than once, +and sampling can miss short peaks. These values are process-tree RSS, not total +allocation or container memory. + +## Installed disk footprint + +Clean production installations on Linux x64 with glibc compare published `1.5.0` +with preview `0.0.0-commit.4fcf99edbe42fe3305dcd41dc884a4c4b7f5fd45`. +The preview binary-size comment measures individual executables and native bindings; +it does not measure the installed dependency tree. + +| Scope | Go (MiB) | tsrs (MiB) | Increase (MiB) | Increase | +| -------------------------------- | -------: | ---------: | -------------: | -------: | +| Complete `node_modules`, on disk | 132.93 | 150.87 | 17.94 | 13.5% | +| Complete `node_modules`, files | 123.39 | 139.27 | 15.88 | 12.9% | +| Backend packages, on disk | 21.63 | 39.69 | 18.06 | 83.5% | + +One MiB is 1,048,576 bytes. On-disk values use `du -s -B1 node_modules` and +include directory allocation. File sizes sum regular-file lengths without following +symlinks or counting an inode twice. The filesystem uses 4 KiB blocks. +Stores, download caches, Node.js, pnpm, and the global `vp` executable are excluded. + +The job used Node.js `24.22.2`, pnpm `10.34.7`, and each version's published +`vp-install-lock.yaml`. It reproduced the global installer's manifest and frozen +installation flags. Separate stores and `--config.package-import-method=copy` +prevent shared hard links from affecting the standalone footprint. Each installation +contained 80 packages. Version output and type-aware linting with TypeScript +diagnostics passed for both installations. The preview contained no `oxlint-tsgolint`. + +The backend totals include `oxlint-tsgolint` and `@oxlint-tsgolint/linux-x64`, or +`tsrs` and `@tsrs/linux-x64-gnu`. The Rust distribution includes a larger executable, +TypeScript library declarations, JavaScript APIs, and source maps. Its package +footprint increases despite the runtime and memory improvements. The total +installation comparison also includes other release-to-preview changes; +the backend row separates the backend packages from those changes. + +The measurement ran on 2026-11-06 in Codespaces job `job-2a30c8ec3fb84524`. +Scripts, raw package inventories, lockfiles, and logs remain outside the repository. +The lockfile SHA256 values are +`eb06955c3e76589da771286d3bfc6e6ea940c1a09ac64f597d2c72eb94403ad1` for `1.5.0` +and `cf8fe673142ace0abaa362552bff78281f3b8f00e01bc13fa0eb93c5b1344683` for the preview. + +## Conditions and interpretation + +The jobs used one GitHub Codespace with four vCPUs, 16 GB advertised RAM, and an +AMD EPYC 9V74 processor for the main run. The focused timing repetition used an +AMD EPYC 7763 after the instance restarted. Usable RAM was 15.62 GiB. +The container used Ubuntu 22.04 on Linux x64 with glibc. Vite+ used Node.js +`24.22.2`; vinext used Node.js `26.13.0`. + +The CLI JavaScript came from `22b07bd150aa57b3e04cc52e0b81d43294ffe982`. +Its native binding, core, and prompts artifacts came from the published Vite+ +`1.5.0` packages. The measured Vite+ source also includes the RFC patch at +`da260f86fce1d43810c62e4e6aad011066c88cb9`. Vinext is pinned to +`249088af307c0a852fa6dc59c08fc6ec80e1620e`, from the requested upgrade branch. +This is a comparison of backend routing in the built CLI, not a full native release build. + +Lint uses four threads. Vinext lint sets +`OXLINT_TSGOLINT_CHECKER_SCHEDULING=sorted`; Vite+ lint uses default scheduling. +Full checks use four threads and default backend scheduling. The rules-only +configuration disables TypeScript diagnostics while retaining the project's +original rules and plugins. Setup, builds, validation, and memory sampling are +excluded from the elapsed-time table. + +Both backends passed all 12 validation commands and every benchmark invocation. +Vite+ checked 385 source files with 174 rules and formatted 632 files. Vinext +checked 1,828 source files with 151 rules and formatted 4,673 files. These clean +corpora do not establish complete diagnostic or compiler parity. + +Vinext retains its original JS plugin rules, including +`vinext-utils/prefer-shared-utils`. The backend switch leaves that shared work +in place. This limits the elapsed-time improvement even though the memory +reduction is larger. The measurements do not include that rule's separate optimization. + +## Run selection + +The main job completed 204 invocations, including warm-ups. +A browser connection used to diagnose an SSH +outage started editor extension activity during vinext's type-checking timing +set. That entire timing set is excluded. The table instead uses the 22 records +from a clean timing repetition. +The repetition uses the same retained build and dependencies, with their hashes +checked before execution. All memory values come from the main job. + +Warm-up runs do not contribute to medians. Each reduction is calculated as +`100 * (1 - rust / go)` from the corresponding backend medians. diff --git a/bench/tsrs-lint.ts b/bench/tsrs-lint.ts new file mode 100644 index 0000000000..5dfb3a42da --- /dev/null +++ b/bench/tsrs-lint.ts @@ -0,0 +1,97 @@ +// 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(new URL('../packages/cli/package.json', import.meta.url)); +const tsgolint = + process.platform === 'win32' + ? ['tsgolint.exe', 'tsgolint.cmd'] + .map((bin) => + fileURLToPath(new URL(`../packages/cli/node_modules/.bin/${bin}`, import.meta.url)), + ) + .find(existsSync) + : require.resolve('oxlint-tsgolint/bin/tsgolint'); +assert.ok(tsgolint, 'Install workspace development dependencies to get the Go baseline.'); +const baselineEnv = { + ...env, + OXLINT_TSGOLINT_PATH: tsgolint, +}; +const args = [ + vp, + 'lint', + '--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_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 a80ae27496..8c0778caf6 100644 --- a/docs/guide/check.md +++ b/docs/guide/check.md @@ -4,9 +4,9 @@ ## Overview -`vp check` is the default command for fast static checks in Vite+. It brings together formatting through [Oxfmt](https://oxc.rs/docs/guide/usage/formatter.html), linting through [Oxlint](https://oxc.rs/docs/guide/usage/linter.html), and TypeScript type checks through [tsgolint](https://github.com/oxc-project/tsgolint). By merging all of these tasks into a single command, `vp check` is faster than running formatting, linting, and type checking as separate tools in separate commands. +`vp check` is the default command for fast static checks in Vite+. It brings together formatting through [Oxfmt](https://oxc.rs/docs/guide/usage/formatter.html), linting through [Oxlint](https://oxc.rs/docs/guide/usage/linter.html), and TypeScript type checks through [tsrs](https://github.com/oxc-project/tsrs) in this experimental build. By merging all of these tasks into a single command, `vp check` is faster than running formatting, linting, and type checking as separate tools in separate commands. -When both `typeAware` and `typeCheck` are enabled in the `lint.options` block in `vite.config.ts`, `vp check` also runs TypeScript type checks through the Oxlint type-aware path powered by the TypeScript Go toolchain and [tsgolint](https://github.com/oxc-project/tsgolint). `vp create` and `vp migrate` enable both `typeAware` and `typeCheck` by default. +When both `typeAware` and `typeCheck` are enabled in the `lint.options` block in `vite.config.ts`, `vp check` also runs TypeScript type checks through the Oxlint type-aware path powered by `tsrs-lint`. `vp create` and `vp migrate` enable both `typeAware` and `typeCheck` by default. We recommend turning both `typeAware` and `typeCheck` on so `vp check` becomes the single command for static checks during development. diff --git a/docs/guide/lint.md b/docs/guide/lint.md index 211008821e..6e37103417 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`. +This experimental build uses [tsrs](https://github.com/oxc-project/tsrs), a Rust port of the TypeScript 7 compiler. Its `tsrs-lint` executable gives Oxlint access to type information and allows type checking directly via `vp lint` and `vp check`. + +The pinned `tsrs` release does not provide a Windows ARM64 binary. This experimental replacement does not support that platform yet. ## JS Plugins diff --git a/packages/cli/package.json b/packages/cli/package.json index bbefe97f75..1f7950851e 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -364,7 +364,7 @@ "@vitest/utils": "catalog:", "oxfmt": "catalog:", "oxlint": "catalog:", - "oxlint-tsgolint": "catalog:", + "tsrs": "catalog:", "vite": "workspace:@voidzero-dev/vite-plus-core@*", "vitest": "catalog:" }, @@ -388,6 +388,7 @@ "lint-staged": "catalog:", "minimatch": "catalog:", "nanotar": "catalog:", + "oxlint-tsgolint": "catalog:", "picocolors": "catalog:", "pirates": "catalog:", "tinyglobby": "catalog:", diff --git a/packages/cli/src/__tests__/resolve-lint.spec.ts b/packages/cli/src/__tests__/resolve-lint.spec.ts index c8220e9200..c5734d7895 100644 --- a/packages/cli/src/__tests__/resolve-lint.spec.ts +++ b/packages/cli/src/__tests__/resolve-lint.spec.ts @@ -2,7 +2,7 @@ import { existsSync } from 'node:fs'; import { describe, expect, it } from 'vitest'; -import { lint, resolveWindowsTsgolintExecutable } from '../resolve-lint.js'; +import { lint, resolveWindowsLintBackendExecutable } from '../resolve-lint.js'; describe('resolve-lint', () => { it('should resolve binPath and OXLINT_TSGOLINT_PATH to existing files', async () => { @@ -14,28 +14,29 @@ describe('resolve-lint', () => { `oxlint binPath should point to an existing file, got: ${result.binPath}`, ).toBe(true); - const tsgolintPath = result.envs.OXLINT_TSGOLINT_PATH; - expect(tsgolintPath).toBeTruthy(); + const backendPath = result.envs.OXLINT_TSGOLINT_PATH; + expect(backendPath).toBeTruthy(); + expect(backendPath).toMatch(/[/\\]tsrs-lint(?:\.(?:cmd|exe))?$/); expect( - existsSync(tsgolintPath), - `OXLINT_TSGOLINT_PATH should point to an existing file, got: ${tsgolintPath}`, + existsSync(backendPath), + `OXLINT_TSGOLINT_PATH should point to an existing file, got: ${backendPath}`, ).toBe(true); }); - it('should keep the Windows tsgolint executable path absolute', () => { - const tsgolintPath = - 'C:\\repo\\node_modules\\.pnpm\\vite-plus@0.1.24\\node_modules\\vite-plus\\node_modules\\.bin\\tsgolint.cmd'; - const result = resolveWindowsTsgolintExecutable( + it('should keep the Windows tsrs-lint executable path absolute', () => { + const backendPath = + 'C:\\repo\\node_modules\\.pnpm\\vite-plus@0.1.24\\node_modules\\vite-plus\\node_modules\\.bin\\tsrs-lint.cmd'; + const result = resolveWindowsLintBackendExecutable( [ - 'C:\\repo\\node_modules\\.pnpm\\vite-plus@0.1.24\\node_modules\\vite-plus\\node_modules\\.bin\\tsgolint.exe', - tsgolintPath, + 'C:\\repo\\node_modules\\.pnpm\\vite-plus@0.1.24\\node_modules\\vite-plus\\node_modules\\.bin\\tsrs-lint.exe', + backendPath, ], { - exists: (path) => path === tsgolintPath, + exists: (path) => path === backendPath, }, ); - expect(result).toBe(tsgolintPath); + expect(result).toBe(backendPath); expect(result).not.toMatch(/^\.\\/); }); }); diff --git a/packages/cli/src/__tests__/toolchain.spec.ts b/packages/cli/src/__tests__/toolchain.spec.ts index 036d1e4a63..c1dc6ab535 100644 --- a/packages/cli/src/__tests__/toolchain.spec.ts +++ b/packages/cli/src/__tests__/toolchain.spec.ts @@ -32,7 +32,7 @@ describe('toolchain export', () => { 'vitest', 'oxlint', 'oxfmt', - 'oxlint-tsgolint', + 'tsrs', 'tsdown', 'vite-task', 'oxc', diff --git a/packages/cli/src/__tests__/versions.spec.ts b/packages/cli/src/__tests__/versions.spec.ts index 646a608397..223f70785c 100644 --- a/packages/cli/src/__tests__/versions.spec.ts +++ b/packages/cli/src/__tests__/versions.spec.ts @@ -115,7 +115,7 @@ describe('versions export', () => { expect(versions.vitest).toBeTypeOf('string'); expect(versions.oxlint).toBeTypeOf('string'); expect(versions.oxfmt).toBeTypeOf('string'); - expect(versions['oxlint-tsgolint']).toBeTypeOf('string'); + expect(versions.tsrs).toBeTypeOf('string'); }); }); }); diff --git a/packages/cli/src/resolve-lint.ts b/packages/cli/src/resolve-lint.ts index f552962c4b..9b0ef98d25 100644 --- a/packages/cli/src/resolve-lint.ts +++ b/packages/cli/src/resolve-lint.ts @@ -13,10 +13,10 @@ import { dirname, join } from 'node:path'; -import { CONFIG_METADATA_ENV, DEFAULT_ENVS, resolve } from './utils/constants.ts'; -import { resolveTsgolintExecutable } from './utils/tsgolint-path.ts'; +import { CONFIG_METADATA_ENV, DEFAULT_ENVS, resolve, resolveBundled } from './utils/constants.ts'; +import { resolveLintBackendExecutable } from './utils/lint-backend-path.ts'; -export { resolveWindowsTsgolintExecutable } from './utils/tsgolint-path.ts'; +export { resolveWindowsLintBackendExecutable } from './utils/lint-backend-path.ts'; /** * Resolves the oxlint binary path and environment variables. @@ -40,16 +40,17 @@ export async function lint(): Promise<{ const oxlintMainPath = resolve('oxlint'); const oxlintPackageRoot = dirname(dirname(oxlintMainPath)); const binPath = join(oxlintPackageRoot, 'bin', 'oxlint'); - const oxlintTsgolintPath = resolveTsgolintExecutable( - resolve('oxlint-tsgolint/bin/tsgolint'), + const tsrsLintPath = resolveLintBackendExecutable( + join(dirname(resolveBundled('tsrs/package.json')), 'bin', 'tsrs-lint'), import.meta.url, + 'tsrs-lint', ); const result = { binPath, // TODO: provide envs inference API envs: { ...DEFAULT_ENVS, - OXLINT_TSGOLINT_PATH: oxlintTsgolintPath, + OXLINT_TSGOLINT_PATH: tsrsLintPath, // oxlint loads vite.config.ts only to read the `lint` block, so skip the // user's Vite plugin factory (lazyPlugins) while it evaluates the config. [CONFIG_METADATA_ENV]: '1', diff --git a/packages/cli/src/utils/tsgolint-path.ts b/packages/cli/src/utils/lint-backend-path.ts similarity index 50% rename from packages/cli/src/utils/tsgolint-path.ts rename to packages/cli/src/utils/lint-backend-path.ts index e98f1d41ca..5b7ac60571 100644 --- a/packages/cli/src/utils/tsgolint-path.ts +++ b/packages/cli/src/utils/lint-backend-path.ts @@ -2,54 +2,58 @@ import { existsSync, realpathSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; -export function resolveWindowsTsgolintExecutable( +export function resolveWindowsLintBackendExecutable( pathCandidates: string[], options: { exists: (path: string) => boolean; getRealpathCandidates?: () => string[]; }, ): string { - let oxlintTsgolintPath = pathCandidates.find((p) => options.exists(p)) ?? ''; - if (!oxlintTsgolintPath && options.getRealpathCandidates) { + let backendPath = pathCandidates.find((p) => options.exists(p)) ?? ''; + if (!backendPath && options.getRealpathCandidates) { try { - oxlintTsgolintPath = options.getRealpathCandidates().find((p) => options.exists(p)) ?? ''; + backendPath = options.getRealpathCandidates().find((p) => options.exists(p)) ?? ''; } catch { // realpath failed, fall through to default } } - if (!oxlintTsgolintPath) { + if (!backendPath) { throw new Error( - 'Unable to resolve oxlint-tsgolint executable, tried:\n' + + 'Unable to resolve type-aware lint executable, tried:\n' + pathCandidates.map((path) => `- ${path}`).join('\n'), ); } - return oxlintTsgolintPath; + return backendPath; } -export function resolveTsgolintExecutable(tsgolintBinPath: string, scriptUrl: string): string { +export function resolveLintBackendExecutable( + backendBinPath: string, + scriptUrl: string, + executableName: string, +): string { if (process.platform !== 'win32') { - return tsgolintBinPath; + return backendBinPath; } // On Windows, try .exe first (bun creates .exe), then .cmd (npm/pnpm/yarn create .cmd) const scriptDir = dirname(fileURLToPath(scriptUrl)); const localBinDir = join(scriptDir, '..', 'node_modules', '.bin'); - const oxlintTsgolintPackagePath = dirname(dirname(tsgolintBinPath)); - const projectBinDir = join(oxlintTsgolintPackagePath, '..', '.bin'); + const backendPackagePath = dirname(dirname(backendBinPath)); + const projectBinDir = join(backendPackagePath, '..', '.bin'); const pathCandidates = [ - join(localBinDir, 'tsgolint.exe'), - join(localBinDir, 'tsgolint.cmd'), - join(projectBinDir, 'tsgolint.exe'), - join(projectBinDir, 'tsgolint.cmd'), + join(localBinDir, `${executableName}.exe`), + join(localBinDir, `${executableName}.cmd`), + join(projectBinDir, `${executableName}.exe`), + join(projectBinDir, `${executableName}.cmd`), ]; - return resolveWindowsTsgolintExecutable(pathCandidates, { + return resolveWindowsLintBackendExecutable(pathCandidates, { exists: existsSync, // Bun stores packages in .bun/ cache dirs where the symlinked paths above won't match. getRealpathCandidates: () => { const realPkgDir = realpathSync(join(scriptDir, '..')); const realBinDir = join(dirname(realPkgDir), '.bin'); - return [join(realBinDir, 'tsgolint.exe'), join(realBinDir, 'tsgolint.cmd')]; + return [join(realBinDir, `${executableName}.exe`), join(realBinDir, `${executableName}.cmd`)]; }, }); } diff --git a/packages/cli/src/version.ts b/packages/cli/src/version.ts index 2a62af26b9..ac5d3f7f22 100644 --- a/packages/cli/src/version.ts +++ b/packages/cli/src/version.ts @@ -15,7 +15,7 @@ const TOOL_DISPLAY_ORDER = [ 'vitest', 'oxfmt', 'oxlint', - 'oxlint-tsgolint', + 'tsrs', 'tsdown', ] as const; diff --git a/packages/cli/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 024acffdc9..43946a0ca8 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.24.0-ts7.1.0-beta' + version: 0.24.0-ts7.1.0-beta typescript: specifier: ~7.0.2 version: 7.0.2 @@ -463,12 +466,12 @@ importers: oxlint: specifier: 'catalog:' version: 1.93.0(oxlint-tsgolint@7.0.2006)(vite-plus@packages+cli) - oxlint-tsgolint: - specifier: 'catalog:' - version: 7.0.2006 playwright: specifier: '*' version: 1.62.1 + tsrs: + specifier: 'catalog:' + version: 0.24.0-ts7.1.0-beta vite: specifier: workspace:@voidzero-dev/vite-plus-core@* version: link:../core @@ -533,6 +536,9 @@ importers: nanotar: specifier: 'catalog:' version: 0.3.0 + oxlint-tsgolint: + specifier: 'catalog:' + version: 7.0.2006 picocolors: specifier: 'catalog:' version: 1.1.1 @@ -3576,6 +3582,52 @@ packages: peerDependencies: tsdown: 0.23.4 + '@tsrs/darwin-arm64@0.24.0-ts7.1.0-beta': + resolution: {integrity: sha512-GxGPRieCGIXevJgGlbne9pRqkfAN+VmBQvBHWWcF8rXLNX9yCxYo78/a7Auqjf723b4eFTlM5mgkclUit/Fu9w==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [darwin] + + '@tsrs/darwin-x64@0.24.0-ts7.1.0-beta': + resolution: {integrity: sha512-TrXdOyEq++blyYp6KphoHGmsGxAn1flYzjm0peLEI3c88z1UQa+pKkcuPgZ6FOxTW6fSaT9572n21kz+0bWi/g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [darwin] + + '@tsrs/linux-arm64-gnu@0.24.0-ts7.1.0-beta': + resolution: {integrity: sha512-Ph5NK0vnaR4gnBW2LgrZsUPx4mKF+9tCQUYZFOHmny6SMojSsm/5GCiBu0sHZEZhmyIqq8UbineUEA7T0QImnw==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@tsrs/linux-arm64-musl@0.24.0-ts7.1.0-beta': + resolution: {integrity: sha512-vjgx8wGuAVSOPLlb2eVQpQVolImZa7vsH/1kvYOaokcP/KZXVUmHzgroxK0t9gxJ0SXvT4VKNjWZ+t+4tDNu1A==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@tsrs/linux-x64-gnu@0.24.0-ts7.1.0-beta': + resolution: {integrity: sha512-tj1SNWBnJoBUzU2zrD18tUu3h6o36FXQGPpmIXsADHSfU5H9pVQdK/QzPIO76rb72P7foXHluE+xGTXXf8nNaA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@tsrs/linux-x64-musl@0.24.0-ts7.1.0-beta': + resolution: {integrity: sha512-sizu0EdGw9ITqEaZKg60TDhDk5PXT9RzVkCdesBjk/BZhS0BVU2cRhvoRBmDbk+2vCUgW14j+Otg3tl+002qNQ==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@tsrs/win32-x64@0.24.0-ts7.1.0-beta': + resolution: {integrity: sha512-WAbzgd5JBOauQ/oL6DTdGEh09tM/q8tlGAcRORskhPK7eIQVVa8Js2pc7DVW0XokWTSADeD9v6HKtU2Ge0r/fQ==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [win32] + '@tybys/wasm-util@0.10.3': resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} @@ -6666,6 +6718,11 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tsrs@0.24.0-ts7.1.0-beta: + resolution: {integrity: sha512-Iy7q93qfRHcI0Y31IKx6fCsDQCsSjtrT01PIgOVolBXPJTMdm84oH1pEYyAMPQ/SnRqWDSWI8wEUHbv6e9TmHA==} + engines: {node: '>=16.20.0'} + hasBin: true + tsx@4.23.15: resolution: {integrity: sha512-Yiex1Ovn8z2xPpOWckIiysV1SSyRMY9BkLF++q0yKiDxCqRhosKfMg3janKkiLBwZ5c/YryloKwGZcrEmtwxKw==} engines: {node: '>=18.0.0'} @@ -8958,6 +9015,27 @@ snapshots: tsdown: 0.23.4(@arethetypeswrong/core@0.18.2)(@tsdown/css@0.23.4)(@tsdown/exe@0.23.4)(@typescript/native-preview@7.0.0-dev.20260707.2)(@vitejs/devtools@0.8.0(crossws@0.4.12(srvx@1.0.6))(srvx@1.0.6)(vite@packages+core))(@volar/typescript@2.4.28)(@vue/language-core@3.3.12)(oxc-resolver@11.24.2)(publint@0.3.24)(tsx@4.23.15)(typescript@7.0.2)(unplugin-unused@0.5.6)(unrun@0.3.1)(vue-tsc@3.3.12(typescript@7.0.2)) verkit: 0.5.0 + '@tsrs/darwin-arm64@0.24.0-ts7.1.0-beta': + optional: true + + '@tsrs/darwin-x64@0.24.0-ts7.1.0-beta': + optional: true + + '@tsrs/linux-arm64-gnu@0.24.0-ts7.1.0-beta': + optional: true + + '@tsrs/linux-arm64-musl@0.24.0-ts7.1.0-beta': + optional: true + + '@tsrs/linux-x64-gnu@0.24.0-ts7.1.0-beta': + optional: true + + '@tsrs/linux-x64-musl@0.24.0-ts7.1.0-beta': + optional: true + + '@tsrs/win32-x64@0.24.0-ts7.1.0-beta': + optional: true + '@tybys/wasm-util@0.10.3': dependencies: tslib: 2.8.1 @@ -12130,6 +12208,16 @@ snapshots: tslib@2.8.1: {} + tsrs@0.24.0-ts7.1.0-beta: + optionalDependencies: + '@tsrs/darwin-arm64': 0.24.0-ts7.1.0-beta + '@tsrs/darwin-x64': 0.24.0-ts7.1.0-beta + '@tsrs/linux-arm64-gnu': 0.24.0-ts7.1.0-beta + '@tsrs/linux-arm64-musl': 0.24.0-ts7.1.0-beta + '@tsrs/linux-x64-gnu': 0.24.0-ts7.1.0-beta + '@tsrs/linux-x64-musl': 0.24.0-ts7.1.0-beta + '@tsrs/win32-x64': 0.24.0-ts7.1.0-beta + tsx@4.23.15: dependencies: esbuild: 0.28.2 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index c9718053da..abdc4f7f07 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.24.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..555ecd37a4 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,60 @@ Commands::Check { args } => commands::delegate::execute(cwd, "check", &args).awa ### NAPI Binding -The `Check` variant is defined in `SynthesizableSubcommand` in `packages/cli/binding/src/cli.rs`. The check command's orchestration logic lives in its own module at `packages/cli/binding/src/check/`, following the same directory-per-command pattern as `exec/`: +The `Check` variant is defined in `SynthesizableSubcommand` in `packages/cli/binding/src/cli/types.rs`. The check command's orchestration logic lives in its own module at `packages/cli/binding/src/check/`, following the same directory-per-command pattern as `exec/`: - `check/mod.rs` — `execute_check()` orchestration (runs fmt + lint sequentially, handles `--fix` re-formatting) - `check/analysis.rs` — Output analysis types (`CheckSummary`, `LintMessageKind`, etc.), parsers, and formatting helpers -The check module reuses `SubcommandResolver` and `resolve_and_capture_output` from `cli.rs` to resolve and run the underlying fmt/lint commands. +The check module reuses `SubcommandResolver` and `resolve_and_capture_output` from `cli/resolver.rs` and `cli/execution.rs` to resolve and run the underlying fmt/lint commands. ### TypeScript Side -No new resolver needed — `vp check` reuses existing `resolve-lint.ts` and `resolve-fmt.ts`. +`vp check` reuses `resolve-lint.ts` and `resolve-fmt.ts`. + +### Experimental type-aware backend + +See the [backend replacement RFC](./tsrs-lint-backend.md) for the rationale, +compatibility considerations, and release conditions. + +This draft replaces the bundled Go backend with `tsrs@0.24.0-ts7.1.0-beta`, +a Rust port of the TypeScript 7 compiler. `resolve-lint.ts` selects the bundled +`tsrs` package and passes its `bin/tsrs-lint` launcher to Oxlint through +`OXLINT_TSGOLINT_PATH`. The same resolver serves `vp lint` and `vp check`. +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 has no Windows ARM64 binary. This draft adds no automatic +Go fallback. A release for all supported Vite+ platforms needs an upstream +Windows ARM64 binary or an explicit fallback. Runtime smoke tests cover macOS +ARM64. The Codespaces comparison also validates both backends on Linux x64 +with Vite+ and vinext. Other platforms still need runtime validation. + +The backend switch preserves the existing formatting phase, lint configuration, +and failure handling. Smoke fixtures compare diagnostics, suppression comments, +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 +251,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 +310,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 +329,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..1df6ae6d7c 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.24.0-ts7.1.0-beta ├── depends on oxfmt@0.57.0 └── compiles vite-task (built 2026-08-06T09:30:00Z, revision ) ``` -These versions show the repository state when this RFC was written. They are -not part of the command contract. +These versions illustrate the output format. They are not part of the command +contract. The tsrs row reflects the experimental default backend. The readable tree can repeat a shared node to show two relationships. JSON has one entry for each node ID. @@ -209,12 +209,19 @@ Filters match a node's: Initial aliases include: -| Query | Node | -| ---------------- | ------------------------------ | -| `vite-plus-core` | `@voidzero-dev/vite-plus-core` | -| `tsgolint` | `oxlint-tsgolint` | -| `vite-task` | Vite Task | -| `oxc-resolver` | Oxc Resolver | +| Query | Node | +| ----------------- | ------------------------------ | +| `vite-plus-core` | `@voidzero-dev/vite-plus-core` | +| `tsrs-lint` | `tsrs` | +| `tsgolint` | `tsrs` | +| `oxlint-tsgolint` | `tsrs` | +| `vite-task` | Vite Task | +| `oxc-resolver` | Oxc Resolver | + +The experimental manifest uses `tsrs` as the backend node ID and package name. +It retains `tsgolint` and `oxlint-tsgolint` as aliases for existing queries. +These aliases select the bundled tsrs node; they do not report a separate Go +dependency. Older manifests retain their original `oxlint-tsgolint` node. Package and tool names are case-sensitive. This matches npm and Cargo names. @@ -412,6 +419,16 @@ the local Vite+ release to use the command. To show the global release, run It reads tool rows from the manifest. Use `vp toolchain` to select tools and show relationships or engine details. +For this experimental backend, the local version output and `vite-plus/versions` +report the `tsrs` version. The generated export uses the `tsrs` key in place of +`oxlint-tsgolint`. The global version command selects the tsrs row when the local +manifest contains it. It retains the Go row for older local manifests. + +The manifest describes the bundled backend. An `OXLINT_TSGOLINT_PATH` override +does not change its nodes or the version exports. The manifest also does not +certify platform support: the pinned tsrs release lacks a Windows ARM64 binary. +See the [backend design](./check-command.md#experimental-type-aware-backend). + ## Relationship to `vp why` `vp why` sends the command to the detected package manager. It keeps the @@ -486,21 +503,21 @@ The documentation states that `vp why` is a package-manager operation. ### CLI snapshot tests -New cases belong in `crates/vite_cli_snapshots/tests/cli_snapshots/`: - -| Scenario | Expected coverage | -| --------------------------------- | -------------------------------------------------------- | -| Full local manifest | Complete tree and local source | -| `vp toolchain vite` | Core, Vite, Rolldown, Oxc, and Oxc Resolver chain | -| Multiple filters | Stable union of branches | -| Alias filter | `vite-plus-core`, `vite-task`, and `tsgolint` resolution | -| `--json` | Valid JSON without header, styling, or trailing text | -| No local package | Global source selection | -| `--global` inside a local project | Global source forced | -| Old local Vite+ package | Unknown-command failure from the local CLI | -| Unknown tool | Status 1 and `vp why` hint | -| `vp why vite` | Package-manager output followed by toolchain hint | -| `vp why vite --json` | Unmodified JSON package-manager output | +New cases belong in `crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/`: + +| Scenario | Expected coverage | +| --------------------------------- | -------------------------------------------------------------------------- | +| Full local manifest | Complete tree and local source | +| `vp toolchain vite` | Core, Vite, Rolldown, Oxc, and Oxc Resolver chain | +| Multiple filters | Stable union of branches | +| Alias filter | `vite-plus-core`, `vite-task`, `tsrs-lint`, and legacy Go alias resolution | +| `--json` | Valid JSON without header, styling, or trailing text | +| No local package | Global source selection | +| `--global` inside a local project | Global source forced | +| Old local Vite+ package | Unknown-command failure from the local CLI | +| Unknown tool | Status 1 and `vp why` hint | +| `vp why vite` | Package-manager output followed by toolchain hint | +| `vp why vite --json` | Unmodified JSON package-manager output | Release artifact tests load the same manifest with each platform binding. The tests compare native versions with the compiled release inputs. diff --git a/rfcs/tsdown-native-declarations.md b/rfcs/tsdown-native-declarations.md new file mode 100644 index 0000000000..2b7f5c95d1 --- /dev/null +++ b/rfcs/tsdown-native-declarations.md @@ -0,0 +1,172 @@ +# RFC: Native declaration generation for `vp pack` + +Status: Proposed. Researched on 2026-11-11 against [PR #3352](https://github.com/voidzero-dev/vite-plus/pull/3352), head `8e76399da0512e2b5fcc6bb0774d4e9cbc967015`. + +## Decision + +Use the `tsrs` dependency introduced by #3352 to generate declarations for projects that do not install `typescript`. Select `dts.generator: 'tsrs'` through a Vite+ adapter. Keep the project's existing compiler when it supplies TypeScript 5, 6, or 7, and honor an explicit generator. Preserve Oxc generation for isolated declarations. + +The latest declaration plugin supports this integration. Its automatic selection does not select `tsrs`, even when that package is installed. Upgrading dependencies alone therefore does not provide the proposed default. The `tsrs` generator uses the TypeScript 7.1-compatible native API; `generator: 'tsgo'` remains a separate choice that loads the Microsoft `typescript` package. [Generator implementation](https://github.com/sxzz/rolldown-plugin-dts/blob/v0.30.1/src/options.ts), [native API loading](https://github.com/sxzz/rolldown-plugin-dts/blob/v0.30.1/src/tsgo.ts). + +This makes a project-level `typescript` dependency unnecessary for supported `vp check` and `vp pack` workflows. It does not replace `tsc` tasks, the editor's compiler, Vue/Volar integrations, or dependencies that use the classic TypeScript API. Application type packages such as `@types/node` remain project dependencies when needed. Existing projects keep `typescript` unless a separate dependency audit establishes that they can remove it. + +Ship the native default first in the experimental release built on #3352. Promote it after the declaration, packaging, and platform gates below pass. TypeScript 7.1 is still a beta, and upstream marks the `tsrs` generator as highly experimental. + +## Version baseline + +These are researched versions, not floating release requirements. Refresh the matrix before implementation and pin the versions used for release qualification. + +| Component | PR #3352 baseline | Proposed target | +| --- | --- | --- | +| `tsdown`, `@tsdown/css`, `@tsdown/exe` | `0.23.4` | `0.23.7`, kept in lockstep | +| Declaration plugin bundled through tsdown | `rolldown-plugin-dts@0.28.8` | `0.30.1` | +| Core's direct declaration-plugin development dependency | Catalog `^0.28.0` | Align with the bundled `0.30.1` | +| Native compiler supplied by Vite+ | `tsrs@0.24.0-ts7.1.0-beta`, used by lint/check | Same exact package, also used by pack | +| Microsoft compiler used as a parity reference | Project/repository compiler varies | Exact `typescript@7.1.0-beta` matching tsrs's source pin | +| Bundled Rolldown | `1.2.18` | A validated `1.3.x` integration; tsdown `0.23.7` requires `~1.3.0` | + +The npm registry currently labels TypeScript `7.0.2` as `latest`, `7.1.0-beta` as `beta`, and `7.1.0-dev.20261111.1` as `next`. The design targets the beta API already supported by the pinned tsrs release, not a presumed stable TypeScript 7.1 release. [TypeScript registry metadata](https://registry.npmjs.org/typescript), [tsrs release and upstream pin](https://github.com/oxc-project/tsrs/blob/v0.24.0/docs/releases/0.24.0.md). + +Two integration constraints precede the dependency update: + +- Core redirects tsdown's Rolldown imports to its shared bundle. Updating tsdown does not install an independent runtime Rolldown for pack. Qualify a Vite version with the selected Rolldown `1.3.x` before changing that shared bundle. If the combination is not ready, defer the tsdown upgrade. Do not force the current bundle past its supported range. [Core import rewriting](../packages/core/build-support/rewrite-imports.ts), [Rolldown 1.3 release warning](https://github.com/rolldown/rolldown/releases/tag/v1.3.0). +- Both target packages require Node `^22.18.0 || ^24.11.0 || >=26.0.0`. The PR's local CLI already declares this range; core still advertises Node 20 and a broader `>=24.11.0` range. Align core's metadata and document the change for direct core consumers. `vp migrate` can report incompatible runtime pins, but cannot make these dependencies run on Node 20. [tsdown manifest](https://github.com/rolldown/tsdown/blob/v0.23.7/package.json), [plugin manifest](https://github.com/sxzz/rolldown-plugin-dts/blob/v0.30.1/package.json). + +## User behavior + +A plain TypeScript library with a supported tsconfig needs only its normal `vite-plus` installation: + +```ts +import { defineConfig } from 'vite-plus'; + +export default defineConfig({ + pack: { + entry: ['src/index.ts'], + dts: true, + }, +}); +``` + +If the project does not provide `typescript`, `vp pack` selects bundled tsrs. If it already provides TypeScript 5/6, the same configuration keeps the classic compiler. An existing project can select bundled tsrs without removing its other compiler: + +```ts +pack: { + dts: { generator: 'tsrs' }, +} +``` + +Keep declaration enablement separate from compiler selection. Preserve `dts: false`, CLI overrides, executable defaults, and tsdown's inference from `types`, `typings`, `exports.types`, and `compilerOptions.declaration`. Installing tsrs must not start generating declarations for projects that previously disabled them. Apply the adapter after tsdown has resolved this enablement, including array/workspace configurations and watch reloads. [tsdown configuration resolution](https://github.com/rolldown/tsdown/blob/v0.23.7/src/config/options.ts). + +Keep `dts.typecheck` at its upstream default, `false`. Declaration emit can still fail on emit diagnostics; a complete type check remains an explicit choice. Keep #3352's existing `lint.options.typeCheck` behavior. This upgrade does not turn on additional checking for all pack builds. + +### Generator selection + +Apply these rules in order for each enabled declaration build. Resolve project packages from the package that owns the build, with workspace inheritance bounded by the workspace root. + +| Condition | Behavior | +| --- | --- | +| Explicit generator string or custom `Generator` object | Preserve it and its options. Validate requirements; do not replace it after an error. | +| Implicit generator with Vue/Volar, `resolver: 'tsc'`, or a requested classic-only capability | Use a compatible project TypeScript 5/6 compiler. If unavailable, report the specific requirement and migration action. | +| `isolatedDeclarations: true`, without conflicting requirements | Keep Oxc. `typecheck: true` needs a checking generator; report the conflict instead of ignoring it. | +| Project TypeScript 5/6 | Keep `tsc` and the selected project version. | +| Project TypeScript exposes the required declaration API | Use `tsgo` with that package's module URL. | +| Project TypeScript 7.0 | Retain the deprecated CLI backend that plugin `0.30.1` still supplies. Report its migration path; do not assume later plugin minors retain it. | +| No project TypeScript, supported tsconfig and native options | Use bundled `tsrs`, with its module URL supplied by core. | +| Unsupported or broken compiler installation/configuration | Fail before emission with a focused diagnostic. Do not reinterpret a broken installation as an absent compiler. | + +An explicit `tsgo` selection continues to require a compatible Microsoft compiler. An explicit `tsrs.moduleUrl` or native executable path remains an advanced override. A `tsgo` options object alone is not a new generator selector; preserve upstream semantics and migrate old boolean selectors through the existing migration rules. + +Native eligibility requires a real tsconfig, a supported platform, and the required `async`/`fs` exports and `Program.prototype.getDeclarationEmit` capability. Check capabilities as well as versions. Classify unsupported options before creating build outputs. Do not retry a failing type check, compiler crash, or declaration emit through a different compiler: that can change published types and conceal errors. + +### Compatibility limits + +The native generators currently read program options from the on-disk tsconfig. They do not apply `dts.compilerOptions` or `dts.tsconfigRaw` to the compiler program. Declaration maps also require `compilerOptions.declarationMap` in that file. Preserve an existing classic compiler when these overrides matter; reject an incompatible explicit native selection with a migration diagnostic instead of silently ignoring settings. [Native option definitions](https://github.com/sxzz/rolldown-plugin-dts/blob/v0.30.1/src/options.ts). + +Retain TypeScript 5/6 for Vue/Volar and classic API consumers. Treat non-Volar custom languages as explicit native configurations that require `vfs` and filename mapping. Preserve `runExternalCode: false` by default. Content mappers require an explicit opt-in and cannot combine with native VFS in the current plugin. TypeScript 7.1's new API does not make old compiler API plugins interchangeable. [Plugin integrations](https://github.com/sxzz/rolldown-plugin-dts/blob/v0.30.1/README.md), [TypeScript 7.1 API](https://devblogs.microsoft.com/typescript/announcing-typescript-7-1-beta/). + +Native support makes the pinned compiler's language and library features available. It does not change `target`, `lib`, module resolution, runtime polyfills, or editor settings. Check syntax that also passes through Rolldown/Oxc, including import attributes, through the full pack pipeline before advertising support. A library that uses newer declaration syntax may need a higher minimum TypeScript version for its consumers; migration cannot downlevel arbitrary public types. + +## Packaging and implementation + +Keep ownership in the local packages. The global CLI continues to delegate to the project's `vite-plus`; it does not provide the compiler. + +1. Add the same exact `tsrs` production dependency to core that #3352 adds to the CLI. Keep the CLI's direct dependency for lint resolution. Package managers can deduplicate matching versions, but neither package may rely on hoisting to the other. Avoid shipping a second Go compiler as a default fallback. +2. Keep `tsrs` and its API subpaths external when rebuilding tsdown. Resolve the default `tsrs.moduleUrl` from core's own dependency. Preserve tsrs's SDK, platform binary, and adjacent standard libraries. The lint executable path is not a declaration API module URL. +3. Update `mergePackageJson()` so upstream peer merging cannot replace the direct tsrs dependency with an optional peer. Keep `typescript` and the framework peers optional. Include TypeScript 7.1 prereleases in the effective peer range: tsdown's broad stable range alone does not admit `7.1.0-beta`. +4. Audit the declaration plugin's new `yuku` dependency and its native packages. The existing build externalizes `yuku-parser@0.19.0`; plugin `0.30.1` uses `yuku@^0.27.0`. Replace the old packaging assumption with the resolved dependency graph and verify native loading from the published tarball. [Plugin dependencies](https://registry.npmjs.org/rolldown-plugin-dts/0.30.1). +5. Put compiler selection in one core pack adapter, used by CLI resolution and public `build`/`resolveUserConfig` entry points. Preserve the remaining `vite-plus/pack` exports and custom generators. Hook after effective package/tsconfig discovery and before declaration-plugin construction, rather than setting a generator once in `pack-bin.ts` before defaults are known. +6. Resolve explicitly selected project TypeScript from the owning package. The plugin's classic compiler loader currently has a module-global cache. Scope compiler contexts by resolved compiler URL and project, or obtain upstream provider injection before claiming mixed-version programmatic builds work. Do not change process-wide module resolution to switch compilers. [Classic compiler loading](https://github.com/sxzz/rolldown-plugin-dts/blob/v0.30.1/src/require.ts). +7. Preserve the declaration externalization rules for `vite-plus`, Vitest, PostCSS, and Lightning CSS, including when declarations are enabled by inference. Keep public `vite-plus/test*` specifiers in output. Verify ESM/CJS filenames, exports, maps, and ambient declarations against the existing pack fixtures. +8. Handle the expected native-backend notice without failing a successful build. `vp pack` defaults to `failOnWarn: true`; the plugin's experimental warning currently sets exit status 1 even when emit succeeds. Give that notice a structured upstream code and report it once as informational in the experimental Vite+ release. If a temporary adapter is needed, match only the pinned, known notice. Preserve failures for other warnings, including ignored compiler settings and missing maps. + +Prefer an upstream integration hook for the adapter. If a local bundle transform is necessary, anchor it to the pinned source shape, assert the expected replacement count, and cover both CLI and programmatic calls. Do not add another public `native: true` switch alongside `dts.generator`. + +Expose the declaration plugin version, bundled tsrs version, and TypeScript source version through toolchain metadata. Debug pack output should include the selected generator, compiler location, and selection reason. Check and pack can use different compilers when a project retains TypeScript; report those identities without claiming diagnostic equivalence. + +## `vp migrate` + +Extend the existing pack migration in [`crates/vp_migration/src/pack_config.rs`](../crates/vp_migration/src/pack_config.rs) and its TypeScript orchestration. Run it for existing Vite+ projects without requiring `--full`, including workspace packages and retained `tsdown.config.*` imports. Read the installed source toolchain before the final install, as the current versioned migration does. + +Separate schema updates from compiler adoption. Apply static, behavior-preserving schema changes on the normal upgrade path. Keep TypeScript 5/6/7 selection and dependencies unless a recognized obsolete native configuration requires a change. Unknown source versions take the conservative path; do not assume a fresh project. + +| Existing configuration | Automatic action | Boundary | +| --- | --- | --- | +| Top-level `dts.vue`, `build`, `parallel`, `eager`, `newContext` | Move them under `dts.tsc` and retain classic compiler selection where required. | Preserve explicit nested values; conflicting values or dynamic objects require a finding. | +| Top-level `dts.incremental` | Move `true`; for `false`, preserve the old behavior of following tsconfig rather than writing the new disabling override. | Conflicts with `dts.tsc.incremental` need review. Do not implement this as a blind key rename. | +| `dts.emitDtsOnly` / `dts.dtsInput` | Normalize equivalent static settings to `dts.dtsOnly`. | Conflicting aliases, or combination with `dts.entry`, need a resolved entry plan before editing. | +| `dts.entry` whose matches are already build entries | Keep the filter; preserve its old base directory when different from the build's `cwd`. | For static workspace paths, rewrite patterns relative to the resolved declaration cwd. Do not change compiler-config discovery by changing `dts.cwd` without checking it. | +| `dts.entry` that used to add declaration-only files | Add a separate declaration-only pack configuration with those inputs, and retain the original JS entry set. | Preserve names, output paths, maps, cleaning, and package exports. Dynamic globs/layouts or output collisions require review; never add JS outputs just to recover `.d.ts` files. | +| Explicit `tsgo` backed only by `@typescript/native-preview` | For supported plain TS projects with no custom executable/options, select bundled `tsrs`; preserve the other project compiler and type-check settings. | Run native eligibility checks. Keep the original config and dependencies when checks fail; report the blocker. | +| TypeScript 7.0 automatic/explicit `tsgo` | Keep the working `0.30.1` CLI bridge and identify it in the migration report. | Do not force a project-wide beta compiler upgrade. Native adoption remains an explicit `generator: 'tsrs'` change. | +| Custom `tsgo.path` | Report the changed contract: on 7.1 it selects an API server executable. | Do not copy a CLI binary path into `tsrs.path` or assume API compatibility. | +| Native compiler overrides or declaration maps configured only in `dts` | Create a project-owned pack tsconfig extending the original; set equivalent compiler options there and point only the affected pack config at it. | Move only static values, preserve effective `include`/`exclude`/`files`, references, relative paths, and project ownership. Keep shared configs unchanged. | + +The `dts.entry` and alias changes come from [tsdown 0.23.7](https://github.com/rolldown/tsdown/releases/tag/v0.23.7). The native-preview removal, executable-path change, and native map behavior come from [plugin 0.29](https://github.com/sxzz/rolldown-plugin-dts/releases/tag/v0.29.0). Preserve deprecated options through the compatibility window where the target plugin still accepts them; use migration to remove warnings and handle semantic changes. + +For a declaration-only split, generate a second `pack` array item using `dts: { dtsOnly: true }`, with no `dts.entry`. Run both through tsdown's existing shared cleanup in `buildWithConfigs`, and avoid competing export writes. Verify that watch cleanup retains each sibling's outputs. Only automate the split when the migrator can preserve the output contract. Otherwise report the exact missing declaration entries and retain the source configuration for review. [Shared cleanup implementation](https://github.com/rolldown/tsdown/blob/v0.23.7/src/build.ts). + +For a native-preview migration, remove that dependency only after checking scripts, imports, CI, shared workspace consumers, and package-manager catalogs. Retain standalone `tsgo` tasks that still use it. Do not add `typescript` to the generic bundled-package removal list, alias `typescript` to `tsrs`, or replace `tsc -b`/`tsc --emitDeclarationOnly` with `vp check`. + +Preserve comments, JSONC, workspace ownership, and explicit values. Use static AST transforms; do not evaluate a config to decide what to rewrite. Spreads, computed keys, conflicting aliases, unresolved package versions, and third-party config files produce actionable findings. Each finding names the file, unsupported setting, and next action. Running migration twice must produce no second diff. Stop a dependent rewrite if its prerequisite fails; do not leave a partially converted native configuration. + +Treat a configuration that the target toolchain cannot run as an upgrade blocker, not a successful migration with a warning. Identify it before the final dependency install and retain the previous toolchain when possible. If the user has already upgraded, report the incomplete migration and the supported rollback or manual edit. Keep harmless deprecations separate from these blockers in the migration report. + +For TypeScript 7 incompatibilities such as removed compiler options, retain the classic generator where possible. Report source-level type errors and public declaration incompatibilities for review. Do not weaken checking with `skipLibCheck`, `noCheck`, or disabled rules to make migration appear successful. + +## Release gates + +| Area | Required evidence | +| --- | --- | +| No project compiler | Published CLI/core tarballs emit inferred declarations with no `typescript`, native-preview, or project `tsrs`; the native standard libraries resolve. | +| Existing compilers | TypeScript 5.9, 6.0, 7.0, and 7.1 beta retain the intended generator. Explicit strings, custom generators, missing/corrupt compilers, and Oxc conflicts produce the documented outcomes. | +| Packaging | npm, pnpm with `hoist: false`, Yarn PnP, and Bun; workspace and standalone installs; local CLI with no global `vp`; public config types with declaration checking enabled. | +| Declarations | Compare old/new exports, `.d.ts`/`.d.mts`/`.d.cts`, maps, JSDoc, JS inputs, referenced projects, entry filters, and retained public imports. Compile representative consumer fixtures with the library's supported TS5/6/7 versions. | +| Native parity | Compare tsrs with exact `typescript@7.1.0-beta` using identical options and inputs. Compare older compilers separately to identify version changes rather than mislabeling them as Rust defects. Assert real process exit codes, including the default `failOnWarn` behavior. | +| Configuration | Arrays, callbacks, shared configs, `cwd`, disabled/inferred dts, CLI flags, inline compiler overrides, no tsconfig, Vue/Volar, VFS, and content-mapper opt-in. | +| Lifecycle | Watch edit/add/delete/config changes, failed rebuild recovery, cancellation, multiple projects/compiler versions, API cleanup, and no stale declarations or leftover native processes. | +| Migration | Existing Vite+ without `--full`, all supported package managers, catalogs, retained consumers, source-version detection, static rewrites, manual findings, and idempotence. | +| Platforms | macOS x64/ARM64, Linux x64/ARM64 glibc/musl, Windows x64; Windows ARM64 remains a blocker inherited from #3352 until tsrs ships and passes native validation. | + +Use existing [`command_pack_dts_compat`](../crates/vp_cli_snapshots/tests/cli_snapshots/fixtures/command_pack_dts_compat/snapshots.toml), pack package-manager/watch fixtures, [`pack.spec.ts`](../packages/cli/src/__tests__/pack.spec.ts), and migration tests. Add fixtures to the snapshot runner with the local registry. The native-preview fixture currently proves a supported path and must become an explicit migration regression rather than disappear. + +Measure cold emit, warm rebuilds, and peak memory on the same projects and compiler settings. Lint benchmarks from #3352 do not establish declaration performance or correctness. Keep tsrs's reference defaults; do not enable its fast preset as part of this change. + +## Delivery order + +1. Qualify the Vite/Rolldown combination and update tsdown, its extensions, declaration plugin, native dependencies, and core engine/peer metadata. Retain supported legacy compiler paths. +2. Add the shared pack adapter, native dependency resolution, toolchain reporting, and the no-TypeScript path. Keep this in the experimental release until its gates pass. +3. Add the versioned migration transformations and manual findings before releasing the upgrade to existing projects. Include the native-preview and `dts.entry` transitions. +4. Update pack documentation and plain TypeScript templates after validation. Omit TypeScript from new templates only when their scripts, editor setup, and framework dependencies permit it. Retain it in templates that need the classic API. + +Keep these as small PRs stacked on #3352. Do not make a stable release until Windows ARM64 and the shared Rolldown integration have a supported outcome. The fallback release plan is to retain the previous qualified pack toolchain while completing these prerequisites, rather than introduce an untested compiler fallback. + +## Design probe + +On macOS ARM64 with Node `24.22.2`, an isolated npm installation of `tsdown@0.23.7`, `rolldown-plugin-dts@0.30.1`, and `tsrs@0.24.0-ts7.1.0-beta` confirmed these behaviors: + +- `typescript` could not resolve, and upstream `dts: true` failed with the missing-TypeScript diagnostic. +- Explicit `generator: 'tsrs'` emitted `export declare const answer: number;` without installing TypeScript. +- Explicit `typecheck: true` rejected a string assigned to that numeric export. +- With `failOnWarn: true`, emit completed and `build()` resolved, but the process exited with status 1 because of the experimental-backend warning. The adapter must handle this before the native default can ship. + +The probe used published packages from npm because the question depends on their shipped native API. Installation disabled lifecycle scripts and automatic peer installation. This checks the upstream integration premise only; it does not validate the proposed Vite+ adapter, migration, cross-platform support, or release performance. diff --git a/rfcs/tsrs-lint-backend.md b/rfcs/tsrs-lint-backend.md new file mode 100644 index 0000000000..40ec37467f --- /dev/null +++ b/rfcs/tsrs-lint-backend.md @@ -0,0 +1,42 @@ +# RFC: Replace tsgolint with tsrs-lint + +Status: Proposed; experimental default in this draft. + +## 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.24.0-ts7.1.0-beta` | +| Development-only backend | None | `oxlint-tsgolint@7.0.2006` for comparisons | +| Backend platform packages | `@oxlint-tsgolint/*`: six optional packages | `@tsrs/*`: seven optional packages | + +Production installs use tsrs; repository development retains Go for comparisons. +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. +- Windows ARM64 remains a release blocker: tsrs has no binary for that target, + and this integration has no automatic Go fallback. + +The [benchmark report](../bench/tsrs-lint.md) contains historical timing, memory, +and disk-usage measurements. Refresh them for the release candidate.