diff --git a/.changeset/tsc-rs.md b/.changeset/tsc-rs.md new file mode 100644 index 000000000..355d1191f --- /dev/null +++ b/.changeset/tsc-rs.md @@ -0,0 +1,5 @@ +--- +'svelte-check': minor +--- + +feat: add `--tsc-rs` flag to use tsc-rs, a Rust port of the TypeScript 7 compiler, for TypeScript diagnostics diff --git a/packages/svelte-check/README.md b/packages/svelte-check/README.md index dfeac46bd..1742c545e 100644 --- a/packages/svelte-check/README.md +++ b/packages/svelte-check/README.md @@ -80,6 +80,7 @@ npm install --save-dev typescript@~6 @typescript/native@npm:typescript@7 | `--threshold ` | Filters the diagnostics to display. `error` will output only errors while `warning` will output warnings and errors. | | `--incremental` | Opts into TypeScript's incremental build cache, which speeds up subsequent runs. Saved within `.svelte-kit` or if not available within `.svelte-check`. This might result in slightly different type check outcomes, and certain patterns are not supported. Specifically, anything that is not in the root dir of your tsconfig.json and is a Svelte file will not be properly loaded and type-checked. | | `--tsgo` | Use TypeScript's Go implementation. Needs to have `@typescript/native-preview` installed. Subject to the same limitations as `--incremental` | +| `--tsc-rs` | Use tsc-rs, a Rust port of TypeScript's Go implementation. Needs to have `tsc-rs` installed. Subject to the same limitations as `--incremental` | ### FAQ diff --git a/packages/svelte-check/package.json b/packages/svelte-check/package.json index 084b0a65a..ac8b4e38f 100644 --- a/packages/svelte-check/package.json +++ b/packages/svelte-check/package.json @@ -58,6 +58,7 @@ "svelte": "^4.2.19", "svelte-language-server": "workspace:*", "svelte2tsx": "workspace:*", + "tsc-rs": "~0.7.0", "typescript": "^6.0.3", "vscode-languageserver": "8.0.2", "vscode-languageserver-protocol": "3.17.2", diff --git a/packages/svelte-check/src/incremental.ts b/packages/svelte-check/src/incremental.ts index 0d04a26e5..60627140e 100644 --- a/packages/svelte-check/src/incremental.ts +++ b/packages/svelte-check/src/incremental.ts @@ -466,22 +466,27 @@ export function writeOverlayTsconfig( } /** - * Spawns a TypeScript compiler process (tsc or tsgo) to perform type-checking + * Spawns a TypeScript compiler process (tsc, tsgo or tsc-rs) to perform type-checking * and collects the diagnostics from its output. * * @param tsconfigPath - Path to the tsconfig.json to use for compilation - * @param useTsgo - When true, uses the experimental native TypeScript compiler (tsgo) + * @param compiler - Which compiler to use: tsc, the native TypeScript compiler (tsgo) or its Rust port (tsc-rs) * @param incremental - Whether to enable incremental compilation for faster subsequent runs * @param cwd - Working directory for the TypeScript compiler process * @returns Parsed diagnostics containing file paths, positions, severity, and messages */ export function runTypeScriptDiagnostics( tsconfigPath: string, - useTsgo: boolean, + compiler: 'tsc' | 'tsgo' | 'tsc-rs', incremental: boolean, cwd: string ): Promise { - const binPath = useTsgo ? getTsGoBinPath(tsconfigPath) : require.resolve('typescript/bin/tsc'); + const binPath = + compiler === 'tsc-rs' + ? getTscRsBinPath(tsconfigPath) + : compiler === 'tsgo' + ? getTsGoBinPath(tsconfigPath) + : require.resolve('typescript/bin/tsc'); if (!binPath || !fs.existsSync(binPath) || !fs.statSync(binPath).isFile()) { throw new Error('Failed to locate TypeScript command-line executable.'); @@ -569,6 +574,24 @@ function getTsGoBinPath(tsconfigPath: string): string | undefined { return binPathRelative ? path.join(path.dirname(pkg.path), binPathRelative) : undefined; } +function getTscRsBinPath(tsconfigPath: string): string | undefined { + let pkgPath: string; + try { + pkgPath = require.resolve('tsc-rs/package.json', { paths: [tsconfigPath, __dirname] }); + } catch (e) { + throw new Error( + 'svelte-check --tsc-rs requires tsc-rs to be installed in the workspace. ' + + 'You can install it via the following command.\n' + + 'npm install --save-dev tsc-rs\n' + ); + } + + const bin = require(pkgPath).bin; + const binPathRelative = typeof bin === 'string' ? bin : bin?.['tsc-rs']; + + return binPathRelative ? path.join(path.dirname(pkgPath), binPathRelative) : undefined; +} + /** * Maps TypeScript CLI diagnostics back to their original Svelte source files. * For Svelte files, uses source maps to translate positions from the generated diff --git a/packages/svelte-check/src/index.ts b/packages/svelte-check/src/index.ts index cd3f95f15..c12b7ca77 100644 --- a/packages/svelte-check/src/index.ts +++ b/packages/svelte-check/src/index.ts @@ -447,8 +447,11 @@ async function runWithVirtualFiles( writer: Writer ): Promise { if (!opts.tsconfig) { - throw new Error('`--incremental` / `--tsgo` requires a tsconfig/jsconfig file'); + throw new Error( + '`--incremental` / `--tsgo` / `--tsc-rs` requires a tsconfig/jsconfig file' + ); } + const compiler = opts.tscRs ? 'tsc-rs' : opts.tsgo ? 'tsgo' : 'tsc'; const emitResult = await emitSvelteFiles( opts.workspaceUri.fsPath, @@ -460,12 +463,13 @@ async function runWithVirtualFiles( opts.tsconfig, emitResult, opts.incremental, - opts.tsgo + // tsc-rs is a port of tsgo, so it needs the same overlay + compiler !== 'tsc' ); const tsDiagnostics = mapCliDiagnosticsToLsp( await runTypeScriptDiagnostics( overlayTsconfig, - opts.tsgo, + compiler, opts.incremental, opts.workspaceUri.fsPath ), @@ -600,6 +604,10 @@ parseOptions(async (opts) => { watch: opts.watch }; + if (opts.tscRs && opts.tsgo) { + throw new Error('--tsc-rs cannot be used with --tsgo'); + } + if (opts.tsgoExperimental) { if (!opts.tsconfig) { throw new Error('--tsgo-experimental-api requires a tsconfig/jsconfig file'); @@ -610,6 +618,9 @@ parseOptions(async (opts) => { if (opts.tsgo) { throw new Error('--tsgo-experimental-api cannot be used with --tsgo'); } + if (opts.tscRs) { + throw new Error('--tsgo-experimental-api cannot be used with --tsc-rs'); + } const pkg = tryParseTsGoVersion(opts.tsconfig); if (!pkg) { throw new Error(formatTsGoNotFoundError('--tsgo-experimental-api')); @@ -642,7 +653,7 @@ parseOptions(async (opts) => { svelteCheckOptions.experimental = { tsgo: { apiModule, astModule } }; } - const useVirtualFiles = opts.incremental || opts.tsgo; + const useVirtualFiles = opts.incremental || opts.tsgo || opts.tscRs; if (useVirtualFiles && opts.watch) { await watchWithVirtualFiles(opts, writer); } else if (useVirtualFiles) { diff --git a/packages/svelte-check/src/options.ts b/packages/svelte-check/src/options.ts index d3b4259fe..98b6a6f8b 100644 --- a/packages/svelte-check/src/options.ts +++ b/packages/svelte-check/src/options.ts @@ -10,6 +10,7 @@ export interface SvelteCheckCliOptions { preserveWatchOutput: boolean; incremental: boolean; tsgo: boolean; + tscRs: boolean; tsgoExperimental: boolean; tsconfig?: string; config?: string; @@ -47,9 +48,14 @@ export function parseOptions(cb: (opts: SvelteCheckCliOptions) => any) { 'Use tsgo for TypeScript diagnostics. Requires writing transpiled Svelte files to disk (.svelte-kit if available, else .svelte-check)', false ) + .option( + '--tsc-rs', + 'Use tsc-rs (a Rust port of the TypeScript 7 compiler) for TypeScript diagnostics. Requires writing transpiled Svelte files to disk (.svelte-kit if available, else .svelte-check)', + false + ) .option( '--tsgo-experimental-api', - 'Use tsgo for TypeScript diagnostics with the experimental API. Cannot be used with --incremental or --tsgo. Experimental feature, might break without warning.', + 'Use tsgo for TypeScript diagnostics with the experimental API. Cannot be used with --incremental, --tsgo or --tsc-rs. Experimental feature, might break without warning.', false ) .option( @@ -106,6 +112,7 @@ export function parseOptions(cb: (opts: SvelteCheckCliOptions) => any) { preserveWatchOutput: !!opts.preserveWatchOutput, incremental: !!opts.incremental, tsgo: !!opts.tsgo, + tscRs: !!opts['tsc-rs'], tsconfig, config, filePathsToIgnore: opts.ignore?.split(',') || [], diff --git a/packages/svelte-check/test-sanity.js b/packages/svelte-check/test-sanity.js index 7043e5a7e..5569d322c 100644 --- a/packages/svelte-check/test-sanity.js +++ b/packages/svelte-check/test-sanity.js @@ -32,6 +32,7 @@ let failed = 0; * @param {boolean} [opts.incremental] * @param {boolean} [opts.tsgoExp] * @param {boolean} [opts.tsgo] + * @param {boolean} [opts.tscRs] * @param {ExpectedError[]} [opts.errors] */ function test(name, opts) { @@ -53,6 +54,9 @@ function test(name, opts) { if (opts.tsgo) { args.push('--tsgo'); } + if (opts.tscRs) { + args.push('--tsc-rs'); + } let stdout; try { @@ -249,6 +253,12 @@ test('clean project --tsgo', { tsgo: true }); +test('clean project --tsc-rs', { + workspace: './test-success', + tsconfig: './tsconfig.json', + tscRs: true +}); + const errors = [ { file: 'Index.svelte', @@ -335,6 +345,13 @@ test('project with errors --tsgo', { errors }); +test('project with errors --tsc-rs', { + workspace: './test-error', + tsconfig: './tsconfig.json', + tscRs: true, + errors +}); + // A compiler that dies must not look like a clean run, and one that dies after reporting // some diagnostics must not look like a completed one. testCrashedCompiler( diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f964f9fe1..d2e74599c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -194,6 +194,9 @@ importers: svelte2tsx: specifier: workspace:* version: link:../svelte2tsx + tsc-rs: + specifier: ~0.7.0 + version: 0.7.0 typescript: specifier: ^6.0.3 version: 6.0.3 @@ -833,6 +836,42 @@ packages: Deprecated: no longer maintained and no longer used by Sinon packages. See https://github.com/sinonjs/nise/issues/243 for replacement details. + '@tsc-rs/darwin-arm64@0.7.0': + resolution: {integrity: sha512-3execGkNh7DXNZr03dhQP2wqmsjB1Q4OJnTMgKIibjeQMsjNK8HTalJZPIG6PkiXeusXUweHP3+4yJjbi5auNw==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [darwin] + + '@tsc-rs/darwin-x64@0.7.0': + resolution: {integrity: sha512-Wi3CNYWgyIOcWTQjihXCFPTul+aTEXVq1og8rO22Cq6/Mz6lKk+9YSXycWOkJql6D0uE0l/S1nMStq4mx7ydpA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [darwin] + + '@tsc-rs/linux-arm64@0.7.0': + resolution: {integrity: sha512-sm+xPELgQGKOoFeklJAmIi99Fi3fIUFgGPYQclC/IiVdUTzHtKw6yg6JRNEI74JIaSxGRAGbtmHcVvTkC93ZLw==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [linux] + + '@tsc-rs/linux-x64@0.7.0': + resolution: {integrity: sha512-gyDPxVNmasL/RMPXzutxhaUqyLlPLTcPhWl5pWCkuZdV0dUfGpR4PG5h1Nht9Db3MMHs9ixzl+uS6Nc5+qA/jg==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [linux] + + '@tsc-rs/win32-arm64@0.7.0': + resolution: {integrity: sha512-dPLJLH5iXgBzMRiuSGz4awMu8dxOI2iXRN0ZDqs/LolZ1ShbjmgIcH1bwQeObdyFwyen44c6vK8mFIiJELsv4Q==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [win32] + + '@tsc-rs/win32-x64@0.7.0': + resolution: {integrity: sha512-n/bJeMWvr0KbBPYVXroe23ia82Q6ZizUBYhgoZvsUasK4RfZ/rsJdIoP1Jpcw0ONXEbC8AKwHVi8BSOiUlSOuw==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [win32] + '@tsconfig/node10@1.0.9': resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} @@ -2015,6 +2054,11 @@ packages: '@swc/wasm': optional: true + tsc-rs@0.7.0: + resolution: {integrity: sha512-diVCBljoXLQjvXRkIuamR3QRrmNyqgYDePKA5SFXTSDPSjku2a2JXj/bOxG4DoPUbYkzWl/XFu1689z/46TCaA==} + engines: {node: '>=16.20.0'} + hasBin: true + tslib@2.5.2: resolution: {integrity: sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA==} @@ -2690,6 +2734,24 @@ snapshots: '@sinonjs/text-encoding@0.7.2': {} + '@tsc-rs/darwin-arm64@0.7.0': + optional: true + + '@tsc-rs/darwin-x64@0.7.0': + optional: true + + '@tsc-rs/linux-arm64@0.7.0': + optional: true + + '@tsc-rs/linux-x64@0.7.0': + optional: true + + '@tsc-rs/win32-arm64@0.7.0': + optional: true + + '@tsc-rs/win32-x64@0.7.0': + optional: true + '@tsconfig/node10@1.0.9': {} '@tsconfig/node12@1.0.11': {} @@ -3840,6 +3902,15 @@ snapshots: v8-compile-cache-lib: 3.0.1 yn: 3.1.1 + tsc-rs@0.7.0: + optionalDependencies: + '@tsc-rs/darwin-arm64': 0.7.0 + '@tsc-rs/darwin-x64': 0.7.0 + '@tsc-rs/linux-arm64': 0.7.0 + '@tsc-rs/linux-x64': 0.7.0 + '@tsc-rs/win32-arm64': 0.7.0 + '@tsc-rs/win32-x64': 0.7.0 + tslib@2.5.2: {} type-detect@4.0.8: {}