UNPKG

26.9 kBJavaScriptView Raw
1(function (factory) {
2 if (typeof module === "object" && typeof module.exports === "object") {
3 var v = factory(require, exports);
4 if (v !== undefined) module.exports = v;
5 }
6 else if (typeof define === "function" && define.amd) {
7 define(["require", "exports", "./scanner"], factory);
8 }
9})(function (require, exports) {
10 /*---------------------------------------------------------------------------------------------
11 * Copyright (c) Microsoft Corporation. All rights reserved.
12 * Licensed under the MIT License. See License.txt in the project root for license information.
13 *--------------------------------------------------------------------------------------------*/
14 'use strict';
15 Object.defineProperty(exports, "__esModule", { value: true });
16 var scanner_1 = require("./scanner");
17 var ParseOptions;
18 (function (ParseOptions) {
19 ParseOptions.DEFAULT = {
20 allowTrailingComma: false
21 };
22 })(ParseOptions || (ParseOptions = {}));
23 /**
24 * 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.
25 */
26 function getLocation(text, position) {
27 var segments = []; // strings or numbers
28 var earlyReturnException = new Object();
29 var previousNode = undefined;
30 var previousNodeInst = {
31 value: {},
32 offset: 0,
33 length: 0,
34 type: 'object',
35 parent: undefined
36 };
37 var isAtPropertyKey = false;
38 function setPreviousNode(value, offset, length, type) {
39 previousNodeInst.value = value;
40 previousNodeInst.offset = offset;
41 previousNodeInst.length = length;
42 previousNodeInst.type = type;
43 previousNodeInst.colonOffset = undefined;
44 previousNode = previousNodeInst;
45 }
46 try {
47 visit(text, {
48 onObjectBegin: function (offset, length) {
49 if (position <= offset) {
50 throw earlyReturnException;
51 }
52 previousNode = undefined;
53 isAtPropertyKey = position > offset;
54 segments.push(''); // push a placeholder (will be replaced)
55 },
56 onObjectProperty: function (name, offset, length) {
57 if (position < offset) {
58 throw earlyReturnException;
59 }
60 setPreviousNode(name, offset, length, 'property');
61 segments[segments.length - 1] = name;
62 if (position <= offset + length) {
63 throw earlyReturnException;
64 }
65 },
66 onObjectEnd: function (offset, length) {
67 if (position <= offset) {
68 throw earlyReturnException;
69 }
70 previousNode = undefined;
71 segments.pop();
72 },
73 onArrayBegin: function (offset, length) {
74 if (position <= offset) {
75 throw earlyReturnException;
76 }
77 previousNode = undefined;
78 segments.push(0);
79 },
80 onArrayEnd: function (offset, length) {
81 if (position <= offset) {
82 throw earlyReturnException;
83 }
84 previousNode = undefined;
85 segments.pop();
86 },
87 onLiteralValue: function (value, offset, length) {
88 if (position < offset) {
89 throw earlyReturnException;
90 }
91 setPreviousNode(value, offset, length, getNodeType(value));
92 if (position <= offset + length) {
93 throw earlyReturnException;
94 }
95 },
96 onSeparator: function (sep, offset, length) {
97 if (position <= offset) {
98 throw earlyReturnException;
99 }
100 if (sep === ':' && previousNode && previousNode.type === 'property') {
101 previousNode.colonOffset = offset;
102 isAtPropertyKey = false;
103 previousNode = undefined;
104 }
105 else if (sep === ',') {
106 var last = segments[segments.length - 1];
107 if (typeof last === 'number') {
108 segments[segments.length - 1] = last + 1;
109 }
110 else {
111 isAtPropertyKey = true;
112 segments[segments.length - 1] = '';
113 }
114 previousNode = undefined;
115 }
116 }
117 });
118 }
119 catch (e) {
120 if (e !== earlyReturnException) {
121 throw e;
122 }
123 }
124 return {
125 path: segments,
126 previousNode: previousNode,
127 isAtPropertyKey: isAtPropertyKey,
128 matches: function (pattern) {
129 var k = 0;
130 for (var i = 0; k < pattern.length && i < segments.length; i++) {
131 if (pattern[k] === segments[i] || pattern[k] === '*') {
132 k++;
133 }
134 else if (pattern[k] !== '**') {
135 return false;
136 }
137 }
138 return k === pattern.length;
139 }
140 };
141 }
142 exports.getLocation = getLocation;
143 /**
144 * 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.
145 * Therefore always check the errors list to find out if the input was valid.
146 */
147 function parse(text, errors, options) {
148 if (errors === void 0) { errors = []; }
149 if (options === void 0) { options = ParseOptions.DEFAULT; }
150 var currentProperty = null;
151 var currentParent = [];
152 var previousParents = [];
153 function onValue(value) {
154 if (Array.isArray(currentParent)) {
155 currentParent.push(value);
156 }
157 else if (currentProperty !== null) {
158 currentParent[currentProperty] = value;
159 }
160 }
161 var visitor = {
162 onObjectBegin: function () {
163 var object = {};
164 onValue(object);
165 previousParents.push(currentParent);
166 currentParent = object;
167 currentProperty = null;
168 },
169 onObjectProperty: function (name) {
170 currentProperty = name;
171 },
172 onObjectEnd: function () {
173 currentParent = previousParents.pop();
174 },
175 onArrayBegin: function () {
176 var array = [];
177 onValue(array);
178 previousParents.push(currentParent);
179 currentParent = array;
180 currentProperty = null;
181 },
182 onArrayEnd: function () {
183 currentParent = previousParents.pop();
184 },
185 onLiteralValue: onValue,
186 onError: function (error, offset, length) {
187 errors.push({ error: error, offset: offset, length: length });
188 }
189 };
190 visit(text, visitor, options);
191 return currentParent[0];
192 }
193 exports.parse = parse;
194 /**
195 * 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.
196 */
197 function parseTree(text, errors, options) {
198 if (errors === void 0) { errors = []; }
199 if (options === void 0) { options = ParseOptions.DEFAULT; }
200 var currentParent = { type: 'array', offset: -1, length: -1, children: [], parent: undefined }; // artificial root
201 function ensurePropertyComplete(endOffset) {
202 if (currentParent.type === 'property') {
203 currentParent.length = endOffset - currentParent.offset;
204 currentParent = currentParent.parent;
205 }
206 }
207 function onValue(valueNode) {
208 currentParent.children.push(valueNode);
209 return valueNode;
210 }
211 var visitor = {
212 onObjectBegin: function (offset) {
213 currentParent = onValue({ type: 'object', offset: offset, length: -1, parent: currentParent, children: [] });
214 },
215 onObjectProperty: function (name, offset, length) {
216 currentParent = onValue({ type: 'property', offset: offset, length: -1, parent: currentParent, children: [] });
217 currentParent.children.push({ type: 'string', value: name, offset: offset, length: length, parent: currentParent });
218 },
219 onObjectEnd: function (offset, length) {
220 ensurePropertyComplete(offset + length); // in case of a missing value for a property: make sure property is complete
221 currentParent.length = offset + length - currentParent.offset;
222 currentParent = currentParent.parent;
223 ensurePropertyComplete(offset + length);
224 },
225 onArrayBegin: function (offset, length) {
226 currentParent = onValue({ type: 'array', offset: offset, length: -1, parent: currentParent, children: [] });
227 },
228 onArrayEnd: function (offset, length) {
229 currentParent.length = offset + length - currentParent.offset;
230 currentParent = currentParent.parent;
231 ensurePropertyComplete(offset + length);
232 },
233 onLiteralValue: function (value, offset, length) {
234 onValue({ type: getNodeType(value), offset: offset, length: length, parent: currentParent, value: value });
235 ensurePropertyComplete(offset + length);
236 },
237 onSeparator: function (sep, offset, length) {
238 if (currentParent.type === 'property') {
239 if (sep === ':') {
240 currentParent.colonOffset = offset;
241 }
242 else if (sep === ',') {
243 ensurePropertyComplete(offset);
244 }
245 }
246 },
247 onError: function (error, offset, length) {
248 errors.push({ error: error, offset: offset, length: length });
249 }
250 };
251 visit(text, visitor, options);
252 var result = currentParent.children[0];
253 if (result) {
254 delete result.parent;
255 }
256 return result;
257 }
258 exports.parseTree = parseTree;
259 /**
260 * Finds the node at the given path in a JSON DOM.
261 */
262 function findNodeAtLocation(root, path) {
263 if (!root) {
264 return undefined;
265 }
266 var node = root;
267 for (var _i = 0, path_1 = path; _i < path_1.length; _i++) {
268 var segment = path_1[_i];
269 if (typeof segment === 'string') {
270 if (node.type !== 'object' || !Array.isArray(node.children)) {
271 return undefined;
272 }
273 var found = false;
274 for (var _a = 0, _b = node.children; _a < _b.length; _a++) {
275 var propertyNode = _b[_a];
276 if (Array.isArray(propertyNode.children) && propertyNode.children[0].value === segment) {
277 node = propertyNode.children[1];
278 found = true;
279 break;
280 }
281 }
282 if (!found) {
283 return undefined;
284 }
285 }
286 else {
287 var index = segment;
288 if (node.type !== 'array' || index < 0 || !Array.isArray(node.children) || index >= node.children.length) {
289 return undefined;
290 }
291 node = node.children[index];
292 }
293 }
294 return node;
295 }
296 exports.findNodeAtLocation = findNodeAtLocation;
297 /**
298 * Gets the JSON path of the given JSON DOM node
299 */
300 function getNodePath(node) {
301 if (!node.parent || !node.parent.children) {
302 return [];
303 }
304 var path = getNodePath(node.parent);
305 if (node.parent.type === 'property') {
306 var key = node.parent.children[0].value;
307 path.push(key);
308 }
309 else if (node.parent.type === 'array') {
310 var index = node.parent.children.indexOf(node);
311 if (index !== -1) {
312 path.push(index);
313 }
314 }
315 return path;
316 }
317 exports.getNodePath = getNodePath;
318 /**
319 * Evaluates the JavaScript object of the given JSON DOM node
320 */
321 function getNodeValue(node) {
322 switch (node.type) {
323 case 'array':
324 return node.children.map(getNodeValue);
325 case 'object':
326 var obj = Object.create(null);
327 for (var _i = 0, _a = node.children; _i < _a.length; _i++) {
328 var prop = _a[_i];
329 var valueNode = prop.children[1];
330 if (valueNode) {
331 obj[prop.children[0].value] = getNodeValue(valueNode);
332 }
333 }
334 return obj;
335 case 'null':
336 case 'string':
337 case 'number':
338 case 'boolean':
339 return node.value;
340 default:
341 return undefined;
342 }
343 }
344 exports.getNodeValue = getNodeValue;
345 function contains(node, offset, includeRightBound) {
346 if (includeRightBound === void 0) { includeRightBound = false; }
347 return (offset >= node.offset && offset < (node.offset + node.length)) || includeRightBound && (offset === (node.offset + node.length));
348 }
349 exports.contains = contains;
350 /**
351 * Finds the most inner node at the given offset. If includeRightBound is set, also finds nodes that end at the given offset.
352 */
353 function findNodeAtOffset(node, offset, includeRightBound) {
354 if (includeRightBound === void 0) { includeRightBound = false; }
355 if (contains(node, offset, includeRightBound)) {
356 var children = node.children;
357 if (Array.isArray(children)) {
358 for (var i = 0; i < children.length && children[i].offset <= offset; i++) {
359 var item = findNodeAtOffset(children[i], offset, includeRightBound);
360 if (item) {
361 return item;
362 }
363 }
364 }
365 return node;
366 }
367 return undefined;
368 }
369 exports.findNodeAtOffset = findNodeAtOffset;
370 /**
371 * Parses the given text and invokes the visitor functions for each object, array and literal reached.
372 */
373 function visit(text, visitor, options) {
374 if (options === void 0) { options = ParseOptions.DEFAULT; }
375 var _scanner = scanner_1.createScanner(text, false);
376 function toNoArgVisit(visitFunction) {
377 return visitFunction ? function () { return visitFunction(_scanner.getTokenOffset(), _scanner.getTokenLength(), _scanner.getTokenStartLine(), _scanner.getTokenStartCharacter()); } : function () { return true; };
378 }
379 function toOneArgVisit(visitFunction) {
380 return visitFunction ? function (arg) { return visitFunction(arg, _scanner.getTokenOffset(), _scanner.getTokenLength(), _scanner.getTokenStartLine(), _scanner.getTokenStartCharacter()); } : function () { return true; };
381 }
382 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);
383 var disallowComments = options && options.disallowComments;
384 var allowTrailingComma = options && options.allowTrailingComma;
385 function scanNext() {
386 while (true) {
387 var token = _scanner.scan();
388 switch (_scanner.getTokenError()) {
389 case 4 /* InvalidUnicode */:
390 handleError(14 /* InvalidUnicode */);
391 break;
392 case 5 /* InvalidEscapeCharacter */:
393 handleError(15 /* InvalidEscapeCharacter */);
394 break;
395 case 3 /* UnexpectedEndOfNumber */:
396 handleError(13 /* UnexpectedEndOfNumber */);
397 break;
398 case 1 /* UnexpectedEndOfComment */:
399 if (!disallowComments) {
400 handleError(11 /* UnexpectedEndOfComment */);
401 }
402 break;
403 case 2 /* UnexpectedEndOfString */:
404 handleError(12 /* UnexpectedEndOfString */);
405 break;
406 case 6 /* InvalidCharacter */:
407 handleError(16 /* InvalidCharacter */);
408 break;
409 }
410 switch (token) {
411 case 12 /* LineCommentTrivia */:
412 case 13 /* BlockCommentTrivia */:
413 if (disallowComments) {
414 handleError(10 /* InvalidCommentToken */);
415 }
416 else {
417 onComment();
418 }
419 break;
420 case 16 /* Unknown */:
421 handleError(1 /* InvalidSymbol */);
422 break;
423 case 15 /* Trivia */:
424 case 14 /* LineBreakTrivia */:
425 break;
426 default:
427 return token;
428 }
429 }
430 }
431 function handleError(error, skipUntilAfter, skipUntil) {
432 if (skipUntilAfter === void 0) { skipUntilAfter = []; }
433 if (skipUntil === void 0) { skipUntil = []; }
434 onError(error);
435 if (skipUntilAfter.length + skipUntil.length > 0) {
436 var token = _scanner.getToken();
437 while (token !== 17 /* EOF */) {
438 if (skipUntilAfter.indexOf(token) !== -1) {
439 scanNext();
440 break;
441 }
442 else if (skipUntil.indexOf(token) !== -1) {
443 break;
444 }
445 token = scanNext();
446 }
447 }
448 }
449 function parseString(isValue) {
450 var value = _scanner.getTokenValue();
451 if (isValue) {
452 onLiteralValue(value);
453 }
454 else {
455 onObjectProperty(value);
456 }
457 scanNext();
458 return true;
459 }
460 function parseLiteral() {
461 switch (_scanner.getToken()) {
462 case 11 /* NumericLiteral */:
463 var value = 0;
464 try {
465 value = JSON.parse(_scanner.getTokenValue());
466 if (typeof value !== 'number') {
467 handleError(2 /* InvalidNumberFormat */);
468 value = 0;
469 }
470 }
471 catch (e) {
472 handleError(2 /* InvalidNumberFormat */);
473 }
474 onLiteralValue(value);
475 break;
476 case 7 /* NullKeyword */:
477 onLiteralValue(null);
478 break;
479 case 8 /* TrueKeyword */:
480 onLiteralValue(true);
481 break;
482 case 9 /* FalseKeyword */:
483 onLiteralValue(false);
484 break;
485 default:
486 return false;
487 }
488 scanNext();
489 return true;
490 }
491 function parseProperty() {
492 if (_scanner.getToken() !== 10 /* StringLiteral */) {
493 handleError(3 /* PropertyNameExpected */, [], [2 /* CloseBraceToken */, 5 /* CommaToken */]);
494 return false;
495 }
496 parseString(false);
497 if (_scanner.getToken() === 6 /* ColonToken */) {
498 onSeparator(':');
499 scanNext(); // consume colon
500 if (!parseValue()) {
501 handleError(4 /* ValueExpected */, [], [2 /* CloseBraceToken */, 5 /* CommaToken */]);
502 }
503 }
504 else {
505 handleError(5 /* ColonExpected */, [], [2 /* CloseBraceToken */, 5 /* CommaToken */]);
506 }
507 return true;
508 }
509 function parseObject() {
510 onObjectBegin();
511 scanNext(); // consume open brace
512 var needsComma = false;
513 while (_scanner.getToken() !== 2 /* CloseBraceToken */ && _scanner.getToken() !== 17 /* EOF */) {
514 if (_scanner.getToken() === 5 /* CommaToken */) {
515 if (!needsComma) {
516 handleError(4 /* ValueExpected */, [], []);
517 }
518 onSeparator(',');
519 scanNext(); // consume comma
520 if (_scanner.getToken() === 2 /* CloseBraceToken */ && allowTrailingComma) {
521 break;
522 }
523 }
524 else if (needsComma) {
525 handleError(6 /* CommaExpected */, [], []);
526 }
527 if (!parseProperty()) {
528 handleError(4 /* ValueExpected */, [], [2 /* CloseBraceToken */, 5 /* CommaToken */]);
529 }
530 needsComma = true;
531 }
532 onObjectEnd();
533 if (_scanner.getToken() !== 2 /* CloseBraceToken */) {
534 handleError(7 /* CloseBraceExpected */, [2 /* CloseBraceToken */], []);
535 }
536 else {
537 scanNext(); // consume close brace
538 }
539 return true;
540 }
541 function parseArray() {
542 onArrayBegin();
543 scanNext(); // consume open bracket
544 var needsComma = false;
545 while (_scanner.getToken() !== 4 /* CloseBracketToken */ && _scanner.getToken() !== 17 /* EOF */) {
546 if (_scanner.getToken() === 5 /* CommaToken */) {
547 if (!needsComma) {
548 handleError(4 /* ValueExpected */, [], []);
549 }
550 onSeparator(',');
551 scanNext(); // consume comma
552 if (_scanner.getToken() === 4 /* CloseBracketToken */ && allowTrailingComma) {
553 break;
554 }
555 }
556 else if (needsComma) {
557 handleError(6 /* CommaExpected */, [], []);
558 }
559 if (!parseValue()) {
560 handleError(4 /* ValueExpected */, [], [4 /* CloseBracketToken */, 5 /* CommaToken */]);
561 }
562 needsComma = true;
563 }
564 onArrayEnd();
565 if (_scanner.getToken() !== 4 /* CloseBracketToken */) {
566 handleError(8 /* CloseBracketExpected */, [4 /* CloseBracketToken */], []);
567 }
568 else {
569 scanNext(); // consume close bracket
570 }
571 return true;
572 }
573 function parseValue() {
574 switch (_scanner.getToken()) {
575 case 3 /* OpenBracketToken */:
576 return parseArray();
577 case 1 /* OpenBraceToken */:
578 return parseObject();
579 case 10 /* StringLiteral */:
580 return parseString(true);
581 default:
582 return parseLiteral();
583 }
584 }
585 scanNext();
586 if (_scanner.getToken() === 17 /* EOF */) {
587 if (options.allowEmptyContent) {
588 return true;
589 }
590 handleError(4 /* ValueExpected */, [], []);
591 return false;
592 }
593 if (!parseValue()) {
594 handleError(4 /* ValueExpected */, [], []);
595 return false;
596 }
597 if (_scanner.getToken() !== 17 /* EOF */) {
598 handleError(9 /* EndOfFileExpected */, [], []);
599 }
600 return true;
601 }
602 exports.visit = visit;
603 /**
604 * Takes JSON with JavaScript-style comments and remove
605 * them. Optionally replaces every none-newline character
606 * of comments with a replaceCharacter
607 */
608 function stripComments(text, replaceCh) {
609 var _scanner = scanner_1.createScanner(text), parts = [], kind, offset = 0, pos;
610 do {
611 pos = _scanner.getPosition();
612 kind = _scanner.scan();
613 switch (kind) {
614 case 12 /* LineCommentTrivia */:
615 case 13 /* BlockCommentTrivia */:
616 case 17 /* EOF */:
617 if (offset !== pos) {
618 parts.push(text.substring(offset, pos));
619 }
620 if (replaceCh !== undefined) {
621 parts.push(_scanner.getTokenValue().replace(/[^\r\n]/g, replaceCh));
622 }
623 offset = _scanner.getPosition();
624 break;
625 }
626 } while (kind !== 17 /* EOF */);
627 return parts.join('');
628 }
629 exports.stripComments = stripComments;
630 function getNodeType(value) {
631 switch (typeof value) {
632 case 'boolean': return 'boolean';
633 case 'number': return 'number';
634 case 'string': return 'string';
635 case 'object': {
636 if (!value) {
637 return 'null';
638 }
639 else if (Array.isArray(value)) {
640 return 'array';
641 }
642 return 'object';
643 }
644 default: return 'null';
645 }
646 }
647 exports.getNodeType = getNodeType;
648});