1 | "use strict";
|
---|
2 | /**
|
---|
3 | * @license
|
---|
4 | * Copyright Google LLC All Rights Reserved.
|
---|
5 | *
|
---|
6 | * Use of this source code is governed by an MIT-style license that can be
|
---|
7 | * found in the LICENSE file at https://angular.io/license
|
---|
8 | */
|
---|
9 | Object.defineProperty(exports, "__esModule", { value: true });
|
---|
10 | exports.parseJson = exports.parseJsonAst = exports.JsonParseMode = exports.PathSpecificJsonException = exports.UnexpectedEndOfInputException = exports.InvalidJsonCharacterException = exports.JsonException = void 0;
|
---|
11 | /* eslint-disable no-constant-condition */
|
---|
12 | const exception_1 = require("../exception");
|
---|
13 | class JsonException extends exception_1.BaseException {
|
---|
14 | }
|
---|
15 | exports.JsonException = JsonException;
|
---|
16 | /**
|
---|
17 | * A character was invalid in this context.
|
---|
18 | * @deprecated Deprecated since version 11. Use 3rd party JSON parsers such as `jsonc-parser` instead.
|
---|
19 | */
|
---|
20 | class InvalidJsonCharacterException extends JsonException {
|
---|
21 | constructor(context) {
|
---|
22 | const pos = context.previous;
|
---|
23 | const invalidChar = JSON.stringify(_peek(context));
|
---|
24 | super(`Invalid JSON character: ${invalidChar} at ${pos.line}:${pos.character}.`);
|
---|
25 | this.invalidChar = invalidChar;
|
---|
26 | this.line = pos.line;
|
---|
27 | this.offset = pos.offset;
|
---|
28 | this.character = pos.character;
|
---|
29 | }
|
---|
30 | }
|
---|
31 | exports.InvalidJsonCharacterException = InvalidJsonCharacterException;
|
---|
32 | /**
|
---|
33 | * More input was expected, but we reached the end of the stream.
|
---|
34 | * @deprecated Deprecated since version 11. Use 3rd party JSON parsers such as `jsonc-parser` instead.
|
---|
35 | */
|
---|
36 | class UnexpectedEndOfInputException extends JsonException {
|
---|
37 | constructor(_context) {
|
---|
38 | super(`Unexpected end of file.`);
|
---|
39 | }
|
---|
40 | }
|
---|
41 | exports.UnexpectedEndOfInputException = UnexpectedEndOfInputException;
|
---|
42 | /**
|
---|
43 | * An error happened within a file.
|
---|
44 | * @deprecated Deprecated since version 11. Use 3rd party JSON parsers such as `jsonc-parser` instead.
|
---|
45 | */
|
---|
46 | class PathSpecificJsonException extends JsonException {
|
---|
47 | constructor(path, exception) {
|
---|
48 | super(`An error happened at file path ${JSON.stringify(path)}: ${exception.message}`);
|
---|
49 | this.path = path;
|
---|
50 | this.exception = exception;
|
---|
51 | }
|
---|
52 | }
|
---|
53 | exports.PathSpecificJsonException = PathSpecificJsonException;
|
---|
54 | /**
|
---|
55 | * Peek and return the next character from the context.
|
---|
56 | * @private
|
---|
57 | */
|
---|
58 | function _peek(context) {
|
---|
59 | return context.original[context.position.offset];
|
---|
60 | }
|
---|
61 | /**
|
---|
62 | * Move the context to the next character, including incrementing the line if necessary.
|
---|
63 | * @private
|
---|
64 | */
|
---|
65 | function _next(context) {
|
---|
66 | context.previous = context.position;
|
---|
67 | let { offset, line, character } = context.position;
|
---|
68 | const char = context.original[offset];
|
---|
69 | offset++;
|
---|
70 | if (char == '\n') {
|
---|
71 | line++;
|
---|
72 | character = 0;
|
---|
73 | }
|
---|
74 | else {
|
---|
75 | character++;
|
---|
76 | }
|
---|
77 | context.position = { offset, line, character };
|
---|
78 | }
|
---|
79 | function _token(context, valid) {
|
---|
80 | const char = _peek(context);
|
---|
81 | if (valid) {
|
---|
82 | if (!char) {
|
---|
83 | throw new UnexpectedEndOfInputException(context);
|
---|
84 | }
|
---|
85 | if (valid.indexOf(char) == -1) {
|
---|
86 | throw new InvalidJsonCharacterException(context);
|
---|
87 | }
|
---|
88 | }
|
---|
89 | // Move the position of the context to the next character.
|
---|
90 | _next(context);
|
---|
91 | return char;
|
---|
92 | }
|
---|
93 | /**
|
---|
94 | * Read the exponent part of a number. The exponent part is looser for JSON than the number
|
---|
95 | * part. `str` is the string of the number itself found so far, and start the position
|
---|
96 | * where the full number started. Returns the node found.
|
---|
97 | * @private
|
---|
98 | */
|
---|
99 | function _readExpNumber(context, start, str, comments) {
|
---|
100 | let char;
|
---|
101 | let signed = false;
|
---|
102 | while (true) {
|
---|
103 | char = _token(context);
|
---|
104 | if (char == '+' || char == '-') {
|
---|
105 | if (signed) {
|
---|
106 | break;
|
---|
107 | }
|
---|
108 | signed = true;
|
---|
109 | str += char;
|
---|
110 | }
|
---|
111 | else if (char == '0' ||
|
---|
112 | char == '1' ||
|
---|
113 | char == '2' ||
|
---|
114 | char == '3' ||
|
---|
115 | char == '4' ||
|
---|
116 | char == '5' ||
|
---|
117 | char == '6' ||
|
---|
118 | char == '7' ||
|
---|
119 | char == '8' ||
|
---|
120 | char == '9') {
|
---|
121 | signed = true;
|
---|
122 | str += char;
|
---|
123 | }
|
---|
124 | else {
|
---|
125 | break;
|
---|
126 | }
|
---|
127 | }
|
---|
128 | // We're done reading this number.
|
---|
129 | context.position = context.previous;
|
---|
130 | return {
|
---|
131 | kind: 'number',
|
---|
132 | start,
|
---|
133 | end: context.position,
|
---|
134 | text: context.original.substring(start.offset, context.position.offset),
|
---|
135 | value: Number.parseFloat(str),
|
---|
136 | comments: comments,
|
---|
137 | };
|
---|
138 | }
|
---|
139 | /**
|
---|
140 | * Read the hexa part of a 0xBADCAFE hexadecimal number.
|
---|
141 | * @private
|
---|
142 | */
|
---|
143 | function _readHexaNumber(context, isNegative, start, comments) {
|
---|
144 | // Read an hexadecimal number, until it's not hexadecimal.
|
---|
145 | let hexa = '';
|
---|
146 | const valid = '0123456789abcdefABCDEF';
|
---|
147 | for (let ch = _peek(context); ch && valid.includes(ch); ch = _peek(context)) {
|
---|
148 | // Add it to the hexa string.
|
---|
149 | hexa += ch;
|
---|
150 | // Move the position of the context to the next character.
|
---|
151 | _next(context);
|
---|
152 | }
|
---|
153 | const value = Number.parseInt(hexa, 16);
|
---|
154 | // We're done reading this number.
|
---|
155 | return {
|
---|
156 | kind: 'number',
|
---|
157 | start,
|
---|
158 | end: context.position,
|
---|
159 | text: context.original.substring(start.offset, context.position.offset),
|
---|
160 | value: isNegative ? -value : value,
|
---|
161 | comments,
|
---|
162 | };
|
---|
163 | }
|
---|
164 | /**
|
---|
165 | * Read a number from the context.
|
---|
166 | * @private
|
---|
167 | */
|
---|
168 | function _readNumber(context, comments = _readBlanks(context)) {
|
---|
169 | let str = '';
|
---|
170 | let dotted = false;
|
---|
171 | const start = context.position;
|
---|
172 | // read until `e` or end of line.
|
---|
173 | while (true) {
|
---|
174 | const char = _token(context);
|
---|
175 | // Read tokens, one by one.
|
---|
176 | if (char == '-') {
|
---|
177 | if (str != '') {
|
---|
178 | throw new InvalidJsonCharacterException(context);
|
---|
179 | }
|
---|
180 | }
|
---|
181 | else if (char == 'I' &&
|
---|
182 | (str == '-' || str == '' || str == '+') &&
|
---|
183 | (context.mode & JsonParseMode.NumberConstantsAllowed) != 0) {
|
---|
184 | // Infinity?
|
---|
185 | // _token(context, 'I'); Already read.
|
---|
186 | _token(context, 'n');
|
---|
187 | _token(context, 'f');
|
---|
188 | _token(context, 'i');
|
---|
189 | _token(context, 'n');
|
---|
190 | _token(context, 'i');
|
---|
191 | _token(context, 't');
|
---|
192 | _token(context, 'y');
|
---|
193 | str += 'Infinity';
|
---|
194 | break;
|
---|
195 | }
|
---|
196 | else if (char == '0') {
|
---|
197 | if (str == '0' || str == '-0') {
|
---|
198 | throw new InvalidJsonCharacterException(context);
|
---|
199 | }
|
---|
200 | }
|
---|
201 | else if (char == '1' ||
|
---|
202 | char == '2' ||
|
---|
203 | char == '3' ||
|
---|
204 | char == '4' ||
|
---|
205 | char == '5' ||
|
---|
206 | char == '6' ||
|
---|
207 | char == '7' ||
|
---|
208 | char == '8' ||
|
---|
209 | char == '9') {
|
---|
210 | if (str == '0' || str == '-0') {
|
---|
211 | throw new InvalidJsonCharacterException(context);
|
---|
212 | }
|
---|
213 | }
|
---|
214 | else if (char == '+' && str == '') {
|
---|
215 | // Pass over.
|
---|
216 | }
|
---|
217 | else if (char == '.') {
|
---|
218 | if (dotted) {
|
---|
219 | throw new InvalidJsonCharacterException(context);
|
---|
220 | }
|
---|
221 | dotted = true;
|
---|
222 | }
|
---|
223 | else if (char == 'e' || char == 'E') {
|
---|
224 | return _readExpNumber(context, start, str + char, comments);
|
---|
225 | }
|
---|
226 | else if (char == 'x' &&
|
---|
227 | (str == '0' || str == '-0') &&
|
---|
228 | (context.mode & JsonParseMode.HexadecimalNumberAllowed) != 0) {
|
---|
229 | return _readHexaNumber(context, str == '-0', start, comments);
|
---|
230 | }
|
---|
231 | else {
|
---|
232 | // We read one too many characters, so rollback the last character.
|
---|
233 | context.position = context.previous;
|
---|
234 | break;
|
---|
235 | }
|
---|
236 | str += char;
|
---|
237 | }
|
---|
238 | // We're done reading this number.
|
---|
239 | if (str.endsWith('.') && (context.mode & JsonParseMode.HexadecimalNumberAllowed) == 0) {
|
---|
240 | throw new InvalidJsonCharacterException(context);
|
---|
241 | }
|
---|
242 | return {
|
---|
243 | kind: 'number',
|
---|
244 | start,
|
---|
245 | end: context.position,
|
---|
246 | text: context.original.substring(start.offset, context.position.offset),
|
---|
247 | value: Number.parseFloat(str),
|
---|
248 | comments,
|
---|
249 | };
|
---|
250 | }
|
---|
251 | /**
|
---|
252 | * Read a string from the context. Takes the comments of the string or read the blanks before the
|
---|
253 | * string.
|
---|
254 | * @private
|
---|
255 | */
|
---|
256 | function _readString(context, comments = _readBlanks(context)) {
|
---|
257 | const start = context.position;
|
---|
258 | // Consume the first string delimiter.
|
---|
259 | const delim = _token(context);
|
---|
260 | if ((context.mode & JsonParseMode.SingleQuotesAllowed) == 0) {
|
---|
261 | if (delim == "'") {
|
---|
262 | throw new InvalidJsonCharacterException(context);
|
---|
263 | }
|
---|
264 | }
|
---|
265 | let str = '';
|
---|
266 | while (true) {
|
---|
267 | let char = _token(context);
|
---|
268 | if (char == delim) {
|
---|
269 | return {
|
---|
270 | kind: 'string',
|
---|
271 | start,
|
---|
272 | end: context.position,
|
---|
273 | text: context.original.substring(start.offset, context.position.offset),
|
---|
274 | value: str,
|
---|
275 | comments: comments,
|
---|
276 | };
|
---|
277 | }
|
---|
278 | else if (char == '\\') {
|
---|
279 | char = _token(context);
|
---|
280 | switch (char) {
|
---|
281 | case '\\':
|
---|
282 | case '/':
|
---|
283 | case '"':
|
---|
284 | case delim:
|
---|
285 | str += char;
|
---|
286 | break;
|
---|
287 | case 'b':
|
---|
288 | str += '\b';
|
---|
289 | break;
|
---|
290 | case 'f':
|
---|
291 | str += '\f';
|
---|
292 | break;
|
---|
293 | case 'n':
|
---|
294 | str += '\n';
|
---|
295 | break;
|
---|
296 | case 'r':
|
---|
297 | str += '\r';
|
---|
298 | break;
|
---|
299 | case 't':
|
---|
300 | str += '\t';
|
---|
301 | break;
|
---|
302 | case 'u':
|
---|
303 | const [c0] = _token(context, '0123456789abcdefABCDEF');
|
---|
304 | const [c1] = _token(context, '0123456789abcdefABCDEF');
|
---|
305 | const [c2] = _token(context, '0123456789abcdefABCDEF');
|
---|
306 | const [c3] = _token(context, '0123456789abcdefABCDEF');
|
---|
307 | str += String.fromCharCode(parseInt(c0 + c1 + c2 + c3, 16));
|
---|
308 | break;
|
---|
309 | case undefined:
|
---|
310 | throw new UnexpectedEndOfInputException(context);
|
---|
311 | case '\n':
|
---|
312 | // Only valid when multiline strings are allowed.
|
---|
313 | if ((context.mode & JsonParseMode.MultiLineStringAllowed) == 0) {
|
---|
314 | throw new InvalidJsonCharacterException(context);
|
---|
315 | }
|
---|
316 | str += char;
|
---|
317 | break;
|
---|
318 | default:
|
---|
319 | throw new InvalidJsonCharacterException(context);
|
---|
320 | }
|
---|
321 | }
|
---|
322 | else if (char === undefined) {
|
---|
323 | throw new UnexpectedEndOfInputException(context);
|
---|
324 | }
|
---|
325 | else if (char == '\b' || char == '\f' || char == '\n' || char == '\r' || char == '\t') {
|
---|
326 | throw new InvalidJsonCharacterException(context);
|
---|
327 | }
|
---|
328 | else {
|
---|
329 | str += char;
|
---|
330 | }
|
---|
331 | }
|
---|
332 | }
|
---|
333 | /**
|
---|
334 | * Read the constant `true` from the context.
|
---|
335 | * @private
|
---|
336 | */
|
---|
337 | function _readTrue(context, comments = _readBlanks(context)) {
|
---|
338 | const start = context.position;
|
---|
339 | _token(context, 't');
|
---|
340 | _token(context, 'r');
|
---|
341 | _token(context, 'u');
|
---|
342 | _token(context, 'e');
|
---|
343 | const end = context.position;
|
---|
344 | return {
|
---|
345 | kind: 'true',
|
---|
346 | start,
|
---|
347 | end,
|
---|
348 | text: context.original.substring(start.offset, end.offset),
|
---|
349 | value: true,
|
---|
350 | comments,
|
---|
351 | };
|
---|
352 | }
|
---|
353 | /**
|
---|
354 | * Read the constant `false` from the context.
|
---|
355 | * @private
|
---|
356 | */
|
---|
357 | function _readFalse(context, comments = _readBlanks(context)) {
|
---|
358 | const start = context.position;
|
---|
359 | _token(context, 'f');
|
---|
360 | _token(context, 'a');
|
---|
361 | _token(context, 'l');
|
---|
362 | _token(context, 's');
|
---|
363 | _token(context, 'e');
|
---|
364 | const end = context.position;
|
---|
365 | return {
|
---|
366 | kind: 'false',
|
---|
367 | start,
|
---|
368 | end,
|
---|
369 | text: context.original.substring(start.offset, end.offset),
|
---|
370 | value: false,
|
---|
371 | comments,
|
---|
372 | };
|
---|
373 | }
|
---|
374 | /**
|
---|
375 | * Read the constant `null` from the context.
|
---|
376 | * @private
|
---|
377 | */
|
---|
378 | function _readNull(context, comments = _readBlanks(context)) {
|
---|
379 | const start = context.position;
|
---|
380 | _token(context, 'n');
|
---|
381 | _token(context, 'u');
|
---|
382 | _token(context, 'l');
|
---|
383 | _token(context, 'l');
|
---|
384 | const end = context.position;
|
---|
385 | return {
|
---|
386 | kind: 'null',
|
---|
387 | start,
|
---|
388 | end,
|
---|
389 | text: context.original.substring(start.offset, end.offset),
|
---|
390 | value: null,
|
---|
391 | comments: comments,
|
---|
392 | };
|
---|
393 | }
|
---|
394 | /**
|
---|
395 | * Read the constant `NaN` from the context.
|
---|
396 | * @private
|
---|
397 | */
|
---|
398 | function _readNaN(context, comments = _readBlanks(context)) {
|
---|
399 | const start = context.position;
|
---|
400 | _token(context, 'N');
|
---|
401 | _token(context, 'a');
|
---|
402 | _token(context, 'N');
|
---|
403 | const end = context.position;
|
---|
404 | return {
|
---|
405 | kind: 'number',
|
---|
406 | start,
|
---|
407 | end,
|
---|
408 | text: context.original.substring(start.offset, end.offset),
|
---|
409 | value: NaN,
|
---|
410 | comments: comments,
|
---|
411 | };
|
---|
412 | }
|
---|
413 | /**
|
---|
414 | * Read an array of JSON values from the context.
|
---|
415 | * @private
|
---|
416 | */
|
---|
417 | function _readArray(context, comments = _readBlanks(context)) {
|
---|
418 | const start = context.position;
|
---|
419 | // Consume the first delimiter.
|
---|
420 | _token(context, '[');
|
---|
421 | const value = [];
|
---|
422 | const elements = [];
|
---|
423 | _readBlanks(context);
|
---|
424 | if (_peek(context) != ']') {
|
---|
425 | const node = _readValue(context);
|
---|
426 | elements.push(node);
|
---|
427 | value.push(node.value);
|
---|
428 | }
|
---|
429 | while (_peek(context) != ']') {
|
---|
430 | _token(context, ',');
|
---|
431 | const valueComments = _readBlanks(context);
|
---|
432 | if ((context.mode & JsonParseMode.TrailingCommasAllowed) !== 0 && _peek(context) === ']') {
|
---|
433 | break;
|
---|
434 | }
|
---|
435 | const node = _readValue(context, valueComments);
|
---|
436 | elements.push(node);
|
---|
437 | value.push(node.value);
|
---|
438 | }
|
---|
439 | _token(context, ']');
|
---|
440 | return {
|
---|
441 | kind: 'array',
|
---|
442 | start,
|
---|
443 | end: context.position,
|
---|
444 | text: context.original.substring(start.offset, context.position.offset),
|
---|
445 | value,
|
---|
446 | elements,
|
---|
447 | comments,
|
---|
448 | };
|
---|
449 | }
|
---|
450 | /**
|
---|
451 | * Read an identifier from the context. An identifier is a valid JavaScript identifier, and this
|
---|
452 | * function is only used in Loose mode.
|
---|
453 | * @private
|
---|
454 | */
|
---|
455 | function _readIdentifier(context, comments = _readBlanks(context)) {
|
---|
456 | const start = context.position;
|
---|
457 | let char = _peek(context);
|
---|
458 | if (char && '0123456789'.indexOf(char) != -1) {
|
---|
459 | const identifierNode = _readNumber(context);
|
---|
460 | return {
|
---|
461 | kind: 'identifier',
|
---|
462 | start,
|
---|
463 | end: identifierNode.end,
|
---|
464 | text: identifierNode.text,
|
---|
465 | value: identifierNode.value.toString(),
|
---|
466 | };
|
---|
467 | }
|
---|
468 | const identValidFirstChar = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMOPQRSTUVWXYZ';
|
---|
469 | const identValidChar = '_$abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMOPQRSTUVWXYZ0123456789';
|
---|
470 | let first = true;
|
---|
471 | let value = '';
|
---|
472 | while (true) {
|
---|
473 | char = _token(context);
|
---|
474 | if (char == undefined ||
|
---|
475 | (first ? identValidFirstChar.indexOf(char) : identValidChar.indexOf(char)) == -1) {
|
---|
476 | context.position = context.previous;
|
---|
477 | return {
|
---|
478 | kind: 'identifier',
|
---|
479 | start,
|
---|
480 | end: context.position,
|
---|
481 | text: context.original.substr(start.offset, context.position.offset),
|
---|
482 | value,
|
---|
483 | comments,
|
---|
484 | };
|
---|
485 | }
|
---|
486 | value += char;
|
---|
487 | first = false;
|
---|
488 | }
|
---|
489 | }
|
---|
490 | /**
|
---|
491 | * Read a property from the context. A property is a string or (in Loose mode only) a number or
|
---|
492 | * an identifier, followed by a colon `:`.
|
---|
493 | * @private
|
---|
494 | */
|
---|
495 | function _readProperty(context, comments = _readBlanks(context)) {
|
---|
496 | const start = context.position;
|
---|
497 | let key;
|
---|
498 | if ((context.mode & JsonParseMode.IdentifierKeyNamesAllowed) != 0) {
|
---|
499 | const top = _peek(context);
|
---|
500 | if (top == '"' || top == "'") {
|
---|
501 | key = _readString(context);
|
---|
502 | }
|
---|
503 | else {
|
---|
504 | key = _readIdentifier(context);
|
---|
505 | }
|
---|
506 | }
|
---|
507 | else {
|
---|
508 | key = _readString(context);
|
---|
509 | }
|
---|
510 | _readBlanks(context);
|
---|
511 | _token(context, ':');
|
---|
512 | const value = _readValue(context);
|
---|
513 | const end = context.position;
|
---|
514 | return {
|
---|
515 | kind: 'keyvalue',
|
---|
516 | key,
|
---|
517 | value,
|
---|
518 | start,
|
---|
519 | end,
|
---|
520 | text: context.original.substring(start.offset, end.offset),
|
---|
521 | comments,
|
---|
522 | };
|
---|
523 | }
|
---|
524 | /**
|
---|
525 | * Read an object of properties -> JSON values from the context.
|
---|
526 | * @private
|
---|
527 | */
|
---|
528 | function _readObject(context, comments = _readBlanks(context)) {
|
---|
529 | const start = context.position;
|
---|
530 | // Consume the first delimiter.
|
---|
531 | _token(context, '{');
|
---|
532 | const value = {};
|
---|
533 | const properties = [];
|
---|
534 | _readBlanks(context);
|
---|
535 | if (_peek(context) != '}') {
|
---|
536 | const property = _readProperty(context);
|
---|
537 | value[property.key.value] = property.value.value;
|
---|
538 | properties.push(property);
|
---|
539 | while (_peek(context) != '}') {
|
---|
540 | _token(context, ',');
|
---|
541 | const propertyComments = _readBlanks(context);
|
---|
542 | if ((context.mode & JsonParseMode.TrailingCommasAllowed) !== 0 && _peek(context) === '}') {
|
---|
543 | break;
|
---|
544 | }
|
---|
545 | const property = _readProperty(context, propertyComments);
|
---|
546 | value[property.key.value] = property.value.value;
|
---|
547 | properties.push(property);
|
---|
548 | }
|
---|
549 | }
|
---|
550 | _token(context, '}');
|
---|
551 | return {
|
---|
552 | kind: 'object',
|
---|
553 | properties,
|
---|
554 | start,
|
---|
555 | end: context.position,
|
---|
556 | value,
|
---|
557 | text: context.original.substring(start.offset, context.position.offset),
|
---|
558 | comments,
|
---|
559 | };
|
---|
560 | }
|
---|
561 | /**
|
---|
562 | * Remove any blank character or comments (in Loose mode) from the context, returning an array
|
---|
563 | * of comments if any are found.
|
---|
564 | * @private
|
---|
565 | */
|
---|
566 | function _readBlanks(context) {
|
---|
567 | if ((context.mode & JsonParseMode.CommentsAllowed) != 0) {
|
---|
568 | const comments = [];
|
---|
569 | while (true) {
|
---|
570 | const char = context.original[context.position.offset];
|
---|
571 | if (char == '/' && context.original[context.position.offset + 1] == '*') {
|
---|
572 | const start = context.position;
|
---|
573 | // Multi line comment.
|
---|
574 | _next(context);
|
---|
575 | _next(context);
|
---|
576 | while (context.original[context.position.offset] != '*' ||
|
---|
577 | context.original[context.position.offset + 1] != '/') {
|
---|
578 | _next(context);
|
---|
579 | if (context.position.offset >= context.original.length) {
|
---|
580 | throw new UnexpectedEndOfInputException(context);
|
---|
581 | }
|
---|
582 | }
|
---|
583 | // Remove "*/".
|
---|
584 | _next(context);
|
---|
585 | _next(context);
|
---|
586 | comments.push({
|
---|
587 | kind: 'multicomment',
|
---|
588 | start,
|
---|
589 | end: context.position,
|
---|
590 | text: context.original.substring(start.offset, context.position.offset),
|
---|
591 | content: context.original.substring(start.offset + 2, context.position.offset - 2),
|
---|
592 | });
|
---|
593 | }
|
---|
594 | else if (char == '/' && context.original[context.position.offset + 1] == '/') {
|
---|
595 | const start = context.position;
|
---|
596 | // Multi line comment.
|
---|
597 | _next(context);
|
---|
598 | _next(context);
|
---|
599 | while (context.original[context.position.offset] != '\n') {
|
---|
600 | _next(context);
|
---|
601 | if (context.position.offset >= context.original.length) {
|
---|
602 | break;
|
---|
603 | }
|
---|
604 | }
|
---|
605 | // Remove "\n".
|
---|
606 | if (context.position.offset < context.original.length) {
|
---|
607 | _next(context);
|
---|
608 | }
|
---|
609 | comments.push({
|
---|
610 | kind: 'comment',
|
---|
611 | start,
|
---|
612 | end: context.position,
|
---|
613 | text: context.original.substring(start.offset, context.position.offset),
|
---|
614 | content: context.original.substring(start.offset + 2, context.position.offset - 1),
|
---|
615 | });
|
---|
616 | }
|
---|
617 | else if (char == ' ' || char == '\t' || char == '\n' || char == '\r' || char == '\f') {
|
---|
618 | _next(context);
|
---|
619 | }
|
---|
620 | else {
|
---|
621 | break;
|
---|
622 | }
|
---|
623 | }
|
---|
624 | return comments;
|
---|
625 | }
|
---|
626 | else {
|
---|
627 | let char = context.original[context.position.offset];
|
---|
628 | while (char == ' ' || char == '\t' || char == '\n' || char == '\r' || char == '\f') {
|
---|
629 | _next(context);
|
---|
630 | char = context.original[context.position.offset];
|
---|
631 | }
|
---|
632 | return [];
|
---|
633 | }
|
---|
634 | }
|
---|
635 | /**
|
---|
636 | * Read a JSON value from the context, which can be any form of JSON value.
|
---|
637 | * @private
|
---|
638 | */
|
---|
639 | function _readValue(context, comments = _readBlanks(context)) {
|
---|
640 | let result;
|
---|
641 | // Clean up before.
|
---|
642 | const char = _peek(context);
|
---|
643 | switch (char) {
|
---|
644 | case undefined:
|
---|
645 | throw new UnexpectedEndOfInputException(context);
|
---|
646 | case '-':
|
---|
647 | case '0':
|
---|
648 | case '1':
|
---|
649 | case '2':
|
---|
650 | case '3':
|
---|
651 | case '4':
|
---|
652 | case '5':
|
---|
653 | case '6':
|
---|
654 | case '7':
|
---|
655 | case '8':
|
---|
656 | case '9':
|
---|
657 | result = _readNumber(context, comments);
|
---|
658 | break;
|
---|
659 | case '.':
|
---|
660 | case '+':
|
---|
661 | if ((context.mode & JsonParseMode.LaxNumberParsingAllowed) == 0) {
|
---|
662 | throw new InvalidJsonCharacterException(context);
|
---|
663 | }
|
---|
664 | result = _readNumber(context, comments);
|
---|
665 | break;
|
---|
666 | case "'":
|
---|
667 | case '"':
|
---|
668 | result = _readString(context, comments);
|
---|
669 | break;
|
---|
670 | case 'I':
|
---|
671 | if ((context.mode & JsonParseMode.NumberConstantsAllowed) == 0) {
|
---|
672 | throw new InvalidJsonCharacterException(context);
|
---|
673 | }
|
---|
674 | result = _readNumber(context, comments);
|
---|
675 | break;
|
---|
676 | case 'N':
|
---|
677 | if ((context.mode & JsonParseMode.NumberConstantsAllowed) == 0) {
|
---|
678 | throw new InvalidJsonCharacterException(context);
|
---|
679 | }
|
---|
680 | result = _readNaN(context, comments);
|
---|
681 | break;
|
---|
682 | case 't':
|
---|
683 | result = _readTrue(context, comments);
|
---|
684 | break;
|
---|
685 | case 'f':
|
---|
686 | result = _readFalse(context, comments);
|
---|
687 | break;
|
---|
688 | case 'n':
|
---|
689 | result = _readNull(context, comments);
|
---|
690 | break;
|
---|
691 | case '[':
|
---|
692 | result = _readArray(context, comments);
|
---|
693 | break;
|
---|
694 | case '{':
|
---|
695 | result = _readObject(context, comments);
|
---|
696 | break;
|
---|
697 | default:
|
---|
698 | throw new InvalidJsonCharacterException(context);
|
---|
699 | }
|
---|
700 | // Clean up after.
|
---|
701 | _readBlanks(context);
|
---|
702 | return result;
|
---|
703 | }
|
---|
704 | /**
|
---|
705 | * The Parse mode used for parsing the JSON string.
|
---|
706 | */
|
---|
707 | var JsonParseMode;
|
---|
708 | (function (JsonParseMode) {
|
---|
709 | JsonParseMode[JsonParseMode["Strict"] = 0] = "Strict";
|
---|
710 | JsonParseMode[JsonParseMode["CommentsAllowed"] = 1] = "CommentsAllowed";
|
---|
711 | JsonParseMode[JsonParseMode["SingleQuotesAllowed"] = 2] = "SingleQuotesAllowed";
|
---|
712 | JsonParseMode[JsonParseMode["IdentifierKeyNamesAllowed"] = 4] = "IdentifierKeyNamesAllowed";
|
---|
713 | JsonParseMode[JsonParseMode["TrailingCommasAllowed"] = 8] = "TrailingCommasAllowed";
|
---|
714 | JsonParseMode[JsonParseMode["HexadecimalNumberAllowed"] = 16] = "HexadecimalNumberAllowed";
|
---|
715 | JsonParseMode[JsonParseMode["MultiLineStringAllowed"] = 32] = "MultiLineStringAllowed";
|
---|
716 | JsonParseMode[JsonParseMode["LaxNumberParsingAllowed"] = 64] = "LaxNumberParsingAllowed";
|
---|
717 | JsonParseMode[JsonParseMode["NumberConstantsAllowed"] = 128] = "NumberConstantsAllowed";
|
---|
718 | JsonParseMode[JsonParseMode["Default"] = 0] = "Default";
|
---|
719 | JsonParseMode[JsonParseMode["Loose"] = 255] = "Loose";
|
---|
720 | JsonParseMode[JsonParseMode["Json"] = 0] = "Json";
|
---|
721 | JsonParseMode[JsonParseMode["Json5"] = 255] = "Json5";
|
---|
722 | })(JsonParseMode = exports.JsonParseMode || (exports.JsonParseMode = {}));
|
---|
723 | /**
|
---|
724 | * Parse the JSON string and return its AST. The AST may be losing data (end comments are
|
---|
725 | * discarded for example, and space characters are not represented in the AST), but all values
|
---|
726 | * will have a single node in the AST (a 1-to-1 mapping).
|
---|
727 | *
|
---|
728 | * @deprecated Deprecated since version 11. Use 3rd party JSON parsers such as `jsonc-parser` instead.
|
---|
729 | * @param input The string to use.
|
---|
730 | * @param mode The mode to parse the input with. {@see JsonParseMode}.
|
---|
731 | * @returns {JsonAstNode} The root node of the value of the AST.
|
---|
732 | */
|
---|
733 | function parseJsonAst(input, mode = JsonParseMode.Default) {
|
---|
734 | if (mode == JsonParseMode.Default) {
|
---|
735 | mode = JsonParseMode.Strict;
|
---|
736 | }
|
---|
737 | const context = {
|
---|
738 | position: { offset: 0, line: 0, character: 0 },
|
---|
739 | previous: { offset: 0, line: 0, character: 0 },
|
---|
740 | original: input,
|
---|
741 | comments: undefined,
|
---|
742 | mode,
|
---|
743 | };
|
---|
744 | const ast = _readValue(context);
|
---|
745 | if (context.position.offset < input.length) {
|
---|
746 | const rest = input.substr(context.position.offset);
|
---|
747 | const i = rest.length > 20 ? rest.substr(0, 20) + '...' : rest;
|
---|
748 | throw new Error(`Expected end of file, got "${i}" at ` +
|
---|
749 | `${context.position.line}:${context.position.character}.`);
|
---|
750 | }
|
---|
751 | return ast;
|
---|
752 | }
|
---|
753 | exports.parseJsonAst = parseJsonAst;
|
---|
754 | /**
|
---|
755 | * Parse a JSON string into its value. This discards the AST and only returns the value itself.
|
---|
756 | *
|
---|
757 | * If a path option is pass, it also absorbs JSON parsing errors and return a new error with the
|
---|
758 | * path in it. Useful for showing errors when parsing from a file.
|
---|
759 | *
|
---|
760 | * @deprecated Deprecated since version 11. Use 3rd party JSON parsers such as `jsonc-parser` instead.
|
---|
761 | * @param input The string to parse.
|
---|
762 | * @param mode The mode to parse the input with. {@see JsonParseMode}.
|
---|
763 | * @param options Additional optinos for parsing.
|
---|
764 | * @returns {JsonValue} The value represented by the JSON string.
|
---|
765 | */
|
---|
766 | function parseJson(input, mode = JsonParseMode.Default, options) {
|
---|
767 | try {
|
---|
768 | // Try parsing for the fastest path available, if error, uses our own parser for better errors.
|
---|
769 | if (mode == JsonParseMode.Strict) {
|
---|
770 | try {
|
---|
771 | return JSON.parse(input);
|
---|
772 | }
|
---|
773 | catch (err) {
|
---|
774 | return parseJsonAst(input, mode).value;
|
---|
775 | }
|
---|
776 | }
|
---|
777 | return parseJsonAst(input, mode).value;
|
---|
778 | }
|
---|
779 | catch (e) {
|
---|
780 | if (options && options.path && e instanceof JsonException) {
|
---|
781 | throw new PathSpecificJsonException(options.path, e);
|
---|
782 | }
|
---|
783 | throw e;
|
---|
784 | }
|
---|
785 | }
|
---|
786 | exports.parseJson = parseJson;
|
---|