src/misc: fix strict type errors in util files

For golang/vscode-go#57.

Change-Id: I79926f9e9a110cfaec69fe5b484faa244a1c3a66
Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/401619
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
Run-TryBot: Jamal Carvalho <jamal@golang.org>
Reviewed-by: Benny Siegert <bsiegert@gmail.com>
TryBot-Result: kokoro <noreply+kokoro@google.com>
diff --git a/src/config.ts b/src/config.ts
index 12f92c5..48cb530 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -17,7 +17,7 @@
 	return getConfig('gopls', uri);
 }
 
-function getConfig(section: string, uri?: vscode.Uri) {
+function getConfig(section: string, uri?: vscode.Uri | null) {
 	if (!uri) {
 		if (vscode.window.activeTextEditor) {
 			uri = vscode.window.activeTextEditor.document.uri;
diff --git a/src/diffUtils.ts b/src/diffUtils.ts
index 0a6fb31..d418ced 100644
--- a/src/diffUtils.ts
+++ b/src/diffUtils.ts
@@ -36,6 +36,7 @@
 	constructor(action: number, start: Position) {
 		this.action = action;
 		this.start = start;
+		this.end = start;
 		this.text = '';
 	}
 
diff --git a/src/pickProcess.ts b/src/pickProcess.ts
index 6b1ac33..be777fa 100644
--- a/src/pickProcess.ts
+++ b/src/pickProcess.ts
@@ -105,7 +105,7 @@
 	}
 	const args = ['version'];
 	processes.forEach((item) => {
-		if (item.executable?.length > 0) {
+		if (item.executable) {
 			args.push(item.executable);
 		}
 	});
@@ -117,7 +117,7 @@
 
 	const goProcesses: AttachItem[] = [];
 	processes.forEach((item) => {
-		if (goProcessExecutables.indexOf(item.executable) >= 0) {
+		if (item.executable && goProcessExecutables.indexOf(item.executable) >= 0) {
 			item.isGo = true;
 			goProcesses.push(item);
 		}
@@ -133,7 +133,7 @@
 	const lines = stdout.toString().split('\n');
 	lines.forEach((line) => {
 		const match = line.match(goVersionRegexp);
-		if (match?.length > 0) {
+		if (match && match.length > 0) {
 			const exe = line.substr(0, line.length - match[0].length);
 			goProcessExes.push(exe);
 		}
@@ -194,8 +194,8 @@
 
 async function runCommand(
 	processCmd: ProcessListCommand
-): Promise<{ err: cp.ExecException; stdout: string; stderr: string }> {
-	return await new Promise<{ err: cp.ExecException; stdout: string; stderr: string }>((resolve) => {
+): Promise<{ err: cp.ExecException | null; stdout: string; stderr: string }> {
+	return await new Promise<{ err: cp.ExecException | null; stdout: string; stderr: string }>((resolve) => {
 		cp.execFile(processCmd.command, processCmd.args, (err, stdout, stderr) => {
 			resolve({ err, stdout, stderr });
 		});
diff --git a/src/stateUtils.ts b/src/stateUtils.ts
index b0ca6a8..75abf55 100644
--- a/src/stateUtils.ts
+++ b/src/stateUtils.ts
@@ -18,7 +18,7 @@
 
 export function updateGlobalState(key: string, value: any) {
 	if (!globalState) {
-		return;
+		return Promise.resolve();
 	}
 	return globalState.update(key, value);
 }
@@ -44,7 +44,7 @@
 
 export function updateWorkspaceState(key: string, value: any) {
 	if (!workspaceState) {
-		return;
+		return Promise.resolve();
 	}
 	return workspaceState.update(key, value);
 }
@@ -75,15 +75,17 @@
 	return [];
 }
 
-async function resetStateQuickPick(state: vscode.Memento, updateFn: (key: string, value: any) => {}) {
+async function resetStateQuickPick(state: vscode.Memento, updateFn: (key: string, value: any) => Thenable<void>) {
 	const items = await vscode.window.showQuickPick(getMementoKeys(state), {
 		canPickMany: true,
 		placeHolder: 'Select the keys to reset.'
 	});
-	resetItemsState(items, updateFn);
+	if (items) {
+		resetItemsState(items, updateFn);
+	}
 }
 
-export function resetItemsState(items: string[], updateFn: (key: string, value: any) => {}) {
+export function resetItemsState(items: string[], updateFn: (key: string, value: any) => Thenable<void>) {
 	if (!items) {
 		return;
 	}
diff --git a/src/testUtils.ts b/src/testUtils.ts
index eb89f54..1c7c2fd 100644
--- a/src/testUtils.ts
+++ b/src/testUtils.ts
@@ -142,7 +142,7 @@
  */
 export async function getTestFunctions(
 	doc: vscode.TextDocument,
-	token: vscode.CancellationToken
+	token?: vscode.CancellationToken
 ): Promise<vscode.DocumentSymbol[] | undefined> {
 	const documentSymbolProvider = new GoDocumentSymbolProvider(true);
 	const symbols = await documentSymbolProvider.provideDocumentSymbols(doc, token);
@@ -171,7 +171,7 @@
 export function extractInstanceTestName(symbolName: string): string {
 	const match = symbolName.match(testMethodRegex);
 	if (!match || match.length !== 3) {
-		return null;
+		return '';
 	}
 	return match[2];
 }
@@ -211,9 +211,9 @@
 	allTests: vscode.DocumentSymbol[]
 ): vscode.DocumentSymbol[] {
 	// get non-instance test functions
-	const testFunctions = allTests.filter((t) => !testMethodRegex.test(t.name));
+	const testFunctions = allTests?.filter((t) => !testMethodRegex.test(t.name));
 	// filter further to ones containing suite.Run()
-	return testFunctions.filter((t) => doc.getText(t.range).includes('suite.Run('));
+	return testFunctions?.filter((t) => doc.getText(t.range).includes('suite.Run(')) ?? [];
 }
 
 /**
@@ -224,7 +224,7 @@
  */
 export async function getBenchmarkFunctions(
 	doc: vscode.TextDocument,
-	token: vscode.CancellationToken
+	token?: vscode.CancellationToken
 ): Promise<vscode.DocumentSymbol[] | undefined> {
 	const documentSymbolProvider = new GoDocumentSymbolProvider();
 	const symbols = await documentSymbolProvider.provideDocumentSymbols(doc, token);
@@ -357,7 +357,9 @@
 		});
 	} catch (err) {
 		outputChannel.appendLine(`Error: ${testType} failed.`);
-		outputChannel.appendLine(err);
+		if (err instanceof Error) {
+			outputChannel.appendLine((err as Error).message);
+		}
 	}
 	if (tmpCoverPath) {
 		await applyCodeCoverageToAllEditors(tmpCoverPath, testconfig.dir);
@@ -368,8 +370,8 @@
 async function getTestTargetPackages(testconfig: TestConfig, outputChannel: vscode.OutputChannel) {
 	const targets = testconfig.includeSubDirectories ? ['./...'] : [];
 	let currentGoWorkspace = '';
-	let getCurrentPackagePromise: Promise<string>;
-	let pkgMapPromise: Promise<Map<string, string>>;
+	let getCurrentPackagePromise: Promise<string> | undefined;
+	let pkgMapPromise: Promise<Map<string, string> | void>;
 	if (testconfig.isMod) {
 		getCurrentPackagePromise = getCurrentPackage(testconfig.dir);
 		// We need the mapping to get absolute paths for the files in the test output.
@@ -381,7 +383,7 @@
 			currentGoWorkspace ? testconfig.dir.substr(currentGoWorkspace.length + 1) : ''
 		);
 		// We dont need mapping, as we can derive the absolute paths from package path
-		pkgMapPromise = Promise.resolve(null);
+		pkgMapPromise = Promise.resolve();
 	}
 
 	let pkgMap = new Map<string, string>();
@@ -412,7 +414,7 @@
 	args: Array<string>; // test command args.
 	outArgs: Array<string>; // compact test command args to show to user.
 	tmpCoverPath?: string; // coverage file path if coverage info is necessary.
-	addJSONFlag: boolean; // true if we add extra -json flag for stream processing.
+	addJSONFlag: boolean | undefined; // true if we add extra -json flag for stream processing.
 } {
 	const args: Array<string> = ['test'];
 	// user-specified flags
@@ -434,7 +436,7 @@
 	}
 
 	// coverage flags
-	let tmpCoverPath: string;
+	let tmpCoverPath: string | undefined;
 	if (testconfig.applyCodeCoverage) {
 		tmpCoverPath = getTempFilePath('go-code-cover');
 		args.push('-coverprofile=' + tmpCoverPath);
diff --git a/src/util.ts b/src/util.ts
index f9110a8..91d6707 100644
--- a/src/util.ts
+++ b/src/util.ts
@@ -167,7 +167,7 @@
 	const legacyCfg = gocfg.get('useGoProxyToCheckForToolUpdates');
 	if (legacyCfg === false) {
 		const cfg = gocfg.inspect('toolsManagement.checkForUpdates');
-		if (cfg.globalValue === undefined && cfg.workspaceValue === undefined) {
+		if (cfg?.globalValue === undefined && cfg?.workspaceValue === undefined) {
 			return 'local';
 		}
 	}
@@ -182,7 +182,7 @@
 
 export interface Prelude {
 	imports: Array<{ kind: string; start: number; end: number; pkgs: string[] }>;
-	pkg: { start: number; end: number; name: string };
+	pkg: { start: number; end: number; name: string } | null;
 }
 
 export function parseFilePrelude(text: string): Prelude {
@@ -270,7 +270,7 @@
 
 	// In case of multiple workspaces, find current workspace by checking if current file is
 	// under any of the workspaces in $GOPATH
-	let currentWorkspace: string = null;
+	let currentWorkspace: string | undefined;
 	for (const workspace of workspaces) {
 		// In case of nested workspaces, (example: both /Users/me and /Users/me/a/b/c are in $GOPATH)
 		// both parent & child workspace in the nested workspaces pair can make it inside the above if block
@@ -456,7 +456,7 @@
 
 	// If any of the folders in multi root have toolsGopath set and the workspace is trusted, use it.
 	for (const folder of vscode.workspace.workspaceFolders) {
-		let toolsGopathFromConfig = <string>getGoConfig(folder.uri).inspect('toolsGopath').workspaceFolderValue;
+		let toolsGopathFromConfig = <string>getGoConfig(folder.uri).inspect('toolsGopath')?.workspaceFolderValue;
 		toolsGopathFromConfig = resolvePath(toolsGopathFromConfig, folder.uri.fsPath);
 		if (toolsGopathFromConfig) {
 			return toolsGopathFromConfig;
@@ -479,10 +479,11 @@
 	uri?: vscode.Uri
 ): { binPath: string; why?: string } {
 	const cfg = getGoConfig(uri);
-	const alternateTools: { [key: string]: string } = cfg.get('alternateTools');
-	const alternateToolPath: string = alternateTools[tool];
+	const alternateTools: { [key: string]: string } | undefined = cfg.get('alternateTools');
+	const alternateToolPath: string | undefined = alternateTools?.[tool];
 
-	const gorootInSetting = resolvePath(cfg.get('goroot'));
+	const goroot = cfg.get<string>('goroot');
+	const gorootInSetting = goroot && resolvePath(goroot);
 
 	let selectedGoPath: string | undefined;
 	if (tool === 'go' && !gorootInSetting) {
@@ -493,7 +494,7 @@
 		tool,
 		tool === 'go' ? [] : [getToolsGopath(), getCurrentGoPath()],
 		tool === 'go' ? gorootInSetting : undefined,
-		selectedGoPath ?? resolvePath(alternateToolPath),
+		selectedGoPath ?? (alternateToolPath && resolvePath(alternateToolPath)),
 		useCache
 	);
 }
@@ -511,14 +512,14 @@
 
 let currentGopath = '';
 export function getCurrentGoPath(workspaceUri?: vscode.Uri): string {
-	const activeEditorUri = vscode.window.activeTextEditor && vscode.window.activeTextEditor.document.uri;
-	const currentFilePath = fixDriveCasingInWindows(activeEditorUri && activeEditorUri.fsPath);
-	const currentRoot = (workspaceUri && workspaceUri.fsPath) || getWorkspaceFolderPath(activeEditorUri);
+	const activeEditorUri = vscode.window.activeTextEditor?.document.uri;
+	const currentFilePath = fixDriveCasingInWindows(activeEditorUri?.fsPath ?? '');
+	const currentRoot = (workspaceUri && workspaceUri.fsPath) || getWorkspaceFolderPath(activeEditorUri) || '';
 	const config = getGoConfig(workspaceUri || activeEditorUri);
 
 	// Infer the GOPATH from the current root or the path of the file opened in current editor
 	// Last resort: Check for the common case where GOPATH itself is opened directly in VS Code
-	let inferredGopath: string;
+	let inferredGopath: string | undefined;
 	if (config['inferGopath'] === true) {
 		inferredGopath = getInferredGopath(currentRoot) || getInferredGopath(currentFilePath);
 		if (!inferredGopath) {
@@ -536,11 +537,11 @@
 	}
 
 	const configGopath = config['gopath'] ? resolvePath(substituteEnv(config['gopath']), currentRoot) : '';
-	currentGopath = inferredGopath ? inferredGopath : configGopath || process.env['GOPATH'];
+	currentGopath = (inferredGopath ? inferredGopath : configGopath || process.env['GOPATH']) ?? '';
 	return currentGopath;
 }
 
-export function getModuleCache(): string {
+export function getModuleCache(): string | undefined {
 	if (process.env['GOMODCACHE']) {
 		return process.env['GOMODCACHE'];
 	}
@@ -550,20 +551,20 @@
 }
 
 export function getExtensionCommands(): any[] {
-	const pkgJSON = vscode.extensions.getExtension(extensionId).packageJSON;
+	const pkgJSON = vscode.extensions.getExtension(extensionId)?.packageJSON;
 	if (!pkgJSON.contributes || !pkgJSON.contributes.commands) {
-		return;
+		return [];
 	}
 	const extensionCommands: any[] = vscode.extensions
 		.getExtension(extensionId)
-		.packageJSON.contributes.commands.filter((x: any) => x.command !== 'go.show.commands');
+		?.packageJSON.contributes.commands.filter((x: any) => x.command !== 'go.show.commands');
 	return extensionCommands;
 }
 
 export class LineBuffer {
 	private buf = '';
 	private lineListeners: { (line: string): void }[] = [];
-	private lastListeners: { (last: string): void }[] = [];
+	private lastListeners: { (last: string | null): void }[] = [];
 
 	public append(chunk: string) {
 		this.buf += chunk;
@@ -586,7 +587,7 @@
 		this.lineListeners.push(listener);
 	}
 
-	public onDone(listener: (last: string) => void) {
+	public onDone(listener: (last: string | null) => void) {
 		this.lastListeners.push(listener);
 	}
 
@@ -594,7 +595,7 @@
 		this.lineListeners.forEach((listener) => listener(line));
 	}
 
-	private fireDone(last: string) {
+	private fireDone(last: string | null) {
 		this.lastListeners.forEach((listener) => listener(last));
 	}
 }
@@ -697,7 +698,7 @@
 export interface ICheckResult {
 	file: string;
 	line: number;
-	col: number;
+	col: number | undefined;
 	msg: string;
 	severity: string;
 }
@@ -814,7 +815,7 @@
 }
 
 export function handleDiagnosticErrors(
-	document: vscode.TextDocument,
+	document: vscode.TextDocument | undefined,
 	errors: ICheckResult[],
 	diagnosticCollection: vscode.DiagnosticCollection,
 	diagnosticSource?: string
@@ -852,7 +853,7 @@
 				doc.lineAt(error.line - 1).range.end.character + 1 // end of the line
 			);
 			const text = doc.getText(tempRange);
-			const [, leading, trailing] = /^(\s*).*(\s*)$/.exec(text);
+			const [, leading, trailing] = /^(\s*).*(\s*)$/.exec(text)!;
 			if (!error.col) {
 				startColumn = leading.length; // beginning of the non-white space.
 			} else {
@@ -882,12 +883,12 @@
 			removeDuplicateDiagnostics(vetDiagnosticCollection, fileUri, newDiagnostics);
 		} else if (buildDiagnosticCollection && buildDiagnosticCollection.has(fileUri)) {
 			// If there are build errors on current file, ignore the new lint/vet warnings co-inciding with them
-			newDiagnostics = deDupeDiagnostics(buildDiagnosticCollection.get(fileUri).slice(), newDiagnostics);
+			newDiagnostics = deDupeDiagnostics(buildDiagnosticCollection.get(fileUri)!.slice(), newDiagnostics);
 		}
 		// If there are errors from the language client that are on the current file, ignore the warnings co-inciding
 		// with them.
 		if (languageClient && languageClient.diagnostics?.has(fileUri)) {
-			newDiagnostics = deDupeDiagnostics(languageClient.diagnostics.get(fileUri).slice(), newDiagnostics);
+			newDiagnostics = deDupeDiagnostics(languageClient.diagnostics.get(fileUri)!.slice(), newDiagnostics);
 		}
 		diagnosticCollection.set(fileUri, newDiagnostics);
 	});
@@ -903,7 +904,7 @@
 	newDiagnostics: vscode.Diagnostic[]
 ) {
 	if (collection && collection.has(fileUri)) {
-		collection.set(fileUri, deDupeDiagnostics(newDiagnostics, collection.get(fileUri).slice()));
+		collection.set(fileUri, deDupeDiagnostics(newDiagnostics, collection.get(fileUri)!.slice()));
 	}
 }
 
@@ -954,7 +955,7 @@
 		const byteDelta = byteOffset - nearest.key;
 
 		if (byteDelta === 0) {
-			return nearest.value;
+			return nearest.value ?? 0;
 		}
 
 		let charDelta: number;
@@ -964,8 +965,8 @@
 			charDelta = -buffer.toString('utf8', byteOffset, nearest.key).length;
 		}
 
-		memo.insert(byteOffset, nearest.value + charDelta);
-		return nearest.value + charDelta;
+		memo.insert(byteOffset, (nearest.value ?? 0) + charDelta);
+		return (nearest.value ?? 0) + charDelta;
 	};
 }
 
@@ -987,7 +988,7 @@
 	}
 }
 
-let tmpDir: string;
+let tmpDir: string | undefined;
 
 /**
  * Returns file path for given name in temp dir
@@ -1022,7 +1023,7 @@
 export function runGodoc(
 	cwd: string,
 	packagePath: string,
-	receiver: string,
+	receiver: string | undefined,
 	symbol: string,
 	token: vscode.CancellationToken
 ) {
diff --git a/src/utils/pathUtils.ts b/src/utils/pathUtils.ts
index 7f68ce5..419cbf2 100644
--- a/src/utils/pathUtils.ts
+++ b/src/utils/pathUtils.ts
@@ -20,7 +20,11 @@
 export const envPath = process.env['PATH'] || (process.platform === 'win32' ? process.env['Path'] : null);
 
 // find the tool's path from the given PATH env var, or null if the tool is not found.
-export function getBinPathFromEnvVar(toolName: string, envVarValue: string, appendBinToPath: boolean): string | null {
+export function getBinPathFromEnvVar(
+	toolName: string,
+	envVarValue: string | null | undefined,
+	appendBinToPath: boolean
+): string | null {
 	toolName = correctBinname(toolName);
 	if (envVarValue) {
 		const paths = envVarValue.split(path.delimiter);
@@ -187,7 +191,7 @@
 }
 
 // Walks up given folder path to return the closest ancestor that has `src` as a child
-export function getInferredGopath(folderPath: string): string {
+export function getInferredGopath(folderPath: string): string | undefined {
 	if (!folderPath) {
 		return;
 	}
@@ -208,7 +212,7 @@
  */
 export function getCurrentGoWorkspaceFromGOPATH(gopath: string, currentFileDirPath: string): string {
 	if (!gopath) {
-		return;
+		return '';
 	}
 	const workspaces: string[] = gopath.split(path.delimiter);
 	let currentWorkspace = '';
diff --git a/src/utils/processUtils.ts b/src/utils/processUtils.ts
index 1caa263..a48f7b0 100644
--- a/src/utils/processUtils.ts
+++ b/src/utils/processUtils.ts
@@ -8,10 +8,7 @@
 import kill = require('tree-kill');
 
 // Kill a process and its children, returning a promise.
-export function killProcessTree(p: ChildProcess, logger?: (...args: any[]) => void): Promise<void> {
-	if (!logger) {
-		logger = console.log;
-	}
+export function killProcessTree(p: ChildProcess, logger: (...args: any[]) => void = console.log): Promise<void> {
 	if (!p || !p.pid || p.exitCode !== null) {
 		return Promise.resolve();
 	}