[6a3a178] | 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;
|
---|