Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 12 additions & 9 deletions packages/vscode-typescript/src/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -661,13 +665,12 @@ async function promptSelectVersion(context: vscode.ExtensionContext, client: Cli
export async function promptUseWorkspaceVersion(context: vscode.ExtensionContext): Promise<void> {
if (!vscode.workspace.isTrusted) return;

const useWorkspaceTsdk = context.workspaceState.get<boolean>(useWorkspaceTsdkStorageKey, false);
if (useWorkspaceTsdk) return; // already opted in

const suppressKey = "typescript.native-preview.suppressPromptWorkspaceTsdk";
if (context.workspaceState.get<boolean>(suppressKey, false)) return;

const workspaceTsdk = await getWorkspaceTsdkForPrompt();
const preference = context.workspaceState.get<boolean>(useWorkspaceTsdkStorageKey);
const promptSuppressed = context.workspaceState.get<boolean>(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.
Expand All @@ -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 {
Expand Down Expand Up @@ -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);
}
}
}
Expand Down
26 changes: 8 additions & 18 deletions packages/vscode-typescript/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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<ExeInfo> {
for (const candidate of getTrustedTsdkCandidates(context, await getTsdkCandidates())) {
const exe = await resolveTsdkPathToExe(candidate.value);
Expand Down Expand Up @@ -192,11 +183,10 @@ async function classifyTsdk(tsdkPath: string): Promise<"lsp" | "tsserver" | unde
}

function getTrustedTsdkCandidates(context: vscode.ExtensionContext, tsdkCandidates: ExplicitConfigValue<string>[]): ExplicitConfigValue<string>[] {
// 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<boolean>(useWorkspaceTsdkStorageKey, false)) {
const preference = context.workspaceState.get<boolean>(useWorkspaceTsdkStorageKey);
const promptSuppressed = context.workspaceState.get<boolean>(suppressPromptWorkspaceTsdkStorageKey, false);
if (!shouldUseWorkspaceTsdk(vscode.workspace.isTrusted, preference, promptSuppressed)) {
return tsdkCandidates.filter(candidate => candidate.target === vscode.ConfigurationTarget.Global);
}
}
Expand Down
6 changes: 6 additions & 0 deletions packages/vscode-typescript/src/workspaceTsdk.ts
Original file line number Diff line number Diff line change
@@ -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);
}
1 change: 1 addition & 0 deletions packages/vscode-typescript/test/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
import "./contentMapperContributions.test";
import "./tsdkPackage.test";
import "./workspaceTsdk.test";
18 changes: 18 additions & 0 deletions packages/vscode-typescript/test/workspaceTsdk.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});