about summary refs log tree commit diff
diff options
context:
space:
mode:
authorTetsuharu Ohzeki <tetsuharu.ohzeki@gmail.com>2023-06-28 04:03:53 +0900
committerTetsuharu Ohzeki <tetsuharu.ohzeki@gmail.com>2023-07-06 16:17:02 +0900
commit72a3883a7128b4eae6f38d6479f33aaaaae4790b (patch)
tree60b9d6f755bbefeca335bcbc10d0832abc719c48
parentbb35d8fa8ef9de3c8282602b411c40b266dc3a4e (diff)
downloadrust-72a3883a7128b4eae6f38d6479f33aaaaae4790b.tar.gz
rust-72a3883a7128b4eae6f38d6479f33aaaaae4790b.zip
editor/code: Enable `noUncheckedIndexedAccess` ts option
https://www.typescriptlang.org/tsconfig#noUncheckedIndexedAccess
-rw-r--r--editors/code/src/ast_inspector.ts6
-rw-r--r--editors/code/src/client.ts7
-rw-r--r--editors/code/src/commands.ts14
-rw-r--r--editors/code/src/config.ts54
-rw-r--r--editors/code/src/debug.ts12
-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.ts9
-rw-r--r--editors/code/src/undefinable.ts19
-rw-r--r--editors/code/tsconfig.json3
14 files changed, 124 insertions, 52 deletions
diff --git a/editors/code/src/ast_inspector.ts b/editors/code/src/ast_inspector.ts
index 176040120f4..fa963d8eb99 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/client.ts b/editors/code/src/client.ts
index f721fcce766..7b151c804af 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 98ccd50dc04..3c6105e89fe 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 15a1d4e0f1d..9595ba05282 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 } = {
@@ -454,7 +454,11 @@ function computeVscodeVar(varName: string): string | null {
     };
 
     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 8fbd427039e..ede90d707dd 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) {
diff --git a/editors/code/src/dependencies_provider.ts b/editors/code/src/dependencies_provider.ts
index d67345258ec..51ba11ecc92 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 9695d8bf26d..a7e0845a278 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 00000000000..e973e162907
--- /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 bdd85243133..c20a5487b77 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 299d29c27ee..1ad93d280ba 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 efb889dc797..5199508c822 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 917a1d6b099..b2c0c669db2 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 */
diff --git a/editors/code/src/undefinable.ts b/editors/code/src/undefinable.ts
new file mode 100644
index 00000000000..813bac5a123
--- /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/tsconfig.json b/editors/code/tsconfig.json
index 3f887a41b3d..12d31f0bee3 100644
--- a/editors/code/tsconfig.json
+++ b/editors/code/tsconfig.json
@@ -14,8 +14,7 @@
         // to update typescript version without any code change.
         "useUnknownInCatchVariables": false,
         "exactOptionalPropertyTypes": false,
-        "noPropertyAccessFromIndexSignature": false,
-        "noUncheckedIndexedAccess": false
+        "noPropertyAccessFromIndexSignature": false
     },
     "exclude": ["node_modules", ".vscode-test"],
     "include": ["src", "tests"]