src/goTest: fix strict type errors
For golang/vscode-go#57.
Change-Id: I13bae5ae849fae74cabcf7fe2fe88145320d95b8
Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/401617
Reviewed-by: Benny Siegert <bsiegert@gmail.com>
Run-TryBot: Jamal Carvalho <jamal@golang.org>
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
TryBot-Result: kokoro <noreply+kokoro@google.com>
diff --git a/src/goTest/explore.ts b/src/goTest/explore.ts
index 6bf28bd..67facfa 100644
--- a/src/goTest/explore.ts
+++ b/src/goTest/explore.ts
@@ -96,7 +96,7 @@
if (!options) return;
try {
- await inst.runner.run(new TestRunRequest([item]), null, options);
+ await inst.runner.run(new TestRunRequest([item]), undefined, options);
} catch (error) {
const m = 'Failed to execute tests';
outputChannel.appendLine(`${m}: ${error}`);
@@ -242,7 +242,7 @@
return;
}
- const ws = this.workspace.getWorkspaceFolder(item.uri);
+ const ws = item.uri && this.workspace.getWorkspaceFolder(item.uri);
if (!ws) {
dispose(this.resolver, item);
}
@@ -255,13 +255,13 @@
protected async didDeleteFile(file: Uri) {
const id = GoTest.id(file, 'file');
- function find(children: TestItemCollection): TestItem {
+ function find(children: TestItemCollection): TestItem | undefined {
return findItem(children, (item) => {
if (item.id === id) {
return item;
}
- if (!file.path.startsWith(item.uri.path)) {
+ if (!item.uri || !file.path.startsWith(item.uri.path)) {
return;
}
@@ -272,6 +272,8 @@
const found = find(this.ctrl.items);
if (found) {
dispose(this.resolver, found);
+ }
+ if (found?.parent) {
disposeIfEmpty(this.resolver, found.parent);
}
}
diff --git a/src/goTest/profile.ts b/src/goTest/profile.ts
index 8d49511..78e57c9 100644
--- a/src/goTest/profile.ts
+++ b/src/goTest/profile.ts
@@ -53,12 +53,12 @@
}
preRun(options: ProfilingOptions, item: TestItem): string[] {
- const kind = Kind.get(options.kind);
+ const kind = options.kind && Kind.get(options.kind);
if (!kind) return [];
const run = new File(kind, item);
const flags = [...run.flags];
- if (this.runs.has(item.id)) this.runs.get(item.id).unshift(run);
+ if (this.runs.has(item.id)) this.runs.get(item.id)?.unshift(run);
else this.runs.set(item.id, [run]);
return flags;
}
@@ -76,12 +76,13 @@
}
async configure(): Promise<ProfilingOptions | undefined> {
- const { profilekind } = await vscode.window.showQuickPick(
- Kind.all.map((x) => ({ label: x.label, profilekind: x })),
- {
- title: 'Profile'
- }
- );
+ const { profilekind } =
+ (await vscode.window.showQuickPick(
+ Kind.all.map((x) => ({ label: x.label, profilekind: x })),
+ {
+ title: 'Profile'
+ }
+ )) ?? {};
if (!profilekind) return;
return {
@@ -125,13 +126,13 @@
get tests() {
const items = Array.from(this.runs.keys());
items.sort((a: string, b: string) => {
- const aWhen = this.runs.get(a)[0].when.getTime();
- const bWhen = this.runs.get(b)[0].when.getTime();
+ const aWhen = this.runs.get(a)?.[0].when.getTime() ?? 0;
+ const bWhen = this.runs.get(b)?.[0].when.getTime() ?? 0;
return bWhen - aWhen;
});
// Filter out any tests that no longer exist
- return items.map((x) => this.resolver.all.get(x)).filter((x) => x);
+ return items.map((x) => this.resolver.all.get(x)).filter((x): x is TestItem => !!x);
}
// Profiles associated with the given test
@@ -168,7 +169,7 @@
const proc = spawn(getBinPath('go'), ['tool', 'pprof', '-http=:', '-no_browser', profile]);
pprofProcesses.add(proc);
- const port = await new Promise<string>((resolve, reject) => {
+ const port = await new Promise<string | undefined>((resolve, reject) => {
proc.on('error', (err) => {
pprofProcesses.delete(proc);
reject(err);
@@ -186,7 +187,7 @@
const m = stderr.match(/^Serving web UI on http:\/\/localhost:(?<port>\d+)\n/);
if (!m) return;
- resolve(m.groups.port);
+ resolve(m.groups?.port);
proc.stdout.off('data', captureStdout);
}
@@ -307,7 +308,7 @@
item.contextValue = 'go:test:test';
const options: TextDocumentShowOptions = {
preserveFocus: false,
- selection: new Range(element.range.start, element.range.start)
+ selection: element.range && new Range(element.range.start, element.range.start)
};
item.command = {
title: 'Go to test',
diff --git a/src/goTest/resolve.ts b/src/goTest/resolve.ts
index e208482..b096657 100644
--- a/src/goTest/resolve.ts
+++ b/src/goTest/resolve.ts
@@ -26,7 +26,7 @@
import { walk, WalkStop } from './walk';
import { importsTestify } from '../testUtils';
-export type ProvideSymbols = (doc: TextDocument, token: CancellationToken) => Thenable<DocumentSymbol[]>;
+export type ProvideSymbols = (doc: TextDocument, token?: CancellationToken) => Thenable<DocumentSymbol[]>;
const testFuncRegex = /^(?<name>(?<kind>Test|Benchmark|Example|Fuzz)($|\P{Ll}.*))/u;
const testMethodRegex = /^\(\*(?<type>[^)]+)\)\.(?<name>(?<kind>Test)($|\P{Ll}.*))$/u;
@@ -77,7 +77,7 @@
return;
}
- if (this.workspace.getWorkspaceFolder(item.uri)) {
+ if (item.uri && this.workspace.getWorkspaceFolder(item.uri)) {
dispose(this, item);
}
});
@@ -105,6 +105,7 @@
const { kind } = GoTest.parseId(item.id);
+ if (!item.uri) return;
// The user expanded a module or workspace - find all packages
if (kind === 'module' || kind === 'workspace') {
await walkPackages(this.workspace.fs, item.uri, async (uri) => {
@@ -140,11 +141,11 @@
function find(items: TestItemCollection) {
items.forEach((item) => {
- const itemStr = item.uri.toString();
+ const itemStr = item.uri?.toString();
if (findStr === itemStr) {
found.push(item);
find(item.children);
- } else if (findStr.startsWith(itemStr)) {
+ } else if (itemStr && findStr.startsWith(itemStr)) {
find(item.children);
}
});
@@ -170,7 +171,8 @@
// Create or Retrieve a sub test or benchmark. The ID will be of the form:
// file:///path/to/mod/file.go?test#TestXxx%2fA%2fB%2fC
- getOrCreateSubTest(item: TestItem, label: string, name: string, dynamic?: boolean): TestItem {
+ getOrCreateSubTest(item: TestItem, label: string, name: string, dynamic?: boolean): TestItem | undefined {
+ if (!item.uri) return;
const { kind } = GoTest.parseId(item.id);
let existing: TestItem | undefined;
@@ -202,7 +204,7 @@
async processDocument(doc: TextDocument, ranges?: Range[]) {
const seen = new Set<string>();
const item = await this.getFile(doc.uri);
- const symbols = await this.provideDocumentSymbols(doc, null);
+ const symbols = await this.provideDocumentSymbols(doc);
const testify = importsTestify(symbols);
for (const symbol of symbols) {
await this.processSymbol(doc, item, seen, testify, symbol);
@@ -210,12 +212,12 @@
item.children.forEach((child) => {
const { name } = GoTest.parseId(child.id);
- if (!seen.has(name)) {
+ if (!name || !seen.has(name)) {
dispose(this, child);
return;
}
- if (ranges?.some((r) => !!child.range.intersection(r))) {
+ if (ranges?.some((r) => !!child.range?.intersection(r))) {
item.children.forEach((x) => dispose(this, x));
}
});
@@ -235,7 +237,7 @@
private shouldSetRange(item: TestItem): boolean {
const config = getGoConfig(item.uri);
- return config.get<boolean>('testExplorer.showDynamicSubtestsInEditor');
+ return !!config.get<boolean>('testExplorer.showDynamicSubtestsInEditor');
}
// Create an item.
@@ -247,7 +249,7 @@
}
// Retrieve an item.
- private getItem(parent: TestItem | undefined, uri: Uri, kind: GoTestKind, name?: string): TestItem {
+ private getItem(parent: TestItem | undefined, uri: Uri, kind: GoTestKind, name?: string): TestItem | undefined {
return (parent?.children || this.ctrl.items).get(GoTest.id(uri, kind, name));
}
@@ -281,7 +283,7 @@
// Retrieve or create an item for a Go module.
private async getModule(uri: Uri): Promise<TestItem> {
- const existing = this.getItem(null, uri, 'module');
+ const existing = this.getItem(undefined, uri, 'module');
if (existing) {
return existing;
}
@@ -292,50 +294,54 @@
// Use the module name as the label
const label = findModuleName(contents.toString());
- const item = this.getOrCreateItem(null, label, uri, 'module');
+ const item = this.getOrCreateItem(undefined, label, uri, 'module');
item.canResolveChildren = true;
return item;
}
// Retrieve or create an item for a workspace folder that is not a module.
private async getWorkspace(ws: WorkspaceFolder): Promise<TestItem> {
- const existing = this.getItem(null, ws.uri, 'workspace');
+ const existing = this.getItem(undefined, ws.uri, 'workspace');
if (existing) {
return existing;
}
// Use the workspace folder name as the label
- const item = this.getOrCreateItem(null, ws.name, ws.uri, 'workspace');
+ const item = this.getOrCreateItem(undefined, ws.name, ws.uri, 'workspace');
item.canResolveChildren = true;
return item;
}
// Retrieve or create an item for a Go package.
- private async getPackage(uri: Uri): Promise<TestItem> {
+ private async getPackage(uri: Uri): Promise<TestItem | undefined> {
let item: TestItem;
const nested = getGoConfig(uri).get('testExplorer.packageDisplayMode') === 'nested';
- const modDir = Uri.file(await getModFolderPath(uri, true)); // TODO support non-file schemes
+ const modDirPath = await getModFolderPath(uri, true);
const wsfolder = workspace.getWorkspaceFolder(uri);
- if (modDir) {
+ if (modDirPath) {
+ const modDir = Uri.file(modDirPath); // TODO support non-file schemes
// If the package is in a module, add it as a child of the module
let parent = await this.getModule(modDir);
- if (uri.path === parent.uri.path) {
+ if (uri.path === parent.uri?.path) {
return parent;
}
if (nested) {
- const bits = path.relative(parent.uri.path, uri.path).split(path.sep);
+ const bits = parent.uri ? path.relative(parent.uri.path, uri.path).split(path.sep) : [];
while (bits.length > 1) {
+ if (!parent.uri?.path) continue;
const dir = bits.shift();
+ if (!dir) continue;
const dirUri = uri.with({ path: path.join(parent.uri.path, dir), query: '', fragment: '' });
parent = this.getOrCreateItem(parent, dir, dirUri, 'package');
}
}
- const label = uri.path.startsWith(parent.uri.path)
- ? uri.path.substring(parent.uri.path.length + 1)
- : uri.path;
+ const label =
+ parent.uri && uri.path.startsWith(parent.uri.path)
+ ? uri.path.substring(parent.uri.path.length + 1)
+ : uri.path;
item = this.getOrCreateItem(parent, label, uri, 'package');
} else if (wsfolder) {
// If the package is in a workspace folder, add it as a child of the workspace
@@ -351,14 +357,14 @@
item = this.getOrCreateItem(workspace, label, uri, 'package');
} else {
// Otherwise, add it directly to the root
- const existing = this.getItem(null, uri, 'package');
+ const existing = this.getItem(undefined, uri, 'package');
if (existing) {
return existing;
}
const srcPath = path.join(getCurrentGoPath(uri), 'src');
const label = uri.path.startsWith(srcPath) ? uri.path.substring(srcPath.length + 1) : uri.path;
- item = this.getOrCreateItem(null, label, uri, 'package');
+ item = this.getOrCreateItem(undefined, label, uri, 'package');
}
item.canResolveChildren = true;
@@ -382,7 +388,7 @@
private getTestSuite(type: string): TestSuite {
if (this.testSuites.has(type)) {
- return this.testSuites.get(type);
+ return this.testSuites.get(type) as TestSuite;
}
const methods = new Set<TestItem>();
@@ -420,21 +426,27 @@
seen.add(symbol.name);
- const kind = match.groups.kind.toLowerCase() as GoTestKind;
- const suite = match.groups.type ? this.getTestSuite(match.groups.type) : undefined;
+ const kind = match.groups?.kind.toLowerCase() as GoTestKind;
+ const suite = match.groups?.type ? this.getTestSuite(match.groups.type) : undefined;
const existing =
this.getItem(file, doc.uri, kind, symbol.name) ||
(suite?.func && this.getItem(suite?.func, doc.uri, kind, symbol.name));
if (existing) {
- if (!existing.range.isEqual(symbol.range)) {
+ if (!existing.range?.isEqual(symbol.range)) {
existing.range = symbol.range;
this.relocateChildren(existing);
}
return existing;
}
- const item = this.getOrCreateItem(suite?.func || file, match.groups.name, doc.uri, kind, symbol.name);
+ const item = this.getOrCreateItem(
+ suite?.func || file,
+ match.groups?.name ?? '<none>',
+ doc.uri,
+ kind,
+ symbol.name
+ );
item.range = symbol.range;
if (suite) {
@@ -460,11 +472,11 @@
const matchRunSuite = text.match(runTestSuiteRegex);
if (matchRunSuite) {
const g = matchRunSuite.groups;
- const suite = this.getTestSuite(g.type1 || g.type2);
+ const suite = this.getTestSuite(g?.type1 || g?.type2 || '');
suite.func = item;
for (const method of suite.methods) {
- if (GoTest.parseId(method.parent.id).kind !== 'file') {
+ if (!method.parent || GoTest.parseId(method.parent.id).kind !== 'file') {
continue;
}
diff --git a/src/goTest/run.ts b/src/goTest/run.ts
index 5005243..0fdb9f2 100644
--- a/src/goTest/run.ts
+++ b/src/goTest/run.ts
@@ -36,7 +36,7 @@
flags: string[];
isMod: boolean;
isBenchmark?: boolean;
- cancel: CancellationToken;
+ cancel?: CancellationToken;
run: TestRun;
options: ProfilingOptions;
@@ -150,7 +150,8 @@
}
const test = tests[0].item;
- const { kind, name } = GoTest.parseId(test.id);
+ const { kind, name = '' } = GoTest.parseId(test.id);
+ if (!test.uri) return;
const doc = await vscode.workspace.openTextDocument(test.uri);
await doc.save();
@@ -163,7 +164,7 @@
const id = `debug #${debugSessionID++} ${name}`;
const subs: vscode.Disposable[] = [];
- const sessionPromise = new Promise<DebugSession>((resolve) => {
+ const sessionPromise = new Promise<DebugSession | null>((resolve) => {
subs.push(
vscode.debug.onDidStartDebugSession((s) => {
if (s.configuration.sessionID === id) {
@@ -184,6 +185,7 @@
});
const run = this.ctrl.createTestRun(request, `Debug ${name}`);
+ if (!testFunctions) return;
const started = await debugTestAtCursor(doc, name, testFunctions, goConfig, id);
if (!started) {
subs.forEach((s) => s.dispose());
@@ -197,7 +199,7 @@
return;
}
- token.onCancellationRequested(() => vscode.debug.stopDebugging(session));
+ token?.onCancellationRequested(() => vscode.debug.stopDebugging(session));
await new Promise<void>((resolve) => {
const sub = vscode.debug.onDidTerminateDebugSession(didTerminateSession);
@@ -208,7 +210,7 @@
});
function didTerminateSession(s: DebugSession) {
- if (s.id !== session.id) return;
+ if (s.id !== session?.id) return;
resolve();
sub.dispose();
}
@@ -264,6 +266,7 @@
let success = true;
const subItems: string[] = [];
for (const [pkg, items] of collected.entries()) {
+ if (!pkg.uri) continue;
const isMod = isInMod(pkg) || (await isModSupported(pkg.uri, true));
const goConfig = getGoConfig(pkg.uri);
const flags = getTestFlags(goConfig);
@@ -288,7 +291,7 @@
const tests: Record<string, TestItem> = {};
const benchmarks: Record<string, TestItem> = {};
for (const { item, explicitlyIncluded } of items) {
- const { kind, name } = GoTest.parseId(item.id);
+ const { kind, name = '' } = GoTest.parseId(item.id);
if (/[/#]/.test(name)) subItems.push(name);
// When the user clicks the run button on a package, they expect all
@@ -303,7 +306,7 @@
continue;
}
- item.error = null;
+ item.error = undefined;
run.enqueued(item);
// Remove subtests created dynamically from test output
@@ -321,7 +324,7 @@
}
const record = new Map<string, string[]>();
- const concat = goConfig.get<boolean>('testExplorer.concatenateMessages');
+ const concat = !!goConfig.get<boolean>('testExplorer.concatenateMessages');
// https://github.com/golang/go/issues/39904
if (subItems.length > 0 && Object.keys(tests).length + Object.keys(benchmarks).length > 1) {
@@ -417,18 +420,21 @@
return;
}
- function getFile(item: TestItem): TestItem {
+ function getFile(item: TestItem): TestItem | undefined {
const { kind } = GoTest.parseId(item.id);
if (kind === 'file') return item;
- return getFile(item.parent);
+ return item.parent && getFile(item.parent);
}
const file = getFile(item);
- files.add(file);
+ if (file) {
+ files.add(file);
+ }
- const pkg = file.parent;
+ const pkg = file?.parent;
+ if (!pkg) return;
if (functions.has(pkg)) {
- functions.get(pkg).push({ item, explicitlyIncluded });
+ functions.get(pkg)?.push({ item, explicitlyIncluded });
} else {
functions.set(pkg, [{ item, explicitlyIncluded }]);
}
@@ -452,7 +458,7 @@
const success = await goTest({
...rest,
outputChannel,
- dir: pkg.uri.fsPath,
+ dir: pkg.uri?.fsPath ?? '',
functions: Object.keys(functions),
goTestOutputConsumer: rest.isBenchmark
? (e) => this.consumeGoBenchmarkEvent(run, functions, complete, e)
@@ -496,11 +502,11 @@
return this.resolver.getOrCreateSubTest(parent, name.substring(pos), name, true);
}
- const subName = name.substring(0, pos + m.index);
+ const subName = name.substring(0, pos + (m.index ?? 0));
const test = parent
- ? this.resolver.getOrCreateSubTest(parent, name.substring(pos, pos + m.index), subName, true)
+ ? this.resolver.getOrCreateSubTest(parent, name.substring(pos, pos + (m.index ?? 0)), subName, true)
: tests[subName];
- return resolve(test, pos + m.index, m[0].length);
+ return resolve(test, pos + (m.index ?? 0), m[0].length);
};
return resolve();
@@ -551,14 +557,14 @@
}
// Find (or create) the (sub)benchmark
- const test = this.resolveTestName(benchmarks, m.groups.name);
+ const test = m.groups && this.resolveTestName(benchmarks, m.groups.name);
if (!test) {
return;
}
// If output includes benchmark results, the benchmark passed. If output
// only includes the benchmark name, the benchmark is running.
- if (m.groups.result) {
+ if (m.groups?.result) {
run.passed(test);
complete.add(test);
vscode.commands.executeCommand('testing.showMostRecentOutput');
@@ -590,7 +596,7 @@
concat: boolean,
e: GoTestOutput
) {
- const test = this.resolveTestName(tests, e.Test);
+ const test = e.Test && this.resolveTestName(tests, e.Test);
if (!test) {
return;
}
@@ -609,7 +615,7 @@
// TODO(firelizzard18): add messages on pass, once that capability
// is added.
complete.add(test);
- run.passed(test, e.Elapsed * 1000);
+ run.passed(test, (e.Elapsed ?? 0) * 1000);
break;
case 'fail': {
@@ -617,21 +623,21 @@
const messages = this.parseOutput(test, record.get(test.id) || []);
if (!concat) {
- run.failed(test, messages, e.Elapsed * 1000);
+ run.failed(test, messages, (e.Elapsed ?? 0) * 1000);
break;
}
const merged = new Map<string, TestMessage>();
for (const { message, location } of messages) {
- const loc = `${location.uri}:${location.range.start.line}`;
+ const loc = `${location?.uri}:${location?.range.start.line}`;
if (merged.has(loc)) {
- merged.get(loc).message += '\n' + message;
+ merged.get(loc)!.message += '\n' + message;
} else {
merged.set(loc, { message, location });
}
}
- run.failed(test, Array.from(merged.values()), e.Elapsed * 1000);
+ run.failed(test, Array.from(merged.values()), (e.Elapsed ?? 0) * 1000);
break;
}
@@ -641,12 +647,12 @@
break;
case 'output':
- if (/^(=== RUN|\s*--- (FAIL|PASS): )/.test(e.Output)) {
+ if (/^(=== RUN|\s*--- (FAIL|PASS): )/.test(e.Output ?? '')) {
break;
}
- if (record.has(test.id)) record.get(test.id).push(e.Output);
- else record.set(test.id, [e.Output]);
+ if (record.has(test.id)) record.get(test.id)!.push(e.Output ?? '');
+ else record.set(test.id, [e.Output ?? '']);
break;
}
}
@@ -661,16 +667,19 @@
const got = output.slice(gotI + 1, wantI).join('');
const want = output.slice(wantI + 1).join('');
const message = TestMessage.diff('Output does not match', want, got);
- message.location = new Location(test.uri, test.range.start);
+ if (test.uri && test.range) {
+ message.location = new Location(test.uri, test.range.start);
+ }
messages.push(message);
output = output.slice(0, gotI);
}
- let current: Location;
- const dir = Uri.joinPath(test.uri, '..');
+ let current: Location | undefined;
for (const line of output) {
const m = line.match(/^\s*(?<file>.*\.go):(?<line>\d+): ?(?<message>.*\n)$/);
- if (m) {
+ if (m?.groups) {
+ if (!test.uri) return [];
+ const dir = Uri.joinPath(test.uri, '..');
const file = Uri.joinPath(dir, m.groups.file);
const ln = Number(m.groups.line) - 1; // VSCode uses 0-based line numbering (internally)
current = new Location(file, new Position(ln, 0));
diff --git a/src/goTest/utils.ts b/src/goTest/utils.ts
index 33040af..de62641 100644
--- a/src/goTest/utils.ts
+++ b/src/goTest/utils.ts
@@ -96,7 +96,7 @@
export function dispose(resolver: GoTestResolver, item: vscode.TestItem) {
resolver.all.delete(item.id);
- item.parent.children.delete(item.id);
+ item.parent?.children.delete(item.id);
}
// Dispose of the item if it has no children, recursively. This facilitates
@@ -113,7 +113,9 @@
}
dispose(resolver, item);
- disposeIfEmpty(resolver, item.parent);
+ if (item.parent) {
+ disposeIfEmpty(resolver, item.parent);
+ }
}
// The 'name' group captures the module name, and the unnamed group ignores any comment that might follow the name.
@@ -124,5 +126,5 @@
if (match === null) {
throw new Error('failed to find module name in go.mod');
}
- return match.groups.name;
+ return match.groups?.name ?? '';
}