diff --git a/packages/bulk-edit/src/browser/bulk-edit-tree/bulk-edit-node-selection.ts b/packages/bulk-edit/src/browser/bulk-edit-tree/bulk-edit-node-selection.ts index 179018ceacd42..04ef24c8f7755 100644 --- a/packages/bulk-edit/src/browser/bulk-edit-tree/bulk-edit-node-selection.ts +++ b/packages/bulk-edit/src/browser/bulk-edit-tree/bulk-edit-node-selection.ts @@ -17,13 +17,14 @@ import { SelectionService } from '@theia/core/lib/common/selection-service'; import { SelectionCommandHandler } from '@theia/core/lib/common/selection-command-handler'; import { ResourceFileEdit, ResourceTextEdit } from '@theia/monaco-editor-core/esm/vs/editor/browser/services/bulkEditService'; +import { isObject } from '@theia/core/lib/common'; export interface BulkEditNodeSelection { bulkEdit: ResourceFileEdit | ResourceTextEdit; } export namespace BulkEditNodeSelection { export function is(arg: unknown): arg is BulkEditNodeSelection { - return !!arg && typeof arg === 'object' && ('bulkEdit' in arg); + return isObject(arg) && 'bulkEdit' in arg; } export class CommandHandler extends SelectionCommandHandler { diff --git a/packages/core/shared/reflect-metadata/index.d.ts b/packages/core/shared/reflect-metadata/index.d.ts new file mode 100644 index 0000000000000..7b873b4cc900a --- /dev/null +++ b/packages/core/shared/reflect-metadata/index.d.ts @@ -0,0 +1 @@ +export * from 'reflect-metadata'; diff --git a/packages/core/shared/reflect-metadata/index.js b/packages/core/shared/reflect-metadata/index.js new file mode 100644 index 0000000000000..2184c277f4f1a --- /dev/null +++ b/packages/core/shared/reflect-metadata/index.js @@ -0,0 +1 @@ +module.exports = require('reflect-metadata'); diff --git a/packages/core/src/browser/frontend-application.ts b/packages/core/src/browser/frontend-application.ts index 3be0d1f878139..162fe3c09b9f8 100644 --- a/packages/core/src/browser/frontend-application.ts +++ b/packages/core/src/browser/frontend-application.ts @@ -15,7 +15,7 @@ // ***************************************************************************** import { inject, injectable, named } from 'inversify'; -import { ContributionProvider, CommandRegistry, MenuModelRegistry, isOSX, BackendStopwatch, LogLevel, Stopwatch } from '../common'; +import { ContributionProvider, CommandRegistry, MenuModelRegistry, isOSX, BackendStopwatch, LogLevel, Stopwatch, isObject } from '../common'; import { MaybePromise } from '../common/types'; import { KeybindingRegistry } from './keybinding'; import { Widget } from './widgets'; @@ -101,7 +101,7 @@ export interface OnWillStopAction { export namespace OnWillStopAction { export function is(candidate: unknown): candidate is OnWillStopAction { - return typeof candidate === 'object' && !!candidate && 'action' in candidate && 'reason' in candidate; + return isObject(candidate) && 'action' in candidate && 'reason' in candidate; } } diff --git a/packages/core/src/browser/label-parser.ts b/packages/core/src/browser/label-parser.ts index 3aa4dec730ae6..a5032f8e9b18d 100644 --- a/packages/core/src/browser/label-parser.ts +++ b/packages/core/src/browser/label-parser.ts @@ -14,6 +14,7 @@ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 // ***************************************************************************** import { injectable } from 'inversify'; +import { isObject, isString } from '../common'; export interface LabelIcon { name: string; @@ -21,8 +22,8 @@ export interface LabelIcon { } export namespace LabelIcon { - export function is(val: object): val is LabelIcon { - return 'name' in val; + export function is(val: unknown): val is LabelIcon { + return isObject(val) && isString(val.name); } } diff --git a/packages/core/src/browser/label-provider.ts b/packages/core/src/browser/label-provider.ts index 48e61a057efb3..c18dc1a77aa31 100644 --- a/packages/core/src/browser/label-provider.ts +++ b/packages/core/src/browser/label-provider.ts @@ -18,8 +18,7 @@ import { inject, injectable, named, postConstruct } from 'inversify'; import * as fileIcons from 'file-icons-js'; import URI from '../common/uri'; import { ContributionProvider } from '../common/contribution-provider'; -import { Prioritizeable } from '../common/types'; -import { Event, Emitter, Disposable, Path } from '../common'; +import { Event, Emitter, Disposable, isObject, Path, Prioritizeable } from '../common'; import { FrontendApplicationContribution } from './frontend-application'; import { EnvVariablesServer } from '../common/env-variables/env-variables-protocol'; import { ResourceLabelFormatter, ResourceLabelFormatting } from '../common/label-protocol'; @@ -99,7 +98,7 @@ export interface URIIconReference { } export namespace URIIconReference { export function is(element: unknown): element is URIIconReference { - return !!element && typeof element === 'object' && 'kind' in element && (element as URIIconReference).kind === 'uriIconReference'; + return isObject(element) && element.kind === 'uriIconReference'; } export function create(id: URIIconReference['id'], uri?: URI): URIIconReference { return { kind: 'uriIconReference', id, uri }; diff --git a/packages/core/src/browser/navigatable-types.ts b/packages/core/src/browser/navigatable-types.ts index a52f39491d139..0104d4dde6dcf 100644 --- a/packages/core/src/browser/navigatable-types.ts +++ b/packages/core/src/browser/navigatable-types.ts @@ -14,8 +14,7 @@ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 // ***************************************************************************** -import URI from '../common/uri'; -import { MaybeArray } from '../common/types'; +import { URI, isObject, MaybeArray } from '../common'; import { Widget, BaseWidget } from './widgets'; /** @@ -34,7 +33,7 @@ export interface Navigatable { export namespace Navigatable { export function is(arg: unknown): arg is Navigatable { - return !!arg && typeof arg === 'object' && 'getResourceUri' in arg && 'createMoveToUri' in arg; + return isObject(arg) && 'getResourceUri' in arg && 'createMoveToUri' in arg; } } @@ -71,12 +70,12 @@ export namespace NavigatableWidget { } export interface NavigatableWidgetOptions { - kind: 'navigatable', - uri: string, - counter?: number, + kind: 'navigatable'; + uri: string; + counter?: number; } export namespace NavigatableWidgetOptions { export function is(arg: unknown): arg is NavigatableWidgetOptions { - return !!arg && typeof arg === 'object' && 'kind' in arg && (arg as NavigatableWidgetOptions).kind === 'navigatable'; + return isObject(arg) && arg.kind === 'navigatable'; } } diff --git a/packages/core/src/browser/preferences/injectable-preference-proxy.ts b/packages/core/src/browser/preferences/injectable-preference-proxy.ts index b5addbf63c613..38a7573f3c272 100644 --- a/packages/core/src/browser/preferences/injectable-preference-proxy.ts +++ b/packages/core/src/browser/preferences/injectable-preference-proxy.ts @@ -16,7 +16,7 @@ import { inject, injectable, postConstruct } from 'inversify'; import { PreferenceSchema } from '../../common/preferences/preference-schema'; -import { Disposable, DisposableCollection, Emitter, Event, MaybePromise } from '../../common'; +import { Disposable, DisposableCollection, Emitter, Event, isObject, MaybePromise } from '../../common'; import { PreferenceChangeEvent, PreferenceEventEmitter, PreferenceProxy, PreferenceProxyOptions, PreferenceRetrieval } from './preference-proxy'; import { PreferenceChange, PreferenceChangeImpl, PreferenceChanges, PreferenceScope, PreferenceService } from './preference-service'; import { JSONValue } from '@phosphor/coreutils'; @@ -103,7 +103,9 @@ export class InjectablePreferenceProxy> impl } get(target: unknown, property: string, receiver: unknown): unknown { - if (typeof property !== 'string') { throw new Error(`Unexpected property: ${String(property)}`); } + if (typeof property !== 'string') { + throw new Error(`Unexpected property: ${String(property)}`); + } const preferenceName = this.prefix + property; if (this.schema && (this.isFlat || !property.includes('.')) && this.schema.properties[preferenceName]) { const { overrideIdentifier } = this; @@ -143,7 +145,7 @@ export class InjectablePreferenceProxy> impl } while (parentSegment && value === undefined); let segment; - while (typeof value === 'object' && (segment = segments.pop())) { + while (isObject(value) && (segment = segments.pop())) { value = value[segment]; } return segments.length ? undefined : value; diff --git a/packages/core/src/browser/preferences/preference-contribution.ts b/packages/core/src/browser/preferences/preference-contribution.ts index 568baa849d1f5..6c58ef88ffb06 100644 --- a/packages/core/src/browser/preferences/preference-contribution.ts +++ b/packages/core/src/browser/preferences/preference-contribution.ts @@ -26,7 +26,7 @@ import { FrontendApplicationConfigProvider } from '../frontend-application-confi import { FrontendApplicationConfig } from '@theia/application-package/lib/application-props'; import { bindPreferenceConfigurations, PreferenceConfigurations } from './preference-configurations'; export { PreferenceSchema, PreferenceSchemaProperties, PreferenceDataSchema, PreferenceItem, PreferenceSchemaProperty, PreferenceDataProperty }; -import { Mutable } from '../../common/types'; +import { isObject, Mutable } from '../../common/types'; import { PreferenceLanguageOverrideService } from './preference-language-override-service'; import { JSONValue } from '@phosphor/coreutils'; @@ -80,7 +80,7 @@ export interface FrontendApplicationPreferenceConfig extends FrontendApplication } export namespace FrontendApplicationPreferenceConfig { export function is(config: FrontendApplicationConfig): config is FrontendApplicationPreferenceConfig { - return 'preferences' in config && typeof config['preferences'] === 'object'; + return isObject(config.preferences); } } diff --git a/packages/core/src/browser/preferences/preference-language-override-service.ts b/packages/core/src/browser/preferences/preference-language-override-service.ts index 37a3cfddd1c58..ed6655c50a70b 100644 --- a/packages/core/src/browser/preferences/preference-language-override-service.ts +++ b/packages/core/src/browser/preferences/preference-language-override-service.ts @@ -15,7 +15,7 @@ // ***************************************************************************** import { injectable } from 'inversify'; -import { escapeRegExpCharacters } from '../../common'; +import { escapeRegExpCharacters, isObject } from '../../common'; import { PreferenceSchemaProperties } from '../../common/preferences/preference-schema'; export interface OverridePreferenceName { @@ -24,7 +24,7 @@ export interface OverridePreferenceName { } export namespace OverridePreferenceName { export function is(arg: unknown): arg is OverridePreferenceName { - return !!arg && typeof arg === 'object' && 'preferenceName' in arg && 'overrideIdentifier' in arg; + return isObject(arg) && 'preferenceName' in arg && 'overrideIdentifier' in arg; } } diff --git a/packages/core/src/browser/preferences/preference-provider.ts b/packages/core/src/browser/preferences/preference-provider.ts index 256d5da180859..a4f0a9faf7f68 100644 --- a/packages/core/src/browser/preferences/preference-provider.ts +++ b/packages/core/src/browser/preferences/preference-provider.ts @@ -20,7 +20,7 @@ import debounce = require('p-debounce'); import { injectable, inject } from 'inversify'; import { JSONExt, JSONValue } from '@phosphor/coreutils'; import URI from '../../common/uri'; -import { Disposable, DisposableCollection, Emitter, Event } from '../../common'; +import { Disposable, DisposableCollection, Emitter, Event, isObject } from '../../common'; import { Deferred } from '../../common/promise-util'; import { PreferenceScope } from './preference-scope'; import { PreferenceLanguageOverrideService } from './preference-language-override-service'; @@ -253,16 +253,12 @@ export abstract class PreferenceProvider implements Disposable { protected getParsedContent(jsonData: any): { [key: string]: any } { const preferences: { [key: string]: any } = {}; - if (typeof jsonData !== 'object') { + if (!isObject(jsonData)) { return preferences; } - // eslint-disable-next-line guard-for-in - for (const preferenceName in jsonData) { - const preferenceValue = jsonData[preferenceName]; + for (const [preferenceName, preferenceValue] of Object.entries(jsonData)) { if (this.preferenceOverrideService.testOverrideValue(preferenceName, preferenceValue)) { - // eslint-disable-next-line guard-for-in - for (const overriddenPreferenceName in preferenceValue) { - const overriddenValue = preferenceValue[overriddenPreferenceName]; + for (const [overriddenPreferenceName, overriddenValue] of Object.entries(preferenceValue)) { preferences[`${preferenceName}.${overriddenPreferenceName}`] = overriddenValue; } } else { diff --git a/packages/core/src/browser/preferences/preference-proxy.ts b/packages/core/src/browser/preferences/preference-proxy.ts index 7a266c9532311..167916bcd91d8 100644 --- a/packages/core/src/browser/preferences/preference-proxy.ts +++ b/packages/core/src/browser/preferences/preference-proxy.ts @@ -16,7 +16,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { Disposable, Event, MaybePromise } from '../../common'; +import { Disposable, Event, isObject, MaybePromise } from '../../common'; import { PreferenceService } from './preference-service'; import { PreferenceSchema } from './preference-contribution'; import { PreferenceScope } from './preference-scope'; @@ -332,7 +332,7 @@ export function createPreferenceProxy(preferences: PreferenceService, promise } while (parentSegment && value === undefined); let segment; - while (typeof value === 'object' && (segment = segments.pop())) { + while (isObject(value) && (segment = segments.pop())) { value = value[segment]; } return segments.length ? undefined : value; diff --git a/packages/core/src/browser/saveable.ts b/packages/core/src/browser/saveable.ts index 06f5571c18140..c0eec0e58c859 100644 --- a/packages/core/src/browser/saveable.ts +++ b/packages/core/src/browser/saveable.ts @@ -22,6 +22,7 @@ import { Key } from './keyboard/keys'; import { AbstractDialog } from './dialogs'; import { waitForClosed } from './widgets'; import { nls } from '../common/nls'; +import { isObject } from '../common'; export interface Saveable { readonly dirty: boolean; @@ -60,10 +61,10 @@ export namespace Saveable { export type Snapshot = { value: string } | { read(): string | null }; export function isSource(arg: unknown): arg is SaveableSource { - return typeof arg === 'object' && !!arg && is((arg as SaveableSource).saveable); + return isObject(arg) && is(arg.saveable); } export function is(arg: unknown): arg is Saveable { - return typeof arg === 'object' && !!arg && 'dirty' in arg && 'onDirtyChanged' in arg; + return isObject(arg) && 'dirty' in arg && 'onDirtyChanged' in arg; } export function get(arg: unknown): Saveable | undefined { if (is(arg)) { diff --git a/packages/core/src/browser/shell/application-shell.ts b/packages/core/src/browser/shell/application-shell.ts index c4e491b041b92..34286db482e2b 100644 --- a/packages/core/src/browser/shell/application-shell.ts +++ b/packages/core/src/browser/shell/application-shell.ts @@ -22,7 +22,7 @@ import { } from '@phosphor/widgets'; import { Message } from '@phosphor/messaging'; import { IDragEvent } from '@phosphor/dragdrop'; -import { RecursivePartial, Event as CommonEvent, DisposableCollection, Disposable, environment } from '../../common'; +import { RecursivePartial, Event as CommonEvent, DisposableCollection, Disposable, environment, isObject } from '../../common'; import { animationFrame } from '../browser'; import { Saveable, SaveableWidget, SaveOptions, SaveableSource } from '../saveable'; import { StatusBarImpl, StatusBarEntry, StatusBarAlignment } from '../status-bar/status-bar'; @@ -2108,7 +2108,7 @@ export namespace ApplicationShell { export namespace TrackableWidgetProvider { export function is(widget: unknown): widget is TrackableWidgetProvider { - return !!widget && typeof widget === 'object' && 'getTrackableWidgets' in widget; + return isObject(widget) && 'getTrackableWidgets' in widget; } } diff --git a/packages/core/src/browser/shell/shell-layout-restorer.ts b/packages/core/src/browser/shell/shell-layout-restorer.ts index ac7f680ff70fe..e7db537a0d0ea 100644 --- a/packages/core/src/browser/shell/shell-layout-restorer.ts +++ b/packages/core/src/browser/shell/shell-layout-restorer.ts @@ -23,11 +23,11 @@ import { ILogger } from '../../common/logger'; import { CommandContribution, CommandRegistry, Command } from '../../common/command'; import { ThemeService } from '../theming'; import { ContributionProvider } from '../../common/contribution-provider'; -import { MaybePromise } from '../../common/types'; import { ApplicationShell, applicationShellLayoutVersion, ApplicationShellLayoutVersion } from './application-shell'; import { CommonCommands } from '../common-frontend-contribution'; import { WindowService } from '../window/window-service'; import { StopReason } from '../../common/frontend-application-state'; +import { isFunction, isObject, MaybePromise } from '../../common'; /** * A contract for widgets that want to store and restore their inner state, between sessions. @@ -47,7 +47,7 @@ export interface StatefulWidget { export namespace StatefulWidget { export function is(arg: unknown): arg is StatefulWidget { - return !!arg && typeof arg === 'object' && typeof (arg as StatefulWidget).storeState === 'function' && typeof (arg as StatefulWidget).restoreState === 'function'; + return isObject(arg) && isFunction(arg.storeState) && isFunction(arg.restoreState); } } @@ -232,11 +232,7 @@ export class ShellLayoutRestorer implements CommandContribution { const parseContext = new ShellLayoutRestorer.ParseContext(); const layout = this.parse(layoutData, parseContext); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - let layoutVersion: number | any; - try { - layoutVersion = 'version' in layout && Number(layout.version); - } catch { /* no-op */ } + const layoutVersion = Number(layout.version); if (typeof layoutVersion !== 'number' || Number.isNaN(layoutVersion)) { throw new Error('could not resolve a layout version'); } @@ -282,9 +278,8 @@ export class ShellLayoutRestorer implements CommandContribution { }); } return widgets; - } else if (value && typeof value === 'object' && !Array.isArray(value)) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const copy: any = {}; + } else if (isObject>(value) && !Array.isArray(value)) { + const copy: Record = {}; for (const p in value) { if (this.isWidgetProperty(p)) { parseContext.push(async context => { @@ -306,7 +301,7 @@ export class ShellLayoutRestorer implements CommandContribution { // don't catch exceptions, if one migration fails all should fail. const migrated = await migration.onWillInflateWidget(desc, context); if (migrated) { - if (migrated.innerWidgetState && typeof migrated.innerWidgetState !== 'string') { + if (isObject(migrated.innerWidgetState)) { // in order to inflate nested widgets migrated.innerWidgetState = JSON.stringify(migrated.innerWidgetState); } diff --git a/packages/core/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar-types.ts b/packages/core/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar-types.ts index acf15dd917bbc..ec173e18b976f 100644 --- a/packages/core/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar-types.ts +++ b/packages/core/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar-types.ts @@ -15,7 +15,7 @@ // ***************************************************************************** import * as React from 'react'; -import { ArrayUtils, Event, MenuPath } from '../../../common'; +import { ArrayUtils, Event, isFunction, isObject, isString, MenuPath } from '../../../common'; import { Widget } from '../../widgets'; /** Items whose group is exactly 'navigation' will be rendered inline. */ @@ -27,13 +27,9 @@ export interface TabBarDelegator extends Widget { } export namespace TabBarDelegator { - export const is = (candidate?: Widget): candidate is TabBarDelegator => { - if (candidate) { - const asDelegator = candidate as TabBarDelegator; - return typeof asDelegator.getTabBarDelegate === 'function'; - } - return false; - }; + export function is(candidate?: Widget): candidate is TabBarDelegator { + return isObject(candidate) && isFunction(candidate.getTabBarDelegate); + } } interface RegisteredToolbarItem { @@ -172,14 +168,13 @@ export namespace TabBarToolbarItem { }; export function is(arg: unknown): arg is TabBarToolbarItem { - return !!arg && typeof arg === 'object' && 'command' in arg && typeof (arg as TabBarToolbarItem).command === 'string'; + return isObject(arg) && isString(arg.command); } } export namespace MenuToolbarItem { export function getMenuPath(item: AnyToolbarItem): MenuPath | undefined { - const asDelegate = item as MenuToolbarItem; - return Array.isArray(asDelegate.menuPath) ? asDelegate.menuPath : undefined; + return Array.isArray(item.menuPath) ? item.menuPath : undefined; } } diff --git a/packages/core/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar.tsx b/packages/core/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar.tsx index 4482e2a6e3889..2f1dd9a9c77cd 100644 --- a/packages/core/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar.tsx +++ b/packages/core/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar.tsx @@ -109,7 +109,7 @@ export class TabBarToolbar extends ReactWidget { const classNames = []; if (item.text) { for (const labelPart of this.labelParser.parse(item.text)) { - if (typeof labelPart !== 'string' && LabelIcon.is(labelPart)) { + if (LabelIcon.is(labelPart)) { const className = `fa fa-${labelPart.name}${labelPart.animation ? ' fa-' + labelPart.animation : ''}`; classNames.push(...className.split(' ')); } else { diff --git a/packages/core/src/browser/source-tree/tree-source.ts b/packages/core/src/browser/source-tree/tree-source.ts index e0dc628eeabe8..f171cc061236f 100644 --- a/packages/core/src/browser/source-tree/tree-source.ts +++ b/packages/core/src/browser/source-tree/tree-source.ts @@ -18,9 +18,7 @@ import { ReactNode } from 'react'; import { injectable, unmanaged } from 'inversify'; -import { Emitter, Event } from '../../common/event'; -import { MaybePromise } from '../../common/types'; -import { Disposable, DisposableCollection } from '../../common/disposable'; +import { Disposable, DisposableCollection, Emitter, Event, isObject, MaybePromise } from '../../common'; import { TreeWidget } from '../tree'; export interface TreeElement { @@ -39,7 +37,7 @@ export interface CompositeTreeElement extends TreeElement { } export namespace CompositeTreeElement { export function is(element: unknown): element is CompositeTreeElement { - return !!element && typeof element === 'object' && 'getElements' in element; + return isObject(element) && 'getElements' in element; } export function hasElements(element: unknown): element is CompositeTreeElement { return is(element) && element.hasElements !== false; diff --git a/packages/core/src/browser/status-bar/status-bar.tsx b/packages/core/src/browser/status-bar/status-bar.tsx index 31d5740f51fee..1a4e7306957df 100644 --- a/packages/core/src/browser/status-bar/status-bar.tsx +++ b/packages/core/src/browser/status-bar/status-bar.tsx @@ -165,7 +165,7 @@ export class StatusBarImpl extends ReactWidget implements StatusBar { const children: JSX.Element[] = []; childStrings.forEach((val, key) => { - if (!(typeof val === 'string') && LabelIcon.is(val)) { + if (LabelIcon.is(val)) { const animation = val.animation ? ` fa-${val.animation}` : ''; if (val.name.startsWith('codicon-')) { children.push(); diff --git a/packages/core/src/browser/tree/tree-selection.ts b/packages/core/src/browser/tree/tree-selection.ts index d358008b91ba7..d7a8e2ebc2b5e 100644 --- a/packages/core/src/browser/tree/tree-selection.ts +++ b/packages/core/src/browser/tree/tree-selection.ts @@ -15,7 +15,7 @@ // ***************************************************************************** import { TreeNode } from './tree'; -import { Event, Disposable, SelectionProvider } from '../../common'; +import { Event, Disposable, isObject, SelectionProvider } from '../../common'; /** * The tree selection service. @@ -87,7 +87,7 @@ export namespace TreeSelection { } export function is(arg: unknown): arg is TreeSelection { - return !!arg && typeof arg === 'object' && 'node' in arg; + return isObject(arg) && 'node' in arg; } export function isRange(arg: TreeSelection | SelectionType | undefined): boolean { diff --git a/packages/core/src/browser/tree/tree.ts b/packages/core/src/browser/tree/tree.ts index 8a65875b61499..cbde1c57ea28e 100644 --- a/packages/core/src/browser/tree/tree.ts +++ b/packages/core/src/browser/tree/tree.ts @@ -18,8 +18,8 @@ import { injectable } from 'inversify'; import { Event, Emitter, WaitUntilEvent } from '../../common/event'; import { Disposable, DisposableCollection } from '../../common/disposable'; import { CancellationToken, CancellationTokenSource } from '../../common/cancellation'; -import { Mutable } from '../../common/types'; import { timeout } from '../../common/promise-util'; +import { isObject, Mutable } from '../../common'; export const Tree = Symbol('Tree'); @@ -124,7 +124,7 @@ export interface TreeNode { export namespace TreeNode { export function is(node: unknown): node is TreeNode { - return !!node && typeof node === 'object' && 'id' in node && 'parent' in node; + return isObject(node) && 'id' in node && 'parent' in node; } export function equals(left: TreeNode | undefined, right: TreeNode | undefined): boolean { @@ -148,7 +148,7 @@ export interface CompositeTreeNode extends TreeNode { export namespace CompositeTreeNode { export function is(node: unknown): node is CompositeTreeNode { - return typeof node === 'object' && !!node && 'children' in node; + return isObject(node) && 'children' in node; } export function getFirstChild(parent: CompositeTreeNode): TreeNode | undefined { diff --git a/packages/core/src/browser/view-container.ts b/packages/core/src/browser/view-container.ts index 0d79cfd985df0..ba6faff5badea 100644 --- a/packages/core/src/browser/view-container.ts +++ b/packages/core/src/browser/view-container.ts @@ -30,7 +30,7 @@ import { FrontendApplicationStateService } from './frontend-application-state'; import { ContextMenuRenderer, Anchor } from './context-menu-renderer'; import { parseCssMagnitude } from './browser'; import { TabBarToolbarRegistry, TabBarToolbarFactory, TabBarToolbar, TabBarDelegator, TabBarToolbarItem } from './shell/tab-bar-toolbar'; -import { isEmpty, nls } from '../common'; +import { isEmpty, isObject, nls } from '../common'; import { WidgetManager } from './widget-manager'; import { Key } from './keys'; import { ProgressBarFactory } from './progress-bar-factory'; @@ -64,13 +64,13 @@ export interface BadgeWidget { export namespace DescriptionWidget { export function is(arg: unknown): arg is DescriptionWidget { - return !!arg && typeof arg === 'object' && 'onDidChangeDescription' in arg; + return isObject(arg) && 'onDidChangeDescription' in arg; } } export namespace BadgeWidget { export function is(arg: unknown): arg is BadgeWidget { - return !!arg && typeof arg === 'object' && 'onDidChangeBadge' in arg; + return isObject(arg) && 'onDidChangeBadge' in arg; } } diff --git a/packages/core/src/browser/widgets/widget.ts b/packages/core/src/browser/widgets/widget.ts index 09e920a7cf032..b436811327927 100644 --- a/packages/core/src/browser/widgets/widget.ts +++ b/packages/core/src/browser/widgets/widget.ts @@ -19,7 +19,7 @@ import { injectable, decorate, unmanaged } from 'inversify'; import { Title, Widget } from '@phosphor/widgets'; import { Message, MessageLoop } from '@phosphor/messaging'; -import { Emitter, Event, Disposable, DisposableCollection, MaybePromise } from '../../common'; +import { Emitter, Event, Disposable, DisposableCollection, MaybePromise, isObject } from '../../common'; import { KeyCode, KeysOrKeyCodes } from '../keyboard/keys'; import PerfectScrollbar from 'perfect-scrollbar'; @@ -248,7 +248,7 @@ export interface EventListenerObject { } export namespace EventListenerObject { export function is(listener: unknown): listener is EventListenerObject { - return !!listener && typeof listener === 'object' && 'handleEvent' in listener; + return isObject(listener) && 'handleEvent' in listener; } } export type EventListenerOrEventListenerObject = EventListener | EventListenerObject; diff --git a/packages/core/src/common/array-utils.ts b/packages/core/src/common/array-utils.ts new file mode 100644 index 0000000000000..62a08f5e32a9a --- /dev/null +++ b/packages/core/src/common/array-utils.ts @@ -0,0 +1,102 @@ +// ***************************************************************************** +// Copyright (C) 2023 Ericsson and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0. +// +// This Source Code may also be made available under the following Secondary +// Licenses when the conditions for such availability set forth in the Eclipse +// Public License v. 2.0 are satisfied: GNU General Public License, version 2 +// with the GNU Classpath Exception which is available at +// https://www.gnu.org/software/classpath/license.html. +// +// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 +// ***************************************************************************** + +export namespace ArrayUtils { + export interface Head extends Array { + head(): T; + } + + export interface Tail extends Array { + tail(): T; + } + + export interface Children extends Array { + children(): Tail + } + + export const TailImpl = { + tail(this: Array): T { + return this[this.length - 1]; + }, + }; + + export const HeadAndChildrenImpl = { + head(this: Array): T { + return this[0]; + }, + + children(this: Array): Tail { + return Object.assign(this.slice(1), TailImpl); + } + }; + + export interface HeadAndTail extends Head, Tail, Children { } + + export function asTail(array: Array): Tail { + return Object.assign(array, TailImpl); + } + + export function asHeadAndTail(array: Array): HeadAndTail { + return Object.assign(array, HeadAndChildrenImpl, TailImpl); + } + + export enum Sort { + LeftBeforeRight = -1, + RightBeforeLeft = 1, + Equal = 0, + } + + // Copied from https://github.com/microsoft/vscode/blob/9c29becfad5f68270b9b23efeafb147722c5feba/src/vs/base/common/arrays.ts + /** + * Performs a binary search algorithm over a sorted collection. Useful for cases + * when we need to perform a binary search over something that isn't actually an + * array, and converting data to an array would defeat the use of binary search + * in the first place. + * + * @param length The collection length. + * @param compareToKey A function that takes an index of an element in the + * collection and returns zero if the value at this index is equal to the + * search key, a negative number if the value precedes the search key in the + * sorting order, or a positive number if the search key precedes the value. + * @return A non-negative index of an element, if found. If not found, the + * result is -(n+1) (or ~n, using bitwise notation), where n is the index + * where the key should be inserted to maintain the sorting order. + */ + export function binarySearch2(length: number, compareToKey: (index: number) => number): number { + let low = 0; + let high = length - 1; + + while (low <= high) { + const mid = ((low + high) / 2) | 0; + const comp = compareToKey(mid); + if (comp < 0) { + low = mid + 1; + } else if (comp > 0) { + high = mid - 1; + } else { + return mid; + } + } + return -(low + 1); + } + + /** + * @returns New array with all falsy values removed. The original array IS NOT modified. + */ + export function coalesce(array: ReadonlyArray): T[] { + return array.filter(e => !!e); + } +} diff --git a/packages/core/src/common/cancellation.ts b/packages/core/src/common/cancellation.ts index 05e93cc18d748..9474d5b03d424 100644 --- a/packages/core/src/common/cancellation.ts +++ b/packages/core/src/common/cancellation.ts @@ -19,6 +19,7 @@ *--------------------------------------------------------------------------------------------*/ import { Event, Emitter } from './event'; +import { isBoolean, isObject } from './types'; export interface CancellationToken { readonly isCancellationRequested: boolean; @@ -51,10 +52,9 @@ export namespace CancellationToken { }); export function is(value: unknown): value is CancellationToken { - const candidate = value as CancellationToken; - return candidate && (candidate === CancellationToken.None - || candidate === CancellationToken.Cancelled - || (typeof candidate.isCancellationRequested === 'boolean' && !!candidate.onCancellationRequested)); + return isObject(value) && (value === CancellationToken.None + || value === CancellationToken.Cancelled + || (isBoolean(value.isCancellationRequested) && !!value.onCancellationRequested)); } } diff --git a/packages/core/src/common/command.ts b/packages/core/src/common/command.ts index b46a29a510305..d7711d87cd97d 100644 --- a/packages/core/src/common/command.ts +++ b/packages/core/src/common/command.ts @@ -20,6 +20,7 @@ import { Disposable, DisposableCollection } from './disposable'; import { ContributionProvider } from './contribution-provider'; import { nls } from './nls'; import debounce = require('p-debounce'); +import { isObject } from './types'; /** * A command is a unique identifier of a function @@ -50,7 +51,7 @@ export interface Command { export namespace Command { /* Determine whether object is a Command */ export function is(arg: unknown): arg is Command { - return !!arg && typeof arg === 'object' && 'id' in arg; + return isObject(arg) && 'id' in arg; } /** Utility function to easily translate commands */ diff --git a/packages/core/src/common/disposable.ts b/packages/core/src/common/disposable.ts index 379b31006f17f..f9be424257f2d 100644 --- a/packages/core/src/common/disposable.ts +++ b/packages/core/src/common/disposable.ts @@ -13,7 +13,9 @@ // // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 // ***************************************************************************** + import { Event, Emitter } from './event'; +import { isFunction, isObject } from './types'; export interface Disposable { /** @@ -24,7 +26,7 @@ export interface Disposable { export namespace Disposable { export function is(arg: unknown): arg is Disposable { - return !!arg && typeof arg === 'object' && typeof (arg as Disposable).dispose === 'function'; + return isObject(arg) && isFunction(arg.dispose); } export function create(func: () => void): Disposable { return { dispose: func }; diff --git a/packages/core/src/common/index.ts b/packages/core/src/common/index.ts index 257f263cc01bf..020585aa3e311 100644 --- a/packages/core/src/common/index.ts +++ b/packages/core/src/common/index.ts @@ -14,36 +14,36 @@ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 // ***************************************************************************** -export * from './types'; -export * from './disposable'; -export * from './reference'; -export * from './event'; +export { environment } from '@theia/application-package/lib/environment'; +export * from './application-error'; export * from './cancellation'; export * from './command'; -export * from './menu'; -export * from './selection-service'; -export * from './objects'; -export * from './os'; -export * from './resource'; +export * from './contribution-filter'; export * from './contribution-provider'; -export * from './path'; +export * from './disposable'; +export * from './event'; export * from './logger'; -export * from './messaging'; +export * from './lsp-types'; +export * from './menu'; export * from './message-rpc'; export * from './message-service'; export * from './message-service-protocol'; +export * from './messaging'; +export * from './nls'; +export * from './numbers'; +export * from './objects'; +export * from './os'; +export * from './path'; +export * from './performance'; export * from './progress-service'; export * from './progress-service-protocol'; export * from './quick-pick-service'; +export * from './reference'; +export * from './resource'; export * from './selection'; +export * from './selection-service'; export * from './strings'; -export * from './application-error'; -export * from './lsp-types'; -export * from './contribution-filter'; -export * from './nls'; -export * from './numbers'; -export * from './performance'; +export * from './types'; +export { default as URI } from './uri'; export * from './view-column'; -import { environment } from '@theia/application-package/lib/environment'; -export { environment }; diff --git a/packages/core/src/common/keybinding.ts b/packages/core/src/common/keybinding.ts index cb334bfd7bc28..cd0d373cb6bc3 100644 --- a/packages/core/src/common/keybinding.ts +++ b/packages/core/src/common/keybinding.ts @@ -13,6 +13,9 @@ // // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 // ***************************************************************************** + +import { isObject } from './types'; + /** * A Keybinding binds a specific key sequence ({@link Keybinding#keybinding}) to trigger a command ({@link Keybinding#command}). A Keybinding optionally may * define a "when clause" ({@link Keybinding#when}) to specify in which context it becomes active. @@ -102,7 +105,7 @@ export namespace Keybinding { /* Determine whether object is a KeyBinding */ export function is(arg: unknown): arg is Keybinding { - return !!arg && typeof arg === 'object' && 'command' in arg && 'keybinding' in arg; + return isObject(arg) && 'command' in arg && 'keybinding' in arg; } } @@ -118,6 +121,6 @@ export interface RawKeybinding extends Omit { export namespace RawKeybinding { export function is(candidate: unknown): candidate is RawKeybinding { - return typeof candidate === 'object' && !!candidate && 'command' in candidate && 'key' in candidate; + return isObject(candidate) && 'command' in candidate && 'key' in candidate; } } diff --git a/packages/core/src/common/keys.ts b/packages/core/src/common/keys.ts index 156c2319b3bd9..e9d33ee3a423f 100644 --- a/packages/core/src/common/keys.ts +++ b/packages/core/src/common/keys.ts @@ -15,6 +15,7 @@ // ***************************************************************************** import { isOSX } from './os'; +import { isObject } from './types'; export type KeySequence = KeyCode[]; export namespace KeySequence { @@ -489,7 +490,7 @@ export namespace SpecialCases { export namespace Key { export function isKey(arg: unknown): arg is Key { - return !!arg && typeof arg === 'object' && 'code' in arg && 'keyCode' in arg; + return isObject(arg) && 'code' in arg && 'keyCode' in arg; } export function getKey(arg: string | number): Key | undefined { diff --git a/packages/core/src/common/lsp-types.ts b/packages/core/src/common/lsp-types.ts index 7af131ef28ae9..0d7a322542fff 100644 --- a/packages/core/src/common/lsp-types.ts +++ b/packages/core/src/common/lsp-types.ts @@ -15,6 +15,7 @@ // ***************************************************************************** import { Range } from 'vscode-languageserver-protocol'; +import { isNumber, isObject, isString, isUndefined } from './types'; export interface TextDocumentContentChangeDelta { readonly range: Range; @@ -24,12 +25,10 @@ export interface TextDocumentContentChangeDelta { export namespace TextDocumentContentChangeDelta { export function is(arg: unknown): arg is TextDocumentContentChangeDelta { - const changeDelta = arg as TextDocumentContentChangeDelta; - return !!changeDelta - && typeof changeDelta === 'object' - && typeof changeDelta.text === 'string' - && (typeof changeDelta.rangeLength === 'number' || typeof changeDelta.rangeLength === 'undefined') - && Range.is(changeDelta.range); + return isObject(arg) + && isString(arg.text) + && (isNumber(arg.rangeLength) || isUndefined(arg.rangeLength)) + && Range.is(arg.range); } } diff --git a/packages/core/src/common/markdown-rendering/markdown-string.ts b/packages/core/src/common/markdown-rendering/markdown-string.ts index 5bb94a4a23553..4a938ee35d2c4 100644 --- a/packages/core/src/common/markdown-rendering/markdown-string.ts +++ b/packages/core/src/common/markdown-rendering/markdown-string.ts @@ -17,6 +17,7 @@ import { escapeRegExpCharacters } from '../strings'; import { UriComponents } from '../uri'; import { escapeIcons } from './icon-utilities'; +import { isObject, isString } from '../types'; export interface MarkdownString { readonly value: string; @@ -37,8 +38,7 @@ export namespace MarkdownString { * @returns whether the candidate satisfies the interface of a markdown string */ export function is(candidate: unknown): candidate is MarkdownString { - const maybeMarkdownString = candidate as MarkdownString; - return typeof maybeMarkdownString === 'object' && !!maybeMarkdownString && typeof maybeMarkdownString.value === 'string'; + return isObject(candidate) && isString(candidate.value); } } diff --git a/packages/core/src/common/menu/menu-types.ts b/packages/core/src/common/menu/menu-types.ts index d52eaa80a095d..82bd60733572f 100644 --- a/packages/core/src/common/menu/menu-types.ts +++ b/packages/core/src/common/menu/menu-types.ts @@ -15,6 +15,7 @@ // ***************************************************************************** import { Disposable } from '../disposable'; +import { isObject } from '../types'; export type MenuPath = string[]; export const MAIN_MENU_BAR: MenuPath = ['menubar']; @@ -86,7 +87,7 @@ export interface MenuAction extends MenuNodeRenderingData, Pick(obj: T): T { - if (!obj || typeof obj !== 'object') { + if (!isObject(obj)) { return obj; } if (obj instanceof RegExp) { @@ -24,9 +26,8 @@ export function deepClone(obj: T): T { // eslint-disable-next-line @typescript-eslint/no-explicit-any const result: any = Array.isArray(obj) ? [] : {}; Object.keys(obj).forEach((key: string) => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const prop = (obj)[key]; - if (prop && typeof prop === 'object') { + const prop = obj[key]; + if (isObject(prop)) { result[key] = deepClone(prop); } else { result[key] = prop; @@ -36,7 +37,7 @@ export function deepClone(obj: T): T { } export function deepFreeze(obj: T): T { - if (!obj || typeof obj !== 'object') { + if (!isObject(obj)) { return obj; } // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -47,7 +48,7 @@ export function deepFreeze(obj: T): T { for (const key in objectToFreeze) { if (_hasOwnProperty.call(objectToFreeze, key)) { const prop = objectToFreeze[key]; - if (typeof prop === 'object' && !Object.isFrozen(prop)) { + if (isObject(prop) && !Object.isFrozen(prop)) { stack.push(prop); } } diff --git a/packages/core/src/common/preferences/preference-schema.ts b/packages/core/src/common/preferences/preference-schema.ts index f5a41cf50faca..5231c9eab41e9 100644 --- a/packages/core/src/common/preferences/preference-schema.ts +++ b/packages/core/src/common/preferences/preference-schema.ts @@ -19,6 +19,7 @@ import { JSONValue } from '@phosphor/coreutils'; import { IJSONSchema } from '../json-schema'; import { PreferenceScope } from './preference-scope'; +import { isObject, isString } from '../types'; export interface PreferenceSchema { [name: string]: any, @@ -28,7 +29,7 @@ export interface PreferenceSchema { } export namespace PreferenceSchema { export function is(obj: unknown): obj is PreferenceSchema { - return !!obj && typeof obj === 'object' && ('properties' in obj) && PreferenceSchemaProperties.is((obj as PreferenceSchema).properties); + return isObject(obj) && PreferenceSchemaProperties.is(obj.properties); } export function getDefaultScope(schema: PreferenceSchema): PreferenceScope { let defaultScope: PreferenceScope = PreferenceScope.Workspace; @@ -46,7 +47,7 @@ export interface PreferenceSchemaProperties { } export namespace PreferenceSchemaProperties { export function is(obj: unknown): obj is PreferenceSchemaProperties { - return !!obj && typeof obj === 'object'; + return isObject(obj); } } @@ -86,7 +87,7 @@ export namespace PreferenceDataProperty { export function fromPreferenceSchemaProperty(schemaProps: PreferenceSchemaProperty, defaultScope: PreferenceScope = PreferenceScope.Workspace): PreferenceDataProperty { if (!schemaProps.scope) { schemaProps.scope = defaultScope; - } else if (typeof schemaProps.scope === 'string') { + } else if (isString(schemaProps.scope)) { return Object.assign(schemaProps, { scope: PreferenceScope.fromString(schemaProps.scope) || defaultScope }); } return schemaProps; diff --git a/packages/core/src/common/prioritizeable.ts b/packages/core/src/common/prioritizeable.ts new file mode 100644 index 0000000000000..122cc48e7d4b5 --- /dev/null +++ b/packages/core/src/common/prioritizeable.ts @@ -0,0 +1,58 @@ +// ***************************************************************************** +// Copyright (C) 2023 Ericsson and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0. +// +// This Source Code may also be made available under the following Secondary +// Licenses when the conditions for such availability set forth in the Eclipse +// Public License v. 2.0 are satisfied: GNU General Public License, version 2 +// with the GNU Classpath Exception which is available at +// https://www.gnu.org/software/classpath/license.html. +// +// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 +// ***************************************************************************** + +import type { MaybeArray, MaybePromise } from './types'; + +export interface Prioritizeable { + readonly priority: number; + readonly value: T; +} +export namespace Prioritizeable { + export type GetPriority = (value: T) => MaybePromise; + export type GetPrioritySync = (value: T) => number; + export async function toPrioritizeable(rawValue: MaybePromise, getPriority: GetPriority): Promise>; + export async function toPrioritizeable(rawValue: MaybePromise[], getPriority: GetPriority): Promise[]>; + export async function toPrioritizeable(rawValue: MaybeArray>, getPriority: GetPriority): Promise>> { + if (rawValue instanceof Array) { + return Promise.all( + rawValue.map(v => toPrioritizeable(v, getPriority)) + ); + } + const value = await rawValue; + const priority = await getPriority(value); + return { priority, value }; + } + export function toPrioritizeableSync(rawValue: T[], getPriority: GetPrioritySync): Prioritizeable[] { + return rawValue.map(v => ({ + value: v, + priority: getPriority(v) + })); + } + export function prioritizeAllSync(values: T[], getPriority: GetPrioritySync): Prioritizeable[] { + const prioritizeable = toPrioritizeableSync(values, getPriority); + return prioritizeable.filter(isValid).sort(compare); + } + export async function prioritizeAll(values: MaybePromise[], getPriority: GetPriority): Promise[]> { + const prioritizeable = await toPrioritizeable(values, getPriority); + return prioritizeable.filter(isValid).sort(compare); + } + export function isValid(p: Prioritizeable): boolean { + return p.priority > 0; + } + export function compare(p: Prioritizeable, p2: Prioritizeable): number { + return p2.priority - p.priority; + } +} diff --git a/packages/core/src/common/promise-util.ts b/packages/core/src/common/promise-util.ts index 301ac51254229..3412b3c874cc9 100644 --- a/packages/core/src/common/promise-util.ts +++ b/packages/core/src/common/promise-util.ts @@ -17,6 +17,7 @@ import { Disposable } from './disposable'; import { Event } from './event'; import { CancellationToken, CancellationError, cancelled } from './cancellation'; +import { isFunction, isObject } from './types'; /** * Simple implementation of the deferred pattern. @@ -126,5 +127,5 @@ export function waitForEvent(event: Event, ms: number, thisArg?: any, disp } export function isThenable(obj: unknown): obj is Promise { - return typeof obj === 'object' && !!obj && typeof (obj as Promise).then === 'function'; + return isObject>(obj) && isFunction(obj.then); } diff --git a/packages/core/src/common/selection.ts b/packages/core/src/common/selection.ts index 62deee5e06a5e..5b1970fccbc7b 100644 --- a/packages/core/src/common/selection.ts +++ b/packages/core/src/common/selection.ts @@ -14,6 +14,7 @@ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 // ***************************************************************************** +import { isObject } from './types'; import URI from './uri'; export interface UriSelection { @@ -23,7 +24,7 @@ export interface UriSelection { export namespace UriSelection { export function is(arg: unknown): arg is UriSelection { - return !!arg && typeof arg === 'object' && ('uri' in arg) && (arg as UriSelection).uri instanceof URI; + return isObject(arg) && arg.uri instanceof URI; } export function getUri(selection: unknown): URI | undefined { diff --git a/packages/core/src/common/types.ts b/packages/core/src/common/types.ts index a01d1c4bef275..fff7231299fce 100644 --- a/packages/core/src/common/types.ts +++ b/packages/core/src/common/types.ts @@ -14,161 +14,81 @@ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 // ***************************************************************************** -export type Mutable = { -readonly [P in keyof T]: T[P] }; -export type MaybeNull = { [P in keyof T]: T[P] | null }; -export type MaybeUndefined = { [P in keyof T]: T[P] | undefined }; - -/** - * Creates a shallow copy with all ownkeys of the original object that are `null` made `undefined` - */ -export function nullToUndefined(nullable: MaybeNull): MaybeUndefined { - const undefinable = { ...nullable } as MaybeUndefined; - for (const key in nullable) { - // eslint-disable-next-line no-null/no-null - if (nullable[key] === null && Object.prototype.hasOwnProperty.call(nullable, key)) { - undefinable[key] = undefined; - } - } - return undefinable; -} +export { ArrayUtils } from './array-utils'; +export { Prioritizeable } from './prioritizeable'; -export type Deferred = { - [P in keyof T]: Promise -}; +export type Deferred = { [P in keyof T]: Promise }; +export type MaybeArray = T | T[]; +export type MaybeNull = { [P in keyof T]: T[P] | null }; +export type MaybePromise = T | PromiseLike; +export type MaybeUndefined = { [P in keyof T]?: T[P] | undefined }; +export type Mutable = { -readonly [P in keyof T]: T[P] }; export type RecursivePartial = { - [P in keyof T]?: T[P] extends Array - ? Array> + [P in keyof T]?: T[P] extends (infer I)[] + ? RecursivePartial[] : RecursivePartial; }; -export type MaybeArray = T | T[]; -export type MaybePromise = T | PromiseLike; -export interface Prioritizeable { - readonly priority: number; - readonly value: T; -} -export namespace Prioritizeable { - export type GetPriority = (value: T) => MaybePromise; - export type GetPrioritySync = (value: T) => number; - export async function toPrioritizeable(rawValue: MaybePromise, getPriority: GetPriority): Promise>; - export async function toPrioritizeable(rawValue: MaybePromise[], getPriority: GetPriority): Promise[]>; - export async function toPrioritizeable(rawValue: MaybeArray>, getPriority: GetPriority): Promise>> { - if (rawValue instanceof Array) { - return Promise.all( - rawValue.map(v => toPrioritizeable(v, getPriority)) - ); - } - const value = await rawValue; - const priority = await getPriority(value); - return { priority, value }; - } - export function toPrioritizeableSync(rawValue: T[], getPriority: GetPrioritySync): Prioritizeable[] { - return rawValue.map(v => ({ - value: v, - priority: getPriority(v) - })); - } - export function prioritizeAllSync(values: T[], getPriority: GetPrioritySync): Prioritizeable[] { - const prioritizeable = toPrioritizeableSync(values, getPriority); - return prioritizeable.filter(isValid).sort(compare); - } - export async function prioritizeAll(values: MaybePromise[], getPriority: GetPriority): Promise[]> { - const prioritizeable = await toPrioritizeable(values, getPriority); - return prioritizeable.filter(isValid).sort(compare); - } - export function isValid(p: Prioritizeable): boolean { - return p.priority > 0; - } - export function compare(p: Prioritizeable, p2: Prioritizeable): number { - return p2.priority - p.priority; - } +export function isBoolean(value: unknown): value is boolean { + return value === true || value === false; } -export namespace ArrayUtils { - export interface Head extends Array { - head(): T; - } - - export interface Tail extends Array { - tail(): T; - } - - export interface Children extends Array { - children(): Tail - } +export function isString(value: unknown): value is string { + return typeof value === 'string' || value instanceof String; +} - export const TailImpl = { - tail(this: Array): T { - return this[this.length - 1]; - }, - }; +export function isNumber(value: unknown): value is number { + return typeof value === 'number' || value instanceof Number; +} - export const HeadAndChildrenImpl = { - head(this: Array): T { - return this[0]; - }, +export function isError(value: unknown): value is Error { + return value instanceof Error; +} - children(this: Array): Tail { - return Object.assign(this.slice(1), TailImpl); - } - }; +export function isErrorLike(value: unknown): value is Error { + return isObject(value) && isString(value.name) && isString(value.message) && (isUndefined(value.stack) || isString(value.stack)); +} - export interface HeadAndTail extends Head, Tail, Children { } +// eslint-disable-next-line space-before-function-paren +export function isFunction unknown>(value: unknown): value is T { + return typeof value === 'function'; +} - export function asTail(array: Array): Tail { - return Object.assign(array, TailImpl); - } +export function isObject>(value: unknown): value is T { + // eslint-disable-next-line no-null/no-null + return typeof value === 'object' && value !== null; +} - export function asHeadAndTail(array: Array): HeadAndTail { - return Object.assign(array, HeadAndChildrenImpl, TailImpl); - } +export function isUndefined(value: unknown): value is undefined { + return typeof value === 'undefined'; +} - export enum Sort { - LeftBeforeRight = -1, - RightBeforeLeft = 1, - Equal = 0, - } +/** + * @param value value to check. + * @param every optional predicate ran on every element of the array. + * @param thisArg value to substitute `this` with when invoking in the predicate. + * @returns whether or not `value` is an array. + */ +export function isArray(value: unknown, every?: (value: unknown) => unknown, thisArg?: unknown): value is T[] { + return Array.isArray(value) && (!isFunction(every) || value.every(every, thisArg)); +} - // Copied from https://github.com/microsoft/vscode/blob/9c29becfad5f68270b9b23efeafb147722c5feba/src/vs/base/common/arrays.ts - /** - * Performs a binary search algorithm over a sorted collection. Useful for cases - * when we need to perform a binary search over something that isn't actually an - * array, and converting data to an array would defeat the use of binary search - * in the first place. - * - * @param length The collection length. - * @param compareToKey A function that takes an index of an element in the - * collection and returns zero if the value at this index is equal to the - * search key, a negative number if the value precedes the search key in the - * sorting order, or a positive number if the search key precedes the value. - * @return A non-negative index of an element, if found. If not found, the - * result is -(n+1) (or ~n, using bitwise notation), where n is the index - * where the key should be inserted to maintain the sorting order. - */ - export function binarySearch2(length: number, compareToKey: (index: number) => number): number { - let low = 0; - let high = length - 1; +export function isStringArray(value: unknown): value is string[] { + return isArray(value, isString); +} - while (low <= high) { - const mid = ((low + high) / 2) | 0; - const comp = compareToKey(mid); - if (comp < 0) { - low = mid + 1; - } else if (comp > 0) { - high = mid - 1; - } else { - return mid; - } +/** + * Creates a shallow copy with all ownkeys of the original object that are `null` made `undefined` + */ +export function nullToUndefined(nullable: MaybeNull): MaybeUndefined { + const undefinable = { ...nullable } as MaybeUndefined; + for (const key in nullable) { + // eslint-disable-next-line no-null/no-null + if (nullable[key] === null && Object.prototype.hasOwnProperty.call(nullable, key)) { + undefinable[key] = undefined; } - return -(low + 1); - } - - /** - * @returns New array with all falsy values removed. The original array IS NOT modified. - */ - export function coalesce(array: ReadonlyArray): T[] { - return array.filter(e => !!e); } + return undefinable; } /** diff --git a/packages/core/src/common/uri-command-handler.ts b/packages/core/src/common/uri-command-handler.ts index a15967600d7c5..a3e23eb4e1fca 100644 --- a/packages/core/src/common/uri-command-handler.ts +++ b/packages/core/src/common/uri-command-handler.ts @@ -20,7 +20,7 @@ import { SelectionService } from '../common/selection-service'; import { UriSelection } from '../common/selection'; import { CommandHandler } from './command'; import URI from './uri'; -import { MaybeArray } from './types'; +import { isArray, MaybeArray } from './types'; export interface UriCommandHandler> extends CommandHandler { @@ -59,7 +59,7 @@ export class UriAwareCommandHandler> implements UriCom protected getUri(...args: any[]): T | undefined { const [maybeUriArray] = args; const firstArgIsOK = this.isMulti() - ? Array.isArray(maybeUriArray) && maybeUriArray.every(uri => uri instanceof URI) + ? isArray(maybeUriArray, uri => uri instanceof URI) : maybeUriArray instanceof URI; if (firstArgIsOK) { diff --git a/packages/core/src/electron-node/token/electron-token-validator.ts b/packages/core/src/electron-node/token/electron-token-validator.ts index 6b58d4b009472..39004a92c9858 100644 --- a/packages/core/src/electron-node/token/electron-token-validator.ts +++ b/packages/core/src/electron-node/token/electron-token-validator.ts @@ -18,7 +18,7 @@ import * as http from 'http'; import * as cookie from 'cookie'; import * as crypto from 'crypto'; import { injectable, postConstruct } from 'inversify'; -import { MaybePromise } from '../../common'; +import { isObject, isString, MaybePromise } from '../../common'; import { ElectronSecurityToken } from '../../electron-common/electron-token'; import { WsRequestValidatorContribution } from '../../node/ws-request-validators'; @@ -44,9 +44,9 @@ export class ElectronTokenValidator implements WsRequestValidatorContribution { */ allowRequest(request: http.IncomingMessage): boolean { const cookieHeader = request.headers.cookie; - if (typeof cookieHeader === 'string') { + if (isString(cookieHeader)) { const token = cookie.parse(cookieHeader)[ElectronSecurityToken]; - if (typeof token === 'string') { + if (isString(token)) { return this.isTokenValid(JSON.parse(token)); } } @@ -60,10 +60,8 @@ export class ElectronTokenValidator implements WsRequestValidatorContribution { * * @param token Parsed object sent by the client as the token. */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - isTokenValid(token: any): boolean { - // eslint-disable-next-line no-null/no-null - if (typeof token === 'object' && token !== null && typeof token.value === 'string') { + isTokenValid(token: unknown): boolean { + if (isObject(token) && isString(token.value)) { try { const received = Buffer.from(token.value, 'utf8'); const expected = Buffer.from(this.electronSecurityToken!.value, 'utf8'); diff --git a/packages/core/src/node/i18n/localization-contribution.ts b/packages/core/src/node/i18n/localization-contribution.ts index 078fb30970c09..9610d5c7a2786 100644 --- a/packages/core/src/node/i18n/localization-contribution.ts +++ b/packages/core/src/node/i18n/localization-contribution.ts @@ -16,7 +16,7 @@ import * as fs from 'fs-extra'; import { inject, injectable, named } from 'inversify'; -import { ContributionProvider } from '../../common'; +import { ContributionProvider, isObject } from '../../common'; import { LanguageInfo, Localization } from '../../common/i18n/localization'; import { LocalizationProvider } from './localization-provider'; @@ -79,12 +79,12 @@ export class LocalizationRegistry { } protected flattenTranslations(localization: unknown): Record { - if (typeof localization === 'object' && localization) { + if (isObject(localization)) { const record: Record = {}; for (const [key, value] of Object.entries(localization)) { if (typeof value === 'string') { record[key] = value; - } else if (value && typeof value === 'object') { + } else if (isObject(value)) { const flattened = this.flattenTranslations(value); for (const [flatKey, flatValue] of Object.entries(flattened)) { record[`${key}/${flatKey}`] = flatValue; diff --git a/packages/debug/src/browser/breakpoint/breakpoint-marker.ts b/packages/debug/src/browser/breakpoint/breakpoint-marker.ts index 74e45500b9a43..f783a6d6a2deb 100644 --- a/packages/debug/src/browser/breakpoint/breakpoint-marker.ts +++ b/packages/debug/src/browser/breakpoint/breakpoint-marker.ts @@ -15,9 +15,9 @@ // ***************************************************************************** import { UUID } from '@theia/core/shared/@phosphor/coreutils'; -import URI from '@theia/core/lib/common/uri'; import { Marker } from '@theia/markers/lib/common/marker'; import { DebugProtocol } from '@vscode/debugprotocol/lib/debugProtocol'; +import { isObject, isString, URI } from '@theia/core/lib/common'; export const BREAKPOINT_KIND = 'breakpoint'; @@ -96,8 +96,7 @@ export namespace InstructionBreakpoint { }; } - export function is(thing: BaseBreakpoint): thing is InstructionBreakpoint { - const candidate = thing as InstructionBreakpoint; - return 'instructionReference' in candidate && typeof candidate.instructionReference === 'string'; + export function is(arg: BaseBreakpoint): arg is InstructionBreakpoint { + return isObject(arg) && isString(arg.instructionReference); } } diff --git a/packages/debug/src/browser/debug-configuration-model.ts b/packages/debug/src/browser/debug-configuration-model.ts index 90260b3aedf6f..2d5ae56d10ad3 100644 --- a/packages/debug/src/browser/debug-configuration-model.ts +++ b/packages/debug/src/browser/debug-configuration-model.ts @@ -20,6 +20,7 @@ import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposa import { DebugConfiguration } from '../common/debug-common'; import { PreferenceService } from '@theia/core/lib/browser/preferences/preference-service'; import { DebugCompound } from '../common/debug-compound'; +import { isObject } from '@theia/core/lib/common'; export class DebugConfigurationModel implements Disposable { @@ -71,7 +72,7 @@ export class DebugConfigurationModel implements Disposable { const configurations: DebugConfiguration[] = []; // eslint-disable-next-line @typescript-eslint/no-explicit-any const { configUri, value } = this.preferences.resolve('launch', undefined, this.workspaceFolderUri); - if (value && typeof value === 'object' && Array.isArray(value.configurations)) { + if (isObject(value) && Array.isArray(value.configurations)) { for (const configuration of value.configurations) { if (DebugConfiguration.is(configuration)) { configurations.push(configuration); @@ -79,7 +80,7 @@ export class DebugConfigurationModel implements Disposable { } } const compounds: DebugCompound[] = []; - if (value && typeof value === 'object' && Array.isArray(value.compounds)) { + if (isObject(value) && Array.isArray(value.compounds)) { for (const compound of value.compounds) { if (DebugCompound.is(compound)) { compounds.push(compound); diff --git a/packages/debug/src/browser/editor/debug-editor-model.ts b/packages/debug/src/browser/editor/debug-editor-model.ts index 8e5aef084f77a..3c3802d2caf36 100644 --- a/packages/debug/src/browser/editor/debug-editor-model.ts +++ b/packages/debug/src/browser/editor/debug-editor-model.ts @@ -20,6 +20,7 @@ import * as monaco from '@theia/monaco-editor-core'; import { IConfigurationService } from '@theia/monaco-editor-core/esm/vs/platform/configuration/common/configuration'; import { StandaloneCodeEditor } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneCodeEditor'; import { IDecorationOptions } from '@theia/monaco-editor-core/esm/vs/editor/common/editorCommon'; +import { IEditorHoverOptions } from '@theia/monaco-editor-core/esm/vs/editor/common/config/editorOptions'; import URI from '@theia/core/lib/common/uri'; import { Disposable, DisposableCollection, MenuPath, isOSX } from '@theia/core'; import { ContextMenuRenderer } from '@theia/core/lib/browser'; @@ -146,7 +147,7 @@ export class DebugEditorModel implements Disposable { resource: model.uri, overrideIdentifier: model.getLanguageId(), }; - const { enabled, delay, sticky } = this.configurationService.getValue('editor.hover', overrides); + const { enabled, delay, sticky } = this.configurationService.getValue('editor.hover', overrides); codeEditor.updateOptions({ hover: { enabled, diff --git a/packages/debug/src/common/debug-compound.ts b/packages/debug/src/common/debug-compound.ts index d019e69b9a142..01332a0fc9514 100644 --- a/packages/debug/src/common/debug-compound.ts +++ b/packages/debug/src/common/debug-compound.ts @@ -14,6 +14,7 @@ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 // ***************************************************************************** +import { isObject } from '@theia/core/lib/common'; import { TaskIdentifier } from '@theia/task/lib/common'; export const defaultCompound: DebugCompound = { name: 'Compound', configurations: [] }; @@ -27,6 +28,6 @@ export interface DebugCompound { export namespace DebugCompound { export function is(arg: unknown): arg is DebugCompound { - return !!arg && typeof arg === 'object' && 'name' in arg && 'configurations' in arg; + return isObject(arg) && 'name' in arg && 'configurations' in arg; } } diff --git a/packages/debug/src/common/debug-configuration.ts b/packages/debug/src/common/debug-configuration.ts index d0ecc48d1e4d3..0f11aa7fda064 100644 --- a/packages/debug/src/common/debug-configuration.ts +++ b/packages/debug/src/common/debug-configuration.ts @@ -13,6 +13,7 @@ // // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 // ***************************************************************************** +import { isObject } from '@theia/core/lib/common'; import { TaskIdentifier } from '@theia/task/lib/common'; /* eslint-disable @typescript-eslint/no-explicit-any */ @@ -74,7 +75,7 @@ export interface DebugConfiguration { } export namespace DebugConfiguration { export function is(arg: unknown): arg is DebugConfiguration { - return !!arg && typeof arg === 'object' && 'type' in arg && 'name' in arg && 'request' in arg; + return isObject(arg) && 'type' in arg && 'name' in arg && 'request' in arg; } } diff --git a/packages/debug/src/node/debug-adapter-factory.ts b/packages/debug/src/node/debug-adapter-factory.ts index dd0b0b9026e3f..b4d15c0805ad8 100644 --- a/packages/debug/src/node/debug-adapter-factory.ts +++ b/packages/debug/src/node/debug-adapter-factory.ts @@ -41,6 +41,7 @@ import { import { DebugAdapterSessionImpl } from '../common/debug-adapter-session'; import { environment } from '@theia/core/shared/@theia/application-package'; import { ProcessDebugAdapter, SocketDebugAdapter } from './stream-debug-adapter'; +import { isObject } from '@theia/core/lib/common'; /** * [DebugAdapterFactory](#DebugAdapterFactory) implementation based on @@ -67,7 +68,7 @@ export class LaunchBasedDebugAdapterFactory implements DebugAdapterFactory { private childProcess(executable: DebugAdapterExecutable): RawProcess { const isForkOptions = (forkOptions: unknown): forkOptions is RawForkOptions => - !!forkOptions && typeof forkOptions === 'object' && 'modulePath' in forkOptions; + isObject(forkOptions) && 'modulePath' in forkOptions; const processOptions: RawProcessOptions | RawForkOptions = { ...executable }; const options: { stdio: (string | number)[], env?: object, execArgv?: string[] } = { stdio: ['pipe', 'pipe', 2] }; diff --git a/packages/editor/src/browser/editor.ts b/packages/editor/src/browser/editor.ts index ce0a2ce198257..2326e52b583b3 100644 --- a/packages/editor/src/browser/editor.ts +++ b/packages/editor/src/browser/editor.ts @@ -17,7 +17,7 @@ import { Position, Range, Location } from '@theia/core/shared/vscode-languageserver-protocol'; import * as lsp from '@theia/core/shared/vscode-languageserver-protocol'; import URI from '@theia/core/lib/common/uri'; -import { Event, Disposable, TextDocumentContentChangeDelta, Reference } from '@theia/core/lib/common'; +import { Event, Disposable, TextDocumentContentChangeDelta, Reference, isObject } from '@theia/core/lib/common'; import { Saveable, Navigatable, Widget } from '@theia/core/lib/browser'; import { EditorDecoration } from './decorations/editor-decoration'; @@ -330,8 +330,8 @@ export interface ReplaceOperation { } export namespace TextEditorSelection { - export function is(e: unknown): e is TextEditorSelection { - return !!e && typeof e === 'object' && (e as TextEditorSelection).uri instanceof URI; + export function is(arg: unknown): arg is TextEditorSelection { + return isObject(arg) && arg.uri instanceof URI; } } diff --git a/packages/filesystem/src/browser/file-selection.ts b/packages/filesystem/src/browser/file-selection.ts index 81aefa5942992..fa98c3ef80086 100644 --- a/packages/filesystem/src/browser/file-selection.ts +++ b/packages/filesystem/src/browser/file-selection.ts @@ -16,6 +16,7 @@ import { SelectionService } from '@theia/core/lib/common/selection-service'; import { SelectionCommandHandler } from '@theia/core/lib/common/selection-command-handler'; +import { isObject } from '@theia/core/lib/common'; import { FileStat } from '../common/files'; export interface FileSelection { @@ -23,7 +24,7 @@ export interface FileSelection { } export namespace FileSelection { export function is(arg: unknown): arg is FileSelection { - return !!arg && typeof arg === 'object' && ('fileStat' in arg) && FileStat.is((arg as FileSelection).fileStat); + return isObject(arg) && FileStat.is(arg.fileStat); } export class CommandHandler extends SelectionCommandHandler { diff --git a/packages/filesystem/src/browser/file-tree/file-tree.ts b/packages/filesystem/src/browser/file-tree/file-tree.ts index 733ec2766e9c4..f6401fc4320bf 100644 --- a/packages/filesystem/src/browser/file-tree/file-tree.ts +++ b/packages/filesystem/src/browser/file-tree/file-tree.ts @@ -16,8 +16,8 @@ import { injectable, inject } from '@theia/core/shared/inversify'; import URI from '@theia/core/lib/common/uri'; +import { isObject, Mutable } from '@theia/core/lib/common'; import { TreeNode, CompositeTreeNode, SelectableTreeNode, ExpandableTreeNode, TreeImpl } from '@theia/core/lib/browser'; -import { Mutable } from '@theia/core/lib/common/types'; import { FileStat, Stat, FileType, FileOperationError, FileOperationResult } from '../../common/files'; import { UriSelection } from '@theia/core/lib/common/selection'; import { MessageService } from '@theia/core/lib/common/message-service'; @@ -102,7 +102,7 @@ export interface FileStatNode extends SelectableTreeNode, Mutable, } export namespace FileStatNode { export function is(node: unknown): node is FileStatNode { - return !!node && typeof node === 'object' && 'fileStat' in node; + return isObject(node) && 'fileStat' in node; } export function getUri(node: TreeNode | undefined): string | undefined { @@ -120,7 +120,7 @@ export type FileStatNodeData = Omit & { }; export namespace FileStatNodeData { export function is(node: unknown): node is FileStatNodeData { - return !!node && typeof node === 'object' && 'uri' in node && ('fileStat' in node || 'stat' in node); + return isObject(node) && 'uri' in node && ('fileStat' in node || 'stat' in node); } } diff --git a/packages/filesystem/src/common/download/file-download-data.ts b/packages/filesystem/src/common/download/file-download-data.ts index 048e8da47bce9..90ac52104a4b1 100644 --- a/packages/filesystem/src/common/download/file-download-data.ts +++ b/packages/filesystem/src/common/download/file-download-data.ts @@ -14,12 +14,14 @@ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 // ***************************************************************************** +import { isObject } from '@theia/core/lib/common'; + export interface FileDownloadData { readonly uris: string[]; } export namespace FileDownloadData { export function is(arg: unknown): arg is FileDownloadData { - return !!arg && typeof arg === 'object' && 'uris' in arg; + return isObject(arg) && 'uris' in arg; } } diff --git a/packages/filesystem/src/common/files.ts b/packages/filesystem/src/common/files.ts index 94d1dc781ead0..cd7a5eef98d42 100644 --- a/packages/filesystem/src/common/files.ts +++ b/packages/filesystem/src/common/files.ts @@ -26,6 +26,7 @@ import { BinaryBuffer, BinaryBufferReadableStream } from '@theia/core/lib/common import type { TextDocumentContentChangeEvent } from '@theia/core/shared/vscode-languageserver-protocol'; import { ReadableStreamEvents } from '@theia/core/lib/common/stream'; import { CancellationToken } from '@theia/core/lib/common/cancellation'; +import { isObject } from '@theia/core/lib/common'; export const enum FileOperation { CREATE, @@ -201,9 +202,9 @@ export interface BaseStat { } export namespace BaseStat { export function is(arg: unknown): arg is BaseStat { - return !!arg && typeof arg === 'object' - && ('resource' in arg && (arg as BaseStat).resource instanceof URI) - && ('name' in arg && typeof (arg as BaseStat).name === 'string'); + return isObject(arg) + && arg.resource instanceof URI + && typeof arg.name === 'string'; } } diff --git a/packages/git/src/common/git-model.ts b/packages/git/src/common/git-model.ts index ebf326195e848..93230226a2261 100644 --- a/packages/git/src/common/git-model.ts +++ b/packages/git/src/common/git-model.ts @@ -15,8 +15,7 @@ // ***************************************************************************** import URI from '@theia/core/lib/common/uri'; -import { Path } from '@theia/core'; -import { nls } from '@theia/core/lib/common/nls'; +import { Path, nls, isObject } from '@theia/core'; export interface WorkingDirectoryStatus { @@ -217,7 +216,7 @@ export namespace Repository { return repository === repository2; } export function is(repository: unknown): repository is Repository { - return !!repository && typeof repository === 'object' && 'localUri' in repository; + return isObject(repository) && 'localUri' in repository; } export function relativePath(repository: Repository | URI, uri: URI | string): Path | undefined { const repositoryUri = new URI(Repository.is(repository) ? repository.localUri : String(repository)); diff --git a/packages/git/src/common/git-watcher.ts b/packages/git/src/common/git-watcher.ts index a078e376f3cb3..05f3111238200 100644 --- a/packages/git/src/common/git-watcher.ts +++ b/packages/git/src/common/git-watcher.ts @@ -15,7 +15,7 @@ // ***************************************************************************** import { injectable, inject } from '@theia/core/shared/inversify'; -import { JsonRpcServer, JsonRpcProxy } from '@theia/core'; +import { JsonRpcServer, JsonRpcProxy, isObject } from '@theia/core'; import { Repository, WorkingDirectoryStatus } from './git-model'; import { Disposable, DisposableCollection, Emitter, Event } from '@theia/core/lib/common'; @@ -48,7 +48,7 @@ export namespace GitStatusChangeEvent { * @param event the argument to check whether it is a Git status change event or not. */ export function is(event: unknown): event is GitStatusChangeEvent { - return !!event && typeof event === 'object' && ('source' in event) && ('status' in event); + return isObject(event) && ('source' in event) && ('status' in event); } } diff --git a/packages/git/src/common/git.ts b/packages/git/src/common/git.ts index 9ed52916f2b35..80335bfa5e527 100644 --- a/packages/git/src/common/git.ts +++ b/packages/git/src/common/git.ts @@ -15,7 +15,7 @@ // ***************************************************************************** import { ChildProcess } from 'child_process'; -import { Disposable } from '@theia/core'; +import { Disposable, isObject } from '@theia/core'; import { Repository, WorkingDirectoryStatus, Branch, GitResult, GitError, GitFileStatus, GitFileChange, CommitWithChanges, GitFileBlame, Remote as RemoteModel, StashEntry @@ -854,42 +854,42 @@ export namespace GitUtils { * `true` if the argument is an option for renaming an existing branch in the repository. */ export function isBranchRename(arg: unknown): arg is Git.Options.BranchCommand.Rename { - return !!arg && typeof arg === 'object' && ('newName' in arg); + return isObject(arg) && 'newName' in arg; } /** * `true` if the argument is an option for deleting an existing branch in the repository. */ export function isBranchDelete(arg: unknown): arg is Git.Options.BranchCommand.Delete { - return !!arg && typeof arg === 'object' && ('toDelete' in arg); + return isObject(arg) && 'toDelete' in arg; } /** * `true` if the argument is an option for creating a new branch in the repository. */ export function isBranchCreate(arg: unknown): arg is Git.Options.BranchCommand.Create { - return !!arg && typeof arg === 'object' && ('toCreate' in arg); + return isObject(arg) && 'toCreate' in arg; } /** * `true` if the argument is an option for listing the branches in a repository. */ export function isBranchList(arg: unknown): arg is Git.Options.BranchCommand.List { - return !!arg && typeof arg === 'object' && ('type' in arg); + return isObject(arg) && 'type' in arg; } /** * `true` if the argument is an option for checking out a new local branch. */ export function isBranchCheckout(arg: unknown): arg is Git.Options.Checkout.CheckoutBranch { - return !!arg && typeof arg === 'object' && ('branch' in arg); + return isObject(arg) && 'branch' in arg; } /** * `true` if the argument is an option for checking out a working tree file. */ export function isWorkingTreeFileCheckout(arg: unknown): arg is Git.Options.Checkout.WorkingTreeFile { - return !!arg && typeof arg === 'object' && ('paths' in arg); + return isObject(arg) && 'paths' in arg; } /** diff --git a/packages/markers/src/browser/problem/problem-selection.ts b/packages/markers/src/browser/problem/problem-selection.ts index 39552b170de0a..0651e5e0fb682 100644 --- a/packages/markers/src/browser/problem/problem-selection.ts +++ b/packages/markers/src/browser/problem/problem-selection.ts @@ -16,6 +16,7 @@ import { SelectionService } from '@theia/core/lib/common/selection-service'; import { SelectionCommandHandler } from '@theia/core/lib/common/selection-command-handler'; +import { isObject } from '@theia/core/lib/common'; import { Marker } from '../../common/marker'; import { ProblemMarker } from '../../common/problem-marker'; @@ -24,7 +25,7 @@ export interface ProblemSelection { } export namespace ProblemSelection { export function is(arg: unknown): arg is ProblemSelection { - return !!arg && typeof arg === 'object' && ('marker' in arg) && ProblemMarker.is((arg as ProblemSelection).marker); + return isObject(arg) && ProblemMarker.is(arg.marker); } export class CommandHandler extends SelectionCommandHandler { diff --git a/packages/monaco/src/browser/monaco-indexed-db.ts b/packages/monaco/src/browser/monaco-indexed-db.ts index 2e29b183b10f4..265df6f1228dc 100644 --- a/packages/monaco/src/browser/monaco-indexed-db.ts +++ b/packages/monaco/src/browser/monaco-indexed-db.ts @@ -21,7 +21,7 @@ import * as monaco from '@theia/monaco-editor-core'; import { injectable } from '@theia/core/shared/inversify'; import type { ThemeMix } from './textmate/monaco-theme-types'; import { Theme } from '@theia/core/lib/common/theme'; -import { Emitter, Event } from '@theia/core'; +import { Emitter, Event, isObject } from '@theia/core'; let _monacoDB: Promise | undefined; if ('indexedDB' in window) { @@ -45,7 +45,7 @@ export interface MonacoThemeState { } export namespace MonacoThemeState { export function is(state: unknown): state is MonacoThemeState { - return !!state && typeof state === 'object' && 'id' in state && 'label' in state && 'uiTheme' in state && 'data' in state; + return isObject(state) && 'id' in state && 'label' in state && 'uiTheme' in state && 'data' in state; } } diff --git a/packages/monaco/src/browser/monaco-snippet-suggest-provider.ts b/packages/monaco/src/browser/monaco-snippet-suggest-provider.ts index 6215a39aebaa7..1e4846010ae3e 100644 --- a/packages/monaco/src/browser/monaco-snippet-suggest-provider.ts +++ b/packages/monaco/src/browser/monaco-snippet-suggest-provider.ts @@ -26,6 +26,7 @@ import { FileService } from '@theia/filesystem/lib/browser/file-service'; import { FileOperationError } from '@theia/filesystem/lib/common/files'; import * as monaco from '@theia/monaco-editor-core'; import { SnippetParser } from '@theia/monaco-editor-core/esm/vs/editor/contrib/snippet/browser/snippetParser'; +import { isObject } from '@theia/core/lib/common'; @injectable() export class MonacoSnippetSuggestProvider implements monaco.languages.CompletionItemProvider { @@ -191,15 +192,11 @@ export class MonacoSnippetSuggestProvider implements monaco.languages.Completion return toDispose; } protected parseSnippets(snippets: JsonSerializedSnippets | undefined, accept: (name: string, snippet: JsonSerializedSnippet) => void): void { - if (typeof snippets === 'object') { - // eslint-disable-next-line guard-for-in - for (const name in snippets) { - const scopeOrTemplate = snippets[name]; - if (JsonSerializedSnippet.is(scopeOrTemplate)) { - accept(name, scopeOrTemplate); - } else { - this.parseSnippets(scopeOrTemplate, accept); - } + for (const [name, scopeOrTemplate] of Object.entries(snippets ?? {})) { + if (JsonSerializedSnippet.is(scopeOrTemplate)) { + accept(name, scopeOrTemplate); + } else { + this.parseSnippets(scopeOrTemplate, accept); } } } @@ -250,7 +247,7 @@ export interface JsonSerializedSnippet { } export namespace JsonSerializedSnippet { export function is(obj: unknown): obj is JsonSerializedSnippet { - return !!obj && typeof obj === 'object' && 'body' in obj && 'prefix' in obj; + return isObject(obj) && 'body' in obj && 'prefix' in obj; } } diff --git a/packages/monaco/src/browser/monaco-workspace.ts b/packages/monaco/src/browser/monaco-workspace.ts index cc10ba029cce2..dd5fbecb7bc0a 100644 --- a/packages/monaco/src/browser/monaco-workspace.ts +++ b/packages/monaco/src/browser/monaco-workspace.ts @@ -26,7 +26,6 @@ import { MonacoTextModelService } from './monaco-text-model-service'; import { WillSaveMonacoModelEvent, MonacoEditorModel, MonacoModelContentChangedEvent } from './monaco-editor-model'; import { MonacoEditor } from './monaco-editor'; import { ProblemManager } from '@theia/markers/lib/browser'; -import { MaybePromise } from '@theia/core/lib/common/types'; import { FileService } from '@theia/filesystem/lib/browser/file-service'; import { FileSystemProviderCapabilities } from '@theia/filesystem/lib/common/files'; import * as monaco from '@theia/monaco-editor-core'; @@ -37,6 +36,7 @@ import { import { IEditorWorkerService } from '@theia/monaco-editor-core/esm/vs/editor/common/services/editorWorker'; import { StandaloneServices } from '@theia/monaco-editor-core/esm/vs/editor/standalone/browser/standaloneServices'; import { EndOfLineSequence } from '@theia/monaco-editor-core/esm/vs/editor/common/model'; +import { isObject, MaybePromise } from '@theia/core/lib/common'; export namespace WorkspaceFileEdit { export function is(arg: Edit): arg is monaco.languages.IWorkspaceFileEdit { @@ -47,12 +47,9 @@ export namespace WorkspaceFileEdit { export namespace WorkspaceTextEdit { export function is(arg: Edit): arg is monaco.languages.IWorkspaceTextEdit { - return !!arg && typeof arg === 'object' - && 'resource' in arg + return isObject(arg) && monaco.Uri.isUri(arg.resource) - && 'textEdit' in arg - && arg.textEdit !== null - && typeof arg.textEdit === 'object'; + && isObject(arg.textEdit); } } @@ -60,8 +57,7 @@ export type Edit = monaco.languages.IWorkspaceFileEdit | monaco.languages.IWorks export namespace ResourceFileEdit { export function is(arg: ResourceEdit): arg is MonacoResourceFileEdit { - return typeof arg === 'object' && (('oldResource' in arg) && monaco.Uri.isUri((arg as MonacoResourceFileEdit).oldResource)) || - ('newResource' in arg && monaco.Uri.isUri((arg as MonacoResourceFileEdit).newResource)); + return isObject(arg) && (monaco.Uri.isUri(arg.oldResource) || monaco.Uri.isUri(arg.newResource)); } } diff --git a/packages/outline-view/src/browser/outline-view-widget.tsx b/packages/outline-view/src/browser/outline-view-widget.tsx index f3b6561a90ae8..a88567877fabe 100644 --- a/packages/outline-view/src/browser/outline-view-widget.tsx +++ b/packages/outline-view/src/browser/outline-view-widget.tsx @@ -29,7 +29,7 @@ import { } from '@theia/core/lib/browser'; import { OutlineViewTreeModel } from './outline-view-tree-model'; import { Message } from '@theia/core/shared/@phosphor/messaging'; -import { Emitter, Mutable, UriSelection } from '@theia/core'; +import { Emitter, isObject, Mutable, UriSelection } from '@theia/core'; import * as React from '@theia/core/shared/react'; import { Range } from '@theia/core/shared/vscode-languageserver-protocol'; import URI from '@theia/core/lib/common/uri'; @@ -64,7 +64,7 @@ export namespace OutlineSymbolInformationNode { } export function hasRange(node: unknown): node is { range: Range } { - return typeof node === 'object' && !!node && 'range' in node && Range.is((node as { range: Range }).range); + return isObject<{ range: Range }>(node) && Range.is(node.range); } } diff --git a/packages/plugin-ext/src/common/plugin-api-rpc.ts b/packages/plugin-ext/src/common/plugin-api-rpc.ts index 02e68b2b8f7f8..ea99203293ff6 100644 --- a/packages/plugin-ext/src/common/plugin-api-rpc.ts +++ b/packages/plugin-ext/src/common/plugin-api-rpc.ts @@ -110,7 +110,7 @@ import type { import { SerializableEnvironmentVariableCollection } from '@theia/terminal/lib/common/base-terminal-protocol'; import { ThemeType } from '@theia/core/lib/common/theme'; import { Disposable } from '@theia/core/lib/common/disposable'; -import { PickOptions, QuickInputButtonHandle } from '@theia/core/lib/common'; +import { isObject, PickOptions, QuickInputButtonHandle } from '@theia/core/lib/common'; import { Severity } from '@theia/core/lib/common/severity'; import { DebugConfiguration, DebugSessionOptions } from '@theia/debug/lib/common/debug-configuration'; @@ -780,7 +780,7 @@ export interface TreeViewSelection { } export namespace TreeViewSelection { export function is(arg: unknown): arg is TreeViewSelection { - return !!arg && typeof arg === 'object' && 'treeViewId' in arg && 'treeItemId' in arg; + return isObject(arg) && 'treeViewId' in arg && 'treeItemId' in arg; } } @@ -826,7 +826,7 @@ export interface ScmCommandArg { } export namespace ScmCommandArg { export function is(arg: unknown): arg is ScmCommandArg { - return !!arg && typeof arg === 'object' && 'sourceControlHandle' in arg; + return isObject(arg) && 'sourceControlHandle' in arg; } } @@ -842,7 +842,7 @@ export interface ScmExt { export namespace TimelineCommandArg { export function is(arg: unknown): arg is TimelineCommandArg { - return !!arg && typeof arg === 'object' && 'timelineHandle' in arg; + return isObject(arg) && 'timelineHandle' in arg; } } export interface TimelineCommandArg { @@ -861,7 +861,7 @@ export interface DecorationReply { [id: number]: DecorationData; } export namespace CommentsCommandArg { export function is(arg: unknown): arg is CommentsCommandArg { - return !!arg && typeof arg === 'object' && 'commentControlHandle' in arg && 'commentThreadHandle' in arg && 'text' in arg && !('commentUniqueId' in arg); + return isObject(arg) && 'commentControlHandle' in arg && 'commentThreadHandle' in arg && 'text' in arg && !('commentUniqueId' in arg); } } export interface CommentsCommandArg { @@ -872,7 +872,7 @@ export interface CommentsCommandArg { export namespace CommentsContextCommandArg { export function is(arg: unknown): arg is CommentsContextCommandArg { - return !!arg && typeof arg === 'object' && 'commentControlHandle' in arg && 'commentThreadHandle' in arg && 'commentUniqueId' in arg && !('text' in arg); + return isObject(arg) && 'commentControlHandle' in arg && 'commentThreadHandle' in arg && 'commentUniqueId' in arg && !('text' in arg); } } export interface CommentsContextCommandArg { @@ -883,7 +883,7 @@ export interface CommentsContextCommandArg { export namespace CommentsEditCommandArg { export function is(arg: unknown): arg is CommentsEditCommandArg { - return !!arg && typeof arg === 'object' && 'commentControlHandle' in arg && 'commentThreadHandle' in arg && 'commentUniqueId' in arg && 'text' in arg; + return isObject(arg) && 'commentControlHandle' in arg && 'commentThreadHandle' in arg && 'commentUniqueId' in arg && 'text' in arg; } } export interface CommentsEditCommandArg { diff --git a/packages/plugin-ext/src/common/rpc-protocol.ts b/packages/plugin-ext/src/common/rpc-protocol.ts index dd47b8f5ae550..b05c107d805af 100644 --- a/packages/plugin-ext/src/common/rpc-protocol.ts +++ b/packages/plugin-ext/src/common/rpc-protocol.ts @@ -22,7 +22,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { Channel, Disposable, DisposableCollection, ReadBuffer, WriteBuffer } from '@theia/core'; +import { Channel, Disposable, DisposableCollection, isObject, ReadBuffer, URI, WriteBuffer } from '@theia/core'; import { Emitter, Event } from '@theia/core/lib/common/event'; import { ChannelMultiplexer, MessageProvider } from '@theia/core/lib/common/message-rpc/channel'; import { MsgPackMessageDecoder, MsgPackMessageEncoder } from '@theia/core/lib/common/message-rpc/rpc-message-encoder'; @@ -30,7 +30,6 @@ import { Uint8ArrayReadBuffer, Uint8ArrayWriteBuffer } from '@theia/core/lib/com import { ClientProxyHandler, RpcInvocationHandler } from './proxy-handler'; import { MsgPackExtensionManager } from '@theia/core/lib/common/message-rpc/msg-pack-extension-manager'; import { URI as VSCodeURI } from '@theia/core/shared/vscode-uri'; -import URI from '@theia/core/lib/common/uri'; import { BinaryBuffer } from '@theia/core/lib/common/buffer'; import { Range, Position } from '../plugin/types-impl'; @@ -74,7 +73,7 @@ export namespace ConnectionClosedError { return Object.assign(new Error(message), { code }); } export function is(error: unknown): error is ConnectionClosedError { - return !!error && typeof error === 'object' && 'code' in error && (error as ConnectionClosedError).code === code; + return isObject(error) && 'code' in error && (error as ConnectionClosedError).code === code; } } diff --git a/packages/plugin-ext/src/common/types.ts b/packages/plugin-ext/src/common/types.ts index 9885fbd32feaa..04aaec77956c1 100644 --- a/packages/plugin-ext/src/common/types.ts +++ b/packages/plugin-ext/src/common/types.ts @@ -19,13 +19,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { isObject as isObject0 } from '@theia/core/lib/common'; + /** * Returns `true` if the parameter has type "object" and not null, an array, a regexp, a date. */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function isObject(obj: any): boolean { - return typeof obj === 'object' - && obj !== null // eslint-disable-line @typescript-eslint/no-explicit-any +export function isObject(obj: unknown): boolean { + return isObject0(obj) && !Array.isArray(obj) && !(obj instanceof RegExp) && !(obj instanceof Date); diff --git a/packages/plugin-ext/src/hosted/node/hosted-plugin-localization-service.ts b/packages/plugin-ext/src/hosted/node/hosted-plugin-localization-service.ts index efa5e59eca35a..14f2c349f48f6 100644 --- a/packages/plugin-ext/src/hosted/node/hosted-plugin-localization-service.ts +++ b/packages/plugin-ext/src/hosted/node/hosted-plugin-localization-service.ts @@ -23,7 +23,7 @@ import { DeployedPlugin, Localization as PluginLocalization, PluginIdentifiers } import { URI } from '@theia/core/shared/vscode-uri'; import { EnvVariablesServer } from '@theia/core/lib/common/env-variables'; import { BackendApplicationContribution } from '@theia/core/lib/node'; -import { Disposable, MaybePromise } from '@theia/core'; +import { Disposable, isObject, MaybePromise } from '@theia/core'; import { Deferred } from '@theia/core/lib/common/promise-util'; export interface VSCodeNlsConfig { @@ -205,7 +205,7 @@ interface LocalizeInfo { } function isLocalizeInfo(obj: unknown): obj is LocalizeInfo { - return typeof obj === 'object' && obj && 'message' in obj || false; + return isObject(obj) && 'message' in obj || false; } function coerceLocalizations(translations: Record): Record { @@ -243,7 +243,7 @@ function localizePackage(value: unknown, translations: PackageTranslation, callb } return result; } - if (typeof value === 'object' && value) { + if (isObject(value)) { const result: Record = {}; for (const [name, item] of Object.entries(value)) { result[name] = localizePackage(item, translations, callback); diff --git a/packages/plugin-ext/src/hosted/node/plugin-host.ts b/packages/plugin-ext/src/hosted/node/plugin-host.ts index 24b67a6304b83..7cd2f82720a50 100644 --- a/packages/plugin-ext/src/hosted/node/plugin-host.ts +++ b/packages/plugin-ext/src/hosted/node/plugin-host.ts @@ -13,8 +13,7 @@ // // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 // ***************************************************************************** -// eslint-disable-next-line import/no-extraneous-dependencies -import 'reflect-metadata'; +import '@theia/core/shared/reflect-metadata'; import { ConnectionClosedError, RPCProtocolImpl } from '../../common/rpc-protocol'; import { ProcessTerminatedMessage, ProcessTerminateMessage } from './hosted-plugin-protocol'; import { PluginHostRPC } from './plugin-host-rpc'; diff --git a/packages/plugin-ext/src/main/browser/view/tree-view-decorator-service.ts b/packages/plugin-ext/src/main/browser/view/tree-view-decorator-service.ts index c6df32ad46596..c8a2d20482de3 100644 --- a/packages/plugin-ext/src/main/browser/view/tree-view-decorator-service.ts +++ b/packages/plugin-ext/src/main/browser/view/tree-view-decorator-service.ts @@ -16,9 +16,9 @@ import { inject, injectable, interfaces, named } from '@theia/core/shared/inversify'; import { AbstractTreeDecoratorService, TreeDecorator } from '@theia/core/lib/browser/tree/tree-decorator'; -import { bindContributionProvider, ContributionProvider } from '@theia/core'; +import { bindContributionProvider, ContributionProvider, isObject } from '@theia/core'; import { TreeNode } from '@theia/core/lib/browser'; -import { TreeItem, Uri } from '@theia/plugin'; +import { TreeItem } from '@theia/plugin'; import URI from '@theia/core/lib/common/uri'; import { FileTreeDecoratorAdapter } from '@theia/filesystem/lib/browser'; @@ -32,9 +32,8 @@ export class TreeViewDecoratorAdapter extends FileTreeDecoratorAdapter { } } - protected isTreeItem(node: unknown): node is TreeItem & { resourceUri: Uri } { - const candidate = node as TreeItem; - return !!candidate && typeof node === 'object' && 'resourceUri' in candidate && !!candidate.resourceUri; + protected isTreeItem(node: unknown): node is TreeItem { + return isObject(node) && !!node.resourceUri; } } diff --git a/packages/plugin-ext/src/plugin/languages/code-action.ts b/packages/plugin-ext/src/plugin/languages/code-action.ts index 5a4729d29cf4e..3e235e698fa42 100644 --- a/packages/plugin-ext/src/plugin/languages/code-action.ts +++ b/packages/plugin-ext/src/plugin/languages/code-action.ts @@ -24,6 +24,7 @@ import { Diagnostics } from './diagnostics'; import { CodeActionKind } from '../types-impl'; import { CommandRegistryImpl } from '../command-registry'; import { DisposableCollection } from '@theia/core/lib/common/disposable'; +import { isObject } from '@theia/core/lib/common'; export class CodeActionAdapter { @@ -147,16 +148,15 @@ export class CodeActionAdapter { } private static _isCommand(arg: unknown): arg is theia.Command { - return !!arg && typeof arg === 'object' && typeof (arg as theia.Command).command === 'string'; + return isObject(arg) && typeof arg.command === 'string'; } - private static _isSelection(obj: unknown): obj is Selection { - const selection = obj as Selection; - return !!obj && typeof obj === 'object' - && typeof selection.selectionStartLineNumber === 'number' - && typeof selection.selectionStartColumn === 'number' - && typeof selection.positionLineNumber === 'number' - && typeof selection.positionColumn === 'number'; + private static _isSelection(arg: unknown): arg is Selection { + return isObject(arg) + && typeof arg.selectionStartLineNumber === 'number' + && typeof arg.selectionStartColumn === 'number' + && typeof arg.positionLineNumber === 'number' + && typeof arg.positionColumn === 'number'; } } diff --git a/packages/plugin-ext/src/plugin/type-converters.ts b/packages/plugin-ext/src/plugin/type-converters.ts index 820fc4dc81aa0..2d1e10f7c29cb 100644 --- a/packages/plugin-ext/src/plugin/type-converters.ts +++ b/packages/plugin-ext/src/plugin/type-converters.ts @@ -28,6 +28,7 @@ import * as types from './types-impl'; import { UriComponents } from '../common/uri-components'; import { isReadonlyArray } from '../common/arrays'; import { MarkdownString as MarkdownStringDTO } from '@theia/core/lib/common/markdown-rendering'; +import { isObject } from '@theia/core/lib/common'; const SIDE_GROUP = -2; const ACTIVE_GROUP = -1; @@ -136,7 +137,7 @@ export function toPosition(position: Position): types.Position { } function isDecorationOptions(arg: unknown): arg is theia.DecorationOptions { - return !!arg && typeof arg === 'object' && typeof (arg as theia.DecorationOptions).range !== 'undefined'; + return isObject(arg) && typeof arg.range !== 'undefined'; } export function isDecorationOptionsArr(something: theia.Range[] | theia.DecorationOptions[]): something is theia.DecorationOptions[] { @@ -180,9 +181,9 @@ interface Codeblock { } function isCodeblock(arg: unknown): arg is Codeblock { - return !!arg && typeof arg === 'object' - && typeof (arg as Codeblock).language === 'string' - && typeof (arg as Codeblock).value === 'string'; + return isObject(arg) + && typeof arg.language === 'string' + && typeof arg.value === 'string'; } export function fromMarkdown(markup: theia.MarkdownString | theia.MarkedString): MarkdownStringDTO { @@ -677,57 +678,47 @@ export function toSymbolTag(kind: model.SymbolTag): types.SymbolTag { } export function isModelLocation(arg: unknown): arg is model.Location { - if (!arg) { - return false; - } - return !!arg && - typeof arg === 'object' && - isModelRange((arg as model.Location).range) && - isUriComponents((arg as model.Location).uri); + return isObject(arg) && + isModelRange(arg.range) && + isUriComponents(arg.uri); } export function isModelRange(arg: unknown): arg is model.Range { - const range = arg as model.Range; - return !!arg && typeof arg === 'object' && - typeof range.startLineNumber === 'number' && - typeof range.startColumn === 'number' && - typeof range.endLineNumber === 'number' && - typeof range.endColumn === 'number'; + return isObject(arg) && + typeof arg.startLineNumber === 'number' && + typeof arg.startColumn === 'number' && + typeof arg.endLineNumber === 'number' && + typeof arg.endColumn === 'number'; } -// eslint-disable-next-line @typescript-eslint/no-explicit-any export function isUriComponents(arg: unknown): arg is UriComponents { - const uriComponents = arg as UriComponents; - return !!arg && typeof arg === 'object' && - typeof uriComponents.scheme === 'string' && - typeof uriComponents.path === 'string' && - typeof uriComponents.query === 'string' && - typeof uriComponents.fragment === 'string'; + return isObject(arg) && + typeof arg.scheme === 'string' && + typeof arg.path === 'string' && + typeof arg.query === 'string' && + typeof arg.fragment === 'string'; } export function isModelCallHierarchyItem(arg: unknown): arg is model.CallHierarchyItem { - const item = arg as model.CallHierarchyItem; - return !!item && typeof item === 'object' - && isModelRange(item.range) - && isModelRange(item.selectionRange) - && isUriComponents(item.uri) - && !!item.name; + return isObject(arg) + && isModelRange(arg.range) + && isModelRange(arg.selectionRange) + && isUriComponents(arg.uri) + && !!arg.name; } export function isModelCallHierarchyIncomingCall(arg: unknown): arg is model.CallHierarchyIncomingCall { - const maybeIncomingCall = arg as model.CallHierarchyIncomingCall; - return !!arg && typeof arg === 'object' && - 'from' in maybeIncomingCall && - 'fromRanges' in maybeIncomingCall && - isModelCallHierarchyItem(maybeIncomingCall.from); + return isObject(arg) && + 'from' in arg && + 'fromRanges' in arg && + isModelCallHierarchyItem(arg.from); } export function isModelCallHierarchyOutgoingCall(arg: unknown): arg is model.CallHierarchyOutgoingCall { - const maybeOutgoingCall = arg as model.CallHierarchyOutgoingCall; - return !!arg && typeof arg === 'object' && - 'to' in maybeOutgoingCall && - 'fromRanges' in maybeOutgoingCall && - isModelCallHierarchyItem(maybeOutgoingCall.to); + return isObject(arg) && + 'to' in arg && + 'fromRanges' in arg && + isModelCallHierarchyItem(arg.to); } export function toLocation(value: model.Location): types.Location { @@ -781,12 +772,11 @@ export function toCallHierarchyOutgoingCall(value: model.CallHierarchyOutgoingCa } export function isModelTypeHierarchyItem(arg: unknown): arg is model.TypeHierarchyItem { - const item = arg as model.TypeHierarchyItem; - return !!item && typeof item === 'object' - && isModelRange(item.range) - && isModelRange(item.selectionRange) - && isUriComponents(item.uri) - && !!item.name; + return isObject(arg) + && isModelRange(arg.range) + && isModelRange(arg.selectionRange) + && isUriComponents(arg.uri) + && !!arg.name; } export function fromTypeHierarchyItem(item: types.TypeHierarchyItem): model.TypeHierarchyItem { diff --git a/packages/plugin-ext/src/plugin/types-impl.ts b/packages/plugin-ext/src/plugin/types-impl.ts index 7a3320b35ddab..ef3f8931911b8 100644 --- a/packages/plugin-ext/src/plugin/types-impl.ts +++ b/packages/plugin-ext/src/plugin/types-impl.ts @@ -31,6 +31,7 @@ import { SymbolKind } from '../common/plugin-api-rpc-model'; import { FileSystemProviderErrorCode, markAsFileSystemProviderError } from '@theia/filesystem/lib/common/files'; import * as paths from 'path'; import { es5ClassCompat } from '../common/types'; +import { isObject, isStringArray } from '@theia/core/lib/common'; /** * This is an implementation of #theia.Uri based on vscode-uri. @@ -540,14 +541,13 @@ export class Range { return new Range(start, end); } - static isRange(thing: unknown): thing is theia.Range { - if (thing instanceof Range) { + static isRange(arg: unknown): arg is theia.Range { + if (arg instanceof Range) { return true; } - const range = thing as theia.Range; - return !!thing && typeof thing === 'object' - && Position.isPosition(range.start) - && Position.isPosition(range.end); + return isObject(arg) + && Position.isPosition(arg.start) + && Position.isPosition(arg.end); } toJSON(): unknown { @@ -730,7 +730,7 @@ export class ThemeIcon { export namespace ThemeIcon { export function is(item: unknown): item is ThemeIcon { - return typeof item === 'object' && !!item && 'id' in item; + return isObject(item) && 'id' in item; } } @@ -3021,7 +3021,7 @@ export class SemanticTokensLegend { } function isStrArrayOrUndefined(arg: unknown): arg is string[] | undefined { - return ((typeof arg === 'undefined') || (Array.isArray(arg) && arg.every(e => typeof e === 'string'))); + return typeof arg === 'undefined' || isStringArray(arg); } @es5ClassCompat diff --git a/packages/preferences/src/browser/views/components/preference-file-input.ts b/packages/preferences/src/browser/views/components/preference-file-input.ts index 4510a1cc26731..9caff14622440 100644 --- a/packages/preferences/src/browser/views/components/preference-file-input.ts +++ b/packages/preferences/src/browser/views/components/preference-file-input.ts @@ -14,6 +14,7 @@ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 // ***************************************************************************** +import { isObject } from '@theia/core/lib/common'; import { nls } from '@theia/core/lib/common/nls'; import { inject, injectable, interfaces } from '@theia/core/shared/inversify'; import { OpenFileDialogProps } from '@theia/filesystem/lib/browser'; @@ -25,13 +26,13 @@ import { PreferenceLeafNodeRendererContribution } from './preference-node-render import { PreferenceStringInputRenderer } from './preference-string-input'; export interface FileNodeTypeDetails { - isFilepath: boolean; + isFilepath: true; selectionProps?: Partial; } export namespace FileNodeTypeDetails { - export function is(typeDetails?: unknown): typeDetails is FileNodeTypeDetails { - return !!typeDetails && typeof typeDetails === 'object' && !!(typeDetails as FileNodeTypeDetails).isFilepath; + export function is(typeDetails: unknown): typeDetails is FileNodeTypeDetails { + return isObject(typeDetails) && !!typeDetails.isFilepath; } } diff --git a/packages/preview/src/browser/preview-handler.ts b/packages/preview/src/browser/preview-handler.ts index f415882e7ee0c..367516b53aa08 100644 --- a/packages/preview/src/browser/preview-handler.ts +++ b/packages/preview/src/browser/preview-handler.ts @@ -16,7 +16,7 @@ import { inject, injectable, named } from '@theia/core/shared/inversify'; import URI from '@theia/core/lib/common/uri'; -import { ContributionProvider, MaybePromise, Prioritizeable } from '@theia/core'; +import { ContributionProvider, isObject, MaybePromise, Prioritizeable } from '@theia/core'; export const PreviewHandler = Symbol('PreviewHandler'); @@ -36,7 +36,7 @@ export interface RenderContentParams { export namespace RenderContentParams { export function is(params: unknown): params is RenderContentParams { - return !!params && typeof params === 'object' && 'content' in params && 'originUri' in params; + return isObject(params) && 'content' in params && 'originUri' in params; } } diff --git a/packages/process/src/node/process.ts b/packages/process/src/node/process.ts index cc88e765e2327..faf3f96b88df3 100644 --- a/packages/process/src/node/process.ts +++ b/packages/process/src/node/process.ts @@ -15,7 +15,7 @@ // ***************************************************************************** import { injectable, unmanaged } from '@theia/core/shared/inversify'; -import { ILogger, Emitter, Event } from '@theia/core/lib/common'; +import { ILogger, Emitter, Event, isObject } from '@theia/core/lib/common'; import { FileUri } from '@theia/core/lib/node'; import { isOSX, isWindows } from '@theia/core'; import { Readable, Writable } from 'stream'; @@ -169,7 +169,7 @@ export abstract class Process implements ManagedProcess { } protected isForkOptions(options: unknown): options is ForkOptions { - return !!options && typeof options === 'object' && !!(options as ForkOptions).modulePath; + return isObject(options) && !!options.modulePath; } protected readonly initialCwd: string; diff --git a/packages/scm-extra/src/browser/scm-file-change-node.ts b/packages/scm-extra/src/browser/scm-file-change-node.ts index a42d7b393e0aa..ce38d77cdf646 100644 --- a/packages/scm-extra/src/browser/scm-file-change-node.ts +++ b/packages/scm-extra/src/browser/scm-file-change-node.ts @@ -16,6 +16,7 @@ import { ScmCommit } from '@theia/scm/lib/browser/scm-provider'; import URI from '@theia/core/lib/common/uri'; +import { isObject } from '@theia/core/lib/common'; export interface ScmFileChangeNode { readonly fileChange: ScmFileChange; @@ -24,7 +25,7 @@ export interface ScmFileChangeNode { } export namespace ScmFileChangeNode { export function is(node: unknown): node is ScmFileChangeNode { - return !!node && typeof node === 'object' && 'fileChange' in node && 'commitId' in node; + return isObject(node) && 'fileChange' in node && 'commitId' in node; } } diff --git a/packages/task/src/browser/task-configuration-model.ts b/packages/task/src/browser/task-configuration-model.ts index e9814fb3080df..c0a4a762d88b7 100644 --- a/packages/task/src/browser/task-configuration-model.ts +++ b/packages/task/src/browser/task-configuration-model.ts @@ -19,6 +19,7 @@ import { Emitter, Event } from '@theia/core/lib/common/event'; import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable'; import { TaskCustomization, TaskConfiguration, TaskConfigurationScope } from '../common/task-protocol'; import { PreferenceProvider, PreferenceProviderDataChanges, PreferenceProviderDataChange } from '@theia/core/lib/browser'; +import { isObject } from '@theia/core/lib/common'; /** * Holds the task configurations associated with a particular file. Uses an editor model to facilitate @@ -80,11 +81,9 @@ export class TaskConfigurationModel implements Disposable { const configurations: (TaskCustomization | TaskConfiguration)[] = []; // eslint-disable-next-line @typescript-eslint/no-explicit-any const { configUri, value } = this.preferences.resolve('tasks', this.getWorkspaceFolder()); - if (value && typeof value === 'object' && 'tasks' in value) { - if (Array.isArray(value.tasks)) { - for (const taskConfig of value.tasks) { - configurations.push(taskConfig); - } + if (isObject(value) && Array.isArray(value.tasks)) { + for (const taskConfig of value.tasks) { + configurations.push(taskConfig); } } return { diff --git a/packages/toolbar/src/browser/toolbar-constants.ts b/packages/toolbar/src/browser/toolbar-constants.ts index 507b53250c7b5..6bdf2dffd9fcd 100644 --- a/packages/toolbar/src/browser/toolbar-constants.ts +++ b/packages/toolbar/src/browser/toolbar-constants.ts @@ -14,7 +14,7 @@ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 // ***************************************************************************** -import { Command, MenuPath, nls } from '@theia/core'; +import { Command, isObject, MenuPath, nls } from '@theia/core'; import { CommonCommands } from '@theia/core/lib/browser'; import URI from '@theia/core/lib/common/uri'; import { UserStorageUri } from '@theia/userstorage/lib/browser'; @@ -74,6 +74,6 @@ export namespace ToolbarMenus { export type ReactInteraction = React.MouseEvent | React.KeyboardEvent; export namespace ReactKeyboardEvent { export function is(obj: unknown): obj is React.KeyboardEvent { - return typeof obj === 'object' && !!obj && 'key' in obj; + return isObject(obj) && 'key' in obj; } } diff --git a/packages/workspace/src/browser/workspace-schema-updater.ts b/packages/workspace/src/browser/workspace-schema-updater.ts index 8556243d92372..8f7fcc48f96d9 100644 --- a/packages/workspace/src/browser/workspace-schema-updater.ts +++ b/packages/workspace/src/browser/workspace-schema-updater.ts @@ -16,7 +16,7 @@ import { inject, injectable, postConstruct } from '@theia/core/shared/inversify'; import { JsonSchemaContribution, JsonSchemaRegisterContext } from '@theia/core/lib/browser/json-schema-store'; -import { InMemoryResources } from '@theia/core/lib/common'; +import { InMemoryResources, isArray, isObject } from '@theia/core/lib/common'; import { IJSONSchema } from '@theia/core/lib/common/json-schema'; import URI from '@theia/core/lib/common/uri'; import { Deferred } from '@theia/core/lib/common/promise-util'; @@ -114,12 +114,11 @@ export class WorkspaceSchemaUpdater implements JsonSchemaContribution { export type WorkspaceSchema = Required>; export namespace WorkspaceSchema { - export const is = (candidate: unknown): candidate is WorkspaceSchema => !!candidate - && typeof candidate === 'object' - && 'properties' in candidate - && typeof (candidate as WorkspaceSchema).properties === 'object' - && 'required' in candidate - && Array.isArray((candidate as WorkspaceSchema).required); + export function is(candidate: unknown): candidate is WorkspaceSchema { + return isObject(candidate) + && typeof candidate.properties === 'object' + && isArray(candidate.required); + } } export const workspaceSchemaId = 'vscode://schemas/workspace';