quick-json5 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/LICENSE.md +7 -0
  2. package/README.md +66 -0
  3. package/dist/exports/Exports.d.ts +6 -0
  4. package/dist/exports/Exports.d.ts.map +1 -0
  5. package/dist/exports/Exports.js +6 -0
  6. package/dist/exports/Exports.js.map +1 -0
  7. package/dist/json/JsonEncoder.d.ts +3 -0
  8. package/dist/json/JsonEncoder.d.ts.map +1 -0
  9. package/dist/json/JsonEncoder.js +147 -0
  10. package/dist/json/JsonEncoder.js.map +1 -0
  11. package/dist/json/JsonParser.d.ts +3 -0
  12. package/dist/json/JsonParser.d.ts.map +1 -0
  13. package/dist/json/JsonParser.js +378 -0
  14. package/dist/json/JsonParser.js.map +1 -0
  15. package/dist/json5/Json5Encoder.d.ts +3 -0
  16. package/dist/json5/Json5Encoder.d.ts.map +1 -0
  17. package/dist/json5/Json5Encoder.js +155 -0
  18. package/dist/json5/Json5Encoder.js.map +1 -0
  19. package/dist/json5/Json5Parser.d.ts +3 -0
  20. package/dist/json5/Json5Parser.d.ts.map +1 -0
  21. package/dist/json5/Json5Parser.js +781 -0
  22. package/dist/json5/Json5Parser.js.map +1 -0
  23. package/dist/tests/Json5EncoderTest.d.ts +2 -0
  24. package/dist/tests/Json5EncoderTest.d.ts.map +1 -0
  25. package/dist/tests/Json5EncoderTest.js +44 -0
  26. package/dist/tests/Json5EncoderTest.js.map +1 -0
  27. package/dist/tests/Json5ParserTests.d.ts +3 -0
  28. package/dist/tests/Json5ParserTests.d.ts.map +1 -0
  29. package/dist/tests/Json5ParserTests.js +211 -0
  30. package/dist/tests/Json5ParserTests.js.map +1 -0
  31. package/dist/tests/JsonEncoderTests.d.ts +2 -0
  32. package/dist/tests/JsonEncoderTests.d.ts.map +1 -0
  33. package/dist/tests/JsonEncoderTests.js +44 -0
  34. package/dist/tests/JsonEncoderTests.js.map +1 -0
  35. package/dist/tests/JsonParserTests.d.ts +3 -0
  36. package/dist/tests/JsonParserTests.d.ts.map +1 -0
  37. package/dist/tests/JsonParserTests.js +140 -0
  38. package/dist/tests/JsonParserTests.js.map +1 -0
  39. package/dist/tests/Test.d.ts +2 -0
  40. package/dist/tests/Test.d.ts.map +1 -0
  41. package/dist/tests/Test.js +9 -0
  42. package/dist/tests/Test.js.map +1 -0
  43. package/dist/types/Types.d.ts +8 -0
  44. package/dist/types/Types.d.ts.map +1 -0
  45. package/dist/types/Types.js +2 -0
  46. package/dist/types/Types.js.map +1 -0
  47. package/dist/unused/Unused.d.ts +2 -0
  48. package/dist/unused/Unused.d.ts.map +1 -0
  49. package/dist/unused/Unused.js +47 -0
  50. package/dist/unused/Unused.js.map +1 -0
  51. package/dist/utilities/JsonParseError.d.ts +7 -0
  52. package/dist/utilities/JsonParseError.d.ts.map +1 -0
  53. package/dist/utilities/JsonParseError.js +10 -0
  54. package/dist/utilities/JsonParseError.js.map +1 -0
  55. package/dist/utilities/ObjectUtilities.d.ts +2 -0
  56. package/dist/utilities/ObjectUtilities.d.ts.map +1 -0
  57. package/dist/utilities/ObjectUtilities.js +142 -0
  58. package/dist/utilities/ObjectUtilities.js.map +1 -0
  59. package/dist/utilities/TypedArray.d.ts +4 -0
  60. package/dist/utilities/TypedArray.d.ts.map +1 -0
  61. package/dist/utilities/TypedArray.js +2 -0
  62. package/dist/utilities/TypedArray.js.map +1 -0
  63. package/dist/utilities/Utilities.d.ts +17 -0
  64. package/dist/utilities/Utilities.d.ts.map +1 -0
  65. package/dist/utilities/Utilities.js +97 -0
  66. package/dist/utilities/Utilities.js.map +1 -0
  67. package/package.json +38 -0
  68. package/src/exports/Exports.ts +7 -0
  69. package/src/json/JsonEncoder.ts +180 -0
  70. package/src/json/JsonParser.ts +491 -0
  71. package/src/json5/Json5Encoder.ts +189 -0
  72. package/src/json5/Json5Parser.ts +959 -0
  73. package/src/tests/Json5EncoderTest.ts +51 -0
  74. package/src/tests/Json5ParserTests.ts +237 -0
  75. package/src/tests/JsonEncoderTests.ts +51 -0
  76. package/src/tests/JsonParserTests.ts +159 -0
  77. package/src/tests/Test.ts +13 -0
  78. package/src/types/Types.ts +9 -0
  79. package/src/unused/Unused.ts +51 -0
  80. package/src/utilities/JsonParseError.ts +7 -0
  81. package/src/utilities/ObjectUtilities.ts +184 -0
  82. package/src/utilities/TypedArray.ts +39 -0
  83. package/src/utilities/Utilities.ts +130 -0
  84. package/tsconfig.json +55 -0
@@ -0,0 +1,491 @@
1
+ import { JsonReviverFunction } from '../types/Types.js'
2
+ import { JsonParserError } from '../utilities/JsonParseError.js'
3
+ import { getPositionInfo, hexCharCodeToNumber, positivePowersOf10 } from '../utilities/Utilities.js'
4
+
5
+ export function parseJSON(jsonString: string, reviver?: JsonReviverFunction) {
6
+ if (typeof jsonString !== 'string') {
7
+ throw new TypeError(`Given JSON string argument is not a string.`)
8
+ }
9
+
10
+ if (reviver !== undefined && typeof reviver !== 'function') {
11
+ throw new TypeError(`Reviver can only be a function.`)
12
+ }
13
+
14
+ let readPosition = 0
15
+
16
+ function parse(initialCharCode: number) {
17
+ ////////////////////////////////////////////////////////////////////////////////////////////
18
+ // Parse string
19
+ ////////////////////////////////////////////////////////////////////////////////////////////
20
+ if (initialCharCode === 34) { // '"' for string start
21
+ readPosition += 1
22
+
23
+ // Try to quickly parse the string for the common case where there are no escape patterns
24
+ {
25
+ const match = jsonString.substring(readPosition).match(/^[^\\\x00-\x1F]*?"/)
26
+
27
+ if (match !== null) {
28
+ const matchString = match[0]
29
+
30
+ const str = matchString.substring(0, matchString.length - 1)
31
+
32
+ readPosition += matchString.length
33
+
34
+ return str
35
+ }
36
+ }
37
+
38
+ // Fallback to slower method if an escape pattern is found or string is unterminated.
39
+ let decodedString = ''
40
+
41
+ while (true) {
42
+ const charCode = readCharCodeAndAdvance()
43
+
44
+ if (charCode === 34) { // '"'
45
+ break
46
+ } else if (charCode === 92) { // '\\'
47
+ const escapedCharCharcode = readCharCodeAndAdvance()
48
+
49
+ if (escapedCharCharcode <= 92) {
50
+ if (escapedCharCharcode === 34 || escapedCharCharcode === 92 || escapedCharCharcode === 47) { // '"', '\' and '/'
51
+ decodedString += String.fromCharCode(escapedCharCharcode)
52
+ } else {
53
+ const positionInfo = getInfoForPosition(readPosition - 2)
54
+ throw new JsonParserError(`Invalid escaped character '${String.fromCharCode(escapedCharCharcode)}' in escape sequence starting at ${positionInfo.positionString}.`, positionInfo)
55
+ }
56
+ } else if (escapedCharCharcode === 110) { // 'n'
57
+ decodedString += '\n'
58
+ } else if (escapedCharCharcode === 114) { // 'r'
59
+ decodedString += '\r'
60
+ } else if (escapedCharCharcode === 116) { // 't'
61
+ decodedString += '\t'
62
+ } else if (escapedCharCharcode === 117) { // 'u'
63
+ if (readPosition + 4 > jsonString.length) {
64
+ const positionInfo = getInfoForCurrentReadPosition()
65
+ throw new JsonParserError(`Unterminated hexadecimal sequence. Expected 4 hexadecimal characters at ${positionInfo.positionString}.`, positionInfo)
66
+ }
67
+
68
+ const codePoint =
69
+ hexCharCodeToNumber(jsonString.charCodeAt(readPosition + 0)) << 12 |
70
+ hexCharCodeToNumber(jsonString.charCodeAt(readPosition + 1)) << 8 |
71
+ hexCharCodeToNumber(jsonString.charCodeAt(readPosition + 2)) << 4 |
72
+ hexCharCodeToNumber(jsonString.charCodeAt(readPosition + 3))
73
+
74
+ decodedString += String.fromCharCode(codePoint)
75
+
76
+ readPosition += 4
77
+ } else if (escapedCharCharcode === 102) { // 'f'
78
+ decodedString += '\f'
79
+ } else if (escapedCharCharcode === 98) { // 'b'
80
+ decodedString += '\b'
81
+ } else {
82
+ const positionInfo = getInfoForPosition(readPosition - 2)
83
+ throw new JsonParserError(`Invalid escaped character '${String.fromCharCode(escapedCharCharcode)}' in escape sequence at ${positionInfo.positionString}.`, positionInfo)
84
+ }
85
+ } else if (charCode === undefined) {
86
+ const positionInfo = getInfoForPosition(jsonString.length - 1)
87
+ throw new JsonParserError(`Unterminated string literal.`, positionInfo)
88
+ } else if (charCode < 32) {
89
+ const positionInfo = getInfoForPosition(readPosition - 1)
90
+ throw new JsonParserError(`Invalid unescaped control character (${charCode}) at ${positionInfo.positionString}.`, positionInfo)
91
+ } else {
92
+ decodedString += String.fromCharCode(charCode)
93
+ }
94
+ }
95
+
96
+ return decodedString
97
+ }
98
+
99
+ ////////////////////////////////////////////////////////////////////////////////////////////
100
+ // Parse number
101
+ ////////////////////////////////////////////////////////////////////////////////////////////
102
+ if (initialCharCode < 91) { // '0'-'9' or '-' for number start
103
+ let numberStringStartPosition = readPosition
104
+ let charCode = initialCharCode
105
+
106
+ let isNegative: boolean
107
+
108
+ if (charCode === 45) { // '-'
109
+ isNegative = true
110
+
111
+ charCode = advanceAndReadCharCode()
112
+ } else {
113
+ isNegative = false
114
+ }
115
+
116
+ let concatenatedInteger = 0
117
+
118
+ // Parse integer part
119
+ {
120
+ const digitsStartPosition = readPosition
121
+
122
+ while (charCode >= 48 && charCode <= 57) { // '0'-'9'
123
+ const digitValue = charCode - 48
124
+
125
+ concatenatedInteger = (concatenatedInteger * 10) + digitValue
126
+
127
+ charCode = advanceAndReadCharCode()
128
+ }
129
+
130
+ const integerPartDigitCount = readPosition - digitsStartPosition
131
+
132
+ if (integerPartDigitCount === 0) {
133
+ const positionInfo = getInfoForCurrentReadPosition()
134
+ throw new JsonParserError(`Invalid character '${String.fromCharCode(charCode)}' at ${positionInfo.positionString}.`, positionInfo)
135
+ }
136
+
137
+ if (integerPartDigitCount > 1 && jsonString[digitsStartPosition] === '0') {
138
+ const positionInfo = getInfoForPosition(digitsStartPosition)
139
+ throw new JsonParserError(`Invalid leading zero found in number literal at ${positionInfo.positionString}.`, positionInfo)
140
+ }
141
+ }
142
+
143
+ let fractionalDigitCount = 0
144
+
145
+ // Parse fractional part
146
+ if (charCode === 46) { // '.'
147
+ charCode = advanceAndReadCharCode()
148
+
149
+ const digitsStartPosition = readPosition
150
+
151
+ while (charCode >= 48 && charCode <= 57) { // '0'-'9'
152
+ const digitValue = charCode - 48
153
+ concatenatedInteger = (concatenatedInteger * 10) + digitValue
154
+
155
+ charCode = advanceAndReadCharCode()
156
+ }
157
+
158
+ fractionalDigitCount = readPosition - digitsStartPosition
159
+
160
+ if (fractionalDigitCount === 0) {
161
+ const positionInfo = getInfoForCurrentReadPosition()
162
+ throw new JsonParserError(`Invalid character '${String.fromCharCode(charCode)}' at ${positionInfo.positionString}. Exepcted at least one decimal digit following '.'.`, positionInfo)
163
+ }
164
+ }
165
+
166
+ let exponent = 0
167
+
168
+ // Parse exponent part
169
+ if (charCode === 101 || charCode === 69) { // 'e' or 'E'
170
+ charCode = advanceAndReadCharCode()
171
+
172
+ let isNegativeExponent = false
173
+
174
+ if (charCode === 43) { // '+'
175
+ charCode = advanceAndReadCharCode()
176
+ } else if (charCode === 45) { // '-'
177
+ isNegativeExponent = true
178
+
179
+ charCode = advanceAndReadCharCode()
180
+ }
181
+
182
+ let exponentDigitsStartPosition = readPosition
183
+
184
+ while (charCode >= 48 && charCode <= 57) { // '0'-'9'
185
+ const digitValue = charCode - 48
186
+ exponent = (exponent * 10) + digitValue
187
+
188
+ charCode = advanceAndReadCharCode()
189
+ }
190
+
191
+ if (readPosition === exponentDigitsStartPosition) {
192
+ const positionInfo = getInfoForCurrentReadPosition()
193
+ throw new JsonParserError(`Invalid character '${String.fromCharCode(charCode)}' at ${positionInfo.positionString}. Exepcted at least one exponent digit.`, positionInfo)
194
+ }
195
+
196
+ if (isNegativeExponent) {
197
+ exponent = -exponent
198
+ }
199
+ }
200
+
201
+ const adjustedExponent = exponent - fractionalDigitCount
202
+
203
+ // Use fast method for cases where the number can be parsed efficiently,
204
+ // or fall back to slower method if not possible.
205
+ if (concatenatedInteger < 2 ** 53 && adjustedExponent >= -22 && adjustedExponent <= 22) {
206
+ let parsedNumber = concatenatedInteger
207
+
208
+ if (adjustedExponent > 0) {
209
+ parsedNumber *= positivePowersOf10[adjustedExponent]
210
+ } else if (adjustedExponent < 0) {
211
+ parsedNumber /= positivePowersOf10[-adjustedExponent]
212
+ }
213
+
214
+ if (isNegative) {
215
+ return -parsedNumber
216
+ } else {
217
+ return parsedNumber
218
+ }
219
+ } else {
220
+ return Number(jsonString.substring(numberStringStartPosition, readPosition))
221
+ }
222
+ }
223
+
224
+ ////////////////////////////////////////////////////////////////////////////////////////////
225
+ // Parse 'true' literal
226
+ ////////////////////////////////////////////////////////////////////////////////////////////
227
+ if (initialCharCode === 116) { // 't' for true
228
+ if (
229
+ jsonString.charCodeAt(readPosition + 1) !== 114 || // 'r'
230
+ jsonString.charCodeAt(readPosition + 2) !== 117 || // 'u'
231
+ jsonString.charCodeAt(readPosition + 3) !== 101) { // 'e'
232
+
233
+ const positionInfo = getInfoForCurrentReadPosition()
234
+ throw new JsonParserError(`Expected 'true' at ${positionInfo.positionString}.`, positionInfo)
235
+ }
236
+
237
+ readPosition += 4
238
+
239
+ return true
240
+ }
241
+
242
+ ////////////////////////////////////////////////////////////////////////////////////////////
243
+ // Parse 'false' literal
244
+ ////////////////////////////////////////////////////////////////////////////////////////////
245
+ if (initialCharCode === 102) { // 'f' for false
246
+ if (
247
+ jsonString.charCodeAt(readPosition + 1) !== 97 || // 'a'
248
+ jsonString.charCodeAt(readPosition + 2) !== 108 || // 'l'
249
+ jsonString.charCodeAt(readPosition + 3) !== 115 || // 's'
250
+ jsonString.charCodeAt(readPosition + 4) !== 101) { // 'e'
251
+
252
+ const positionInfo = getInfoForCurrentReadPosition()
253
+ throw new JsonParserError(`Expected 'false' at ${positionInfo.positionString}.`, positionInfo)
254
+ }
255
+
256
+ readPosition += 5
257
+
258
+ return false
259
+ }
260
+
261
+ ////////////////////////////////////////////////////////////////////////////////////////////
262
+ // Parse 'null' literal
263
+ ////////////////////////////////////////////////////////////////////////////////////////////
264
+ if (initialCharCode === 110) { // 'n' for null
265
+ if (
266
+ jsonString.charCodeAt(readPosition + 1) !== 117 || // 'u'
267
+ jsonString.charCodeAt(readPosition + 2) !== 108 || // 'l'
268
+ jsonString.charCodeAt(readPosition + 3) !== 108) { // 'l'
269
+
270
+ const positionInfo = getInfoForCurrentReadPosition()
271
+ throw new JsonParserError(`Expected 'null' at ${positionInfo.positionString}.`, positionInfo)
272
+ }
273
+
274
+ readPosition += 4
275
+
276
+ return null
277
+ }
278
+
279
+ ////////////////////////////////////////////////////////////////////////////////////////////
280
+ // Parse array
281
+ ////////////////////////////////////////////////////////////////////////////////////////////
282
+ if (initialCharCode === 91) { // '[' for array start
283
+ readPosition += 1
284
+
285
+ let charCode = skipToNextReadableCharCode()
286
+
287
+ if (charCode === 93) { // ']' for array end
288
+ readPosition += 1
289
+
290
+ return []
291
+ }
292
+
293
+ const arr: any[] = []
294
+
295
+ while (true) {
296
+ const elementStartPosition = readPosition
297
+
298
+ let element = parse(charCode)
299
+
300
+ if (reviver !== undefined) {
301
+ element = applyReviver('', element, elementStartPosition)
302
+ }
303
+
304
+ arr.push(element)
305
+
306
+ charCode = skipToNextReadableCharCode()
307
+
308
+ if (charCode === 44) { // ',' for comma
309
+ readPosition += 1
310
+
311
+ charCode = skipToNextReadableCharCode()
312
+
313
+ continue
314
+ }
315
+
316
+ if (charCode === 93) { // ']' for array end
317
+ readPosition += 1
318
+
319
+ break
320
+ }
321
+
322
+ {
323
+ const positionInfo = getInfoForCurrentReadPosition()
324
+ throw new JsonParserError(`Invalid character '${String.fromCharCode(charCode)}' in array expression at ${positionInfo.positionString}. Expected ',' or ']'.`, positionInfo)
325
+ }
326
+ }
327
+
328
+ return arr
329
+ }
330
+
331
+ ////////////////////////////////////////////////////////////////////////////////////////////
332
+ // Parse object
333
+ ////////////////////////////////////////////////////////////////////////////////////////////
334
+ if (initialCharCode === 123) { // '{' for object start
335
+ readPosition += 1
336
+
337
+ let charCode = skipToNextReadableCharCode()
338
+
339
+ if (charCode === 125) { // '}' for object end
340
+ readPosition += 1
341
+
342
+ return {}
343
+ }
344
+
345
+ const obj: any = {}
346
+
347
+ while (true) {
348
+ if (charCode !== 34) {
349
+ const positionInfo = getInfoForCurrentReadPosition()
350
+ throw new JsonParserError(`Invalid character '${String.fromCharCode(charCode)}' at ${positionInfo.positionString}. Expected '"'.`, positionInfo)
351
+ }
352
+
353
+ const key = parse(charCode)
354
+
355
+ charCode = skipToNextReadableCharCode()
356
+
357
+ if (charCode !== 58) { // ':'
358
+ const positionInfo = getInfoForCurrentReadPosition()
359
+ throw new JsonParserError(`Invalid character '${String.fromCharCode(charCode)}' at ${positionInfo.positionString}. Expected ':'.`, positionInfo)
360
+ }
361
+
362
+ readPosition += 1
363
+
364
+ charCode = skipToNextReadableCharCode()
365
+
366
+ const valueStartPosition = readPosition
367
+
368
+ let value = parse(charCode)
369
+
370
+ if (reviver !== undefined) {
371
+ value = applyReviver(key, value, valueStartPosition, obj)
372
+ }
373
+
374
+ obj[key] = value
375
+
376
+ const commaOrClosingBraceCharCode = skipToNextReadableCharCode()
377
+
378
+ if (commaOrClosingBraceCharCode === 44) { // ','
379
+ readPosition += 1
380
+
381
+ charCode = skipToNextReadableCharCode()
382
+
383
+ continue
384
+ }
385
+
386
+ if (commaOrClosingBraceCharCode === 125) { // '}'
387
+ readPosition += 1
388
+
389
+ break
390
+ }
391
+
392
+ {
393
+ const positionInfo = getInfoForCurrentReadPosition()
394
+ throw new JsonParserError(`Invalid character '${String.fromCharCode(commaOrClosingBraceCharCode)}' in object expression at ${positionInfo.positionString}. Expected ',' or '}'.`, positionInfo)
395
+ }
396
+ }
397
+
398
+ return obj
399
+ }
400
+
401
+ {
402
+ const positionInfo = getInfoForCurrentReadPosition()
403
+ throw new JsonParserError(`Invalid character '${String.fromCharCode(initialCharCode)}' at ${positionInfo.positionString}.`, positionInfo)
404
+ }
405
+ }
406
+
407
+ function advanceAndReadCharCode() {
408
+ return jsonString.charCodeAt(++readPosition)
409
+ }
410
+
411
+ function readCharCodeAndAdvance() {
412
+ return jsonString.charCodeAt(readPosition++)
413
+ }
414
+
415
+ function skipWhitespace() {
416
+ while (readPosition < jsonString.length) {
417
+ const charCode = jsonString.charCodeAt(readPosition)
418
+
419
+ if (charCode > 32 || (charCode !== 32 && charCode !== 10 && charCode !== 13 && charCode !== 9)) {
420
+ return charCode
421
+ }
422
+
423
+ readPosition += 1
424
+ }
425
+
426
+ return undefined
427
+ }
428
+
429
+ function skipToNextReadableCharCode() {
430
+ const nextCharCode = skipWhitespace()
431
+
432
+ if (nextCharCode === undefined) {
433
+ const positionInfo = getInfoForPosition(jsonString.length - 1)
434
+ throw new JsonParserError(`Unexpected termination of JSON input.`, positionInfo)
435
+ }
436
+
437
+ return nextCharCode
438
+ }
439
+
440
+ function getInfoForCurrentReadPosition() {
441
+ return getInfoForPosition(readPosition)
442
+ }
443
+
444
+ function getInfoForPosition(position: number) {
445
+ return getPositionInfo(jsonString, position)
446
+ }
447
+
448
+ function applyReviver(key: string, value: any, valueStartPosition: number, thisArg?: any) {
449
+ if (reviver === undefined) {
450
+ return
451
+ }
452
+
453
+ let context: string | undefined
454
+
455
+ if (typeof value !== 'object' || value === null) {
456
+ context = jsonString.substring(valueStartPosition, readPosition)
457
+ }
458
+
459
+ let result: any
460
+
461
+ if (thisArg) {
462
+ result = reviver.call(thisArg, key, value, context)
463
+ } else {
464
+ result = reviver(key, value, context)
465
+ }
466
+
467
+ return result
468
+ }
469
+
470
+ // Parse the given string
471
+ {
472
+ const initialCharCode = skipToNextReadableCharCode()
473
+
474
+ const documentStartPosition = readPosition
475
+
476
+ let result = parse(initialCharCode)
477
+
478
+ if (reviver !== undefined) {
479
+ result = applyReviver('', result, documentStartPosition)
480
+ }
481
+
482
+ const finalSkipResult = skipWhitespace()
483
+
484
+ if (finalSkipResult !== undefined) {
485
+ const positionInfo = getInfoForCurrentReadPosition()
486
+ throw new JsonParserError(`Unexpected trailing character(s) starting at ${positionInfo.positionString}.`, positionInfo)
487
+ }
488
+
489
+ return result
490
+ }
491
+ }
@@ -0,0 +1,189 @@
1
+ import { Json5Options, JsonReplacerFunction, JsonReplacerType } from '../types/Types.js'
2
+ import { charCodeToHex } from '../utilities/Utilities.js'
3
+
4
+ export function stringify(obj: any, replacer?: JsonReplacerType, space = 0, options?: Json5Options) {
5
+ const extensionsEnabled = options?.enableExtensions
6
+
7
+ let replacerFunc: JsonReplacerFunction | undefined
8
+
9
+ if (replacer != null) {
10
+ if (typeof replacer === 'function') {
11
+ replacerFunc = replacer
12
+ } else if (Array.isArray(replacer)) {
13
+ const replacerSet = new Set()
14
+
15
+ for (const key of replacer) {
16
+ replacerSet.add(String(key))
17
+ }
18
+
19
+ replacerFunc = (key, value) => {
20
+ if (replacerSet.has(key)) {
21
+ return value
22
+ }
23
+ }
24
+ } else {
25
+ throw new TypeError('Invalid replacer argument.')
26
+ }
27
+ }
28
+
29
+ const baseIndentString = ' '.repeat(space)
30
+
31
+ let currentIndentLevel = 0
32
+
33
+ function getIndentString() {
34
+ if (space === 0) {
35
+ return ''
36
+ }
37
+
38
+ const spaces = baseIndentString.repeat(currentIndentLevel)
39
+
40
+ return `\n${spaces}`
41
+ }
42
+
43
+ function encode(obj: any) {
44
+ if (obj === undefined) {
45
+ return ''
46
+ }
47
+
48
+ const typeofObj = typeof obj
49
+
50
+ if (obj === null || typeofObj === 'number' || typeofObj === 'boolean') {
51
+ return String(obj)
52
+ }
53
+
54
+ if (typeofObj === 'bigint') {
55
+ if (extensionsEnabled === false) {
56
+ throw new Error(`Encoding BigInt values requires JSON5 extensions to be enabled in options.`)
57
+ }
58
+
59
+ return String(obj)
60
+ }
61
+
62
+ if (typeofObj === 'string') {
63
+ return `"${escapeStringIfNeeded(obj)}"`
64
+ }
65
+
66
+ if (typeof obj !== 'object') {
67
+ throw new Error(`Type '${typeof obj}' can't be encoded in JSON5.`)
68
+ }
69
+
70
+ if (typeof obj.toJSON === 'function') {
71
+ return obj.toJSON()
72
+ }
73
+
74
+ if (Array.isArray(obj)) {
75
+ let str = '['
76
+ let isFirstElement = true
77
+
78
+ for (let i = 0; i < obj.length; i++) {
79
+ const element = obj[i]
80
+
81
+ if (element === undefined) {
82
+ continue
83
+ }
84
+
85
+ currentIndentLevel += 1
86
+
87
+ const saparatingComma = isFirstElement === true ? '' : ','
88
+ const indentString = getIndentString()
89
+
90
+ const encodedElement = encode(element)
91
+
92
+ str += `${saparatingComma}${indentString}${encodedElement}`
93
+
94
+ currentIndentLevel -= 1
95
+
96
+ isFirstElement = false
97
+ }
98
+
99
+ str += `${getIndentString()}]`
100
+
101
+ return str
102
+ } else {
103
+ let str = '{'
104
+ let isFirstEntry = true
105
+
106
+ for (const key in obj) {
107
+ let value = obj[key]
108
+
109
+ if (replacerFunc !== undefined) {
110
+ value = replacerFunc(key, value)
111
+ }
112
+
113
+ if (value === undefined) {
114
+ continue
115
+ }
116
+
117
+ currentIndentLevel += 1
118
+
119
+ const saparatingComma = isFirstEntry === true ? '' : ','
120
+ const indentString = getIndentString()
121
+
122
+ const keyDoesNotRequireQuoting = /^[\p{ID_Start}\$_\u200C\u200D](?:[\p{ID_Continue}\$_\u200C\u200D])*$/u.test(key)
123
+ const keyWithPossibleQuotes = keyDoesNotRequireQuoting ? key : `"${escapeStringIfNeeded(key)}"`
124
+
125
+ const spaceAfterColons = space > 0 ? ' ' : ''
126
+
127
+ const encodedValue = encode(value)
128
+
129
+ str += `${saparatingComma}${indentString}${keyWithPossibleQuotes}:${spaceAfterColons}${encodedValue}`
130
+
131
+ currentIndentLevel -= 1
132
+
133
+ isFirstEntry = false
134
+ }
135
+
136
+ str += `${getIndentString()}}`
137
+
138
+ return str
139
+ }
140
+ }
141
+
142
+ return encode(obj)
143
+ }
144
+
145
+ function escapeStringIfNeeded(str: string): string {
146
+ if (!/["\\\x00-\x1F]/.test(str)) {
147
+ return str
148
+ } else {
149
+ return escapeString(str)
150
+ }
151
+ }
152
+
153
+ function escapeString(str: string): string {
154
+ let escapedStr = ''
155
+
156
+ for (let i = 0; i < str.length; i++) {
157
+ const charCode = str.charCodeAt(i)
158
+
159
+ if (charCode >= 32) {
160
+ if (charCode === 34) { // '"'
161
+ escapedStr += '\\"'
162
+ } else if (charCode === 92) { // '\\'
163
+ escapedStr += '\\\\'
164
+ } else {
165
+ escapedStr += String.fromCharCode(charCode)
166
+ }
167
+ } else {
168
+ if (charCode === 10) { // '\n'
169
+ escapedStr += '\\n'
170
+ } else if (charCode === 13) { // '\r'
171
+ escapedStr += '\\r'
172
+ } else if (charCode === 9) { // '\t'
173
+ escapedStr += '\\t'
174
+ } else if (charCode === 12) { // '\f'
175
+ escapedStr += '\\f'
176
+ } else if (charCode === 8) { // '\b'
177
+ escapedStr += '\\b'
178
+ } else if (charCode === 11) { // '\v'
179
+ escapedStr += '\\v'
180
+ } else if (charCode === 0) { // '\v'
181
+ escapedStr += '\\0'
182
+ } else {
183
+ escapedStr += `\\u${charCodeToHex(charCode)}`
184
+ }
185
+ }
186
+ }
187
+
188
+ return escapedStr
189
+ }