index.js 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. 'use strict';
  2. const isRegexp = require('is-regexp');
  3. const isObj = require('is-obj');
  4. const getOwnEnumPropSymbols = require('get-own-enumerable-property-symbols').default;
  5. module.exports = (val, opts, pad) => {
  6. const seen = [];
  7. return (function stringify(val, opts, pad) {
  8. opts = opts || {};
  9. opts.indent = opts.indent || '\t';
  10. pad = pad || '';
  11. let tokens;
  12. if (opts.inlineCharacterLimit === undefined) {
  13. tokens = {
  14. newLine: '\n',
  15. newLineOrSpace: '\n',
  16. pad,
  17. indent: pad + opts.indent
  18. };
  19. } else {
  20. tokens = {
  21. newLine: '@@__STRINGIFY_OBJECT_NEW_LINE__@@',
  22. newLineOrSpace: '@@__STRINGIFY_OBJECT_NEW_LINE_OR_SPACE__@@',
  23. pad: '@@__STRINGIFY_OBJECT_PAD__@@',
  24. indent: '@@__STRINGIFY_OBJECT_INDENT__@@'
  25. };
  26. }
  27. const expandWhiteSpace = string => {
  28. if (opts.inlineCharacterLimit === undefined) {
  29. return string;
  30. }
  31. const oneLined = string
  32. .replace(new RegExp(tokens.newLine, 'g'), '')
  33. .replace(new RegExp(tokens.newLineOrSpace, 'g'), ' ')
  34. .replace(new RegExp(tokens.pad + '|' + tokens.indent, 'g'), '');
  35. if (oneLined.length <= opts.inlineCharacterLimit) {
  36. return oneLined;
  37. }
  38. return string
  39. .replace(new RegExp(tokens.newLine + '|' + tokens.newLineOrSpace, 'g'), '\n')
  40. .replace(new RegExp(tokens.pad, 'g'), pad)
  41. .replace(new RegExp(tokens.indent, 'g'), pad + opts.indent);
  42. };
  43. if (seen.indexOf(val) !== -1) {
  44. return '"[Circular]"';
  45. }
  46. if (val === null ||
  47. val === undefined ||
  48. typeof val === 'number' ||
  49. typeof val === 'boolean' ||
  50. typeof val === 'function' ||
  51. typeof val === 'symbol' ||
  52. isRegexp(val)) {
  53. return String(val);
  54. }
  55. if (val instanceof Date) {
  56. return `new Date('${val.toISOString()}')`;
  57. }
  58. if (Array.isArray(val)) {
  59. if (val.length === 0) {
  60. return '[]';
  61. }
  62. seen.push(val);
  63. const ret = '[' + tokens.newLine + val.map((el, i) => {
  64. const eol = val.length - 1 === i ? tokens.newLine : ',' + tokens.newLineOrSpace;
  65. let value = stringify(el, opts, pad + opts.indent);
  66. if (opts.transform) {
  67. value = opts.transform(val, i, value);
  68. }
  69. return tokens.indent + value + eol;
  70. }).join('') + tokens.pad + ']';
  71. seen.pop();
  72. return expandWhiteSpace(ret);
  73. }
  74. if (isObj(val)) {
  75. let objKeys = Object.keys(val).concat(getOwnEnumPropSymbols(val));
  76. if (opts.filter) {
  77. objKeys = objKeys.filter(el => opts.filter(val, el));
  78. }
  79. if (objKeys.length === 0) {
  80. return '{}';
  81. }
  82. seen.push(val);
  83. const ret = '{' + tokens.newLine + objKeys.map((el, i) => {
  84. const eol = objKeys.length - 1 === i ? tokens.newLine : ',' + tokens.newLineOrSpace;
  85. const isSymbol = typeof el === 'symbol';
  86. const isClassic = !isSymbol && /^[a-z$_][a-z$_0-9]*$/i.test(el);
  87. const key = isSymbol || isClassic ? el : stringify(el, opts);
  88. let value = stringify(val[el], opts, pad + opts.indent);
  89. if (opts.transform) {
  90. value = opts.transform(val, el, value);
  91. }
  92. return tokens.indent + String(key) + ': ' + value + eol;
  93. }).join('') + tokens.pad + '}';
  94. seen.pop();
  95. return expandWhiteSpace(ret);
  96. }
  97. val = String(val).replace(/[\r\n]/g, x => x === '\n' ? '\\n' : '\\r');
  98. if (opts.singleQuotes === false) {
  99. val = val.replace(/"/g, '\\"');
  100. return `"${val}"`;
  101. }
  102. val = val.replace(/\\?'/g, '\\\'');
  103. return `'${val}'`;
  104. })(val, opts, pad);
  105. };