[ts][lint] Improve typecheck and linting #3 Include the `tseslint.configs.recommendedTypeChecked` preset configuration and refactor code to fix the new lint warnings. Bug: 497015838 Change-Id: I6deb465b6ec5a2962ad4d229865c3dc692924d90 Reviewed-on: https://fuchsia-review.googlesource.com/c/vscode-plugins/+/1559520 Kokoro: Kokoro <noreply+kokoro@google.com> Reviewed-by: Jacob Rutherford <jruthe@google.com>
diff --git a/eslint.config.mjs b/eslint.config.mjs index 9c136c1..bdbcd6e 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs
@@ -11,6 +11,12 @@ export default defineConfig( eslint.configs.recommended, tseslint.configs.recommended, + ...tseslint.configs.recommendedTypeChecked.map(config => ({ + ...config, + + // Only apply type-checked rules to TypeScript files. + files: ['**/*.ts'], + })), [ { ignores: ['.vscode-test/**', 'dist/**', 'test-dist/**'], @@ -47,27 +53,19 @@ // TypeScript specific rules { files: ['**/*.ts'], - plugins: { - '@typescript-eslint': tseslint.plugin, - }, - languageOptions: { - parser: tseslint.parser, - ecmaVersion: 'latest', - sourceType: 'module', - parserOptions: { project: 'tsconfig.json', }, }, + plugins: { + '@typescript-eslint': tseslint.plugin, + }, + rules: { - // Overrides/additions to recommended + // Additions to recommended '@typescript-eslint/naming-convention': 'error', - '@typescript-eslint/no-floating-promises': 'error', - '@typescript-eslint/no-misused-promises': 'error', - '@typescript-eslint/only-throw-error': 'error', - '@typescript-eslint/require-await': 'error', }, },
diff --git a/src/analytics/ga4.ts b/src/analytics/ga4.ts index 5f3f5fd..a64e78e 100644 --- a/src/analytics/ga4.ts +++ b/src/analytics/ga4.ts
@@ -70,21 +70,29 @@ try { const req = https.request(options, (resp) => { if (debugLevel > 0) { + let responseText = ''; + resp.setEncoding('utf-8'); resp.on('data', (c: Buffer | string) => { + responseText += c.toString(); + }); + resp.on('end', () => { try { - const gaDebugResp = JSON.parse(c.toString()); - if (gaDebugResp && gaDebugResp.validationMessages && - gaDebugResp.validationMessages.length === 0) { - logger.debug('GA4 Sent OK!'); - } else if ( - gaDebugResp && gaDebugResp.validationMessages && - gaDebugResp.validationMessages.length > 0) { - logger.debug(`Invalid GA4 hit: ${c?.toString()}`); - } else { - logger.debug(`Unexpected GA4 debug response: ${c?.toString()}`); + const gaDebugResp = JSON.parse(responseText) as unknown; + if (typeof gaDebugResp !== 'object' || + gaDebugResp === null || + !('validationMessages' in gaDebugResp) || + !Array.isArray(gaDebugResp.validationMessages)) { + logger.debug(`Unexpected GA4 debug response: ${responseText}`); + return; } + if (gaDebugResp.validationMessages.length > 0) { + logger.debug(`Invalid GA4 hit: ${responseText}`); + return; + } + + logger.debug('GA4 Sent OK!'); } catch { - logger.error(`Error in GA4 debug response: ${c?.toString()}`); + logger.error(`Error in GA4 debug response: ${responseText}`); } }); } @@ -113,7 +121,7 @@ * @param e the captured error */ function handleError(e: unknown) { - console.log(`Failed to send analytics, disabling for session: ${e}`); + console.log(`Failed to send analytics, disabling for session: ${String(e)}`); } /**
diff --git a/src/analytics/info.ts b/src/analytics/info.ts index 0c6ef87..9a7105e 100644 --- a/src/analytics/info.ts +++ b/src/analytics/info.ts
@@ -57,12 +57,12 @@ return AnalyticsInfo.instance.isInternalUserInternal; } - static async init(context: vscode.ExtensionContext): Promise<void> { - await AnalyticsInfo.instance.initInternal(context); + static init(context: vscode.ExtensionContext): void { + AnalyticsInfo.instance.initInternal(context); } private initInternal(context: vscode.ExtensionContext): void { - const packageJson = context.extension.packageJSON; + const packageJson = context.extension.packageJSON as { version: string }; this.extensionVersionInternal = packageJson.version; this.osInternal = os.type(); this.archInternal = os.machine();
diff --git a/src/analytics/init.ts b/src/analytics/init.ts index 044f26b..c022607 100644 --- a/src/analytics/init.ts +++ b/src/analytics/init.ts
@@ -20,7 +20,7 @@ return; } - await AnalyticsInfo.init(context); + AnalyticsInfo.init(context); AnalyticsState.initDebugLevel(); if (AnalyticsInfo.isInternalUser) {
diff --git a/src/components.ts b/src/components.ts index da728a5..9f90285 100644 --- a/src/components.ts +++ b/src/components.ts
@@ -103,8 +103,8 @@ registerCommandWithAnalyticsEvent( command, async (info?: ComponentInfo) => { const args = [...commandOptions.args]; - let moniker: string | undefined = info?.moniker.value ?? ''; - let url: string | undefined = info?.url ?? ''; + let moniker: string | undefined = info?.moniker.value; + let url: string | undefined = info?.url; if (!moniker) { moniker = await vscode.window.showInputBox({ @@ -116,22 +116,22 @@ if (!moniker) { return; } - if (!url && commandOptions.requiresURL) { - url = await vscode.window.showInputBox({ - placeHolder: '(e.g. fuchsia-pkg://fuchsia.com/hello-world-cpp#meta/hello-world-cpp.cm)', - value: '', - prompt: 'Enter a component URL' - }); - } - if (!url && commandOptions.requiresURL) { - return; - } + args.push(moniker); if (commandOptions.requiresURL) { - args.push(moniker!, url!); - } else { - args.push(moniker!); + if (!url) { + url = await vscode.window.showInputBox({ + placeHolder: '(e.g. fuchsia-pkg://fuchsia.com/hello-world-cpp#meta/hello-world-cpp.cm)', + value: '', + prompt: 'Enter a component URL' + }); + } + if (!url) { + return; + } + args.push(url); } + await setup.ffx.runFfx(args); if (commandOptions.refresh) { @@ -259,7 +259,7 @@ return new ComponentTreeItem(element, collapsibleState); } - async getChildren(element?: ComponentInfo | undefined): Promise<ComponentInfo[]> { + async getChildren(element?: ComponentInfo): Promise<ComponentInfo[]> { if (element) { return element.children; } else if (this.currentDevice) { @@ -400,7 +400,7 @@ return new TaskTreeItem(element, collapsibleState); } - async getChildren(element?: TaskInfo | undefined): Promise<TaskInfo[]> { + async getChildren(element?: TaskInfo): Promise<TaskInfo[]> { if (element) { return element.children; } else if (this.currentDevice) {
diff --git a/src/doc_provider.ts b/src/doc_provider.ts index b4b9ba7..c18fe82 100644 --- a/src/doc_provider.ts +++ b/src/doc_provider.ts
@@ -52,8 +52,8 @@ const fileUri = vscode.Uri.file(fullPath); // The range in the document to underline link - const start = document.positionAt(fileMatch.index!); - const end = document.positionAt(fileMatch.index! + linkText.length); + const start = document.positionAt(fileMatch.index); + const end = document.positionAt(fileMatch.index + linkText.length); const linkRange = new vscode.Range(start, end); // Link format is file:///path/to/file.ts#Lline,column
diff --git a/src/extension.ts b/src/extension.ts index a7aced1..593eb29 100644 --- a/src/extension.ts +++ b/src/extension.ts
@@ -153,7 +153,7 @@ const output = await command(); logger.info(output, 'fuchsia'); } catch (err: unknown) { - logger.error(`${err}`); + logger.error(`${String(err)}`); void vscode.window.showErrorMessage( `error with ${commandName}, see output for details`); } @@ -190,7 +190,7 @@ const output = await command(device); logger.info(output, commandName); } catch (err: unknown) { - logger.error(`${err}`, commandName); + logger.error(`${String(err)}`, commandName); void vscode.window.showErrorMessage( `error with ${commandName}, see output for details`); }
diff --git a/src/ffx.ts b/src/ffx.ts index 568d7bd..b5337f0 100644 --- a/src/ffx.ts +++ b/src/ffx.ts
@@ -24,7 +24,7 @@ public readonly isFfxDefault: boolean = false; constructor(data: Record<string, unknown>) { - this._data = JSON.parse(JSON.stringify(data)); + this._data = JSON.parse(JSON.stringify(data)) as Record<string, unknown>; this.nodeName = (data['nodename'] as string) ?? '<not set>!'; this.connected = data['rcs_state'] === 'Y'; this.serial = (data['serial'] as string) ?? ''; @@ -320,7 +320,7 @@ if (this.path) { timeoutMillis = timeoutMillis ?? vscode.workspace.getConfiguration().get('fuchsia.connectionTimeout'); - const devices = (await this.runFfxJson(['target', 'list'], timeoutMillis, null)) as Record<string, unknown>[]; + const devices = await this.runFfxJson<Record<string, unknown>[]>(['target', 'list'], timeoutMillis, null); for (const obj of devices) { const device = new FuchsiaDevice(obj); @@ -419,7 +419,7 @@ // Default `device` to the default device's nodeName, if available. if (device === undefined) { if (!this.targetDevice) { - throw new Error(`Unable to resolve the default target for ffx command: ${args}`); + throw new Error(`Unable to resolve the default target for ffx command: ${args.join(' ')}`); } device = this.targetDevice.nodeName; } @@ -521,7 +521,7 @@ try { cmd = this.runFfxStreaming(args, device); } catch (e) { - return reject(e); + return reject(e instanceof Error ? e : new Error(String(e))); } let hasTimedOut = false; let timeout: NodeJS.Timeout | undefined = undefined; @@ -556,7 +556,7 @@ cmd?.on('close', (code, signal) => { if (!hasError) { if (hasTimedOut) { - logger.debug(`Timeout: args = ${args}`, 'ffx'); + logger.debug(`Timeout: args = ${args.join(' ')}`, 'ffx'); return reject(new Error('ffx command timeout')); } else if (timeout) { clearTimeout(timeout);
diff --git a/src/fx.ts b/src/fx.ts index dab87de..10c96e6 100644 --- a/src/fx.ts +++ b/src/fx.ts
@@ -98,7 +98,7 @@ this.runStreaming(args), onData, (data) => { - logger.warn(`Error [fx ${args}]: ${data}`); + logger.warn(`Error [fx ${args.join(' ')}]: ${data.toString()}`); }); }
diff --git a/src/git_helper.ts b/src/git_helper.ts index 02a0b68..79dd7f4 100644 --- a/src/git_helper.ts +++ b/src/git_helper.ts
@@ -17,8 +17,8 @@ * Initialize the openInCodeSearch command. */ export function setUpGitHelper(ctx: vscode.ExtensionContext) { - ctx.subscriptions.push(vscode.commands.registerCommand('fuchsia.openInCodeSearch', async (arg) => { - await openAtMain(arg); + ctx.subscriptions.push(vscode.commands.registerCommand('fuchsia.openInCodeSearch', async (arg: unknown) => { + await openAtMain(arg as ContextMenuCommandArg | undefined); })); } @@ -41,7 +41,7 @@ /** * Returns the path segment within the Fuchsia tree for the given context menu argument. */ -function getPathSegment(arg: ContextMenuCommandArg) { +function getPathSegment(arg?: ContextMenuCommandArg) { const root = getRoot()?.fsPath; const fullPath = @@ -80,7 +80,7 @@ /** * Opens the current file, at the current line, on branch main in osscs. */ -async function openAtMain(arg: ContextMenuCommandArg) { +async function openAtMain(arg?: ContextMenuCommandArg) { const cutPath = getPathSegment(arg); const line = getLineSegment(); await vscode.commands.executeCommand('vscode.open', makeUrl(cutPath, line));
diff --git a/src/process.ts b/src/process.ts index f12d139..2adad98 100644 --- a/src/process.ts +++ b/src/process.ts
@@ -5,9 +5,6 @@ import { type ChildProcessWithoutNullStreams } from 'child_process'; import JsonParser from 'jsonparse'; -/** - * A stream of Data coming from a process. - */ export class DataStreamProcess { private outputChunks: Buffer[] = []; @@ -16,13 +13,19 @@ onData: (data: Buffer) => void, onError: (data: Buffer) => void, ) { - this.process.stdout.on('data', chunk => { - this.outputChunks.push(chunk); - onData(chunk); + this.process.stdout.on('data', (chunk: unknown) => { + const buffer = Buffer.isBuffer(chunk) ? + chunk : + Buffer.from(typeof chunk === 'string' ? chunk : String(chunk)); + this.outputChunks.push(buffer); + onData(buffer); }); - this.process.stderr.on('data', chunk => { - this.outputChunks.push(chunk); - onError(chunk); + this.process.stderr.on('data', (chunk: unknown) => { + const buffer = Buffer.isBuffer(chunk) ? + chunk : + Buffer.from(typeof chunk === 'string' ? chunk : String(chunk)); + this.outputChunks.push(buffer); + onError(buffer); }); }
diff --git a/src/test/suite/commands.test.ts b/src/test/suite/commands.test.ts index 4fefd91..667390c 100644 --- a/src/test/suite/commands.test.ts +++ b/src/test/suite/commands.test.ts
@@ -14,7 +14,11 @@ let registeredCommands: string[]; before(async function() { const packageDir = path.join(__dirname, '..'); - const packageJSON = JSON.parse(await fs.readFile(path.join(packageDir, 'package.json'), 'utf8')); + const packageJSON = JSON.parse(await fs.readFile(path.join(packageDir, 'package.json'), 'utf8')) as { + contributes: { + commands: { command: string }[]; + }; + }; // sort to make comparison easier userCommands = packageJSON.contributes.commands.
diff --git a/src/test/suite/ffx.test.ts b/src/test/suite/ffx.test.ts index ebe6668..35a096d 100644 --- a/src/test/suite/ffx.test.ts +++ b/src/test/suite/ffx.test.ts
@@ -35,8 +35,8 @@ assert.strictEqual(device.targetState, data['target_state']); const testAddresses = data['addresses']; const actualAddresses = device.addresses; - for (const i in testAddresses) { - assert.strictEqual(testAddresses[i], actualAddresses[i]); + for (const [i, address] of testAddresses.entries()) { + assert.strictEqual(address, actualAddresses[i]); } }); }); @@ -123,7 +123,7 @@ const ffx = new Ffx(TEST_CWD, TEST_FFX); - const errorMessage = 'Unable to resolve the default target for ffx command: target,reboot'; + const errorMessage = 'Unable to resolve the default target for ffx command: target reboot'; await assert.rejects(() => ffx.rebootTarget(), new Error(errorMessage)); assert.deepStrictEqual(actualInvocations, expectedInvocations); }); @@ -469,7 +469,7 @@ it('does not call ffx target off without a target', async function() { const ffx = new Ffx(TEST_CWD, TEST_FFX); - const errorMessage = 'Unable to resolve the default target for ffx command: target,off'; + const errorMessage = 'Unable to resolve the default target for ffx command: target off'; const {actualInvocations, expectedInvocations} = mockFfx(); await assert.rejects(() => ffx.poweroffTarget(), new Error(errorMessage)); assert.deepStrictEqual(actualInvocations, expectedInvocations); @@ -654,7 +654,7 @@ const {actualInvocations, expectedInvocations} = mockFfx(); const ffx = new Ffx(TEST_CWD, TEST_FFX); - const errorMessage = 'Unable to resolve the default target for ffx command: target,show'; + const errorMessage = 'Unable to resolve the default target for ffx command: target show'; await assert.rejects(() => ffx.showTarget(), new Error(errorMessage)); assert.deepStrictEqual(actualInvocations, expectedInvocations); }); @@ -716,7 +716,7 @@ const {actualInvocations, expectedInvocations} = mockFfx(); const ffx = new Ffx(TEST_CWD, TEST_FFX); - const errorMessage = 'Unable to resolve the default target for ffx command: target,snapshot,-d,/path/to/workspace'; + const errorMessage = 'Unable to resolve the default target for ffx command: target snapshot -d /path/to/workspace'; await assert.rejects(() => ffx.exportSnapshotToCWD(), new Error(errorMessage)); assert.deepStrictEqual(actualInvocations, expectedInvocations); });
diff --git a/src/test/suite/index.ts b/src/test/suite/index.ts index b3a135f..37348f4 100644 --- a/src/test/suite/index.ts +++ b/src/test/suite/index.ts
@@ -40,7 +40,7 @@ }); } catch (err) { console.error(err); - e(err); + e(err instanceof Error ? err : new Error(String(err))); } }); }
diff --git a/src/test/suite/problem_matcher.test.ts b/src/test/suite/problem_matcher.test.ts index e9017c3..90ca7ee 100644 --- a/src/test/suite/problem_matcher.test.ts +++ b/src/test/suite/problem_matcher.test.ts
@@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import { FuchsiaDiagnostics } from '../../workflow/problem_matcher'; +import { FuchsiaDiagnostics, type PackageJsonStub } from '../../workflow/problem_matcher'; import { describe, it } from 'mocha'; import assert from 'assert'; import * as vscode from 'vscode'; @@ -20,7 +20,7 @@ ]; const ext = vscode.extensions.getExtension('fuchsia-authors.vscode-fuchsia'); - const diagnostics = new FuchsiaDiagnostics(ext?.packageJSON); + const diagnostics = new FuchsiaDiagnostics(ext?.packageJSON as PackageJsonStub); this.beforeEach(function () { diagnostics.clear();
diff --git a/src/test/suite/reporter.ts b/src/test/suite/reporter.ts index 0f7943f..ff200b1 100644 --- a/src/test/suite/reporter.ts +++ b/src/test/suite/reporter.ts
@@ -4,6 +4,7 @@ import mocha from 'mocha'; import * as fs from 'fs'; +import * as stream from 'stream'; type SuiteChild = Suite | Test; @@ -34,18 +35,21 @@ } export default class TestReporter extends mocha.reporters.Base { - out: fs.WriteStream; + out: stream.Writable; constructor(runner: mocha.Runner, options: mocha.MochaOptions) { super(runner, options); - const outputName = options?.reporterOptions?.output; - this.out = fs.createWriteStream(outputName); + const reporterOptions = options?.reporterOptions as Record<string, unknown> | undefined; + if (typeof reporterOptions?.output === 'string') { + this.out = fs.createWriteStream(reporterOptions.output); + } else { + this.out = process.stdout; + } - const suiteName = options?.reporterOptions?.suiteName ?? 'Mocha Tests'; + const suiteName = (reporterOptions?.suiteName as string) ?? 'Mocha Tests'; - const suiteStack: Suite[] = [ - ]; + const suiteStack: Suite[] = []; let currentSuite: Suite = { // root name: suiteName, children: [], @@ -120,9 +124,9 @@ this.out.write(`<testsuite name="${suite.name.replaceAll('"', '"')}" failures="${suite.failures}" tests="${suite.tests}">\n`); for (const child of suite.children) { if ('status' in child) { - this.writeTest(child as Test); + this.writeTest(child); } else { - this.writeSuite(child as Suite); + this.writeSuite(child); } } this.out.write('</testsuite>\n');
diff --git a/src/test/suite/snap.test.ts b/src/test/suite/snap.test.ts index 5140524..3f83793 100644 --- a/src/test/suite/snap.test.ts +++ b/src/test/suite/snap.test.ts
@@ -49,7 +49,7 @@ } // need the filename to indicate that this is JSON, no a plist - return await tm.parseRawGrammar(contents, `${ext}.json`); + return tm.parseRawGrammar(contents, `${ext}.json`); } }); }
diff --git a/src/test/suite/target_status_bar_item.test.ts b/src/test/suite/target_status_bar_item.test.ts index c979360..7b2870c 100644 --- a/src/test/suite/target_status_bar_item.test.ts +++ b/src/test/suite/target_status_bar_item.test.ts
@@ -164,7 +164,7 @@ // Check that the item selected exists. if (!itemSelection) { quickPickErrors.push(new Error( - `Selection = "${selection}" is not in items = [${items.map(e => e.label)}]` + `Selection = "${selection ?? 'undefined'}" is not in items = [${items.map(e => e.label).join(', ')}]` )); }
diff --git a/src/test/suite/test_controller/discovery.test.ts b/src/test/suite/test_controller/discovery.test.ts index 6ca748d..9b6981c 100644 --- a/src/test/suite/test_controller/discovery.test.ts +++ b/src/test/suite/test_controller/discovery.test.ts
@@ -358,12 +358,13 @@ const barTestItem = controller.createTestItem('bar', 'bar'); - const discoverFn = sandbox.stub().callsFake((_controller, _fx, testItem) => { - testItem.children.add( - controller.createTestItem(`${testItem.id}.testCase`, `${testItem.id}.testCase`) - ); - return Promise.resolve(); - }); + const discoverFn = sandbox.stub().callsFake( + (_controller, _fx, testItem: vscode.TestItem) => { + testItem.children.add( + controller.createTestItem(`${testItem.id}.testCase`, `${testItem.id}.testCase`) + ); + return Promise.resolve(); + }); const queue = TestcaseDiscoveryQueue.createForTesting(controller, fx, discoverFn); @@ -383,12 +384,13 @@ const fooTestItem = controller.createTestItem('foo', 'foo'); const barTestItem = controller.createTestItem('bar', 'bar'); - const discoverFn = sandbox.stub().callsFake(async (_controller, _fx, testItem) => { - await new Promise(resolve => setTimeout(resolve, 50)); - testItem.children.add( - controller.createTestItem(`${testItem.id}.testCase`, `${testItem.id}.testCase`) - ); - }); + const discoverFn = sandbox.stub().callsFake( + async (_controller, _fx, testItem: vscode.TestItem) => { + await new Promise(resolve => setTimeout(resolve, 50)); + testItem.children.add( + controller.createTestItem(`${testItem.id}.testCase`, `${testItem.id}.testCase`) + ); + }); const queue = TestcaseDiscoveryQueue.createForTesting(controller, fx, discoverFn);
diff --git a/src/test/suite/utils.ts b/src/test/suite/utils.ts index ea66051..e355a9b 100644 --- a/src/test/suite/utils.ts +++ b/src/test/suite/utils.ts
@@ -10,7 +10,7 @@ import * as vscode from 'vscode'; // eslint-disable-next-line @typescript-eslint/no-require-imports -const childProcess = require('child_process'); +const childProcess = require('child_process') as typeof import('child_process'); /** * Waits for a single macrotask to complete on the macrotask event loop. @@ -87,9 +87,10 @@ */ export function createFakeChildProcess(): ChildProcess { const spawnEvent: ChildProcess = new EventEmitter() as ChildProcess; - spawnEvent.stdout = new EventEmitter() as Readable; - spawnEvent.stderr = new EventEmitter() as Readable; - spawnEvent.kill = (signal?: number | undefined) => { + spawnEvent.stdout = new Readable({ read() {} }); + spawnEvent.stderr = new Readable({ read() {} }); + + spawnEvent.kill = (signal?: number) => { spawnEvent.emit('exit', signal); spawnEvent.emit('close', signal); return true; @@ -255,7 +256,8 @@ setTimeout(() => { if (command !== expectedInvocations[idx]?.at(0)) { console.warn( - `[setupMockedCommand] Ignoring non-matching command ${command}, ${args}, ${options}` + `[setupMockedCommand] Ignoring non-matching command ${command}, ` + + `${JSON.stringify(args)}, ${JSON.stringify(options)}` ); fakeSpawnedProcess.emit('exit', 0); fakeSpawnedProcess.emit('close', 0); @@ -271,7 +273,7 @@ if (JSON.stringify(expectedArgs) !== JSON.stringify(args) || options.cwd !== cwd) { const errmsg = `[setupMockedCommand] Unexpected call. Expected invocations:\n${expectedInvocations.join('\n')}, CWD=${cwd} -Actual invocations:\n${actualInvocations.join('\n')}, CWD=${options.cwd}`; +Actual invocations:\n${actualInvocations.join('\n')}, CWD=${String(options.cwd)}`; console.error(errmsg); fakeSpawnedProcess.stderr?.emit('data', JSON.stringify(errmsg)); fakeSpawnedProcess.emit('exit', 1);
diff --git a/src/test/suite/workflow.test.ts b/src/test/suite/workflow.test.ts index ecb99d4..166d7d6 100644 --- a/src/test/suite/workflow.test.ts +++ b/src/test/suite/workflow.test.ts
@@ -137,7 +137,7 @@ const selection = pkgHistory.update([] as Array<Packages>); const packageList = pkgHistory.toList(selection); packageList.forEach((pkg) => { - if (pkg.kind !== -1) { + if (pkg.kind !== vscode.QuickPickItemKind.Separator) { assert.equal(pkg.picked, false); } });
diff --git a/src/test/suite/zxdb/async_backtrace.test.ts b/src/test/suite/zxdb/async_backtrace.test.ts index 51ead2e..6e856cc 100644 --- a/src/test/suite/zxdb/async_backtrace.test.ts +++ b/src/test/suite/zxdb/async_backtrace.test.ts
@@ -336,13 +336,13 @@ it('should jump to definition with an absolute path', function () { const item = provider.getTreeItem(taskA1); assert.ok(item.command); - const cmd = item.command!; + const cmd = item.command; assert.strictEqual(cmd.command, 'vscode.open'); assert.ok(cmd.arguments); - assert.strictEqual(cmd.arguments!.length, 2); + assert.strictEqual(cmd.arguments.length, 2); const expectedPath = vscode.Uri.file('/path/to/a1.ts').fsPath; - assert.strictEqual((cmd.arguments![0] as vscode.Uri).fsPath, expectedPath); - assert.deepStrictEqual(cmd.arguments![1], { + assert.strictEqual((cmd.arguments[0] as vscode.Uri).fsPath, expectedPath); + assert.deepStrictEqual(cmd.arguments[1], { selection: new vscode.Range(9, 0, 9, 0) }); }); @@ -350,13 +350,13 @@ it('should jump to definition with a relative path', function () { const item = provider.getTreeItem(taskLong); assert.ok(item.command); - const cmd = item.command!; + const cmd = item.command; assert.strictEqual(cmd.command, 'vscode.open'); assert.ok(cmd.arguments); - assert.strictEqual(cmd.arguments!.length, 2); + assert.strictEqual(cmd.arguments.length, 2); const expectedPath = vscode.Uri.file('/some/workspace/relative/path/to/long.ts').fsPath; - assert.strictEqual((cmd.arguments![0] as vscode.Uri).fsPath, expectedPath); - assert.deepStrictEqual(cmd.arguments![1], { + assert.strictEqual((cmd.arguments[0] as vscode.Uri).fsPath, expectedPath); + assert.deepStrictEqual(cmd.arguments[1], { selection: new vscode.Range(11, 0, 11, 0) }); }); @@ -416,8 +416,8 @@ const children = provider.getChildren() as AsyncBacktrace[]; const updated = children.find(t => t.sessionId === 'session-1' && t.koid === 1); assert.ok(updated); - assert.strictEqual(updated!.name, 'foo1-updated'); - assert.deepStrictEqual(updated!.children, [taskB1]); + assert.strictEqual(updated.name, 'foo1-updated'); + assert.deepStrictEqual(updated.children, [taskB1]); }); it('should add new thread and fire event', function () { @@ -456,7 +456,7 @@ const threads = provider.getChildren() as AsyncBacktrace[]; const updated = threads.find(t => t.sessionId === 'session-1' && t.koid === 1); assert.ok(updated); - assert.deepStrictEqual(updated!.children, []); + assert.deepStrictEqual(updated.children, []); const tasks = provider.getChildren(emptyThread) as unknown[]; assert.deepStrictEqual(tasks, []); @@ -476,8 +476,8 @@ assert.ok(foo1); assert.ok(foo2); - assert.strictEqual(foo1!.name, 'foo1-updated'); - assert.strictEqual(foo2!.name, 'foo2'); // Verify foo2 was not overwritten + assert.strictEqual(foo1.name, 'foo1-updated'); + assert.strictEqual(foo2.name, 'foo2'); // Verify foo2 was not overwritten }); });
diff --git a/src/test/suite/zxdb/console.test.ts b/src/test/suite/zxdb/console.test.ts index e148e57..5df2521 100644 --- a/src/test/suite/zxdb/console.test.ts +++ b/src/test/suite/zxdb/console.test.ts
@@ -101,7 +101,7 @@ // bypass "TypeError: Descriptor for property Socket is non-configurable and non-writable" // eslint-disable-next-line @typescript-eslint/no-require-imports - const mutableNet = require('net'); + const mutableNet = require('net') as typeof Net; sandbox.stub(mutableNet, 'Socket').returns(socket); ffxCallArgs = []; @@ -135,7 +135,7 @@ listeningListener(); return server; }); - server.close.callsFake((callback?: ((err?: Error | undefined) => void) | undefined) => { + server.close.callsFake((callback?: ((err?: Error) => void)) => { if (callback) { callback(); } return server; });
diff --git a/src/test_controller/discovery.ts b/src/test_controller/discovery.ts index 561434b..e01532e 100644 --- a/src/test_controller/discovery.ts +++ b/src/test_controller/discovery.ts
@@ -143,7 +143,7 @@ // or starting testcase discovery for a different test. this.queue.push([testItem, discoveredCallback]); logger.error(`Testcase discovery failed: ${testItem.id}`, 'discoverFromQueue'); - logger.trace(`Error details: ${e}`, 'discoverFromQueue'); + logger.trace(`Error details: ${String(e)}`, 'discoverFromQueue'); } // After all pending discoveries settle, check if any were nonsuccessful and present an @@ -197,9 +197,16 @@ const bytes = await vscode.workspace.fs.readFile(uri); const content = new TextDecoder().decode(bytes); - const tests = JSON.parse(content); + const tests = JSON.parse(content) as unknown; + if (!Array.isArray(tests)) { + logger.error('tests.json does not contain a valid array'); + return; + } for (const testDescriptor of tests) { - const test = testDescriptor.test; + if (typeof testDescriptor !== 'object' || testDescriptor === null || !('test' in testDescriptor)) { + continue; + } + const test = (testDescriptor as { test: unknown }).test; if (!test) { continue; } @@ -243,7 +250,7 @@ await scanTestsJson(testsJsonFile); } catch (ex) { // The tests.json file doesn't exist yet. - logger.debug(`Unable to scan tests.json: ${ex}`); + logger.debug(`Unable to scan tests.json: ${String(ex)}`); } } @@ -261,7 +268,7 @@ await scanBuildDir(fxBuildDirFile); } catch (ex) { // The .fx-build-dir file doesn't exist yet. - logger.debug(`Unable to scan .fx-build-dir: ${ex}`); + logger.debug(`Unable to scan .fx-build-dir: ${String(ex)}`); } }) );
diff --git a/src/tool_finder.ts b/src/tool_finder.ts index a37ed01..c14afc2 100644 --- a/src/tool_finder.ts +++ b/src/tool_finder.ts
@@ -84,7 +84,7 @@ if (!process.env.HOME) { throw new Error(`Unable to locate $HOME to expand ~ in tool path. path = "${toolPath}".`); } - return path.join(process.env.HOME, toolPath.substring(1)) as string; + return path.join(process.env.HOME, toolPath.substring(1)); } else if (path.isAbsolute(toolPath)) { return toolPath; } else { @@ -92,7 +92,7 @@ throw new Error('Could not evaluate the workspace' + `relative path for ffx. path = "${toolPath}".`); } - return path.join(workspacePath, toolPath) as string; + return path.join(workspacePath, toolPath); } }
diff --git a/src/workflow/build.ts b/src/workflow/build.ts index 7aa9ea9..421a1c5 100644 --- a/src/workflow/build.ts +++ b/src/workflow/build.ts
@@ -127,7 +127,7 @@ void window.showErrorMessage( 'Build stopped, [see output for details](command:fuchsia.showOutput)' ); - return Promise.reject(exitCode); + return Promise.reject(new Error(`fx build failed with exit code ${exitCode}`)); } } ); @@ -161,7 +161,9 @@ progress.report({ message: '[(details)](command:fuchsia.showOutput)' }); const exitCode = await cmd?.exitCode; - return exitCode === 0 ? Promise.resolve() : Promise.reject(exitCode); + return exitCode === 0 + ? Promise.resolve() + : Promise.reject(new Error(`fx ota failed with exit code ${exitCode}`)); } ); }); @@ -194,7 +196,9 @@ progress.report({ message: '[(details)](command:fuchsia.showOutput)' }); const exitCode = await cmd?.exitCode; - return exitCode === 0 ? Promise.resolve() : Promise.reject(exitCode); + return exitCode === 0 + ? Promise.resolve() + : Promise.reject(new Error(`fx ota failed with exit code ${exitCode}`)); } ); });
diff --git a/src/workflow/index.ts b/src/workflow/index.ts index df6c192..e67f299 100644 --- a/src/workflow/index.ts +++ b/src/workflow/index.ts
@@ -8,14 +8,14 @@ import * as logger from '../logger'; import { registerCommandWithAnalyticsEvent } from '../analytics/vscode_events'; import { registerBuildCommands } from './build'; -import { FuchsiaDiagnostics } from './problem_matcher'; +import { FuchsiaDiagnostics, type PackageJsonStub } from './problem_matcher'; import { PkgHistory, registerClearPackageHistoryCommand, registerRemovePackageCommand, registerSetCommand, toList } from './set'; /** * initialize fuchsia workflow interaction commands */ export function setUpWorkflowInteraction(ctx: vscode.ExtensionContext, setup: Setup) { - const collection = new FuchsiaDiagnostics(ctx.extension.packageJSON); + const collection = new FuchsiaDiagnostics(ctx.extension.packageJSON as PackageJsonStub); const pkgHistory = new PkgHistory(ctx.workspaceState); registerBuildCommands(ctx, setup, collection); @@ -38,7 +38,9 @@ }); const exitCode = await cmd?.exitCode; - return exitCode === 0 ? Promise.resolve() : Promise.reject(exitCode); + return exitCode === 0 + ? Promise.resolve() + : Promise.reject(new Error(`fx serve failed with exit code ${exitCode}`)); /** callback to handle buffer from process */ function handleData(buffer: Buffer) { @@ -65,7 +67,7 @@ logger.info('Stopped the repository server', 'fx serve'); }) .catch((err) => { - logger.error(err); + logger.error(String(err)); }); });
diff --git a/src/workflow/problem_matcher.ts b/src/workflow/problem_matcher.ts index 72dca69..a64c8f1 100644 --- a/src/workflow/problem_matcher.ts +++ b/src/workflow/problem_matcher.ts
@@ -23,7 +23,7 @@ }; } -interface PackageJsonStub { +export interface PackageJsonStub { contributes: { problemMatchers: ProblemMatcher[]; };
diff --git a/src/zxdb/async_backtrace.ts b/src/zxdb/async_backtrace.ts index 6d8f11c..7a04b40 100644 --- a/src/zxdb/async_backtrace.ts +++ b/src/zxdb/async_backtrace.ts
@@ -282,7 +282,7 @@ // Assumes that the data from the DAP server conforms to the interface. try { - const threadBacktrace = new AsyncBacktrace(e.body, e.session.id); + const threadBacktrace = new AsyncBacktrace(e.body as AsyncBacktraceUpdateData, e.session.id); if (threadBacktrace.threadExists) { provider.updateAsyncBacktrace(threadBacktrace); } else { @@ -290,7 +290,7 @@ } } catch (err) { logger.error( - `Failed to update async backtrace pane from DAP server event: ${err}`, + `Failed to update async backtrace pane from DAP server event: ${String(err)}`, 'async_backtrace', ); logger.error(`Corresponding DAP server event: ${JSON.stringify(e.body)}`, 'async_backtrace');
diff --git a/src/zxdb/console.ts b/src/zxdb/console.ts index 87682ca..c16879c 100644 --- a/src/zxdb/console.ts +++ b/src/zxdb/console.ts
@@ -179,16 +179,20 @@ try { logger.debug(`Starting new zxdb process connected to port ${portNumber}`, 'zxdb-console'); this.zxdbProcess = fx.runStreaming(ZXDB_COMMAND_OPT.concat(['--debug-adapter-port', `${portNumber}`])); + this.zxdbProcess?.stdout.setEncoding('utf8'); + this.zxdbProcess?.stderr.setEncoding('utf8'); if (!this.zxdbProcess) { logger.warn('Cannot start zxdb, no path to ffx'); return; } let errorMsg = ''; - this.zxdbProcess.stdout.on('data', (data) => { this.processOutput(session, 'stdout', data); }); - this.zxdbProcess.stderr.on('data', (data) => { - this.processOutput(session, 'stderr', data); - errorMsg = `${errorMsg}${data}\n`; + this.zxdbProcess.stdout.on('data', (data: unknown) => { + this.processOutput(session, 'stdout', String(data)); + }); + this.zxdbProcess.stderr.on('data', (data: unknown) => { + this.processOutput(session, 'stderr', String(data)); + errorMsg = `${errorMsg}${String(data)}\n`; }); this.zxdbProcess.on('close', (code) => { onCloseCallback(errorMsg); @@ -197,7 +201,7 @@ } catch (e) { logger.error('Cannot start zxdb console: ', 'zxdb-console', e); void vscode.window.showErrorMessage( - `Cannot start console: ${(e instanceof Error) ? e.message : e}`); + `Cannot start console: ${(e instanceof Error) ? e.message : String(e)}`); } }
diff --git a/src/zxdb/index.ts b/src/zxdb/index.ts index 1c99716..38f4fd6 100644 --- a/src/zxdb/index.ts +++ b/src/zxdb/index.ts
@@ -55,9 +55,10 @@ */ function setUpZxdbPrompts(ctx: vscode.ExtensionContext) { ctx.subscriptions.push(vscode.commands.registerCommand( - 'fuchsia.internal.zxdb.pickProcess', config => { + 'fuchsia.internal.zxdb.pickProcess', (config: unknown) => { + const cfg = config as vscode.DebugConfiguration | undefined; let promptText: string; - if (config.request === 'launch') { + if (cfg?.request === 'launch') { promptText = 'Launch(zxdb): Enter name of the process that will be launched.' + ' Hint: For components, it\'s usually the component name.'; @@ -198,8 +199,8 @@ logger.info('Creating the debug adapter client.'); return new vscode.DebugAdapterServer(portNumber); } catch (err) { - logger.error(`${err}.\nSee ${TROUBLESHOOTING_URL}`); - const dap = new ErrorFakeDap(`${err}`); + logger.error(`${String(err)}.\nSee ${TROUBLESHOOTING_URL}`); + const dap = new ErrorFakeDap(`${String(err)}`); return new vscode.DebugAdapterInlineImplementation(dap); } } @@ -263,9 +264,8 @@ // backend and hence debug adapter reports connection loss errors which // needs to be ignored. It might confuse users if these errors are shown. this.ignoreError = true; - if (request.arguments && request.arguments.restart) { - this.restart = request.arguments.restart; - } + const disconnectRequest = request as { arguments?: { restart?: boolean } }; + this.restart = !!disconnectRequest?.arguments?.restart; } if (request.command === 'terminate') { // Ignore errors reported after this. The connection is closed by the // backend and hence debug adapter reports connection loss errors. @@ -385,7 +385,7 @@ try { await ffx.runFfx(['component', 'stop', 'core/debugger']); } catch (err) { - logger.error(`Unable to stop existing zxdb sessions: ${err}`, 'zxdb'); + logger.error(`Unable to stop existing zxdb sessions: ${String(err)}`, 'zxdb'); throw err; } }
diff --git a/syntax/cml.ts b/syntax/cml.ts index da36a20..49318e8 100644 --- a/syntax/cml.ts +++ b/syntax/cml.ts
@@ -346,7 +346,7 @@ * @returns NamedPattern. */ word: function (word: Pattern): NamedPattern { - return new NamedPattern(`\\b${word}\\b`, getPatternNames(word)); + return new NamedPattern(`\\b${word.toString()}\\b`, getPatternNames(word)); }, /** @@ -356,7 +356,7 @@ * @returns NamedPattern. */ oneString: function (word: Pattern, type?: string): NamedPattern { - return patterns.named(`"${word}"`, type ?? 'string.quoted.double'); + return patterns.named(`"${word.toString()}"`, type ?? 'string.quoted.double'); }, /** @@ -442,7 +442,7 @@ * @returns NamedPattern */ named: function (pattern: Pattern, name: string) { - return new NamedPattern(`(${pattern})`, [name, ...getPatternNames(pattern)]); + return new NamedPattern(`(${pattern.toString()})`, [name, ...getPatternNames(pattern)]); }, /**
diff --git a/syntax/fidl.ts b/syntax/fidl.ts index 731b1d9..1f29fac 100644 --- a/syntax/fidl.ts +++ b/syntax/fidl.ts
@@ -213,7 +213,7 @@ * @returns NamedPattern. */ word: function (word: Pattern): NamedPattern { - return new NamedPattern(`\\b${word}\\b`, getPatternNames(word)); + return new NamedPattern(`\\b${word.toString()}\\b`, getPatternNames(word)); }, /** @@ -241,7 +241,7 @@ * @returns NamedPattern */ zeroOrMore: function (pattern: Pattern): NamedPattern { - return new NamedPattern(`(?:${pattern}\\s*)*`, getPatternNames(pattern)); + return new NamedPattern(`(?:${pattern.toString()}\\s*)*`, getPatternNames(pattern)); }, /** @@ -250,7 +250,7 @@ * @returns NamedPattern */ optional: function (pattern: Pattern): NamedPattern { - return new NamedPattern(`(?:${pattern})?`, getPatternNames(pattern)); + return new NamedPattern(`(?:${pattern.toString()})?`, getPatternNames(pattern)); }, /** @@ -260,7 +260,7 @@ * @returns NamedPattern */ named: function (pattern: Pattern, name: string) { - return new NamedPattern(`(${pattern})`, [name, ...getPatternNames(pattern)]); + return new NamedPattern(`(${pattern.toString()})`, [name, ...getPatternNames(pattern)]); }, /**
diff --git a/syntax/fuchsia-log.ts b/syntax/fuchsia-log.ts index 2cbcb68..88a2b31 100644 --- a/syntax/fuchsia-log.ts +++ b/syntax/fuchsia-log.ts
@@ -166,7 +166,7 @@ * @returns NamedPattern */ zeroOrMore: function (pattern: Pattern): NamedPattern { - return new NamedPattern(`(?:${pattern})*`, getPatternNames(pattern)); + return new NamedPattern(`(?:${pattern.toString()})*`, getPatternNames(pattern)); }, /** @@ -176,7 +176,7 @@ * @returns NamedPattern */ named: function (pattern: Pattern, name: string) { - return new NamedPattern(`(${pattern})`, [name, ...getPatternNames(pattern)]); + return new NamedPattern(`(${pattern.toString()})`, [name, ...getPatternNames(pattern)]); }, }; @@ -188,7 +188,7 @@ const USER_TAGS = patterns.named(patterns.zeroOrMore('\\[([^\\]]*)\\]'), 'entity.name.type'); const TAGS = new NamedPattern( - `^${TIMESTAMP_TAG}${USER_TAGS}`, + `^${TIMESTAMP_TAG.toString()}${USER_TAGS.toString()}`, getPatternNames([TIMESTAMP_TAG, USER_TAGS]) );