[go: nahoru, domu]

blob: 8b2dd0ebd223db5de2f245062f2900681929307e [file] [log] [blame]
// Copyright 2023 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
export const mod = (a: number, n: number): number => {
return ((a % n) + n) % n;
};
export function assert<T>(
predicate: T,
message = 'Assertion failed!',
): asserts predicate {
if (!predicate) {
throw new Error(message);
}
}
export type Keys<T> = T extends T ? keyof T : never;
export type RequiredKeys<T> = {
[K in keyof T] -?: {} extends Pick<T, K>? never : K;
}[keyof T];
export type OptionalKeys<T> = {
[K in keyof T] -?: {} extends Pick<T, K>? K : never;
}[keyof T];
export type DeepImmutable<T> = {
readonly[K in keyof T]: DeepImmutable<T[K]>;
};
export type DeepMutable<T> = {
-readonly[K in keyof T]: DeepMutable<T[K]>;
};
export type DeepPartial<T> = {
[K in keyof T]?: DeepPartial<Exclude<T[K], undefined>>;
};
export type Mutable<T> = {
-readonly[K in keyof T]: T[K];
};
export const deepFreeze = <T extends object>(object: T): DeepImmutable<T> => {
for (const name of Reflect.ownKeys(object)) {
const value = object[name as keyof T];
if ((value && typeof value === 'object') || typeof value === 'function') {
deepFreeze(value);
}
}
return Object.freeze(object);
};
export class InsertAssignment<T> {
value: T;
constructor(value: T) {
this.value = value;
}
}
export class ArrayAssignments<T> {
value: {[n: number]: T};
constructor(value: {[n: number]: T}) {
this.value = value;
}
}
export type Assignments<T> = T extends Readonly<Array<infer R>>?
R[]|ArrayAssignments<Assignments<R>|InsertAssignment<R>>:
{[K in keyof T]: Assignments<T[K]>};
export const immutableDeepAssign = <T>(
object: DeepImmutable<T>,
assignments: DeepImmutable<DeepPartial<Assignments<T>>>,
): DeepImmutable<T> => {
if (assignments instanceof ArrayAssignments) {
assert(Array.isArray(object), `Expected an array. Got ${typeof object}.`);
const updatedObject = [...object] as Mutable<typeof object>;
const keys = Object.keys(assignments.value)
.sort(
(a, b) => Number(b) - Number(a),
) as (keyof typeof updatedObject)[];
for (const key of keys) {
const update = assignments.value[Number(key)];
if (update === undefined) {
updatedObject.splice(Number(key), 1);
} else if (update instanceof InsertAssignment) {
updatedObject.splice(Number(key), 0, update.value);
} else {
updatedObject[Number(key)] = immutableDeepAssign(
updatedObject[key],
update,
);
}
}
return Object.freeze(updatedObject);
}
if (typeof assignments === 'object' && !Array.isArray(assignments)) {
assert(!Array.isArray(object), 'Expected an object. Got an array.');
const updatedObject = {...object} as Mutable<typeof object>;
const keys = Object.keys(assignments) as (keyof typeof assignments&keyof typeof updatedObject)[];
for (const key of keys) {
const update = assignments[key];
if (update === undefined) {
delete updatedObject[key];
} else {
updatedObject[key] = immutableDeepAssign(
updatedObject[key],
update as typeof updatedObject[typeof key],
);
}
}
return Object.freeze(updatedObject);
}
return assignments as DeepImmutable<T>;
};