source: imaps-frontend/node_modules/@discoveryjs/json-ext/cjs/stringify-info.cjs

main
Last change on this file was 79a0317, checked in by stefan toskovski <stefantoska84@…>, 4 days ago

F4 Finalna Verzija

  • Property mode set to 100644
File size: 7.0 KB
Line 
1'use strict';
2
3const utils = require('./utils.cjs');
4
5const hasOwn = typeof Object.hasOwn === 'function'
6 ? Object.hasOwn
7 : (object, key) => Object.hasOwnProperty.call(object, key);
8
9// https://tc39.es/ecma262/#table-json-single-character-escapes
10const escapableCharCodeSubstitution = { // JSON Single Character Escape Sequences
11 0x08: '\\b',
12 0x09: '\\t',
13 0x0a: '\\n',
14 0x0c: '\\f',
15 0x0d: '\\r',
16 0x22: '\\\"',
17 0x5c: '\\\\'
18};
19
20const charLength2048 = Uint8Array.from({ length: 2048 }, (_, code) => {
21 if (hasOwn(escapableCharCodeSubstitution, code)) {
22 return 2; // \X
23 }
24
25 if (code < 0x20) {
26 return 6; // \uXXXX
27 }
28
29 return code < 128 ? 1 : 2; // UTF8 bytes
30});
31
32function isLeadingSurrogate(code) {
33 return code >= 0xD800 && code <= 0xDBFF;
34}
35
36function isTrailingSurrogate(code) {
37 return code >= 0xDC00 && code <= 0xDFFF;
38}
39
40function stringLength(str) {
41 // Fast path to compute length when a string contains only characters encoded as single bytes
42 if (!/[^\x20\x21\x23-\x5B\x5D-\x7F]/.test(str)) {
43 return str.length + 2;
44 }
45
46 let len = 0;
47 let prevLeadingSurrogate = false;
48
49 for (let i = 0; i < str.length; i++) {
50 const code = str.charCodeAt(i);
51
52 if (code < 2048) {
53 len += charLength2048[code];
54 } else if (isLeadingSurrogate(code)) {
55 len += 6; // \uXXXX since no pair with trailing surrogate yet
56 prevLeadingSurrogate = true;
57 continue;
58 } else if (isTrailingSurrogate(code)) {
59 len = prevLeadingSurrogate
60 ? len - 2 // surrogate pair (4 bytes), since we calculate prev leading surrogate as 6 bytes, substruct 2 bytes
61 : len + 6; // \uXXXX
62 } else {
63 len += 3; // code >= 2048 is 3 bytes length for UTF8
64 }
65
66 prevLeadingSurrogate = false;
67 }
68
69 return len + 2; // +2 for quotes
70}
71
72// avoid producing a string from a number
73function intLength(num) {
74 let len = 0;
75
76 if (num < 0) {
77 len = 1;
78 num = -num;
79 }
80
81 if (num >= 1e9) {
82 len += 9;
83 num = (num - num % 1e9) / 1e9;
84 }
85
86 if (num >= 1e4) {
87 if (num >= 1e6) {
88 return len + (num >= 1e8
89 ? 9
90 : num >= 1e7 ? 8 : 7
91 );
92 }
93 return len + (num >= 1e5 ? 6 : 5);
94 }
95
96 return len + (num >= 1e2
97 ? num >= 1e3 ? 4 : 3
98 : num >= 10 ? 2 : 1
99 );
100}
101function primitiveLength(value) {
102 switch (typeof value) {
103 case 'string':
104 return stringLength(value);
105
106 case 'number':
107 return Number.isFinite(value)
108 ? Number.isInteger(value)
109 ? intLength(value)
110 : String(value).length
111 : 4 /* null */;
112
113 case 'boolean':
114 return value ? 4 /* true */ : 5 /* false */;
115
116 case 'undefined':
117 case 'object':
118 return 4; /* null */
119
120 default:
121 return 0;
122 }
123}
124
125function stringifyInfo(value, ...args) {
126 const { replacer, getKeys, ...options } = utils.normalizeStringifyOptions(...args);
127 const continueOnCircular = Boolean(options.continueOnCircular);
128 const space = options.space?.length || 0;
129
130 const keysLength = new Map();
131 const visited = new Map();
132 const circular = new Set();
133 const stack = [];
134 const root = { '': value };
135 let stop = false;
136 let bytes = 0;
137 let spaceBytes = 0;
138 let objects = 0;
139
140 walk(root, '', value);
141
142 // when value is undefined or replaced for undefined
143 if (bytes === 0) {
144 bytes += 9; // FIXME: that's the length of undefined, should we normalize behaviour to convert it to null?
145 }
146
147 return {
148 bytes: isNaN(bytes) ? Infinity : bytes + spaceBytes,
149 spaceBytes: space > 0 && isNaN(bytes) ? Infinity : spaceBytes,
150 circular: [...circular]
151 };
152
153 function walk(holder, key, value) {
154 if (stop) {
155 return;
156 }
157
158 value = utils.replaceValue(holder, key, value, replacer);
159
160 if (value === null || typeof value !== 'object') {
161 // primitive
162 if (value !== undefined || Array.isArray(holder)) {
163 bytes += primitiveLength(value);
164 }
165 } else {
166 // check for circular references
167 if (stack.includes(value)) {
168 circular.add(value);
169 bytes += 4; // treat as null
170
171 if (!continueOnCircular) {
172 stop = true;
173 }
174
175 return;
176 }
177
178 // Using 'visited' allows avoiding hang-ups in cases of highly interconnected object graphs;
179 // for example, a list of git commits with references to parents can lead to N^2 complexity for traversal,
180 // and N when 'visited' is used
181 if (visited.has(value)) {
182 bytes += visited.get(value);
183
184 return;
185 }
186
187 objects++;
188
189 const prevObjects = objects;
190 const valueBytes = bytes;
191 let valueLength = 0;
192
193 stack.push(value);
194
195 if (Array.isArray(value)) {
196 // array
197 valueLength = value.length;
198
199 for (let i = 0; i < valueLength; i++) {
200 walk(value, i, value[i]);
201 }
202 } else {
203 // object
204 let prevLength = bytes;
205
206 for (const key of getKeys(value)) {
207 walk(value, key, value[key]);
208
209 if (prevLength !== bytes) {
210 let keyLen = keysLength.get(key);
211
212 if (keyLen === undefined) {
213 keysLength.set(key, keyLen = stringLength(key) + 1); // "key":
214 }
215
216 // value is printed
217 bytes += keyLen;
218 valueLength++;
219 prevLength = bytes;
220 }
221 }
222 }
223
224 bytes += valueLength === 0
225 ? 2 // {} or []
226 : 1 + valueLength; // {} or [] + commas
227
228 if (space > 0 && valueLength > 0) {
229 spaceBytes +=
230 // a space between ":" and a value for each object entry
231 (Array.isArray(value) ? 0 : valueLength) +
232 // the formula results from folding the following components:
233 // - for each key-value or element: ident + newline
234 // (1 + stack.length * space) * valueLength
235 // - ident (one space less) before "}" or "]" + newline
236 // (stack.length - 1) * space + 1
237 (1 + stack.length * space) * (valueLength + 1) - space;
238 }
239
240 stack.pop();
241
242 // add to 'visited' only objects that contain nested objects
243 if (prevObjects !== objects) {
244 visited.set(value, bytes - valueBytes);
245 }
246 }
247 }
248}
249
250exports.stringifyInfo = stringifyInfo;
Note: See TracBrowser for help on using the repository browser.