From b282eee40ce77bf8743f8daae7c82f52aea3447a Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Sun, 9 Nov 2025 07:07:11 +0000 Subject: [PATCH] Remove usages of in (#276333) * Remove usages of in * Update extensions/ipynb/src/serializers.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- eslint.config.js | 10 ------ extensions/ipynb/src/common.ts | 33 +++++++++++++++++++ extensions/ipynb/src/deserializers.ts | 4 +-- extensions/ipynb/src/notebookImagePaste.ts | 2 +- extensions/ipynb/src/serializers.ts | 6 ++-- extensions/notebook-renderers/src/index.ts | 8 ++--- .../contrib/debug/notebookBreakpoints.ts | 3 +- .../browser/contrib/find/findModel.ts | 5 +-- .../browser/diff/diffElementViewModel.ts | 2 +- .../browser/view/cellParts/cellExecution.ts | 3 +- .../common/model/notebookTextModel.ts | 18 +++++----- .../notebook/common/notebookEditorModel.ts | 4 +-- 12 files changed, 62 insertions(+), 36 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index e3be2d2e218..c1608532381 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -193,10 +193,6 @@ export default tseslint.config( 'extensions/git/src/blame.ts', 'extensions/github/src/links.ts', 'extensions/github-authentication/src/node/fetch.ts', - 'extensions/ipynb/src/deserializers.ts', - 'extensions/ipynb/src/notebookImagePaste.ts', - 'extensions/ipynb/src/serializers.ts', - 'extensions/notebook-renderers/src/index.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', @@ -314,17 +310,11 @@ export default tseslint.config( 'src/vs/workbench/contrib/mcp/common/mcpServerRequestHandler.ts', 'src/vs/workbench/contrib/mcp/test/common/mcpRegistryTypes.ts', 'src/vs/workbench/contrib/mcp/test/common/mcpServerRequestHandler.test.ts', - 'src/vs/workbench/contrib/notebook/browser/contrib/debug/notebookBreakpoints.ts', - 'src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts', 'src/vs/workbench/contrib/notebook/browser/controller/cellOutputActions.ts', 'src/vs/workbench/contrib/notebook/browser/controller/chat/notebook.chat.contribution.ts', 'src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts', - 'src/vs/workbench/contrib/notebook/browser/diff/diffElementViewModel.ts', - 'src/vs/workbench/contrib/notebook/browser/view/cellParts/cellExecution.ts', 'src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts', 'src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelView.ts', - 'src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts', - 'src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts', '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', diff --git a/extensions/ipynb/src/common.ts b/extensions/ipynb/src/common.ts index dbd3ea1a618..f0330c88440 100644 --- a/extensions/ipynb/src/common.ts +++ b/extensions/ipynb/src/common.ts @@ -65,3 +65,36 @@ export interface CellMetadata { execution_count?: number | null; } + + +type KeysOfUnionType = T extends T ? keyof T : never; +type FilterType = T extends TTest ? T : never; +type MakeOptionalAndBool = { [K in keyof T]?: boolean }; + +/** + * Type guard that checks if an object has specific keys and narrows the type accordingly. + * + * @param x - The object to check + * @param key - An object with boolean values indicating which keys to check for + * @returns true if all specified keys exist in the object, false otherwise + * + * @example + * ```typescript + * type A = { a: string }; + * type B = { b: number }; + * const obj: A | B = getObject(); + * + * if (hasKey(obj, { a: true })) { + * // obj is now narrowed to type A + * console.log(obj.a); + * } + * ``` + */ +export function hasKey(x: T, key: TKeys & MakeOptionalAndBool): x is FilterType & keyof TKeys]: unknown }> { + for (const k in key) { + if (!(k in x)) { + return false; + } + } + return true; +} diff --git a/extensions/ipynb/src/deserializers.ts b/extensions/ipynb/src/deserializers.ts index 930092f6feb..b3a347cfe5c 100644 --- a/extensions/ipynb/src/deserializers.ts +++ b/extensions/ipynb/src/deserializers.ts @@ -151,7 +151,7 @@ function convertJupyterOutputToBuffer(mime: string, value: unknown): NotebookCel } } -function getNotebookCellMetadata(cell: nbformat.IBaseCell): { +function getNotebookCellMetadata(cell: nbformat.ICell): { [key: string]: any; } { // We put this only for VSC to display in diff view. @@ -169,7 +169,7 @@ function getNotebookCellMetadata(cell: nbformat.IBaseCell): { cellMetadata['metadata'] = JSON.parse(JSON.stringify(cell['metadata'])); } - if ('id' in cell && typeof cell.id === 'string') { + if (typeof cell.id === 'string') { cellMetadata.id = cell.id; } diff --git a/extensions/ipynb/src/notebookImagePaste.ts b/extensions/ipynb/src/notebookImagePaste.ts index 70a24e9bf2d..97c2ee73946 100644 --- a/extensions/ipynb/src/notebookImagePaste.ts +++ b/extensions/ipynb/src/notebookImagePaste.ts @@ -274,7 +274,7 @@ function buildAttachment( const filenameWithoutExt = basename(attachment.fileName, fileExt); let tempFilename = filenameWithoutExt + fileExt; - for (let appendValue = 2; tempFilename in cellMetadata.attachments; appendValue++) { + for (let appendValue = 2; cellMetadata.attachments[tempFilename]; appendValue++) { const objEntries = Object.entries(cellMetadata.attachments[tempFilename]); if (objEntries.length) { // check that mime:b64 are present const [mime, attachmentb64] = objEntries[0]; diff --git a/extensions/ipynb/src/serializers.ts b/extensions/ipynb/src/serializers.ts index a38ae39b6c7..39413b868df 100644 --- a/extensions/ipynb/src/serializers.ts +++ b/extensions/ipynb/src/serializers.ts @@ -5,7 +5,7 @@ import type * as nbformat from '@jupyterlab/nbformat'; import type { NotebookCell, NotebookCellData, NotebookCellOutput, NotebookData, NotebookDocument } from 'vscode'; -import { CellOutputMetadata, type CellMetadata } from './common'; +import { CellOutputMetadata, hasKey, type CellMetadata } from './common'; import { textMimeTypes, NotebookCellKindMarkup, CellOutputMimeTypes, defaultNotebookFormat } from './constants'; const textDecoder = new TextDecoder(); @@ -50,7 +50,7 @@ export function sortObjectPropertiesRecursively(obj: any): any { } export function getCellMetadata(options: { cell: NotebookCell | NotebookCellData } | { metadata?: { [key: string]: any } }): CellMetadata { - if ('cell' in options) { + if (hasKey(options, { cell: true })) { const cell = options.cell; const metadata = { execution_count: null, @@ -472,7 +472,7 @@ export function serializeNotebookToString(data: NotebookData): string { .map(cell => createJupyterCellFromNotebookCell(cell, preferredCellLanguage)) .map(pruneCell); - const indentAmount = data.metadata && 'indentAmount' in data.metadata && typeof data.metadata.indentAmount === 'string' ? + const indentAmount = data.metadata && typeof data.metadata.indentAmount === 'string' ? data.metadata.indentAmount : ' '; diff --git a/extensions/notebook-renderers/src/index.ts b/extensions/notebook-renderers/src/index.ts index 6c3205fa7b7..2cfa01024ee 100644 --- a/extensions/notebook-renderers/src/index.ts +++ b/extensions/notebook-renderers/src/index.ts @@ -76,8 +76,8 @@ const domEval = (container: Element) => { }; function getAltText(outputInfo: OutputItem) { - const metadata = outputInfo.metadata; - if (typeof metadata === 'object' && metadata && 'vscode_altText' in metadata && typeof metadata.vscode_altText === 'string') { + const metadata = outputInfo.metadata as Record | undefined; + if (typeof metadata === 'object' && metadata && typeof metadata.vscode_altText === 'string') { return metadata.vscode_altText; } return undefined; @@ -337,9 +337,9 @@ function findScrolledHeight(container: HTMLElement): number | undefined { } function scrollingEnabled(output: OutputItem, options: RenderOptions) { - const metadata = output.metadata; + const metadata = output.metadata as Record | undefined; return (typeof metadata === 'object' && metadata - && 'scrollable' in metadata && typeof metadata.scrollable === 'boolean') ? + && typeof metadata.scrollable === 'boolean') ? metadata.scrollable : options.outputScrolling; } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/debug/notebookBreakpoints.ts b/src/vs/workbench/contrib/notebook/browser/contrib/debug/notebookBreakpoints.ts index 29b503e2639..a049be3d2c5 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/debug/notebookBreakpoints.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/debug/notebookBreakpoints.ts @@ -16,6 +16,7 @@ import { CellUri, NotebookCellsChangeType } from '../../../common/notebookCommon import { INotebookService } from '../../../common/notebookService.js'; import { IEditorService } from '../../../../../services/editor/common/editorService.js'; import { LifecyclePhase } from '../../../../../services/lifecycle/common/lifecycle.js'; +import { hasKey } from '../../../../../../base/common/types.js'; class NotebookBreakpoints extends Disposable implements IWorkbenchContribution { constructor( @@ -58,7 +59,7 @@ class NotebookBreakpoints extends Disposable implements IWorkbenchContribution { })); this._register(this._debugService.getModel().onDidChangeBreakpoints(e => { - const newCellBp = e?.added?.find(bp => 'uri' in bp && bp.uri.scheme === Schemas.vscodeNotebookCell) as IBreakpoint | undefined; + const newCellBp = e?.added?.find(bp => hasKey(bp, { uri: true }) && bp.uri.scheme === Schemas.vscodeNotebookCell) as IBreakpoint | undefined; if (newCellBp) { const parsed = CellUri.parse(newCellBp.uri); if (!parsed) { diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts index 072ba66abf4..ae53643d6d7 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts @@ -18,6 +18,7 @@ import { CellEditState, CellFindMatchWithIndex, CellWebviewFindMatch, ICellViewM import { NotebookViewModel } from '../../viewModel/notebookViewModelImpl.js'; import { NotebookTextModel } from '../../../common/model/notebookTextModel.js'; import { CellKind, INotebookFindOptions, NotebookCellsChangeType } from '../../../common/notebookCommon.js'; +import { hasKey } from '../../../../../../base/common/types.js'; export class CellFindMatchModel implements CellFindMatchWithIndex { readonly cell: ICellViewModel; @@ -239,14 +240,14 @@ export class FindModel extends Disposable { // let currCell; if (!this._findMatchesStarts) { this.set(this._findMatches, true); - if ('index' in option) { + if (hasKey(option, { index: true })) { this._currentMatch = option.index; } } else { // const currIndex = this._findMatchesStarts!.getIndexOf(this._currentMatch); // currCell = this._findMatches[currIndex.index].cell; const totalVal = this._findMatchesStarts.getTotalSum(); - if ('index' in option) { + if (hasKey(option, { index: true })) { this._currentMatch = option.index; } else if (this._currentMatch === -1) { diff --git a/src/vs/workbench/contrib/notebook/browser/diff/diffElementViewModel.ts b/src/vs/workbench/contrib/notebook/browser/diff/diffElementViewModel.ts index cd153dbe702..f9e3ac054d0 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/diffElementViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/diffElementViewModel.ts @@ -745,7 +745,7 @@ export class SideBySideDiffElementViewModel extends DiffElementCellViewModelBase const modifiedMedataRaw = Object.assign({}, this.modified.metadata); const originalCellMetadata = this.original.metadata; for (const key of cellMetadataKeys) { - if (key in originalCellMetadata) { + if (Object.hasOwn(originalCellMetadata, key)) { modifiedMedataRaw[key] = originalCellMetadata[key]; } } diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellExecution.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellExecution.ts index 6b4ba9c20bd..b8e9d472870 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellExecution.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellExecution.ts @@ -16,6 +16,7 @@ import { INotebookExecutionStateService } from '../../../common/notebookExecutio import { executingStateIcon } from '../../notebookIcons.js'; import { renderLabelWithIcons } from '../../../../../../base/browser/ui/iconLabel/iconLabels.js'; import { CellViewModelStateChangeEvent } from '../../notebookViewEvents.js'; +import { hasKey } from '../../../../../../base/common/types.js'; const UPDATE_EXECUTION_ORDER_GRACE_PERIOD = 200; @@ -38,7 +39,7 @@ export class CellExecutionPart extends CellContentPart { // Add a method to watch for cell execution state changes this._register(this._notebookExecutionStateService.onDidChangeExecution(e => { - if (this.currentCell && 'affectsCell' in e && e.affectsCell(this.currentCell.uri)) { + if (this.currentCell && hasKey(e, { affectsCell: true }) && e.affectsCell(this.currentCell.uri)) { this._updatePosition(); } })); diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts index ceb9a5bee0e..98bc59a29b2 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts @@ -10,7 +10,7 @@ import { Disposable, dispose, IDisposable } from '../../../../../base/common/lif import { Schemas } from '../../../../../base/common/network.js'; import { filter } from '../../../../../base/common/objects.js'; import { isEqual } from '../../../../../base/common/resources.js'; -import { isDefined } from '../../../../../base/common/types.js'; +import { hasKey, isDefined } from '../../../../../base/common/types.js'; import { URI } from '../../../../../base/common/uri.js'; import { Position } from '../../../../../editor/common/core/position.js'; import { Range } from '../../../../../editor/common/core/range.js'; @@ -436,11 +436,11 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel } private _getCellIndexWithOutputIdHandleFromEdits(outputId: string, rawEdits: ICellEditOperation[]) { - const edit = rawEdits.find(e => 'outputs' in e && e.outputs.some(o => o.outputId === outputId)); + const edit = rawEdits.find(e => hasKey(e, { outputs: true }) && e.outputs.some(o => o.outputId === outputId)); if (edit) { - if ('index' in edit) { + if (hasKey(edit, { index: true })) { return edit.index; - } else if ('handle' in edit) { + } else if (hasKey(edit, { handle: true })) { const cellIndex = this._getCellIndexByHandle(edit.handle); this._assertIndex(cellIndex); return cellIndex; @@ -621,10 +621,10 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel return false; } - if (('index' in edit) && !this.newCellsFromLastEdit.has(this.cells[edit.index].handle)) { + if (hasKey(edit, { index: true }) && !this.newCellsFromLastEdit.has(this.cells[edit.index].handle)) { return false; } - if ('handle' in edit && !this.newCellsFromLastEdit.has(edit.handle)) { + if (hasKey(edit, { handle: true }) && !this.newCellsFromLastEdit.has(edit.handle)) { return false; } } @@ -675,12 +675,12 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel private _doApplyEdits(rawEdits: ICellEditOperation[], synchronous: boolean, computeUndoRedo: boolean, beginSelectionState: ISelectionState | undefined, undoRedoGroup: UndoRedoGroup | undefined): void { const editsWithDetails = rawEdits.map((edit, index) => { let cellIndex: number = -1; - if ('index' in edit) { + if (hasKey(edit, { index: true })) { cellIndex = edit.index; - } else if ('handle' in edit) { + } else if (hasKey(edit, { handle: true })) { cellIndex = this._getCellIndexByHandle(edit.handle); this._assertIndex(cellIndex); - } else if ('outputId' in edit) { + } else if (hasKey(edit, { outputId: true })) { cellIndex = this._getCellIndexWithOutputIdHandle(edit.outputId); if (this._indexIsInvalid(cellIndex)) { // The referenced output may have been created in this batch of edits diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts index 53ad06b5e3c..5ce068885bb 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts @@ -10,7 +10,7 @@ import { Emitter, Event } from '../../../../base/common/event.js'; import { IMarkdownString } from '../../../../base/common/htmlContent.js'; import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js'; import { Schemas } from '../../../../base/common/network.js'; -import { assertType } from '../../../../base/common/types.js'; +import { assertType, hasKey } from '../../../../base/common/types.js'; import { URI } from '../../../../base/common/uri.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { IWriteFileOptions, IFileStatWithMetadata, FileOperationError, FileOperationResult } from '../../../../platform/files/common/files.js'; @@ -111,7 +111,7 @@ export class SimpleNotebookEditorModel extends EditorModel implements INotebookE } get hasErrorState(): boolean { - if (this._workingCopy && 'hasState' in this._workingCopy) { + if (this._workingCopy && hasKey(this._workingCopy, { hasState: true })) { return this._workingCopy.hasState(StoredFileWorkingCopyState.ERROR); }