From 2a55ee3d693dc331983833c0b1d8afa7e32f2a2c Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 4 Feb 2026 17:16:21 -0600 Subject: [PATCH] replace in checks with `Object.hasOwn` (#292956) replace in checks with Object.hasOwn --- eslint.config.js | 10 ------- .../src/fig/fig-autocomplete-shared/mixins.ts | 2 +- .../fig-autocomplete-shared/specMetadata.ts | 10 +++---- .../terminal-suggest/src/fig/figInterface.ts | 8 +++--- .../src/terminalSuggestMain.ts | 2 +- .../src/test/env/pathExecutableCache.test.ts | 4 +-- .../tasks/browser/abstractTaskService.ts | 14 +++++----- .../tasks/browser/taskTerminalStatus.ts | 2 +- .../tasks/browser/terminalTaskSystem.ts | 12 ++++----- .../chatAgentTools/browser/taskHelpers.ts | 26 ++++++++++--------- .../browser/tools/monitoring/outputMonitor.ts | 14 +++++----- 11 files changed, 48 insertions(+), 56 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 47eeebf7347..e6555bd7eeb 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -196,11 +196,6 @@ export default tseslint.config( 'extensions/emmet/src/updateImageSize.ts', 'extensions/emmet/src/util.ts', 'extensions/github-authentication/src/node/fetch.ts', - 'extensions/terminal-suggest/src/fig/figInterface.ts', - 'extensions/terminal-suggest/src/fig/fig-autocomplete-shared/mixins.ts', - 'extensions/terminal-suggest/src/fig/fig-autocomplete-shared/specMetadata.ts', - 'extensions/terminal-suggest/src/terminalSuggestMain.ts', - 'extensions/terminal-suggest/src/test/env/pathExecutableCache.test.ts', 'extensions/tunnel-forwarding/src/extension.ts', 'extensions/typescript-language-features/src/utils/platform.ts', 'extensions/typescript-language-features/web/src/webServer.ts', @@ -307,11 +302,6 @@ export default tseslint.config( 'src/vs/workbench/contrib/output/browser/outputView.ts', 'src/vs/workbench/contrib/preferences/browser/settingsTree.ts', 'src/vs/workbench/contrib/remoteTunnel/electron-browser/remoteTunnel.contribution.ts', - 'src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts', - 'src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts', - 'src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts', - 'src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/taskHelpers.ts', - 'src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts', 'src/vs/workbench/contrib/testing/browser/explorerProjections/listProjection.ts', 'src/vs/workbench/contrib/testing/browser/explorerProjections/treeProjection.ts', 'src/vs/workbench/contrib/testing/browser/testCoverageBars.ts', diff --git a/extensions/terminal-suggest/src/fig/fig-autocomplete-shared/mixins.ts b/extensions/terminal-suggest/src/fig/fig-autocomplete-shared/mixins.ts index c7564ac3f51..926931e782e 100644 --- a/extensions/terminal-suggest/src/fig/fig-autocomplete-shared/mixins.ts +++ b/extensions/terminal-suggest/src/fig/fig-autocomplete-shared/mixins.ts @@ -88,7 +88,7 @@ const mergeNamedObjectArrays = ( if (!partial) { throw new Error('Invalid object passed to merge'); } - const existingNames = makeArray(partial.name).filter((name) => name in existingNameIndexMap); + const existingNames = makeArray(partial.name).filter((name) => Object.hasOwn(existingNameIndexMap, name)); if (existingNames.length === 0) { mergedObjects.push(partial); } else { diff --git a/extensions/terminal-suggest/src/fig/fig-autocomplete-shared/specMetadata.ts b/extensions/terminal-suggest/src/fig/fig-autocomplete-shared/specMetadata.ts index 9e51c7dd427..82390803382 100644 --- a/extensions/terminal-suggest/src/fig/fig-autocomplete-shared/specMetadata.ts +++ b/extensions/terminal-suggest/src/fig/fig-autocomplete-shared/specMetadata.ts @@ -41,14 +41,14 @@ export function convertLoadSpec( if (typeof loadSpec === 'function') { return (...args) => - loadSpec(...args).then((result) => { + loadSpec(...args).then((result): Fig.SpecLocation[] | Subcommand => { if (Array.isArray(result)) { - return result; + return result as Fig.SpecLocation[]; } - if ('type' in result) { - return [result]; + if (Object.hasOwn(result, 'type')) { + return [result as Fig.SpecLocation]; } - return convertSubcommand(result, initialize); + return convertSubcommand(result as Fig.Subcommand, initialize); }); } diff --git a/extensions/terminal-suggest/src/fig/figInterface.ts b/extensions/terminal-suggest/src/fig/figInterface.ts index 0f1fcc20d6d..2cc49e60676 100644 --- a/extensions/terminal-suggest/src/fig/figInterface.ts +++ b/extensions/terminal-suggest/src/fig/figInterface.ts @@ -277,7 +277,7 @@ export async function collectCompletionItemResult( let itemKind = kind; const lastArgType: string | undefined = parsedArguments?.annotations.at(-1)?.type; if (lastArgType === 'subcommand_arg') { - if (typeof item === 'object' && 'args' in item && (asArray(item.args ?? [])).length > 0) { + if (typeof item === 'object' && Object.hasOwn(item, 'args') && (asArray((item as Fig.Option).args ?? [])).length > 0) { itemKind = vscode.TerminalCompletionItemKind.Option; } } @@ -287,8 +287,8 @@ export async function collectCompletionItemResult( // Add for every argument let detail: string | undefined; - if (typeof item === 'object' && 'args' in item) { - const args = asArray(item.args); + if (typeof item === 'object' && Object.hasOwn(item, 'args')) { + const args = asArray((item as Fig.Option).args); if (args.every(e => !!e?.name)) { if (args.length > 0) { detail = ' ' + args.map(e => { @@ -352,7 +352,7 @@ function convertEnvRecordToArray(env: Record): EnvironmentVariab } export function getFixSuggestionDescription(spec: Fig.Spec): string { - if ('description' in spec) { + if (typeof spec !== 'function' && Object.hasOwn(spec, 'description')) { return spec.description ?? ''; } return ''; diff --git a/extensions/terminal-suggest/src/terminalSuggestMain.ts b/extensions/terminal-suggest/src/terminalSuggestMain.ts index 599f4b93849..e0193e01db6 100644 --- a/extensions/terminal-suggest/src/terminalSuggestMain.ts +++ b/extensions/terminal-suggest/src/terminalSuggestMain.ts @@ -268,7 +268,7 @@ export async function activate(context: vscode.ExtensionContext) { return; } - const shellType: string | undefined = 'shell' in terminal.state ? terminal.state.shell as string : undefined; + const shellType: string | undefined = Object.hasOwn(terminal.state, 'shell') ? terminal.state.shell as string : undefined; const terminalShellType = getTerminalShellType(shellType); if (!terminalShellType) { console.debug(`#terminalCompletions Shell type ${shellType} not supported`); diff --git a/extensions/terminal-suggest/src/test/env/pathExecutableCache.test.ts b/extensions/terminal-suggest/src/test/env/pathExecutableCache.test.ts index 83e006a391a..1f02412c8d2 100644 --- a/extensions/terminal-suggest/src/test/env/pathExecutableCache.test.ts +++ b/extensions/terminal-suggest/src/test/env/pathExecutableCache.test.ts @@ -59,8 +59,8 @@ suite('PathExecutableCache', () => { symlinkDocRaw = resource.documentation; } } - const realDoc = typeof realDocRaw === 'string' ? realDocRaw : (realDocRaw && 'value' in realDocRaw ? realDocRaw.value : undefined); - const symlinkDoc = typeof symlinkDocRaw === 'string' ? symlinkDocRaw : (symlinkDocRaw && 'value' in symlinkDocRaw ? symlinkDocRaw.value : undefined); + const realDoc = typeof realDocRaw === 'string' ? realDocRaw : (realDocRaw && Object.hasOwn(realDocRaw, 'value') ? realDocRaw.value : undefined); + const symlinkDoc = typeof symlinkDocRaw === 'string' ? symlinkDocRaw : (symlinkDocRaw && Object.hasOwn(symlinkDocRaw, 'value') ? symlinkDocRaw.value : undefined); const realPath = path.join(fixtureDir, 'real-executable.sh'); const symlinkPath = path.join(fixtureDir, 'symlink-executable.sh'); diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index c32ce00d8fe..d1de3666a97 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -918,7 +918,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer const tasks = await this.getWorkspaceTasks(); for (const [, workspaceTasks] of tasks) { if (workspaceTasks.configurations) { - for (const taskName in workspaceTasks.configurations.byIdentifier) { + for (const taskName of Object.keys(workspaceTasks.configurations.byIdentifier)) { const task = workspaceTasks.configurations.byIdentifier[taskName]; if (predicate(task, workspaceTasks.workspaceFolder)) { result.push(task); @@ -1256,7 +1256,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer readTasksMap.set(taskKey, task); } }); - for (const configuration in customized) { + for (const configuration of Object.keys(customized)) { const taskKey = customized[configuration].getKey(); if (taskKey) { readTasksMap.set(taskKey, customized[configuration]); @@ -1308,7 +1308,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer version: '2.0.0', tasks: [customizations] }, TaskRunSource.System, custom, customized, TaskConfig.TaskConfigSource.TasksJson, true); - for (const configuration in customized) { + for (const configuration of Object.keys(customized)) { key = customized[configuration].getKey()!; } } @@ -1351,7 +1351,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer version: '2.0.0', tasks: [customizations] }, TaskRunSource.System, custom, customized, TaskConfig.TaskConfigSource.TasksJson, true); - for (const configuration in customized) { + for (const configuration of Object.keys(customized)) { key = customized[configuration].getKey()!; } } @@ -3816,7 +3816,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer entry = task; } } - const task: Task | undefined | null = entry && 'task' in entry ? entry.task : undefined; + const task: Task | undefined | null = entry && Object.hasOwn(entry, 'task') ? (entry as IQuickPickItem & { task: Task }).task : undefined; if ((task === undefined) || (task === null)) { return; } @@ -3835,7 +3835,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer placeHolder: nls.localize('TaskService.pickDefaultBuildTask', 'Select the task to be used as the default build task') }). then((entry) => { - const task: Task | undefined | null = entry && 'task' in entry ? entry.task : undefined; + const task: Task | undefined | null = entry && Object.hasOwn(entry, 'task') ? (entry as IQuickPickItem & { task: Task }).task : undefined; if ((task === undefined) || (task === null)) { return; } @@ -3885,7 +3885,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer this._showIgnoredFoldersMessage().then(() => { this._showQuickPick(tasks, nls.localize('TaskService.pickDefaultTestTask', 'Select the task to be used as the default test task'), undefined, true, false, selectedEntry).then((entry) => { - const task: Task | undefined | null = entry ? entry.task : undefined; + const task: Task | undefined | null = entry && Object.hasOwn(entry, 'task') ? entry.task : undefined; if (!task) { return; } diff --git a/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts b/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts index d42b08000d2..9f5f923e886 100644 --- a/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts +++ b/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts @@ -88,7 +88,7 @@ export class TaskTerminalStatus extends Disposable { } private terminalFromEvent(event: { terminalId: number | undefined }): ITerminalData | undefined { - if (!('terminalId' in event) || !event.terminalId) { + if (!Object.hasOwn(event, 'terminalId') || !event.terminalId) { return undefined; } return this.terminalMap.get(event.terminalId); diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index 3c13bc0fdad..050202fed8f 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -294,7 +294,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { getTerminalsForTasks(tasks: Types.SingleOrMany): URI[] | undefined { const results: URI[] = []; for (const t of asArray(tasks)) { - for (const key in this._terminals) { + for (const key of Object.keys(this._terminals)) { const value = this._terminals[key]; if (value.lastTask === t.getMapKey()) { results.push(value.terminal.resource); @@ -850,7 +850,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { // Check that the task hasn't changed to include new variables let hasAllVariables = true; variables.forEach(value => { - if (value.substring(2, value.length - 1) in lastTask.getVerifiedTask().resolvedVariables) { + if (Object.hasOwn(lastTask.getVerifiedTask().resolvedVariables, value.substring(2, value.length - 1))) { hasAllVariables = false; } }); @@ -1305,7 +1305,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { shellLaunchConfig.args = windowsShellArgs ? combinedShellArgs.join(' ') : combinedShellArgs; if (task.command.presentation && task.command.presentation.echo) { if (needsFolderQualification && workspaceFolder) { - const folder = cwd && typeof cwd === 'object' && 'path' in cwd ? path.basename(cwd.path) : workspaceFolder.name; + const folder = cwd && typeof cwd === 'object' && Object.hasOwn(cwd, 'path') ? path.basename(cwd.path) : workspaceFolder.name; shellLaunchConfig.initialText = this.taskShellIntegrationStartSequence(cwd) + formatMessageForTerminal(nls.localize({ key: 'task.executingInFolder', comment: ['The workspace folder the task is running in', 'The task command line or label'] @@ -1411,7 +1411,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { const reconnectedTerminal = await this._reconnectToTerminal(task); const onDisposed = (terminal: ITerminalInstance) => this._fireTaskEvent(TaskEvent.terminated(task, terminal.instanceId, terminal.exitReason)); if (reconnectedTerminal) { - if ('command' in task && task.command.presentation) { + if ((CustomTask.is(task) || ContributedTask.is(task)) && task.command.presentation) { reconnectedTerminal.waitOnExit = getWaitOnExitValue(task.command.presentation, task.configurationProperties); } this._register(reconnectedTerminal.onDisposed(onDisposed)); @@ -1717,7 +1717,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { } else if (Array.isArray(definition)) { definition.forEach((element: any) => this._collectDefinitionVariables(variables, element)); } else if (Types.isObject(definition)) { - for (const key in definition) { + for (const key of Object.keys(definition)) { this._collectDefinitionVariables(variables, definition[key]); } } @@ -1947,7 +1947,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { public async getTaskForTerminal(instanceId: number): Promise { // First check if there's an active task for this terminal - for (const key in this._activeTasks) { + for (const key of Object.keys(this._activeTasks)) { const activeTask = this._activeTasks[key]; if (activeTask.terminal?.instanceId === instanceId) { return activeTask.task; diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/taskHelpers.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/taskHelpers.ts index 855ce3a8162..9786f71a25c 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/taskHelpers.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/taskHelpers.ts @@ -39,12 +39,13 @@ export function getTaskDefinition(id: string) { } export function getTaskRepresentation(task: IConfiguredTask | Task): string { - if ('label' in task && task.label) { - return task.label; - } else if ('script' in task && task.script) { - return task.script; - } else if ('command' in task && task.command) { - return isString(task.command) ? task.command : task.command.name?.toString() || ''; + if (Object.hasOwn(task, 'label') && (task as IConfiguredTask).label) { + return (task as IConfiguredTask).label!; + } else if (Object.hasOwn(task, 'script') && (task as IConfiguredTask).script) { + return (task as IConfiguredTask).script!; + } else if (Object.hasOwn(task, 'command') && (task as IConfiguredTask).command) { + const command = (task as IConfiguredTask).command; + return isString(command) ? command : command!.name?.toString() || ''; } return ''; } @@ -81,7 +82,7 @@ export async function getTaskForTool(id: string | undefined, taskDefinition: { t } } for (const configTask of configTasks) { - if ((!allowParentTask && !configTask.type) || ('hide' in configTask && configTask.hide)) { + if ((!allowParentTask && !configTask.type) || (Object.hasOwn(configTask, 'hide') && configTask.hide)) { // Skip these as they are not included in the agent prompt and we need to align with // the indices used there. continue; @@ -144,11 +145,12 @@ export interface IConfiguredTask { label?: string; type?: string; script?: string; - command?: string; + command?: string | { name?: string }; args?: string[]; isBackground?: boolean; problemMatcher?: string[]; group?: string; + hide?: boolean; } export async function resolveDependencyTasks(parentTask: Task, workspaceFolder: string, configurationService: IConfigurationService, taskService: ITaskService): Promise { @@ -219,16 +221,16 @@ export async function collectTerminalResults( // Use reconnection data if possible to match, since the properties here are unique const reconnectionData = instance.reconnectionProperties?.data as IReconnectionTaskData | undefined; if (reconnectionData) { - if (reconnectionData.lastTask in commonTaskIdToTaskMap) { + if (Object.hasOwn(commonTaskIdToTaskMap, reconnectionData.lastTask)) { terminalTask = commonTaskIdToTaskMap[reconnectionData.lastTask]; - } else if (reconnectionData.id in taskIdToTaskMap) { + } else if (Object.hasOwn(taskIdToTaskMap, reconnectionData.id)) { terminalTask = taskIdToTaskMap[reconnectionData.id]; } } else { // Otherwise, fallback to label matching - if (instance.shellLaunchConfig.name && instance.shellLaunchConfig.name in taskLabelToTaskMap) { + if (instance.shellLaunchConfig.name && Object.hasOwn(taskLabelToTaskMap, instance.shellLaunchConfig.name)) { terminalTask = taskLabelToTaskMap[instance.shellLaunchConfig.name]; - } else if (instance.title in taskLabelToTaskMap) { + } else if (Object.hasOwn(taskLabelToTaskMap, instance.title)) { terminalTask = taskLabelToTaskMap[instance.title]; } } diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts index 8fe480e079e..18c7354ccd4 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts @@ -478,14 +478,14 @@ export class OutputMonitor extends Disposable implements IOutputMonitor { try { const match = responseText.match(/\{[\s\S]*\}/); if (match) { - const obj = JSON.parse(match[0]) as unknown; + const parsed = JSON.parse(match[0]) as unknown; if ( - isObject(obj) && - 'prompt' in obj && isString(obj.prompt) && - 'options' in obj && - 'options' in obj && - 'freeFormInput' in obj && typeof obj.freeFormInput === 'boolean' + isObject(parsed) && + Object.hasOwn(parsed, 'prompt') && isString((parsed as Record).prompt) && + Object.hasOwn(parsed, 'options') && + Object.hasOwn(parsed, 'freeFormInput') && typeof (parsed as Record).freeFormInput === 'boolean' ) { + const obj = parsed as { prompt: string; options: unknown; freeFormInput: boolean }; if (this._lastPrompt === obj.prompt) { return; } @@ -644,7 +644,7 @@ export class OutputMonitor extends Disposable implements IOutputMonitor { let option: string | undefined = undefined; if (value === true) { option = suggestedOptionValue; - } else if (typeof value === 'object' && 'label' in value) { + } else if (typeof value === 'object' && Object.hasOwn(value, 'label')) { option = value.label.split(' (')[0]; } this._outputMonitorTelemetryCounters.inputToolManualAcceptCount++;