136 lines
3.6 KiB
JavaScript
136 lines
3.6 KiB
JavaScript
import isRegexp from 'is-regexp';
|
|
import isObject from 'is-obj';
|
|
import getOwnEnumerableKeys from 'get-own-enumerable-keys';
|
|
|
|
export default function stringifyObject(input, options, pad) {
|
|
const seen = [];
|
|
|
|
return (function stringify(input, options = {}, pad = '') {
|
|
const indent = options.indent || '\t';
|
|
|
|
let tokens;
|
|
if (options.inlineCharacterLimit === undefined) {
|
|
tokens = {
|
|
newline: '\n',
|
|
newlineOrSpace: '\n',
|
|
pad,
|
|
indent: pad + indent,
|
|
};
|
|
} else {
|
|
tokens = {
|
|
newline: '@@__STRINGIFY_OBJECT_NEW_LINE__@@',
|
|
newlineOrSpace: '@@__STRINGIFY_OBJECT_NEW_LINE_OR_SPACE__@@',
|
|
pad: '@@__STRINGIFY_OBJECT_PAD__@@',
|
|
indent: '@@__STRINGIFY_OBJECT_INDENT__@@',
|
|
};
|
|
}
|
|
|
|
const expandWhiteSpace = string => {
|
|
if (options.inlineCharacterLimit === undefined) {
|
|
return string;
|
|
}
|
|
|
|
const oneLined = string
|
|
.replace(new RegExp(tokens.newline, 'g'), '')
|
|
.replace(new RegExp(tokens.newlineOrSpace, 'g'), ' ')
|
|
.replace(new RegExp(tokens.pad + '|' + tokens.indent, 'g'), '');
|
|
|
|
if (oneLined.length <= options.inlineCharacterLimit) {
|
|
return oneLined;
|
|
}
|
|
|
|
return string
|
|
.replace(new RegExp(tokens.newline + '|' + tokens.newlineOrSpace, 'g'), '\n')
|
|
.replace(new RegExp(tokens.pad, 'g'), pad)
|
|
.replace(new RegExp(tokens.indent, 'g'), pad + indent);
|
|
};
|
|
|
|
if (seen.includes(input)) {
|
|
return '"[Circular]"';
|
|
}
|
|
|
|
if (
|
|
input === null
|
|
|| input === undefined
|
|
|| typeof input === 'number'
|
|
|| typeof input === 'boolean'
|
|
|| typeof input === 'function'
|
|
|| typeof input === 'symbol'
|
|
|| isRegexp(input)
|
|
) {
|
|
return String(input);
|
|
}
|
|
|
|
if (input instanceof Date) {
|
|
return `new Date('${input.toISOString()}')`;
|
|
}
|
|
|
|
if (Array.isArray(input)) {
|
|
if (input.length === 0) {
|
|
return '[]';
|
|
}
|
|
|
|
seen.push(input);
|
|
|
|
const returnValue = '[' + tokens.newline + input.map((element, i) => {
|
|
const eol = input.length - 1 === i ? tokens.newline : ',' + tokens.newlineOrSpace;
|
|
|
|
let value = stringify(element, options, pad + indent);
|
|
if (options.transform) {
|
|
value = options.transform(input, i, value);
|
|
}
|
|
|
|
return tokens.indent + value + eol;
|
|
}).join('') + tokens.pad + ']';
|
|
|
|
seen.pop();
|
|
|
|
return expandWhiteSpace(returnValue);
|
|
}
|
|
|
|
if (isObject(input)) {
|
|
let objectKeys = getOwnEnumerableKeys(input);
|
|
|
|
if (options.filter) {
|
|
// eslint-disable-next-line unicorn/no-array-callback-reference, unicorn/no-array-method-this-argument
|
|
objectKeys = objectKeys.filter(element => options.filter(input, element));
|
|
}
|
|
|
|
if (objectKeys.length === 0) {
|
|
return '{}';
|
|
}
|
|
|
|
seen.push(input);
|
|
|
|
const returnValue = '{' + tokens.newline + objectKeys.map((element, index) => {
|
|
const eol = objectKeys.length - 1 === index ? tokens.newline : ',' + tokens.newlineOrSpace;
|
|
const isSymbol = typeof element === 'symbol';
|
|
const isClassic = !isSymbol && /^[a-z$_][$\w]*$/i.test(element);
|
|
const key = isSymbol || isClassic ? element : stringify(element, options);
|
|
|
|
let value = stringify(input[element], options, pad + indent);
|
|
if (options.transform) {
|
|
value = options.transform(input, element, value);
|
|
}
|
|
|
|
return tokens.indent + String(key) + ': ' + value + eol;
|
|
}).join('') + tokens.pad + '}';
|
|
|
|
seen.pop();
|
|
|
|
return expandWhiteSpace(returnValue);
|
|
}
|
|
|
|
input = input.replace(/\\/g, '\\\\');
|
|
input = String(input).replace(/[\r\n]/g, x => x === '\n' ? '\\n' : '\\r');
|
|
|
|
if (options.singleQuotes === false) {
|
|
input = input.replace(/"/g, '\\"');
|
|
return `"${input}"`;
|
|
}
|
|
|
|
input = input.replace(/'/g, '\\\'');
|
|
return `'${input}'`;
|
|
})(input, options, pad);
|
|
}
|