editor/code: Enable noUncheckedIndexedAccess ts option

https://www.typescriptlang.org/tsconfig#noUncheckedIndexedAccess
This commit is contained in:
Tetsuharu Ohzeki 2023-06-28 04:03:53 +09:00
parent bb35d8fa8e
commit 72a3883a71
14 changed files with 124 additions and 52 deletions

View file

@ -2,6 +2,7 @@ import * as vscode from "vscode";
import { Ctx, Disposable } from "./ctx"; import { Ctx, Disposable } from "./ctx";
import { RustEditor, isRustEditor } from "./util"; import { RustEditor, isRustEditor } from "./util";
import { unwrapUndefinable } from "./undefinable";
// FIXME: consider implementing this via the Tree View API? // FIXME: consider implementing this via the Tree View API?
// https://code.visualstudio.com/api/extension-guides/tree-view // https://code.visualstudio.com/api/extension-guides/tree-view
@ -164,8 +165,9 @@ export class AstInspector implements vscode.HoverProvider, vscode.DefinitionProv
if (!parsedRange) return; if (!parsedRange) return;
const [begin, end] = parsedRange.slice(1).map((off) => this.positionAt(doc, +off)); const [begin, end] = parsedRange.slice(1).map((off) => this.positionAt(doc, +off));
const actualBegin = unwrapUndefinable(begin);
return new vscode.Range(begin, end); const actualEnd = unwrapUndefinable(end);
return new vscode.Range(actualBegin, actualEnd);
} }
// Memoize the last value, otherwise the CPU is at 100% single core // Memoize the last value, otherwise the CPU is at 100% single core

View file

@ -9,6 +9,7 @@ import { WorkspaceEdit } from "vscode";
import { Config, prepareVSCodeConfig } from "./config"; import { Config, prepareVSCodeConfig } from "./config";
import { randomUUID } from "crypto"; import { randomUUID } from "crypto";
import { sep as pathSeparator } from "path"; import { sep as pathSeparator } from "path";
import { unwrapUndefinable } from "./undefinable";
export interface Env { export interface Env {
[name: string]: string; [name: string]: string;
@ -323,10 +324,12 @@ export async function createClient(
} }
for (const [group, { index, items }] of groups) { for (const [group, { index, items }] of groups) {
if (items.length === 1) { if (items.length === 1) {
result[index] = items[0]; const item = unwrapUndefinable(items[0]);
result[index] = item;
} else { } else {
const action = new vscode.CodeAction(group); const action = new vscode.CodeAction(group);
action.kind = items[0].kind; const item = unwrapUndefinable(items[0]);
action.kind = item.kind;
action.command = { action.command = {
command: "rust-analyzer.applyActionGroup", command: "rust-analyzer.applyActionGroup",
title: "", title: "",

View file

@ -20,6 +20,7 @@ import { startDebugSession, makeDebugConfig } from "./debug";
import { LanguageClient } from "vscode-languageclient/node"; import { LanguageClient } from "vscode-languageclient/node";
import { LINKED_COMMANDS } from "./client"; import { LINKED_COMMANDS } from "./client";
import { DependencyId } from "./dependencies_provider"; import { DependencyId } from "./dependencies_provider";
import { unwrapUndefinable } from "./undefinable";
export * from "./ast_inspector"; export * from "./ast_inspector";
export * from "./run"; export * from "./run";
@ -129,7 +130,8 @@ export function matchingBrace(ctx: CtxInit): Cmd {
), ),
}); });
editor.selections = editor.selections.map((sel, idx) => { 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; const anchor = sel.isEmpty ? active : sel.anchor;
return new vscode.Selection(anchor, active); return new vscode.Selection(anchor, active);
}); });
@ -231,7 +233,7 @@ export function parentModule(ctx: CtxInit): Cmd {
if (!locations) return; if (!locations) return;
if (locations.length === 1) { if (locations.length === 1) {
const loc = locations[0]; const loc = unwrapUndefinable(locations[0]);
const uri = client.protocol2CodeConverter.asUri(loc.targetUri); const uri = client.protocol2CodeConverter.asUri(loc.targetUri);
const range = client.protocol2CodeConverter.asRange(loc.targetRange); const range = client.protocol2CodeConverter.asRange(loc.targetRange);
@ -331,7 +333,13 @@ async function revealParentChain(document: RustDocument, ctx: CtxInit) {
} while (!ctx.dependencies?.contains(documentPath)); } while (!ctx.dependencies?.contains(documentPath));
parentChain.reverse(); parentChain.reverse();
for (const idx in parentChain) { 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 });
} }
} }

View file

@ -4,6 +4,7 @@ import * as path from "path";
import * as vscode from "vscode"; import * as vscode from "vscode";
import { Env } from "./client"; import { Env } from "./client";
import { log } from "./util"; import { log } from "./util";
import { expectNotUndefined, unwrapUndefinable } from "./undefinable";
export type RunnableEnvCfg = export type RunnableEnvCfg =
| undefined | undefined
@ -338,7 +339,7 @@ export function substituteVariablesInEnv(env: Env): Env {
const depRe = new RegExp(/\${(?<depName>.+?)}/g); const depRe = new RegExp(/\${(?<depName>.+?)}/g);
let match = undefined; let match = undefined;
while ((match = depRe.exec(value))) { while ((match = depRe.exec(value))) {
const depName = match.groups!.depName; const depName = unwrapUndefinable(match.groups?.depName);
deps.add(depName); deps.add(depName);
// `depName` at this point can have a form of `expression` or // `depName` at this point can have a form of `expression` or
// `prefix:expression` // `prefix:expression`
@ -356,7 +357,7 @@ export function substituteVariablesInEnv(env: Env): Env {
if (match) { if (match) {
const { prefix, body } = match.groups!; const { prefix, body } = match.groups!;
if (prefix === "env") { if (prefix === "env") {
const envName = body; const envName = unwrapUndefinable(body);
envWithDeps[dep] = { envWithDeps[dep] = {
value: process.env[envName] ?? "", value: process.env[envName] ?? "",
deps: [], deps: [],
@ -384,13 +385,12 @@ export function substituteVariablesInEnv(env: Env): Env {
do { do {
leftToResolveSize = toResolve.size; leftToResolveSize = toResolve.size;
for (const key of toResolve) { for (const key of toResolve) {
if (envWithDeps[key].deps.every((dep) => resolved.has(dep))) { const item = unwrapUndefinable(envWithDeps[key]);
envWithDeps[key].value = envWithDeps[key].value.replace( if (item.deps.every((dep) => resolved.has(dep))) {
/\${(?<depName>.+?)}/g, item.value = item.value.replace(/\${(?<depName>.+?)}/g, (_wholeMatch, depName) => {
(_wholeMatch, depName) => { const item = unwrapUndefinable(envWithDeps[depName]);
return envWithDeps[depName].value; return item.value;
} });
);
resolved.add(key); resolved.add(key);
toResolve.delete(key); toResolve.delete(key);
} }
@ -399,7 +399,8 @@ export function substituteVariablesInEnv(env: Env): Env {
const resolvedEnv: Env = {}; const resolvedEnv: Env = {};
for (const key of Object.keys(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; return resolvedEnv;
} }
@ -418,20 +419,19 @@ function substituteVSCodeVariableInString(val: string): string {
function computeVscodeVar(varName: string): string | null { function computeVscodeVar(varName: string): string | null {
const workspaceFolder = () => { const workspaceFolder = () => {
const folders = vscode.workspace.workspaceFolders ?? []; const folders = vscode.workspace.workspaceFolders ?? [];
if (folders.length === 1) { const folder = folders[0];
// TODO: support for remote workspaces? // TODO: support for remote workspaces?
return folders[0].uri.fsPath; const fsPath: string =
} else if (folders.length > 1) { folder === undefined
// could use currently opened document to detect the correct ? // no workspace opened
""
: // could use currently opened document to detect the correct
// workspace. However, that would be determined by the document // workspace. However, that would be determined by the document
// user has opened on Editor startup. Could lead to // user has opened on Editor startup. Could lead to
// unpredictable workspace selection in practice. // unpredictable workspace selection in practice.
// It's better to pick the first one // It's better to pick the first one
return folders[0].uri.fsPath; folder.uri.fsPath;
} else { return fsPath;
// no workspace opened
return "";
}
}; };
// https://code.visualstudio.com/docs/editor/variables-reference // https://code.visualstudio.com/docs/editor/variables-reference
const supportedVariables: { [k: string]: () => string } = { const supportedVariables: { [k: string]: () => string } = {
@ -454,7 +454,11 @@ function computeVscodeVar(varName: string): string | null {
}; };
if (varName in supportedVariables) { if (varName in supportedVariables) {
return supportedVariables[varName](); const fn = expectNotUndefined(
supportedVariables[varName],
`${varName} should not be undefined here`
);
return fn();
} else { } else {
// return "${" + varName + "}"; // return "${" + varName + "}";
return null; return null;

View file

@ -6,6 +6,7 @@ import * as ra from "./lsp_ext";
import { Cargo, getRustcId, getSysroot } from "./toolchain"; import { Cargo, getRustcId, getSysroot } from "./toolchain";
import { Ctx } from "./ctx"; import { Ctx } from "./ctx";
import { prepareEnv } from "./run"; import { prepareEnv } from "./run";
import { unwrapUndefinable } from "./undefinable";
const debugOutput = vscode.window.createOutputChannel("Debug"); const debugOutput = vscode.window.createOutputChannel("Debug");
type DebugConfigProvider = ( type DebugConfigProvider = (
@ -105,12 +106,13 @@ async function getDebugConfiguration(
const workspaceFolders = vscode.workspace.workspaceFolders!; const workspaceFolders = vscode.workspace.workspaceFolders!;
const isMultiFolderWorkspace = workspaceFolders.length > 1; const isMultiFolderWorkspace = workspaceFolders.length > 1;
const firstWorkspace = workspaceFolders[0]; const firstWorkspace = workspaceFolders[0];
const workspace = const maybeWorkspace =
!isMultiFolderWorkspace || !runnable.args.workspaceRoot !isMultiFolderWorkspace || !runnable.args.workspaceRoot
? firstWorkspace ? firstWorkspace
: workspaceFolders.find((w) => runnable.args.workspaceRoot?.includes(w.uri.fsPath)) || : workspaceFolders.find((w) => runnable.args.workspaceRoot?.includes(w.uri.fsPath)) ||
firstWorkspace; firstWorkspace;
const workspace = unwrapUndefinable(maybeWorkspace);
const wsFolder = path.normalize(workspace.uri.fsPath); const wsFolder = path.normalize(workspace.uri.fsPath);
const workspaceQualifier = isMultiFolderWorkspace ? `:${workspace.name}` : ""; const workspaceQualifier = isMultiFolderWorkspace ? `:${workspace.name}` : "";
function simplifyPath(p: string): string { function simplifyPath(p: string): string {
@ -130,12 +132,8 @@ async function getDebugConfiguration(
sourceFileMap[`/rustc/${commitHash}/`] = rustlib; sourceFileMap[`/rustc/${commitHash}/`] = rustlib;
} }
const debugConfig = knownEngines[debugEngine.id]( const provider = unwrapUndefinable(knownEngines[debugEngine.id]);
runnable, const debugConfig = provider(runnable, simplifyPath(executable), env, sourceFileMap);
simplifyPath(executable),
env,
sourceFileMap
);
if (debugConfig.type in debugOptions.engineSettings) { if (debugConfig.type in debugOptions.engineSettings) {
const settingsMap = (debugOptions.engineSettings as any)[debugConfig.type]; const settingsMap = (debugOptions.engineSettings as any)[debugConfig.type];
for (var key in settingsMap) { for (var key in settingsMap) {

View file

@ -4,6 +4,7 @@ import * as fs from "fs";
import { CtxInit } from "./ctx"; import { CtxInit } from "./ctx";
import * as ra from "./lsp_ext"; import * as ra from "./lsp_ext";
import { FetchDependencyListResult } from "./lsp_ext"; import { FetchDependencyListResult } from "./lsp_ext";
import { unwrapUndefinable } from "./undefinable";
export class RustDependenciesProvider export class RustDependenciesProvider
implements vscode.TreeDataProvider<Dependency | DependencyFile> implements vscode.TreeDataProvider<Dependency | DependencyFile>
@ -49,7 +50,12 @@ export class RustDependenciesProvider
} }
getTreeItem(element: Dependency | DependencyFile): vscode.TreeItem | Thenable<vscode.TreeItem> { 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; return element;
} }

View file

@ -2,6 +2,7 @@ import * as anser from "anser";
import * as vscode from "vscode"; import * as vscode from "vscode";
import { ProviderResult, Range, TextEditorDecorationType, ThemeColor, window } from "vscode"; import { ProviderResult, Range, TextEditorDecorationType, ThemeColor, window } from "vscode";
import { Ctx } from "./ctx"; import { Ctx } from "./ctx";
import { unwrapUndefinable } from "./undefinable";
export const URI_SCHEME = "rust-analyzer-diagnostics-view"; 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, // 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: // so just fake a single foreground control char with the palette number:
const spans = anser.ansiToJson(`\x1b[38;5;${paletteColor}m`); 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) { if (rgb) {
return `rgb(${rgb})`; return `rgb(${rgb})`;

View file

@ -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\``);
}

View file

@ -6,6 +6,7 @@ import * as tasks from "./tasks";
import { CtxInit } from "./ctx"; import { CtxInit } from "./ctx";
import { makeDebugConfig } from "./debug"; import { makeDebugConfig } from "./debug";
import { Config, RunnableEnvCfg } from "./config"; import { Config, RunnableEnvCfg } from "./config";
import { unwrapUndefinable } from "./undefinable";
const quickPickButtons = [ const quickPickButtons = [
{ iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configuration." }, { iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configuration." },
@ -68,12 +69,14 @@ export async function selectRunnable(
quickPick.onDidHide(() => close()), quickPick.onDidHide(() => close()),
quickPick.onDidAccept(() => close(quickPick.selectedItems[0])), quickPick.onDidAccept(() => close(quickPick.selectedItems[0])),
quickPick.onDidTriggerButton(async (_button) => { quickPick.onDidTriggerButton(async (_button) => {
await makeDebugConfig(ctx, quickPick.activeItems[0].runnable); const runnable = unwrapUndefinable(quickPick.activeItems[0]).runnable;
await makeDebugConfig(ctx, runnable);
close(); close();
}), }),
quickPick.onDidChangeActive((active) => { quickPick.onDidChangeActive((activeList) => {
if (showButtons && active.length > 0) { if (showButtons && activeList.length > 0) {
if (active[0].label.startsWith("cargo")) { const active = unwrapUndefinable(activeList[0]);
if (active.label.startsWith("cargo")) {
// save button makes no sense for `cargo test` or `cargo check` // save button makes no sense for `cargo test` or `cargo check`
quickPick.buttons = []; quickPick.buttons = [];
} else if (quickPick.buttons.length === 0) { } else if (quickPick.buttons.length === 0) {

View file

@ -1,10 +1,11 @@
import * as vscode from "vscode"; import * as vscode from "vscode";
import { assert } from "./util"; import { assert } from "./util";
import { unwrapUndefinable } from "./undefinable";
export async function applySnippetWorkspaceEdit(edit: vscode.WorkspaceEdit) { export async function applySnippetWorkspaceEdit(edit: vscode.WorkspaceEdit) {
if (edit.entries().length === 1) { if (edit.entries().length === 1) {
const [uri, edits] = edit.entries()[0]; const [uri, edits] = unwrapUndefinable(edit.entries()[0]);
const editor = await editorFromUri(uri); const editor = await editorFromUri(uri);
if (editor) await applySnippetTextEdits(editor, edits); if (editor) await applySnippetTextEdits(editor, edits);
return; return;
@ -68,7 +69,8 @@ export async function applySnippetTextEdits(editor: vscode.TextEditor, edits: vs
}); });
if (selections.length > 0) editor.selections = selections; if (selections.length > 0) editor.selections = selections;
if (selections.length === 1) { if (selections.length === 1) {
editor.revealRange(selections[0], vscode.TextEditorRevealType.InCenterIfOutsideViewport); const selection = unwrapUndefinable(selections[0]);
editor.revealRange(selection, vscode.TextEditorRevealType.InCenterIfOutsideViewport);
} }
} }

View file

@ -2,6 +2,7 @@ import * as vscode from "vscode";
import * as toolchain from "./toolchain"; import * as toolchain from "./toolchain";
import { Config } from "./config"; import { Config } from "./config";
import { log } from "./util"; import { log } from "./util";
import { unwrapUndefinable } from "./undefinable";
// This ends up as the `type` key in tasks.json. RLS also uses `cargo` and // 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. // our configuration should be compatible with it so use the same key.
@ -120,7 +121,8 @@ export async function buildCargoTask(
const fullCommand = [...cargoCommand, ...args]; 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( return new vscode.Task(

View file

@ -4,6 +4,8 @@ import * as path from "path";
import * as readline from "readline"; import * as readline from "readline";
import * as vscode from "vscode"; import * as vscode from "vscode";
import { execute, log, memoizeAsync } from "./util"; import { execute, log, memoizeAsync } from "./util";
import { unwrapNullable } from "./nullable";
import { unwrapUndefinable } from "./undefinable";
interface CompilationArtifact { interface CompilationArtifact {
fileName: string; fileName: string;
@ -93,7 +95,8 @@ export class Cargo {
throw new Error("Multiple compilation artifacts are not supported."); throw new Error("Multiple compilation artifacts are not supported.");
} }
return artifacts[0].fileName; const artifact = unwrapUndefinable(artifacts[0]);
return artifact.fileName;
} }
private async runCargo( 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 data = await execute(`${rustcPath} -V -v`, { cwd: dir });
const rx = /commit-hash:\s(.*)$/m; 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 */ /** Mirrors `toolchain::cargo()` implementation */

View file

@ -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\``);
}

View file

@ -14,8 +14,7 @@
// to update typescript version without any code change. // to update typescript version without any code change.
"useUnknownInCatchVariables": false, "useUnknownInCatchVariables": false,
"exactOptionalPropertyTypes": false, "exactOptionalPropertyTypes": false,
"noPropertyAccessFromIndexSignature": false, "noPropertyAccessFromIndexSignature": false
"noUncheckedIndexedAccess": false
}, },
"exclude": ["node_modules", ".vscode-test"], "exclude": ["node_modules", ".vscode-test"],
"include": ["src", "tests"] "include": ["src", "tests"]