Unnamed repository; edit this file 'description' to name the repository.
Auto merge of #15160 - tetsuharuohzeki:enable-noUncheckedIndexedAccess, r=Veykril
editor/code: Enable `--noUncheckedIndexedAccess` & `--noPropertyAccessFromIndexSignature` ts option This enables typescript's these option: - [`--noUncheckedIndexedAccess`](https://www.typescriptlang.org/tsconfig#noUncheckedIndexedAccess) - This checks whether indexed access is not `null` (or `undefined`) strictly just as like checking by `std::option::Option::unwrap()`. - [`--noPropertyAccessFromIndexSignature`](https://www.typescriptlang.org/tsconfig#noPropertyAccessFromIndexSignature) - This disallows `bar.foo` access if the `bar` type is `{ [key: string]: someType; }`. ---- Additionally, to enable `--noUncheckedIndexedAccess` easily, this pull request introduces [option-t](https://www.npmjs.com/package/option-t) as a dependency instead of defining a function in this repository like `unwrapUndefinable()` . I'll remove it and define them byself if our dependency management policy is that to avoid to add a new package as possible.
bors 2023-07-06
parent aa91eda · parent f708453 · commit 537f9b3
-rw-r--r--editors/code/src/ast_inspector.ts6
-rw-r--r--editors/code/src/bootstrap.ts2
-rw-r--r--editors/code/src/client.ts7
-rw-r--r--editors/code/src/commands.ts14
-rw-r--r--editors/code/src/config.ts56
-rw-r--r--editors/code/src/debug.ts17
-rw-r--r--editors/code/src/dependencies_provider.ts8
-rw-r--r--editors/code/src/diagnostics.ts4
-rw-r--r--editors/code/src/nullable.ts19
-rw-r--r--editors/code/src/run.ts11
-rw-r--r--editors/code/src/snippets.ts6
-rw-r--r--editors/code/src/tasks.ts4
-rw-r--r--editors/code/src/toolchain.ts11
-rw-r--r--editors/code/src/undefinable.ts19
-rw-r--r--editors/code/tests/unit/settings.test.ts2
-rw-r--r--editors/code/tsconfig.json5
16 files changed, 132 insertions, 59 deletions
diff --git a/editors/code/src/ast_inspector.ts b/editors/code/src/ast_inspector.ts
index 176040120f..fa963d8eb9 100644
--- a/editors/code/src/ast_inspector.ts
+++ b/editors/code/src/ast_inspector.ts
@@ -2,6 +2,7 @@ import * as vscode from "vscode";
import { Ctx, Disposable } from "./ctx";
import { RustEditor, isRustEditor } from "./util";
+import { unwrapUndefinable } from "./undefinable";
// FIXME: consider implementing this via the Tree View API?
// https://code.visualstudio.com/api/extension-guides/tree-view
@@ -164,8 +165,9 @@ export class AstInspector implements vscode.HoverProvider, vscode.DefinitionProv
if (!parsedRange) return;
const [begin, end] = parsedRange.slice(1).map((off) => this.positionAt(doc, +off));
-
- return new vscode.Range(begin, end);
+ const actualBegin = unwrapUndefinable(begin);
+ const actualEnd = unwrapUndefinable(end);
+ return new vscode.Range(actualBegin, actualEnd);
}
// Memoize the last value, otherwise the CPU is at 100% single core
diff --git a/editors/code/src/bootstrap.ts b/editors/code/src/bootstrap.ts
index b38fa06a85..7b831a8a69 100644
--- a/editors/code/src/bootstrap.ts
+++ b/editors/code/src/bootstrap.ts
@@ -36,7 +36,7 @@ async function getServer(
config: Config,
state: PersistentState
): Promise<string | undefined> {
- const explicitPath = process.env.__RA_LSP_SERVER_DEBUG ?? config.serverPath;
+ const explicitPath = process.env["__RA_LSP_SERVER_DEBUG"] ?? config.serverPath;
if (explicitPath) {
if (explicitPath.startsWith("~/")) {
return os.homedir() + explicitPath.slice("~".length);
diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts
index f721fcce76..7b151c804a 100644
--- a/editors/code/src/client.ts
+++ b/editors/code/src/client.ts
@@ -9,6 +9,7 @@ import { WorkspaceEdit } from "vscode";
import { Config, prepareVSCodeConfig } from "./config";
import { randomUUID } from "crypto";
import { sep as pathSeparator } from "path";
+import { unwrapUndefinable } from "./undefinable";
export interface Env {
[name: string]: string;
@@ -323,10 +324,12 @@ export async function createClient(
}
for (const [group, { index, items }] of groups) {
if (items.length === 1) {
- result[index] = items[0];
+ const item = unwrapUndefinable(items[0]);
+ result[index] = item;
} else {
const action = new vscode.CodeAction(group);
- action.kind = items[0].kind;
+ const item = unwrapUndefinable(items[0]);
+ action.kind = item.kind;
action.command = {
command: "rust-analyzer.applyActionGroup",
title: "",
diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts
index 98ccd50dc0..3c6105e89f 100644
--- a/editors/code/src/commands.ts
+++ b/editors/code/src/commands.ts
@@ -20,6 +20,7 @@ import { startDebugSession, makeDebugConfig } from "./debug";
import { LanguageClient } from "vscode-languageclient/node";
import { LINKED_COMMANDS } from "./client";
import { DependencyId } from "./dependencies_provider";
+import { unwrapUndefinable } from "./undefinable";
export * from "./ast_inspector";
export * from "./run";
@@ -129,7 +130,8 @@ export function matchingBrace(ctx: CtxInit): Cmd {
),
});
editor.selections = editor.selections.map((sel, idx) => {
- const active = client.protocol2CodeConverter.asPosition(response[idx]);
+ const position = unwrapUndefinable(response[idx]);
+ const active = client.protocol2CodeConverter.asPosition(position);
const anchor = sel.isEmpty ? active : sel.anchor;
return new vscode.Selection(anchor, active);
});
@@ -231,7 +233,7 @@ export function parentModule(ctx: CtxInit): Cmd {
if (!locations) return;
if (locations.length === 1) {
- const loc = locations[0];
+ const loc = unwrapUndefinable(locations[0]);
const uri = client.protocol2CodeConverter.asUri(loc.targetUri);
const range = client.protocol2CodeConverter.asRange(loc.targetRange);
@@ -331,7 +333,13 @@ async function revealParentChain(document: RustDocument, ctx: CtxInit) {
} while (!ctx.dependencies?.contains(documentPath));
parentChain.reverse();
for (const idx in parentChain) {
- await ctx.treeView?.reveal(parentChain[idx], { select: true, expand: true });
+ const treeView = ctx.treeView;
+ if (!treeView) {
+ continue;
+ }
+
+ const dependency = unwrapUndefinable(parentChain[idx]);
+ await treeView.reveal(dependency, { select: true, expand: true });
}
}
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts
index 15a1d4e0f1..deea958f8d 100644
--- a/editors/code/src/config.ts
+++ b/editors/code/src/config.ts
@@ -4,6 +4,7 @@ import * as path from "path";
import * as vscode from "vscode";
import { Env } from "./client";
import { log } from "./util";
+import { expectNotUndefined, unwrapUndefinable } from "./undefinable";
export type RunnableEnvCfg =
| undefined
@@ -338,7 +339,7 @@ export function substituteVariablesInEnv(env: Env): Env {
const depRe = new RegExp(/\${(?<depName>.+?)}/g);
let match = undefined;
while ((match = depRe.exec(value))) {
- const depName = match.groups!.depName;
+ const depName = unwrapUndefinable(match.groups?.["depName"]);
deps.add(depName);
// `depName` at this point can have a form of `expression` or
// `prefix:expression`
@@ -356,7 +357,7 @@ export function substituteVariablesInEnv(env: Env): Env {
if (match) {
const { prefix, body } = match.groups!;
if (prefix === "env") {
- const envName = body;
+ const envName = unwrapUndefinable(body);
envWithDeps[dep] = {
value: process.env[envName] ?? "",
deps: [],
@@ -384,13 +385,12 @@ export function substituteVariablesInEnv(env: Env): Env {
do {
leftToResolveSize = toResolve.size;
for (const key of toResolve) {
- if (envWithDeps[key].deps.every((dep) => resolved.has(dep))) {
- envWithDeps[key].value = envWithDeps[key].value.replace(
- /\${(?<depName>.+?)}/g,
- (_wholeMatch, depName) => {
- return envWithDeps[depName].value;
- }
- );
+ const item = unwrapUndefinable(envWithDeps[key]);
+ if (item.deps.every((dep) => resolved.has(dep))) {
+ item.value = item.value.replace(/\${(?<depName>.+?)}/g, (_wholeMatch, depName) => {
+ const item = unwrapUndefinable(envWithDeps[depName]);
+ return item.value;
+ });
resolved.add(key);
toResolve.delete(key);
}
@@ -399,7 +399,8 @@ export function substituteVariablesInEnv(env: Env): Env {
const resolvedEnv: Env = {};
for (const key of Object.keys(env)) {
- resolvedEnv[key] = envWithDeps[`env:${key}`].value;
+ const item = unwrapUndefinable(envWithDeps[`env:${key}`]);
+ resolvedEnv[key] = item.value;
}
return resolvedEnv;
}
@@ -418,20 +419,19 @@ function substituteVSCodeVariableInString(val: string): string {
function computeVscodeVar(varName: string): string | null {
const workspaceFolder = () => {
const folders = vscode.workspace.workspaceFolders ?? [];
- if (folders.length === 1) {
- // TODO: support for remote workspaces?
- return folders[0].uri.fsPath;
- } else if (folders.length > 1) {
- // could use currently opened document to detect the correct
- // workspace. However, that would be determined by the document
- // user has opened on Editor startup. Could lead to
- // unpredictable workspace selection in practice.
- // It's better to pick the first one
- return folders[0].uri.fsPath;
- } else {
- // no workspace opened
- return "";
- }
+ const folder = folders[0];
+ // TODO: support for remote workspaces?
+ const fsPath: string =
+ folder === undefined
+ ? // no workspace opened
+ ""
+ : // could use currently opened document to detect the correct
+ // workspace. However, that would be determined by the document
+ // user has opened on Editor startup. Could lead to
+ // unpredictable workspace selection in practice.
+ // It's better to pick the first one
+ folder.uri.fsPath;
+ return fsPath;
};
// https://code.visualstudio.com/docs/editor/variables-reference
const supportedVariables: { [k: string]: () => string } = {
@@ -448,13 +448,17 @@ function computeVscodeVar(varName: string): string | null {
// https://github.com/microsoft/vscode/blob/08ac1bb67ca2459496b272d8f4a908757f24f56f/src/vs/workbench/api/common/extHostVariableResolverService.ts#L81
// or
// https://github.com/microsoft/vscode/blob/29eb316bb9f154b7870eb5204ec7f2e7cf649bec/src/vs/server/node/remoteTerminalChannel.ts#L56
- execPath: () => process.env.VSCODE_EXEC_PATH ?? process.execPath,
+ execPath: () => process.env["VSCODE_EXEC_PATH"] ?? process.execPath,
pathSeparator: () => path.sep,
};
if (varName in supportedVariables) {
- return supportedVariables[varName]();
+ const fn = expectNotUndefined(
+ supportedVariables[varName],
+ `${varName} should not be undefined here`
+ );
+ return fn();
} else {
// return "${" + varName + "}";
return null;
diff --git a/editors/code/src/debug.ts b/editors/code/src/debug.ts
index 8fbd427039..29758feafe 100644
--- a/editors/code/src/debug.ts
+++ b/editors/code/src/debug.ts
@@ -6,6 +6,7 @@ import * as ra from "./lsp_ext";
import { Cargo, getRustcId, getSysroot } from "./toolchain";
import { Ctx } from "./ctx";
import { prepareEnv } from "./run";
+import { unwrapUndefinable } from "./undefinable";
const debugOutput = vscode.window.createOutputChannel("Debug");
type DebugConfigProvider = (
@@ -105,12 +106,13 @@ async function getDebugConfiguration(
const workspaceFolders = vscode.workspace.workspaceFolders!;
const isMultiFolderWorkspace = workspaceFolders.length > 1;
const firstWorkspace = workspaceFolders[0];
- const workspace =
+ const maybeWorkspace =
!isMultiFolderWorkspace || !runnable.args.workspaceRoot
? firstWorkspace
: workspaceFolders.find((w) => runnable.args.workspaceRoot?.includes(w.uri.fsPath)) ||
firstWorkspace;
+ const workspace = unwrapUndefinable(maybeWorkspace);
const wsFolder = path.normalize(workspace.uri.fsPath);
const workspaceQualifier = isMultiFolderWorkspace ? `:${workspace.name}` : "";
function simplifyPath(p: string): string {
@@ -130,12 +132,8 @@ async function getDebugConfiguration(
sourceFileMap[`/rustc/${commitHash}/`] = rustlib;
}
- const debugConfig = knownEngines[debugEngine.id](
- runnable,
- simplifyPath(executable),
- env,
- sourceFileMap
- );
+ const provider = unwrapUndefinable(knownEngines[debugEngine.id]);
+ const debugConfig = provider(runnable, simplifyPath(executable), env, sourceFileMap);
if (debugConfig.type in debugOptions.engineSettings) {
const settingsMap = (debugOptions.engineSettings as any)[debugConfig.type];
for (var key in settingsMap) {
@@ -149,8 +147,9 @@ async function getDebugConfiguration(
debugConfig.name = `run ${path.basename(executable)}`;
}
- if (debugConfig.cwd) {
- debugConfig.cwd = simplifyPath(debugConfig.cwd);
+ const cwd = debugConfig["cwd"];
+ if (cwd) {
+ debugConfig["cwd"] = simplifyPath(cwd);
}
return debugConfig;
diff --git a/editors/code/src/dependencies_provider.ts b/editors/code/src/dependencies_provider.ts
index d67345258e..51ba11ecc9 100644
--- a/editors/code/src/dependencies_provider.ts
+++ b/editors/code/src/dependencies_provider.ts
@@ -4,6 +4,7 @@ import * as fs from "fs";
import { CtxInit } from "./ctx";
import * as ra from "./lsp_ext";
import { FetchDependencyListResult } from "./lsp_ext";
+import { unwrapUndefinable } from "./undefinable";
export class RustDependenciesProvider
implements vscode.TreeDataProvider<Dependency | DependencyFile>
@@ -49,7 +50,12 @@ export class RustDependenciesProvider
}
getTreeItem(element: Dependency | DependencyFile): vscode.TreeItem | Thenable<vscode.TreeItem> {
- if (element.id! in this.dependenciesMap) return this.dependenciesMap[element.id!];
+ const dependenciesMap = this.dependenciesMap;
+ const elementId = element.id!;
+ if (elementId in dependenciesMap) {
+ const dependency = unwrapUndefinable(dependenciesMap[elementId]);
+ return dependency;
+ }
return element;
}
diff --git a/editors/code/src/diagnostics.ts b/editors/code/src/diagnostics.ts
index 9695d8bf26..a7e0845a27 100644
--- a/editors/code/src/diagnostics.ts
+++ b/editors/code/src/diagnostics.ts
@@ -2,6 +2,7 @@ import * as anser from "anser";
import * as vscode from "vscode";
import { ProviderResult, Range, TextEditorDecorationType, ThemeColor, window } from "vscode";
import { Ctx } from "./ctx";
+import { unwrapUndefinable } from "./undefinable";
export const URI_SCHEME = "rust-analyzer-diagnostics-view";
@@ -195,7 +196,8 @@ export class AnsiDecorationProvider implements vscode.Disposable {
// anser won't return both the RGB and the color name at the same time,
// so just fake a single foreground control char with the palette number:
const spans = anser.ansiToJson(`\x1b[38;5;${paletteColor}m`);
- const rgb = spans[1].fg;
+ const span = unwrapUndefinable(spans[1]);
+ const rgb = span.fg;
if (rgb) {
return `rgb(${rgb})`;
diff --git a/editors/code/src/nullable.ts b/editors/code/src/nullable.ts
new file mode 100644
index 0000000000..e973e16290
--- /dev/null
+++ b/editors/code/src/nullable.ts
@@ -0,0 +1,19 @@
+export type NotNull<T> = T extends null ? never : T;
+
+export type Nullable<T> = T | null;
+
+function isNotNull<T>(input: Nullable<T>): input is NotNull<T> {
+ return input !== null;
+}
+
+function expectNotNull<T>(input: Nullable<T>, msg: string): NotNull<T> {
+ if (isNotNull(input)) {
+ return input;
+ }
+
+ throw new TypeError(msg);
+}
+
+export function unwrapNullable<T>(input: Nullable<T>): NotNull<T> {
+ return expectNotNull(input, `unwrapping \`null\``);
+}
diff --git a/editors/code/src/run.ts b/editors/code/src/run.ts
index bdd8524313..c20a5487b7 100644
--- a/editors/code/src/run.ts
+++ b/editors/code/src/run.ts
@@ -6,6 +6,7 @@ import * as tasks from "./tasks";
import { CtxInit } from "./ctx";
import { makeDebugConfig } from "./debug";
import { Config, RunnableEnvCfg } from "./config";
+import { unwrapUndefinable } from "./undefinable";
const quickPickButtons = [
{ iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configuration." },
@@ -68,12 +69,14 @@ export async function selectRunnable(
quickPick.onDidHide(() => close()),
quickPick.onDidAccept(() => close(quickPick.selectedItems[0])),
quickPick.onDidTriggerButton(async (_button) => {
- await makeDebugConfig(ctx, quickPick.activeItems[0].runnable);
+ const runnable = unwrapUndefinable(quickPick.activeItems[0]).runnable;
+ await makeDebugConfig(ctx, runnable);
close();
}),
- quickPick.onDidChangeActive((active) => {
- if (showButtons && active.length > 0) {
- if (active[0].label.startsWith("cargo")) {
+ quickPick.onDidChangeActive((activeList) => {
+ if (showButtons && activeList.length > 0) {
+ const active = unwrapUndefinable(activeList[0]);
+ if (active.label.startsWith("cargo")) {
// save button makes no sense for `cargo test` or `cargo check`
quickPick.buttons = [];
} else if (quickPick.buttons.length === 0) {
diff --git a/editors/code/src/snippets.ts b/editors/code/src/snippets.ts
index 299d29c27e..1ad93d280b 100644
--- a/editors/code/src/snippets.ts
+++ b/editors/code/src/snippets.ts
@@ -1,10 +1,11 @@
import * as vscode from "vscode";
import { assert } from "./util";
+import { unwrapUndefinable } from "./undefinable";
export async function applySnippetWorkspaceEdit(edit: vscode.WorkspaceEdit) {
if (edit.entries().length === 1) {
- const [uri, edits] = edit.entries()[0];
+ const [uri, edits] = unwrapUndefinable(edit.entries()[0]);
const editor = await editorFromUri(uri);
if (editor) await applySnippetTextEdits(editor, edits);
return;
@@ -68,7 +69,8 @@ export async function applySnippetTextEdits(editor: vscode.TextEditor, edits: vs
});
if (selections.length > 0) editor.selections = selections;
if (selections.length === 1) {
- editor.revealRange(selections[0], vscode.TextEditorRevealType.InCenterIfOutsideViewport);
+ const selection = unwrapUndefinable(selections[0]);
+ editor.revealRange(selection, vscode.TextEditorRevealType.InCenterIfOutsideViewport);
}
}
diff --git a/editors/code/src/tasks.ts b/editors/code/src/tasks.ts
index efb889dc79..5199508c82 100644
--- a/editors/code/src/tasks.ts
+++ b/editors/code/src/tasks.ts
@@ -2,6 +2,7 @@ import * as vscode from "vscode";
import * as toolchain from "./toolchain";
import { Config } from "./config";
import { log } from "./util";
+import { unwrapUndefinable } from "./undefinable";
// This ends up as the `type` key in tasks.json. RLS also uses `cargo` and
// our configuration should be compatible with it so use the same key.
@@ -120,7 +121,8 @@ export async function buildCargoTask(
const fullCommand = [...cargoCommand, ...args];
- exec = new vscode.ProcessExecution(fullCommand[0], fullCommand.slice(1), definition);
+ const processName = unwrapUndefinable(fullCommand[0]);
+ exec = new vscode.ProcessExecution(processName, fullCommand.slice(1), definition);
}
return new vscode.Task(
diff --git a/editors/code/src/toolchain.ts b/editors/code/src/toolchain.ts
index 917a1d6b09..014e6b66f6 100644
--- a/editors/code/src/toolchain.ts
+++ b/editors/code/src/toolchain.ts
@@ -4,6 +4,8 @@ import * as path from "path";
import * as readline from "readline";
import * as vscode from "vscode";
import { execute, log, memoizeAsync } from "./util";
+import { unwrapNullable } from "./nullable";
+import { unwrapUndefinable } from "./undefinable";
interface CompilationArtifact {
fileName: string;
@@ -93,7 +95,8 @@ export class Cargo {
throw new Error("Multiple compilation artifacts are not supported.");
}
- return artifacts[0].fileName;
+ const artifact = unwrapUndefinable(artifacts[0]);
+ return artifact.fileName;
}
private async runCargo(
@@ -142,7 +145,9 @@ export async function getRustcId(dir: string): Promise<string> {
const data = await execute(`${rustcPath} -V -v`, { cwd: dir });
const rx = /commit-hash:\s(.*)$/m;
- return rx.exec(data)![1];
+ const result = unwrapNullable(rx.exec(data));
+ const first = unwrapUndefinable(result[1]);
+ return first;
}
/** Mirrors `toolchain::cargo()` implementation */
@@ -171,7 +176,7 @@ export const getPathForExecutable = memoizeAsync(
);
async function lookupInPath(exec: string): Promise<boolean> {
- const paths = process.env.PATH ?? "";
+ const paths = process.env["PATH"] ?? "";
const candidates = paths.split(path.delimiter).flatMap((dirInPath) => {
const candidate = path.join(dirInPath, exec);
diff --git a/editors/code/src/undefinable.ts b/editors/code/src/undefinable.ts
new file mode 100644
index 0000000000..813bac5a12
--- /dev/null
+++ b/editors/code/src/undefinable.ts
@@ -0,0 +1,19 @@
+export type NotUndefined<T> = T extends undefined ? never : T;
+
+export type Undefinable<T> = T | undefined;
+
+function isNotUndefined<T>(input: Undefinable<T>): input is NotUndefined<T> {
+ return input !== undefined;
+}
+
+export function expectNotUndefined<T>(input: Undefinable<T>, msg: string): NotUndefined<T> {
+ if (isNotUndefined(input)) {
+ return input;
+ }
+
+ throw new TypeError(msg);
+}
+
+export function unwrapUndefinable<T>(input: Undefinable<T>): NotUndefined<T> {
+ return expectNotUndefined(input, `unwrapping \`undefined\``);
+}
diff --git a/editors/code/tests/unit/settings.test.ts b/editors/code/tests/unit/settings.test.ts
index 2cc1b670dc..f171f5f74f 100644
--- a/editors/code/tests/unit/settings.test.ts
+++ b/editors/code/tests/unit/settings.test.ts
@@ -57,7 +57,7 @@ export async function getTests(ctx: Context) {
USING_VSCODE_VAR: "${workspaceFolderBasename}",
};
const actualEnv = await substituteVariablesInEnv(envJson);
- assert.deepStrictEqual(actualEnv.USING_VSCODE_VAR, "code");
+ assert.deepStrictEqual(actualEnv["USING_VSCODE_VAR"], "code");
});
});
}
diff --git a/editors/code/tsconfig.json b/editors/code/tsconfig.json
index 04bd456937..4b107a5d25 100644
--- a/editors/code/tsconfig.json
+++ b/editors/code/tsconfig.json
@@ -3,6 +3,7 @@
"compilerOptions": {
"esModuleInterop": false,
"module": "commonjs",
+ "moduleResolution": "node16",
"target": "es2021",
"outDir": "out",
"lib": ["es2021"],
@@ -12,9 +13,7 @@
// These disables some enhancement type checking options
// to update typescript version without any code change.
"useUnknownInCatchVariables": false,
- "exactOptionalPropertyTypes": false,
- "noPropertyAccessFromIndexSignature": false,
- "noUncheckedIndexedAccess": false
+ "exactOptionalPropertyTypes": false
},
"exclude": ["node_modules", ".vscode-test"],
"include": ["src", "tests"]