Files
react-native/Libraries/Utilities/stringifySafe.js
T
Rubén Norte 4409642811 Migrate large amount of modules to flow strict and strict-local
Summary:
| Group | Before | After | Change |
| Untyped | 50 | 49 | -1 |
| flow | 197 | 155 | -42 |
| flow strict-local | 226 | 185 | -41 |
| flow strict | 33 | 117 | +84

Changelog: [Changed] Improved Flow typing of multiple modules (with migrations to `flow strict` and `flow strict-local`

Reviewed By: motiz88

Differential Revision: D22549140

fbshipit-source-id: ed29415332cfce15b244ee4dea9e13d035543175
2020-07-22 09:46:16 -07:00

122 lines
3.5 KiB
JavaScript

/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow strict
*/
'use strict';
import invariant from 'invariant';
/**
* Tries to stringify with JSON.stringify and toString, but catches exceptions
* (e.g. from circular objects) and always returns a string and never throws.
*/
export function createStringifySafeWithLimits(limits: {|
maxDepth?: number,
maxStringLimit?: number,
maxArrayLimit?: number,
maxObjectKeysLimit?: number,
|}): mixed => string {
const {
maxDepth = Number.POSITIVE_INFINITY,
maxStringLimit = Number.POSITIVE_INFINITY,
maxArrayLimit = Number.POSITIVE_INFINITY,
maxObjectKeysLimit = Number.POSITIVE_INFINITY,
} = limits;
const stack = [];
function replacer(key: string, value: mixed): mixed {
while (stack.length && this !== stack[0]) {
stack.shift();
}
if (typeof value === 'string') {
const truncatedString = '...(truncated)...';
if (value.length > maxStringLimit + truncatedString.length) {
return value.substring(0, maxStringLimit) + truncatedString;
}
return value;
}
if (typeof value !== 'object' || value === null) {
return value;
}
let retval = value;
if (Array.isArray(value)) {
if (stack.length >= maxDepth) {
retval = `[ ... array with ${value.length} values ... ]`;
} else if (value.length > maxArrayLimit) {
retval = value
.slice(0, maxArrayLimit)
.concat([
`... extra ${value.length - maxArrayLimit} values truncated ...`,
]);
}
} else {
// Add refinement after Array.isArray call.
invariant(typeof value === 'object', 'This was already found earlier');
let keys = Object.keys(value);
if (stack.length >= maxDepth) {
retval = `{ ... object with ${keys.length} keys ... }`;
} else if (keys.length > maxObjectKeysLimit) {
// Return a sample of the keys.
retval = {};
for (let k of keys.slice(0, maxObjectKeysLimit)) {
retval[k] = value[k];
}
const truncatedKey = '...(truncated keys)...';
retval[truncatedKey] = keys.length - maxObjectKeysLimit;
}
}
stack.unshift(retval);
return retval;
}
return function stringifySafe(arg: mixed): string {
if (arg === undefined) {
return 'undefined';
} else if (arg === null) {
return 'null';
} else if (typeof arg === 'function') {
try {
return arg.toString();
} catch (e) {
return '[function unknown]';
}
} else if (arg instanceof Error) {
return arg.name + ': ' + arg.message;
} else {
// Perform a try catch, just in case the object has a circular
// reference or stringify throws for some other reason.
try {
const ret = JSON.stringify(arg, replacer);
if (ret === undefined) {
return '["' + typeof arg + '" failed to stringify]';
}
return ret;
} catch (e) {
if (typeof arg.toString === 'function') {
try {
// $FlowFixMe: toString shouldn't take any arguments in general.
return arg.toString();
} catch (E) {}
}
}
}
return '["' + typeof arg + '" failed to stringify]';
};
}
const stringifySafe: mixed => string = createStringifySafeWithLimits({
maxDepth: 10,
maxStringLimit: 100,
maxArrayLimit: 50,
maxObjectKeysLimit: 50,
});
export default stringifySafe;