1 |
|
2 | export default function toSource(
|
3 | object: unknown,
|
4 | replacer?: (a: any) => any,
|
5 | indent: false | string = ' ',
|
6 | startingIndent: string = '',
|
7 | ): string {
|
8 | const seen: any[] = [];
|
9 | return walk(
|
10 | object,
|
11 | replacer,
|
12 | indent === false ? '' : indent,
|
13 | startingIndent,
|
14 | seen,
|
15 | );
|
16 |
|
17 | function walk(
|
18 | object: any,
|
19 | replacer: ((a: any) => any) | undefined,
|
20 | indent: string,
|
21 | currentIndent: string,
|
22 | seen: any[],
|
23 | ): string {
|
24 | const nextIndent = currentIndent + indent;
|
25 | object = replacer ? replacer(object) : object;
|
26 |
|
27 | switch (typeof object) {
|
28 | case 'string':
|
29 | return JSON.stringify(object);
|
30 | case 'number':
|
31 | if (Object.is(object, -0)) {
|
32 | return '-0';
|
33 | }
|
34 | return String(object);
|
35 | case 'boolean':
|
36 | case 'undefined':
|
37 | return String(object);
|
38 | case 'function':
|
39 | return object.toString();
|
40 | }
|
41 |
|
42 | if (object === null) {
|
43 | return 'null';
|
44 | }
|
45 | if (object instanceof RegExp) {
|
46 | return object.toString();
|
47 | }
|
48 | if (object instanceof Date) {
|
49 | return `new Date(${object.getTime()})`;
|
50 | }
|
51 | if (object instanceof Set) {
|
52 | return `new Set(${walk(
|
53 | Array.from(object.values()),
|
54 | replacer,
|
55 | indent,
|
56 | nextIndent,
|
57 | seen,
|
58 | )})`;
|
59 | }
|
60 | if (object instanceof Map) {
|
61 | return `new Map(${walk(
|
62 | Array.from(object.entries()),
|
63 | replacer,
|
64 | indent,
|
65 | nextIndent,
|
66 | seen,
|
67 | )})`;
|
68 | }
|
69 |
|
70 | if (seen.indexOf(object) >= 0) {
|
71 | return '{$circularReference:1}';
|
72 | }
|
73 | seen.push(object);
|
74 |
|
75 | function join(elements: any[]) {
|
76 | return (
|
77 | indent.slice(1) +
|
78 | elements.join(',' + (indent && '\n') + nextIndent) +
|
79 | (indent ? ' ' : '')
|
80 | );
|
81 | }
|
82 |
|
83 | if (Array.isArray(object)) {
|
84 | return `[${join(
|
85 | object.map((element) =>
|
86 | walk(element, replacer, indent, nextIndent, seen.slice()),
|
87 | ),
|
88 | )}]`;
|
89 | }
|
90 | const keys = Object.keys(object);
|
91 | if (keys.length) {
|
92 | return `{${join(
|
93 | keys.map(
|
94 | (key) =>
|
95 | (legalKey(key) ? key : JSON.stringify(key)) +
|
96 | ':' +
|
97 | walk(object[key], replacer, indent, nextIndent, seen.slice()),
|
98 | ),
|
99 | )}}`;
|
100 | }
|
101 | return '{}';
|
102 | }
|
103 | }
|
104 |
|
105 | const KEYWORD_REGEXP =
|
106 | /^(abstract|boolean|break|byte|case|catch|char|class|const|continue|debugger|default|delete|do|double|else|enum|export|extends|false|final|finally|float|for|function|goto|if|implements|import|in|instanceof|int|interface|long|native|new|null|package|private|protected|public|return|short|static|super|switch|synchronized|this|throw|throws|transient|true|try|typeof|undefined|var|void|volatile|while|with)$/;
|
107 |
|
108 | function legalKey(key: string) {
|
109 | return (
|
110 | /^([a-z_$][0-9a-z_$]*|[0-9]+)$/gi.test(key) && !KEYWORD_REGEXP.test(key)
|
111 | );
|
112 | }
|