From 6326ba7aff048232acccbb76c496825d465bc59f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 25 Aug 2026 21:16:33 +0000 Subject: [PATCH 1/3] Initial plan From ec11239e6b4aa3289e8d171ebdebe779ba31de58 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 25 Aug 2026 21:20:17 +0000 Subject: [PATCH 2/3] Use configured tsdk in trusted workspaces Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com> --- packages/vscode-typescript/src/session.ts | 21 ++++++++------- packages/vscode-typescript/src/util.ts | 26 ++++++------------- .../vscode-typescript/src/workspaceTsdk.ts | 6 +++++ packages/vscode-typescript/test/index.test.ts | 1 + .../test/workspaceTsdk.test.ts | 18 +++++++++++++ 5 files changed, 45 insertions(+), 27 deletions(-) create mode 100644 packages/vscode-typescript/src/workspaceTsdk.ts create mode 100644 packages/vscode-typescript/test/workspaceTsdk.test.ts diff --git a/packages/vscode-typescript/src/session.ts b/packages/vscode-typescript/src/session.ts index 46303606b836f..6f0c8a50f4008 100644 --- a/packages/vscode-typescript/src/session.ts +++ b/packages/vscode-typescript/src/session.ts @@ -24,9 +24,13 @@ import { resolveTsdkPath, resolveTsdkPathToExe, updateWorkspaceTsdkConfig, - useWorkspaceTsdkStorageKey, workspaceConfigBase, } from "./util"; +import { + shouldUseWorkspaceTsdk, + suppressPromptWorkspaceTsdkStorageKey, + useWorkspaceTsdkStorageKey, +} from "./workspaceTsdk"; /** * SessionManager's lifetime is equal to that of the extension. It is responsible @@ -661,13 +665,12 @@ async function promptSelectVersion(context: vscode.ExtensionContext, client: Cli export async function promptUseWorkspaceVersion(context: vscode.ExtensionContext): Promise { if (!vscode.workspace.isTrusted) return; - const useWorkspaceTsdk = context.workspaceState.get(useWorkspaceTsdkStorageKey, false); - if (useWorkspaceTsdk) return; // already opted in - - const suppressKey = "typescript.native-preview.suppressPromptWorkspaceTsdk"; - if (context.workspaceState.get(suppressKey, false)) return; - const workspaceTsdk = await getWorkspaceTsdkForPrompt(); + const preference = context.workspaceState.get(useWorkspaceTsdkStorageKey); + const promptSuppressed = context.workspaceState.get(suppressPromptWorkspaceTsdkStorageKey, false); + if (preference === true || workspaceTsdk !== undefined && shouldUseWorkspaceTsdk(true, preference, promptSuppressed)) return; + if (promptSuppressed) return; + if (workspaceTsdk !== undefined) { // The workspace config already specifies a tsdk location, but the // user hasn't consented to using it. Just need their approval. @@ -690,7 +693,7 @@ export async function promptUseWorkspaceVersion(context: vscode.ExtensionContext await vscode.commands.executeCommand("typescript.native-preview.restart"); } else if (result === suppress) { - await context.workspaceState.update(suppressKey, true); + await context.workspaceState.update(suppressPromptWorkspaceTsdkStorageKey, true); } } else { @@ -718,7 +721,7 @@ export async function promptUseWorkspaceVersion(context: vscode.ExtensionContext await vscode.commands.executeCommand("typescript.native-preview.restart"); } else if (result === suppress) { - await context.workspaceState.update(suppressKey, true); + await context.workspaceState.update(suppressPromptWorkspaceTsdkStorageKey, true); } } } diff --git a/packages/vscode-typescript/src/util.ts b/packages/vscode-typescript/src/util.ts index a299423f81948..a195c2ff471df 100644 --- a/packages/vscode-typescript/src/util.ts +++ b/packages/vscode-typescript/src/util.ts @@ -2,6 +2,11 @@ import * as fs from "fs"; import * as path from "path"; import * as vscode from "vscode"; import { resolvePackageExecutable } from "./tsdkPackage"; +import { + shouldUseWorkspaceTsdk, + suppressPromptWorkspaceTsdkStorageKey, + useWorkspaceTsdkStorageKey, +} from "./workspaceTsdk"; export const aiConnectionString = "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255"; @@ -140,20 +145,6 @@ function workspaceResolve(relativePath: string): vscode.Uri { return vscode.Uri.file(relativePath); } -/** - * Memento used to control whether the user has opted into using a tsdk location defined - * in workspace settings. This is *not* a trust boundary - workspace trust is required - * before the extension will prompt to set this memento to true. This setting is here to - * provide users a way to opt out of using the workspace-provided tsdk without changing - * committed workspace settings, e.g. when the workspace tsdk is very outdated or the user - * is trialing a nightly TS version. Since the stored value is only a boolean, it does not - * protect against executing a different tsdk than the one the user originally opted into - * if the workspace settings or node_modules content changes - that's why workspace trust - * is always required, and why the prompts that set this value should not be interpreted - * as indicating trust for a specific tsdk installation. - */ -export const useWorkspaceTsdkStorageKey = "typescript.native-preview.useWorkspaceTsdk"; - export async function getExe(context: vscode.ExtensionContext): Promise { for (const candidate of getTrustedTsdkCandidates(context, await getTsdkCandidates())) { const exe = await resolveTsdkPathToExe(candidate.value); @@ -192,11 +183,10 @@ async function classifyTsdk(tsdkPath: string): Promise<"lsp" | "tsserver" | unde } function getTrustedTsdkCandidates(context: vscode.ExtensionContext, tsdkCandidates: ExplicitConfigValue[]): ExplicitConfigValue[] { - // If tsdk is set at the workspace level, require both workspace trust and - // explicit user opt-in. Workspace trust can be revoked after the memento is - // set, so we must always check both. if (tsdkCandidates.some(candidate => candidate.target !== vscode.ConfigurationTarget.Global)) { - if (!vscode.workspace.isTrusted || !context.workspaceState.get(useWorkspaceTsdkStorageKey, false)) { + const preference = context.workspaceState.get(useWorkspaceTsdkStorageKey); + const promptSuppressed = context.workspaceState.get(suppressPromptWorkspaceTsdkStorageKey, false); + if (!shouldUseWorkspaceTsdk(vscode.workspace.isTrusted, preference, promptSuppressed)) { return tsdkCandidates.filter(candidate => candidate.target === vscode.ConfigurationTarget.Global); } } diff --git a/packages/vscode-typescript/src/workspaceTsdk.ts b/packages/vscode-typescript/src/workspaceTsdk.ts new file mode 100644 index 0000000000000..4901058907c8c --- /dev/null +++ b/packages/vscode-typescript/src/workspaceTsdk.ts @@ -0,0 +1,6 @@ +export const useWorkspaceTsdkStorageKey = "typescript.native-preview.useWorkspaceTsdk"; +export const suppressPromptWorkspaceTsdkStorageKey = "typescript.native-preview.suppressPromptWorkspaceTsdk"; + +export function shouldUseWorkspaceTsdk(isTrusted: boolean, preference: boolean | undefined, promptSuppressed: boolean): boolean { + return isTrusted && (preference ?? !promptSuppressed); +} diff --git a/packages/vscode-typescript/test/index.test.ts b/packages/vscode-typescript/test/index.test.ts index 1688ac53bc213..eeec628a549f4 100644 --- a/packages/vscode-typescript/test/index.test.ts +++ b/packages/vscode-typescript/test/index.test.ts @@ -1,2 +1,3 @@ import "./contentMapperContributions.test"; import "./tsdkPackage.test"; +import "./workspaceTsdk.test"; diff --git a/packages/vscode-typescript/test/workspaceTsdk.test.ts b/packages/vscode-typescript/test/workspaceTsdk.test.ts new file mode 100644 index 0000000000000..649740853062c --- /dev/null +++ b/packages/vscode-typescript/test/workspaceTsdk.test.ts @@ -0,0 +1,18 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { shouldUseWorkspaceTsdk } from "../src/workspaceTsdk"; + +test("uses a configured tsdk in a trusted workspace by default", () => { + assert.equal(shouldUseWorkspaceTsdk(true, undefined, false), true); +}); + +test("does not use a configured tsdk in an untrusted workspace", () => { + assert.equal(shouldUseWorkspaceTsdk(false, undefined, false), false); + assert.equal(shouldUseWorkspaceTsdk(false, true, false), false); +}); + +test("respects existing workspace tsdk preferences", () => { + assert.equal(shouldUseWorkspaceTsdk(true, false, false), false); + assert.equal(shouldUseWorkspaceTsdk(true, undefined, true), false); + assert.equal(shouldUseWorkspaceTsdk(true, true, true), true); +}); From fd3b4c6cf77a0199c4ab3967ba2611a1b4bacaf9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 25 Aug 2026 21:20:57 +0000 Subject: [PATCH 3/3] Format workspace tsdk changes Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com> --- .../vscode-typescript/src/workspaceTsdk.ts | 12 +++---- .../test/workspaceTsdk.test.ts | 36 +++++++++---------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/packages/vscode-typescript/src/workspaceTsdk.ts b/packages/vscode-typescript/src/workspaceTsdk.ts index 4901058907c8c..409962e8037a7 100644 --- a/packages/vscode-typescript/src/workspaceTsdk.ts +++ b/packages/vscode-typescript/src/workspaceTsdk.ts @@ -1,6 +1,6 @@ -export const useWorkspaceTsdkStorageKey = "typescript.native-preview.useWorkspaceTsdk"; -export const suppressPromptWorkspaceTsdkStorageKey = "typescript.native-preview.suppressPromptWorkspaceTsdk"; - -export function shouldUseWorkspaceTsdk(isTrusted: boolean, preference: boolean | undefined, promptSuppressed: boolean): boolean { - return isTrusted && (preference ?? !promptSuppressed); -} +export const useWorkspaceTsdkStorageKey = "typescript.native-preview.useWorkspaceTsdk"; +export const suppressPromptWorkspaceTsdkStorageKey = "typescript.native-preview.suppressPromptWorkspaceTsdk"; + +export function shouldUseWorkspaceTsdk(isTrusted: boolean, preference: boolean | undefined, promptSuppressed: boolean): boolean { + return isTrusted && (preference ?? !promptSuppressed); +} diff --git a/packages/vscode-typescript/test/workspaceTsdk.test.ts b/packages/vscode-typescript/test/workspaceTsdk.test.ts index 649740853062c..ab8b543fbf854 100644 --- a/packages/vscode-typescript/test/workspaceTsdk.test.ts +++ b/packages/vscode-typescript/test/workspaceTsdk.test.ts @@ -1,18 +1,18 @@ -import assert from "node:assert/strict"; -import test from "node:test"; -import { shouldUseWorkspaceTsdk } from "../src/workspaceTsdk"; - -test("uses a configured tsdk in a trusted workspace by default", () => { - assert.equal(shouldUseWorkspaceTsdk(true, undefined, false), true); -}); - -test("does not use a configured tsdk in an untrusted workspace", () => { - assert.equal(shouldUseWorkspaceTsdk(false, undefined, false), false); - assert.equal(shouldUseWorkspaceTsdk(false, true, false), false); -}); - -test("respects existing workspace tsdk preferences", () => { - assert.equal(shouldUseWorkspaceTsdk(true, false, false), false); - assert.equal(shouldUseWorkspaceTsdk(true, undefined, true), false); - assert.equal(shouldUseWorkspaceTsdk(true, true, true), true); -}); +import assert from "node:assert/strict"; +import test from "node:test"; +import { shouldUseWorkspaceTsdk } from "../src/workspaceTsdk"; + +test("uses a configured tsdk in a trusted workspace by default", () => { + assert.equal(shouldUseWorkspaceTsdk(true, undefined, false), true); +}); + +test("does not use a configured tsdk in an untrusted workspace", () => { + assert.equal(shouldUseWorkspaceTsdk(false, undefined, false), false); + assert.equal(shouldUseWorkspaceTsdk(false, true, false), false); +}); + +test("respects existing workspace tsdk preferences", () => { + assert.equal(shouldUseWorkspaceTsdk(true, false, false), false); + assert.equal(shouldUseWorkspaceTsdk(true, undefined, true), false); + assert.equal(shouldUseWorkspaceTsdk(true, true, true), true); +});