xref: /aosp_15_r20/external/perfetto/ui/src/base/utils.ts (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1// Copyright (C) 2023 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// Return true if value is not nullish - i.e. not null or undefined
16// Allows doing the following
17//   exists(val) && m('div', val)
18// Even if val is a non-nullish falsey value like 0 or ''
19export function exists<T>(value: T): value is NonNullable<T> {
20  return value !== undefined && value !== null;
21}
22
23// Type util to make sure that exactly one of the passed keys is defined.
24// Example usage:
25// type FooOrBar = ExactlyOne<{foo: number; bar: number}>;
26// const x : FooOrBar = {foo: 42};      // OK
27// const x : FooOrBar = {bar: 42};      // OK
28// const x : FooOrBar = {};             // Compiler error
29// const x : FooOrBar = {foo:1, bar:2}; // Compiler error
30export type ExactlyOne<T, K extends keyof T = keyof T> = K extends keyof T
31  ? {[P in K]: T[P]} & {[P in Exclude<keyof T, K>]?: undefined}
32  : never;
33
34// Escape characters that are not allowed inside a css selector
35export function escapeCSSSelector(selector: string): string {
36  return selector.replace(/([!"#$%&'()*+,.\/:;<=>?@[\\\]^`{|}~])/g, '\\$1');
37}
38
39// Make field K required in T
40export type RequiredField<T, K extends keyof T> = Omit<T, K> &
41  Required<Pick<T, K>>;
42
43// The lowest common denoninator between Map<> and WeakMap<>.
44// This is just to avoid duplication of the getOrCreate below.
45interface MapLike<K, V> {
46  get(key: K): V | undefined;
47  set(key: K, value: V): this;
48}
49
50export function getOrCreate<K, V>(
51  map: MapLike<K, V>,
52  key: K,
53  factory: () => V,
54): V {
55  let value = map.get(key);
56  if (value !== undefined) return value;
57  value = factory();
58  map.set(key, value);
59  return value;
60}
61
62// Allows to take an existing class instance (`target`) and override some of its
63// methods via `overrides`. We use this for cases where we want to expose a
64// "manager" (e.g. TrackManager, SidebarManager) to the plugins, but we want to
65// override few of its methods (e.g. to inject the pluginId in the args).
66export function createProxy<T extends object>(
67  target: T,
68  overrides: Partial<T>,
69): T {
70  return new Proxy(target, {
71    get: (target: T, prop: string | symbol, receiver) => {
72      // If the property is overriden, use that; otherwise, use target
73      const overrideValue = (overrides as {[key: symbol | string]: {}})[prop];
74      if (overrideValue !== undefined) {
75        return typeof overrideValue === 'function'
76          ? overrideValue.bind(overrides)
77          : overrideValue;
78      }
79      const baseValue = Reflect.get(target, prop, receiver);
80      return typeof baseValue === 'function'
81        ? baseValue.bind(target)
82        : baseValue;
83    },
84  }) as T;
85}
86