1 /*---------------------------------------------------------------------------------------------
2 * Copyright (c) Microsoft Corporation. All rights reserved.
3 * Licensed under the MIT License. See License.txt in the project root for license information.
4 *--------------------------------------------------------------------------------------------*/
6 import { createScanner } from './scanner';
8 (function (ParseOptions) {
9 ParseOptions.DEFAULT = {
10 allowTrailingComma: false
12 })(ParseOptions || (ParseOptions = {}));
14 * For a given offset, evaluate the location in the JSON document. Each segment in the location path is either a property name or an array index.
16 export function getLocation(text, position) {
17 var segments = []; // strings or numbers
18 var earlyReturnException = new Object();
19 var previousNode = undefined;
20 var previousNodeInst = {
27 var isAtPropertyKey = false;
28 function setPreviousNode(value, offset, length, type) {
29 previousNodeInst.value = value;
30 previousNodeInst.offset = offset;
31 previousNodeInst.length = length;
32 previousNodeInst.type = type;
33 previousNodeInst.colonOffset = undefined;
34 previousNode = previousNodeInst;
38 onObjectBegin: function (offset, length) {
39 if (position <= offset) {
40 throw earlyReturnException;
42 previousNode = undefined;
43 isAtPropertyKey = position > offset;
44 segments.push(''); // push a placeholder (will be replaced)
46 onObjectProperty: function (name, offset, length) {
47 if (position < offset) {
48 throw earlyReturnException;
50 setPreviousNode(name, offset, length, 'property');
51 segments[segments.length - 1] = name;
52 if (position <= offset + length) {
53 throw earlyReturnException;
56 onObjectEnd: function (offset, length) {
57 if (position <= offset) {
58 throw earlyReturnException;
60 previousNode = undefined;
63 onArrayBegin: function (offset, length) {
64 if (position <= offset) {
65 throw earlyReturnException;
67 previousNode = undefined;
70 onArrayEnd: function (offset, length) {
71 if (position <= offset) {
72 throw earlyReturnException;
74 previousNode = undefined;
77 onLiteralValue: function (value, offset, length) {
78 if (position < offset) {
79 throw earlyReturnException;
81 setPreviousNode(value, offset, length, getNodeType(value));
82 if (position <= offset + length) {
83 throw earlyReturnException;
86 onSeparator: function (sep, offset, length) {
87 if (position <= offset) {
88 throw earlyReturnException;
90 if (sep === ':' && previousNode && previousNode.type === 'property') {
91 previousNode.colonOffset = offset;
92 isAtPropertyKey = false;
93 previousNode = undefined;
95 else if (sep === ',') {
96 var last = segments[segments.length - 1];
97 if (typeof last === 'number') {
98 segments[segments.length - 1] = last + 1;
101 isAtPropertyKey = true;
102 segments[segments.length - 1] = '';
104 previousNode = undefined;
110 if (e !== earlyReturnException) {
116 previousNode: previousNode,
117 isAtPropertyKey: isAtPropertyKey,
118 matches: function (pattern) {
120 for (var i = 0; k < pattern.length && i < segments.length; i++) {
121 if (pattern[k] === segments[i] || pattern[k] === '*') {
124 else if (pattern[k] !== '**') {
128 return k === pattern.length;
133 * Parses the given text and returns the object the JSON content represents. On invalid input, the parser tries to be as fault tolerant as possible, but still return a result.
134 * Therefore always check the errors list to find out if the input was valid.
136 export function parse(text, errors, options) {
137 if (errors === void 0) { errors = []; }
138 if (options === void 0) { options = ParseOptions.DEFAULT; }
139 var currentProperty = null;
140 var currentParent = [];
141 var previousParents = [];
142 function onValue(value) {
143 if (Array.isArray(currentParent)) {
144 currentParent.push(value);
146 else if (currentProperty !== null) {
147 currentParent[currentProperty] = value;
151 onObjectBegin: function () {
154 previousParents.push(currentParent);
155 currentParent = object;
156 currentProperty = null;
158 onObjectProperty: function (name) {
159 currentProperty = name;
161 onObjectEnd: function () {
162 currentParent = previousParents.pop();
164 onArrayBegin: function () {
167 previousParents.push(currentParent);
168 currentParent = array;
169 currentProperty = null;
171 onArrayEnd: function () {
172 currentParent = previousParents.pop();
174 onLiteralValue: onValue,
175 onError: function (error, offset, length) {
176 errors.push({ error: error, offset: offset, length: length });
179 visit(text, visitor, options);
180 return currentParent[0];
183 * Parses the given text and returns a tree representation the JSON content. On invalid input, the parser tries to be as fault tolerant as possible, but still return a result.
185 export function parseTree(text, errors, options) {
186 if (errors === void 0) { errors = []; }
187 if (options === void 0) { options = ParseOptions.DEFAULT; }
188 var currentParent = { type: 'array', offset: -1, length: -1, children: [], parent: undefined }; // artificial root
189 function ensurePropertyComplete(endOffset) {
190 if (currentParent.type === 'property') {
191 currentParent.length = endOffset - currentParent.offset;
192 currentParent = currentParent.parent;
195 function onValue(valueNode) {
196 currentParent.children.push(valueNode);
200 onObjectBegin: function (offset) {
201 currentParent = onValue({ type: 'object', offset: offset, length: -1, parent: currentParent, children: [] });
203 onObjectProperty: function (name, offset, length) {
204 currentParent = onValue({ type: 'property', offset: offset, length: -1, parent: currentParent, children: [] });
205 currentParent.children.push({ type: 'string', value: name, offset: offset, length: length, parent: currentParent });
207 onObjectEnd: function (offset, length) {
208 ensurePropertyComplete(offset + length); // in case of a missing value for a property: make sure property is complete
209 currentParent.length = offset + length - currentParent.offset;
210 currentParent = currentParent.parent;
211 ensurePropertyComplete(offset + length);
213 onArrayBegin: function (offset, length) {
214 currentParent = onValue({ type: 'array', offset: offset, length: -1, parent: currentParent, children: [] });
216 onArrayEnd: function (offset, length) {
217 currentParent.length = offset + length - currentParent.offset;
218 currentParent = currentParent.parent;
219 ensurePropertyComplete(offset + length);
221 onLiteralValue: function (value, offset, length) {
222 onValue({ type: getNodeType(value), offset: offset, length: length, parent: currentParent, value: value });
223 ensurePropertyComplete(offset + length);
225 onSeparator: function (sep, offset, length) {
226 if (currentParent.type === 'property') {
228 currentParent.colonOffset = offset;
230 else if (sep === ',') {
231 ensurePropertyComplete(offset);
235 onError: function (error, offset, length) {
236 errors.push({ error: error, offset: offset, length: length });
239 visit(text, visitor, options);
240 var result = currentParent.children[0];
242 delete result.parent;
247 * Finds the node at the given path in a JSON DOM.
249 export function findNodeAtLocation(root, path) {
254 for (var _i = 0, path_1 = path; _i < path_1.length; _i++) {
255 var segment = path_1[_i];
256 if (typeof segment === 'string') {
257 if (node.type !== 'object' || !Array.isArray(node.children)) {
261 for (var _a = 0, _b = node.children; _a < _b.length; _a++) {
262 var propertyNode = _b[_a];
263 if (Array.isArray(propertyNode.children) && propertyNode.children[0].value === segment) {
264 node = propertyNode.children[1];
275 if (node.type !== 'array' || index < 0 || !Array.isArray(node.children) || index >= node.children.length) {
278 node = node.children[index];
284 * Gets the JSON path of the given JSON DOM node
286 export function getNodePath(node) {
287 if (!node.parent || !node.parent.children) {
290 var path = getNodePath(node.parent);
291 if (node.parent.type === 'property') {
292 var key = node.parent.children[0].value;
295 else if (node.parent.type === 'array') {
296 var index = node.parent.children.indexOf(node);
304 * Evaluates the JavaScript object of the given JSON DOM node
306 export function getNodeValue(node) {
309 return node.children.map(getNodeValue);
311 var obj = Object.create(null);
312 for (var _i = 0, _a = node.children; _i < _a.length; _i++) {
314 var valueNode = prop.children[1];
316 obj[prop.children[0].value] = getNodeValue(valueNode);
329 export function contains(node, offset, includeRightBound) {
330 if (includeRightBound === void 0) { includeRightBound = false; }
331 return (offset >= node.offset && offset < (node.offset + node.length)) || includeRightBound && (offset === (node.offset + node.length));
334 * Finds the most inner node at the given offset. If includeRightBound is set, also finds nodes that end at the given offset.
336 export function findNodeAtOffset(node, offset, includeRightBound) {
337 if (includeRightBound === void 0) { includeRightBound = false; }
338 if (contains(node, offset, includeRightBound)) {
339 var children = node.children;
340 if (Array.isArray(children)) {
341 for (var i = 0; i < children.length && children[i].offset <= offset; i++) {
342 var item = findNodeAtOffset(children[i], offset, includeRightBound);
353 * Parses the given text and invokes the visitor functions for each object, array and literal reached.
355 export function visit(text, visitor, options) {
356 if (options === void 0) { options = ParseOptions.DEFAULT; }
357 var _scanner = createScanner(text, false);
358 function toNoArgVisit(visitFunction) {
359 return visitFunction ? function () { return visitFunction(_scanner.getTokenOffset(), _scanner.getTokenLength(), _scanner.getTokenStartLine(), _scanner.getTokenStartCharacter()); } : function () { return true; };
361 function toOneArgVisit(visitFunction) {
362 return visitFunction ? function (arg) { return visitFunction(arg, _scanner.getTokenOffset(), _scanner.getTokenLength(), _scanner.getTokenStartLine(), _scanner.getTokenStartCharacter()); } : function () { return true; };
364 var onObjectBegin = toNoArgVisit(visitor.onObjectBegin), onObjectProperty = toOneArgVisit(visitor.onObjectProperty), onObjectEnd = toNoArgVisit(visitor.onObjectEnd), onArrayBegin = toNoArgVisit(visitor.onArrayBegin), onArrayEnd = toNoArgVisit(visitor.onArrayEnd), onLiteralValue = toOneArgVisit(visitor.onLiteralValue), onSeparator = toOneArgVisit(visitor.onSeparator), onComment = toNoArgVisit(visitor.onComment), onError = toOneArgVisit(visitor.onError);
365 var disallowComments = options && options.disallowComments;
366 var allowTrailingComma = options && options.allowTrailingComma;
367 function scanNext() {
369 var token = _scanner.scan();
370 switch (_scanner.getTokenError()) {
371 case 4 /* InvalidUnicode */:
372 handleError(14 /* InvalidUnicode */);
374 case 5 /* InvalidEscapeCharacter */:
375 handleError(15 /* InvalidEscapeCharacter */);
377 case 3 /* UnexpectedEndOfNumber */:
378 handleError(13 /* UnexpectedEndOfNumber */);
380 case 1 /* UnexpectedEndOfComment */:
381 if (!disallowComments) {
382 handleError(11 /* UnexpectedEndOfComment */);
385 case 2 /* UnexpectedEndOfString */:
386 handleError(12 /* UnexpectedEndOfString */);
388 case 6 /* InvalidCharacter */:
389 handleError(16 /* InvalidCharacter */);
393 case 12 /* LineCommentTrivia */:
394 case 13 /* BlockCommentTrivia */:
395 if (disallowComments) {
396 handleError(10 /* InvalidCommentToken */);
402 case 16 /* Unknown */:
403 handleError(1 /* InvalidSymbol */);
405 case 15 /* Trivia */:
406 case 14 /* LineBreakTrivia */:
413 function handleError(error, skipUntilAfter, skipUntil) {
414 if (skipUntilAfter === void 0) { skipUntilAfter = []; }
415 if (skipUntil === void 0) { skipUntil = []; }
417 if (skipUntilAfter.length + skipUntil.length > 0) {
418 var token = _scanner.getToken();
419 while (token !== 17 /* EOF */) {
420 if (skipUntilAfter.indexOf(token) !== -1) {
424 else if (skipUntil.indexOf(token) !== -1) {
431 function parseString(isValue) {
432 var value = _scanner.getTokenValue();
434 onLiteralValue(value);
437 onObjectProperty(value);
442 function parseLiteral() {
443 switch (_scanner.getToken()) {
444 case 11 /* NumericLiteral */:
445 var tokenValue = _scanner.getTokenValue();
446 var value = Number(tokenValue);
448 handleError(2 /* InvalidNumberFormat */);
451 onLiteralValue(value);
453 case 7 /* NullKeyword */:
454 onLiteralValue(null);
456 case 8 /* TrueKeyword */:
457 onLiteralValue(true);
459 case 9 /* FalseKeyword */:
460 onLiteralValue(false);
468 function parseProperty() {
469 if (_scanner.getToken() !== 10 /* StringLiteral */) {
470 handleError(3 /* PropertyNameExpected */, [], [2 /* CloseBraceToken */, 5 /* CommaToken */]);
474 if (_scanner.getToken() === 6 /* ColonToken */) {
476 scanNext(); // consume colon
478 handleError(4 /* ValueExpected */, [], [2 /* CloseBraceToken */, 5 /* CommaToken */]);
482 handleError(5 /* ColonExpected */, [], [2 /* CloseBraceToken */, 5 /* CommaToken */]);
486 function parseObject() {
488 scanNext(); // consume open brace
489 var needsComma = false;
490 while (_scanner.getToken() !== 2 /* CloseBraceToken */ && _scanner.getToken() !== 17 /* EOF */) {
491 if (_scanner.getToken() === 5 /* CommaToken */) {
493 handleError(4 /* ValueExpected */, [], []);
496 scanNext(); // consume comma
497 if (_scanner.getToken() === 2 /* CloseBraceToken */ && allowTrailingComma) {
501 else if (needsComma) {
502 handleError(6 /* CommaExpected */, [], []);
504 if (!parseProperty()) {
505 handleError(4 /* ValueExpected */, [], [2 /* CloseBraceToken */, 5 /* CommaToken */]);
510 if (_scanner.getToken() !== 2 /* CloseBraceToken */) {
511 handleError(7 /* CloseBraceExpected */, [2 /* CloseBraceToken */], []);
514 scanNext(); // consume close brace
518 function parseArray() {
520 scanNext(); // consume open bracket
521 var needsComma = false;
522 while (_scanner.getToken() !== 4 /* CloseBracketToken */ && _scanner.getToken() !== 17 /* EOF */) {
523 if (_scanner.getToken() === 5 /* CommaToken */) {
525 handleError(4 /* ValueExpected */, [], []);
528 scanNext(); // consume comma
529 if (_scanner.getToken() === 4 /* CloseBracketToken */ && allowTrailingComma) {
533 else if (needsComma) {
534 handleError(6 /* CommaExpected */, [], []);
537 handleError(4 /* ValueExpected */, [], [4 /* CloseBracketToken */, 5 /* CommaToken */]);
542 if (_scanner.getToken() !== 4 /* CloseBracketToken */) {
543 handleError(8 /* CloseBracketExpected */, [4 /* CloseBracketToken */], []);
546 scanNext(); // consume close bracket
550 function parseValue() {
551 switch (_scanner.getToken()) {
552 case 3 /* OpenBracketToken */:
554 case 1 /* OpenBraceToken */:
555 return parseObject();
556 case 10 /* StringLiteral */:
557 return parseString(true);
559 return parseLiteral();
563 if (_scanner.getToken() === 17 /* EOF */) {
564 if (options.allowEmptyContent) {
567 handleError(4 /* ValueExpected */, [], []);
571 handleError(4 /* ValueExpected */, [], []);
574 if (_scanner.getToken() !== 17 /* EOF */) {
575 handleError(9 /* EndOfFileExpected */, [], []);
580 * Takes JSON with JavaScript-style comments and remove
581 * them. Optionally replaces every none-newline character
582 * of comments with a replaceCharacter
584 export function stripComments(text, replaceCh) {
585 var _scanner = createScanner(text), parts = [], kind, offset = 0, pos;
587 pos = _scanner.getPosition();
588 kind = _scanner.scan();
590 case 12 /* LineCommentTrivia */:
591 case 13 /* BlockCommentTrivia */:
593 if (offset !== pos) {
594 parts.push(text.substring(offset, pos));
596 if (replaceCh !== undefined) {
597 parts.push(_scanner.getTokenValue().replace(/[^\r\n]/g, replaceCh));
599 offset = _scanner.getPosition();
602 } while (kind !== 17 /* EOF */);
603 return parts.join('');
605 export function getNodeType(value) {
606 switch (typeof value) {
607 case 'boolean': return 'boolean';
608 case 'number': return 'number';
609 case 'string': return 'string';
614 else if (Array.isArray(value)) {
619 default: return 'null';