diff --git a/packages/vscode-typescript/src/session.ts b/packages/vscode-typescript/src/session.ts index 46303606b836f..a9107f3dbc15c 100644 --- a/packages/vscode-typescript/src/session.ts +++ b/packages/vscode-typescript/src/session.ts @@ -20,6 +20,7 @@ import { getExe, getWorkspaceTsdkConfigValue, getWorkspaceTsdkForPrompt, + pathHasTsserverJs, readNativePreviewConfig, resolveTsdkPath, resolveTsdkPathToExe, @@ -498,18 +499,30 @@ async function getStradaExtensionVersion(extensionId: string, pathToTypescript: } } +const workspacePackageSubpaths: readonly (readonly string[])[] = [ + ["node_modules", "typescript"], + ["node_modules", "@typescript", "native-preview"], +]; + async function findWorkspaceNativePreviewPackages(): Promise { const results: DetectedVersion[] = []; for (const folder of vscode.workspace.workspaceFolders ?? []) { - const packagePath = vscode.Uri.joinPath(folder.uri, "node_modules", "@typescript", "native-preview"); - const resolved = await resolveTsdkPathToExe(path.normalize(packagePath.fsPath)); - if (!resolved) continue; - results.push({ - folder, - version: resolved?.version ?? "unknown", - tsdkPath: path.normalize(packagePath.fsPath), - exePath: resolved?.path ?? "", - }); + for (const candidate of workspacePackageSubpaths) { + const packagePath = vscode.Uri.joinPath(folder.uri, ...candidate); + const normalizedPath = path.normalize(packagePath.fsPath); + if (await pathHasTsserverJs(normalizedPath)) { + continue; + } + const resolved = await resolveTsdkPathToExe(normalizedPath); + if (!resolved) continue; + results.push({ + folder, + version: resolved.version ?? "unknown", + tsdkPath: normalizedPath, + exePath: resolved.path ?? "", + }); + break; + } } return results; } @@ -655,7 +668,7 @@ async function promptSelectVersion(context: vscode.ExtensionContext, client: Cli /** * If the workspace has a tsdk setting pending consent, or has - * `@typescript/typescript` installed in node_modules, prompt the user + * TypeScript 7 installed in node_modules, prompt the user * to allow using it. */ export async function promptUseWorkspaceVersion(context: vscode.ExtensionContext): Promise { @@ -694,7 +707,7 @@ export async function promptUseWorkspaceVersion(context: vscode.ExtensionContext } } else { - // No workspace tsdk config, but check if native-preview is installed + // No workspace tsdk config, but check if TypeScript 7 is installed // in the workspace's node_modules. const workspaceVersions = await findWorkspaceNativePreviewPackages(); if (workspaceVersions.length === 0) return; diff --git a/packages/vscode-typescript/src/util.ts b/packages/vscode-typescript/src/util.ts index a299423f81948..4b18a296b667f 100644 --- a/packages/vscode-typescript/src/util.ts +++ b/packages/vscode-typescript/src/util.ts @@ -299,7 +299,7 @@ async function getTsdkCandidates(options?: { nativeOnly?: boolean; filter?: (can return candidates.filter(options?.filter ?? (() => true)).sort(compareExplicitConfigValues); } -async function pathHasTsserverJs(tsdkPath: string): Promise { +export async function pathHasTsserverJs(tsdkPath: string): Promise { const resolved = workspaceResolve(tsdkPath); for (const candidate of [vscode.Uri.joinPath(resolved, "tsserver.js"), vscode.Uri.joinPath(resolved, "lib", "tsserver.js")]) { try { diff --git a/packages/vscode-typescript/test/tsdkPackage.test.ts b/packages/vscode-typescript/test/tsdkPackage.test.ts index 5a96fa1246110..9894ade065a3a 100644 --- a/packages/vscode-typescript/test/tsdkPackage.test.ts +++ b/packages/vscode-typescript/test/tsdkPackage.test.ts @@ -6,15 +6,16 @@ import test, { describe } from "node:test"; import { resolvePackageExecutable } from "../src/tsdkPackage"; const platformPackage = `typescript-${process.platform}-${process.arch}`; +const nativePlatformPackage = `native-preview-${process.platform}-${process.arch}`; const exeSuffix = process.platform === "win32" ? ".exe" : ""; const exeName = `tsc${exeSuffix}`; const nativeExeName = `tsgo${exeSuffix}`; -function createPackage(root: string, relativePath: string): string { +function createPackage(root: string, relativePath: string, packageJson: object = {}): string { const packagePath = path.join(root, relativePath); fs.mkdirSync(packagePath, { recursive: true }); const packageJsonPath = path.join(packagePath, "package.json"); - fs.writeFileSync(packageJsonPath, "{}"); + fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, undefined, 2)); return packageJsonPath; } @@ -75,14 +76,20 @@ describe("tsdk package resolution", { concurrency: true }, () => { platformPath: `node_modules/@typescript/${platformPackage}`, }); - const nativePlatformPackage = `native-preview-${process.platform}-${process.arch}`; - testResolution("resolves the native-preview package", { + testResolution("resolves the @typescript/typescript native-preview package", { packagePath: "node_modules/@typescript/typescript", platformPath: `node_modules/@typescript/${nativePlatformPackage}`, platformPackage: nativePlatformPackage, exeName: nativeExeName, }); + testResolution("resolves the @typescript/native-preview package", { + packagePath: "node_modules/@typescript/native-preview", + platformPath: `node_modules/@typescript/${nativePlatformPackage}`, + platformPackage: nativePlatformPackage, + exeName: nativeExeName, + }); + testResolution("resolves a non-hoisted platform package", { packagePath: "node_modules/@typescript/native", platformPath: `node_modules/@typescript/native/node_modules/@typescript/${platformPackage}`,