( key: unknown, // this type is a tiny bit of a cheat, since this function does handle NaN (which is technically a number), but for // our internal use, it'll do value: Exclude<unknown, string | number | boolean | null>, )
| 195 | * @returns A stringified representation of the given value |
| 196 | */ |
| 197 | export function stringifyValue( |
| 198 | key: unknown, |
| 199 | // this type is a tiny bit of a cheat, since this function does handle NaN (which is technically a number), but for |
| 200 | // our internal use, it'll do |
| 201 | value: Exclude<unknown, string | number | boolean | null>, |
| 202 | ): string { |
| 203 | try { |
| 204 | // Runtime-specific stringifications (browser, framework integrations) are registered on the |
| 205 | // async-context strategy. Consult them before the universal fallbacks below. |
| 206 | if (stringifier) { |
| 207 | const stringified = stringifier(value); |
| 208 | // Safe to ignore empty strings here as well, we wont stringify to this |
| 209 | if (stringified) { |
| 210 | return stringified; |
| 211 | } |
| 212 | } |
| 213 | |
| 214 | if (typeof global !== 'undefined' && value === global) { |
| 215 | return '[Global]'; |
| 216 | } |
| 217 | |
| 218 | if (typeof value === 'number' && !Number.isFinite(value)) { |
| 219 | return `[${value}]`; |
| 220 | } |
| 221 | |
| 222 | if (typeof value === 'function') { |
| 223 | return `[Function: ${getFunctionName(value)}]`; |
| 224 | } |
| 225 | |
| 226 | if (typeof value === 'symbol') { |
| 227 | return `[${String(value)}]`; |
| 228 | } |
| 229 | |
| 230 | // stringified BigInts are indistinguishable from regular numbers, so we need to label them to avoid confusion |
| 231 | if (typeof value === 'bigint') { |
| 232 | return `[BigInt: ${String(value)}]`; |
| 233 | } |
| 234 | |
| 235 | // Now that we've knocked out all the special cases and the primitives, all we have left are objects. Simply casting |
| 236 | // them to strings means that instances of classes which haven't defined their `toStringTag` will just come out as |
| 237 | // `"[object Object]"`. If we instead look at the constructor's name (which is the same as the name of the class), |
| 238 | // we can make sure that only plain objects come out that way. |
| 239 | const objName = getConstructorName(value); |
| 240 | |
| 241 | return `[object ${objName}]`; |
| 242 | } catch (err) { |
| 243 | return `**non-serializable** (${err})`; |
| 244 | } |
| 245 | } |
| 246 | /* eslint-enable complexity */ |
| 247 | |
| 248 | function getConstructorName(value: unknown): string { |
no test coverage detected