/*! * Copyright (c) Microsoft Corporation and contributors. All rights reserved. * Licensed under the MIT License. */ /** * Any mapping from a string to values of type `T` * @legacy @beta */ export interface MapLike { [index: string]: T; } /** * A loosely-typed mapping from strings to any value. * * @remarks Property sets are expected to be JSON-stringify-able. * * @privateRemarks PropertySet is typed using `any` because when you include * custom methods such as toJSON(), JSON.stringify accepts most types other than * functions * @legacy @beta */ // eslint-disable-next-line @typescript-eslint/no-explicit-any export type PropertySet = MapLike; /** * Compares two PropertySets for equality. * * @internal */ export function matchProperties( a: PropertySet | undefined, b: PropertySet | undefined, ): boolean { if (!a && !b) { return true; } const keysA = a ? Object.keys(a) : []; const keysB = b ? Object.keys(b) : []; if (keysA.length !== keysB.length) { return false; } for (const key of keysA) { if (b?.[key] === undefined) { return false; } else if (typeof b[key] === "object") { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument if (!matchProperties(a?.[key], b[key])) { return false; } } else if (b[key] !== a?.[key]) { return false; } } return true; } /** * Adds properties from one PropertySet to another. * * @internal */ export function extend(base: MapLike, extension: MapLike | undefined): MapLike { if (extension !== undefined) { for (const [key, v] of Object.entries(extension)) { if (v === undefined) { continue; } else if (v === null) { // eslint-disable-next-line @typescript-eslint/no-dynamic-delete delete base[key]; } else { base[key] = v; } } } return base; } /** * Clones properties in a given PropertySet into a new PropertySet. * * @internal */ export function clone(extension: MapLike | undefined): MapLike | undefined { if (extension === undefined) { return undefined; } const cloneMap = createMap(); return extend(cloneMap, extension); } /** * Add properties in one PropertySet to another PropertySet. If the PropertySet we are adding * to does not exist, create one. * * @internal */ export function addProperties( oldProps: PropertySet | undefined, newProps: PropertySet, ): PropertySet { return extend(oldProps ?? createMap(), newProps); } /** * Replace values of undefined in one PropertySet with values for the same key from another PropertySet. * * @internal */ export function extendIfUndefined( base: MapLike, extension: MapLike | undefined, ): MapLike { if (extension !== undefined) { for (const [key, value] of Object.entries(extension)) { // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- using ??= could change behavior if value is falsy if (base[key] === undefined) { base[key] = value; } } } return base; } /** * Create a MapLike with good performance. * * @internal */ export function createMap(): MapLike { return Object.create(null) as MapLike; }