All files utils.ts

100% Statements 69/69
100% Branches 74/74
100% Functions 9/9
100% Lines 65/65

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149  21x       19x       169x       82x       61x       54x       42x       40x                         143x 49x     94x   21x     73x 4x     69x 13x 2x   11x 14x 4x     7x     56x 5x 1x   4x 5x 1x     3x 4x 1x     2x     51x 3x 1x   2x 5x 1x     1x     48x       5x 1x   4x 4x 4x 20x 2x     2x     43x 3x     40x 2x   38x       2x     36x 36x 36x 4x     32x 51x 1x       31x 48x 48x 10x       21x    
export function isDefined<T>(value?: T): value is NonNullable<T> {
  return value !== undefined && value !== null
}
 
export function isTruthy<T>(value?: T | null): value is T {
  return Boolean(value)
}
 
function isObject(value: unknown): value is Record<string, unknown> {
  return typeof value === 'object' && value !== null
}
 
function isArray(value: unknown): value is unknown[] {
  return Array.isArray(value)
}
 
function isMap(value: unknown): value is Map<unknown, unknown> {
  return value instanceof Map
}
 
function isSet(value: unknown): value is Set<unknown> {
  return value instanceof Set
}
 
function hasCustomValueOf(value: Record<string, unknown>): value is Record<string, unknown> & { valueOf(): unknown } {
  return typeof value.valueOf === 'function' && value.valueOf !== Object.prototype.valueOf
}
 
function hasCustomToString(value: Record<string, unknown>): value is { toString(): string } {
  return typeof value.toString === 'function' && value.toString !== Object.prototype.toString
}
 
/**
 * Structural equality check.
 *
 * Adapted from fast-deep-equal (MIT), kept close to upstream so it stays easy to diff against the
 * original while keeping the SDK free of runtime dependencies. The upstream implementation is
 * untyped JS; this port narrows `unknown` with type guards instead.
 *
 * @see https://github.com/epoberezkin/fast-deep-equal/blob/master/src/index.jst
 */
export function deepEqual(a: unknown, b: unknown): boolean {
  if (a === b) {
    return true
  }
 
  if (!isObject(a) || !isObject(b)) {
    // true if both NaN, false otherwise
    return typeof a === 'number' && typeof b === 'number' && Number.isNaN(a) && Number.isNaN(b)
  }
 
  if (a.constructor !== b.constructor) {
    return false
  }
 
  if (isArray(a) && isArray(b)) {
    if (a.length !== b.length) {
      return false
    }
    for (let i = a.length; i-- !== 0;) {
      if (!deepEqual(a[i], b[i])) {
        return false
      }
    }
    return true
  }
 
  if (isMap(a) && isMap(b)) {
    if (a.size !== b.size) {
      return false
    }
    for (const key of a.keys()) {
      if (!b.has(key)) {
        return false
      }
    }
    for (const [key, value] of a) {
      if (!deepEqual(value, b.get(key))) {
        return false
      }
    }
    return true
  }
 
  if (isSet(a) && isSet(b)) {
    if (a.size !== b.size) {
      return false
    }
    for (const value of a.values()) {
      if (!b.has(value)) {
        return false
      }
    }
    return true
  }
 
  if (ArrayBuffer.isView(a) && ArrayBuffer.isView(b)) {
    // Upstream indexes the typed arrays directly; `ArrayBufferView` is not index-typed and casting to
    // `ArrayLike<number>` is unsound, so compare the raw bytes instead. Constructors already matched
    // above, so equal byte contents imply equal element contents.
    if (a.byteLength !== b.byteLength) {
      return false
    }
    const bytesA = new Uint8Array(a.buffer, a.byteOffset, a.byteLength)
    const bytesB = new Uint8Array(b.buffer, b.byteOffset, b.byteLength)
    for (let i = bytesA.length; i-- !== 0;) {
      if (bytesA[i] !== bytesB[i]) {
        return false
      }
    }
    return true
  }
 
  if (a instanceof RegExp && b instanceof RegExp) {
    return a.source === b.source && a.flags === b.flags
  }
 
  if (hasCustomValueOf(a) && hasCustomValueOf(b)) {
    return a.valueOf() === b.valueOf()
  }
  if (hasCustomToString(a) && hasCustomToString(b)) {
    // The guards above already established that `toString` is overridden (not `Object.prototype`'s),
    // so it will not produce the default `[object Object]`; `no-base-to-string` can't see through the guard.
    // eslint-disable-next-line @typescript-eslint/no-base-to-string
    return a.toString() === b.toString()
  }
 
  const keys = Object.keys(a)
  const { length } = keys
  if (length !== Object.keys(b).length) {
    return false
  }
 
  for (let i = length; i-- !== 0;) {
    if (!Object.prototype.hasOwnProperty.call(b, keys[i])) {
      return false
    }
  }
 
  for (let i = length; i-- !== 0;) {
    const key = keys[i]
    if (!deepEqual(a[key], b[key])) {
      return false
    }
  }
 
  return true
}