UNPKG

@sussudio/base

Version:

Internal APIs for VS Code's utilities and user interface building blocks.

1,013 lines (1,012 loc) 29.4 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ export var ParseOptions; (function (ParseOptions) { ParseOptions.DEFAULT = { allowTrailingComma: true, }; })(ParseOptions || (ParseOptions = {})); /** * Creates a JSON scanner on the given text. * If ignoreTrivia is set, whitespaces or comments are ignored. */ export function createScanner(text, ignoreTrivia = false) { let pos = 0; const len = text.length; let value = ''; let tokenOffset = 0; let token = 16; /* SyntaxKind.Unknown */ let scanError = 0; /* ScanError.None */ function scanHexDigits(count) { let digits = 0; let hexValue = 0; while (digits < count) { const ch = text.charCodeAt(pos); if (ch >= 48 /* CharacterCodes._0 */ && ch <= 57 /* CharacterCodes._9 */) { hexValue = hexValue * 16 + ch - 48 /* CharacterCodes._0 */; } else if (ch >= 65 /* CharacterCodes.A */ && ch <= 70 /* CharacterCodes.F */) { hexValue = hexValue * 16 + ch - 65 /* CharacterCodes.A */ + 10; } else if (ch >= 97 /* CharacterCodes.a */ && ch <= 102 /* CharacterCodes.f */) { hexValue = hexValue * 16 + ch - 97 /* CharacterCodes.a */ + 10; } else { break; } pos++; digits++; } if (digits < count) { hexValue = -1; } return hexValue; } function setPosition(newPosition) { pos = newPosition; value = ''; tokenOffset = 0; token = 16 /* SyntaxKind.Unknown */; scanError = 0 /* ScanError.None */; } function scanNumber() { const start = pos; if (text.charCodeAt(pos) === 48 /* CharacterCodes._0 */) { pos++; } else { pos++; while (pos < text.length && isDigit(text.charCodeAt(pos))) { pos++; } } if (pos < text.length && text.charCodeAt(pos) === 46 /* CharacterCodes.dot */) { pos++; if (pos < text.length && isDigit(text.charCodeAt(pos))) { pos++; while (pos < text.length && isDigit(text.charCodeAt(pos))) { pos++; } } else { scanError = 3 /* ScanError.UnexpectedEndOfNumber */; return text.substring(start, pos); } } let end = pos; if ( pos < text.length && (text.charCodeAt(pos) === 69 /* CharacterCodes.E */ || text.charCodeAt(pos) === 101) /* CharacterCodes.e */ ) { pos++; if ( (pos < text.length && text.charCodeAt(pos) === 43) /* CharacterCodes.plus */ || text.charCodeAt(pos) === 45 /* CharacterCodes.minus */ ) { pos++; } if (pos < text.length && isDigit(text.charCodeAt(pos))) { pos++; while (pos < text.length && isDigit(text.charCodeAt(pos))) { pos++; } end = pos; } else { scanError = 3 /* ScanError.UnexpectedEndOfNumber */; } } return text.substring(start, end); } function scanString() { let result = '', start = pos; while (true) { if (pos >= len) { result += text.substring(start, pos); scanError = 2 /* ScanError.UnexpectedEndOfString */; break; } const ch = text.charCodeAt(pos); if (ch === 34 /* CharacterCodes.doubleQuote */) { result += text.substring(start, pos); pos++; break; } if (ch === 92 /* CharacterCodes.backslash */) { result += text.substring(start, pos); pos++; if (pos >= len) { scanError = 2 /* ScanError.UnexpectedEndOfString */; break; } const ch2 = text.charCodeAt(pos++); switch (ch2) { case 34 /* CharacterCodes.doubleQuote */: result += '"'; break; case 92 /* CharacterCodes.backslash */: result += '\\'; break; case 47 /* CharacterCodes.slash */: result += '/'; break; case 98 /* CharacterCodes.b */: result += '\b'; break; case 102 /* CharacterCodes.f */: result += '\f'; break; case 110 /* CharacterCodes.n */: result += '\n'; break; case 114 /* CharacterCodes.r */: result += '\r'; break; case 116 /* CharacterCodes.t */: result += '\t'; break; case 117 /* CharacterCodes.u */: { const ch3 = scanHexDigits(4); if (ch3 >= 0) { result += String.fromCharCode(ch3); } else { scanError = 4 /* ScanError.InvalidUnicode */; } break; } default: scanError = 5 /* ScanError.InvalidEscapeCharacter */; } start = pos; continue; } if (ch >= 0 && ch <= 0x1f) { if (isLineBreak(ch)) { result += text.substring(start, pos); scanError = 2 /* ScanError.UnexpectedEndOfString */; break; } else { scanError = 6 /* ScanError.InvalidCharacter */; // mark as error but continue with string } } pos++; } return result; } function scanNext() { value = ''; scanError = 0 /* ScanError.None */; tokenOffset = pos; if (pos >= len) { // at the end tokenOffset = len; return (token = 17) /* SyntaxKind.EOF */; } let code = text.charCodeAt(pos); // trivia: whitespace if (isWhitespace(code)) { do { pos++; value += String.fromCharCode(code); code = text.charCodeAt(pos); } while (isWhitespace(code)); return (token = 15) /* SyntaxKind.Trivia */; } // trivia: newlines if (isLineBreak(code)) { pos++; value += String.fromCharCode(code); if ( code === 13 /* CharacterCodes.carriageReturn */ && text.charCodeAt(pos) === 10 /* CharacterCodes.lineFeed */ ) { pos++; value += '\n'; } return (token = 14) /* SyntaxKind.LineBreakTrivia */; } switch (code) { // tokens: []{}:, case 123 /* CharacterCodes.openBrace */: pos++; return (token = 1) /* SyntaxKind.OpenBraceToken */; case 125 /* CharacterCodes.closeBrace */: pos++; return (token = 2) /* SyntaxKind.CloseBraceToken */; case 91 /* CharacterCodes.openBracket */: pos++; return (token = 3) /* SyntaxKind.OpenBracketToken */; case 93 /* CharacterCodes.closeBracket */: pos++; return (token = 4) /* SyntaxKind.CloseBracketToken */; case 58 /* CharacterCodes.colon */: pos++; return (token = 6) /* SyntaxKind.ColonToken */; case 44 /* CharacterCodes.comma */: pos++; return (token = 5) /* SyntaxKind.CommaToken */; // strings case 34 /* CharacterCodes.doubleQuote */: pos++; value = scanString(); return (token = 10) /* SyntaxKind.StringLiteral */; // comments case 47 /* CharacterCodes.slash */: { const start = pos - 1; // Single-line comment if (text.charCodeAt(pos + 1) === 47 /* CharacterCodes.slash */) { pos += 2; while (pos < len) { if (isLineBreak(text.charCodeAt(pos))) { break; } pos++; } value = text.substring(start, pos); return (token = 12) /* SyntaxKind.LineCommentTrivia */; } // Multi-line comment if (text.charCodeAt(pos + 1) === 42 /* CharacterCodes.asterisk */) { pos += 2; const safeLength = len - 1; // For lookahead. let commentClosed = false; while (pos < safeLength) { const ch = text.charCodeAt(pos); if (ch === 42 /* CharacterCodes.asterisk */ && text.charCodeAt(pos + 1) === 47 /* CharacterCodes.slash */) { pos += 2; commentClosed = true; break; } pos++; } if (!commentClosed) { pos++; scanError = 1 /* ScanError.UnexpectedEndOfComment */; } value = text.substring(start, pos); return (token = 13) /* SyntaxKind.BlockCommentTrivia */; } // just a single slash value += String.fromCharCode(code); pos++; return (token = 16) /* SyntaxKind.Unknown */; } // numbers case 45 /* CharacterCodes.minus */: value += String.fromCharCode(code); pos++; if (pos === len || !isDigit(text.charCodeAt(pos))) { return (token = 16) /* SyntaxKind.Unknown */; } // found a minus, followed by a number so // we fall through to proceed with scanning // numbers case 48 /* CharacterCodes._0 */: case 49 /* CharacterCodes._1 */: case 50 /* CharacterCodes._2 */: case 51 /* CharacterCodes._3 */: case 52 /* CharacterCodes._4 */: case 53 /* CharacterCodes._5 */: case 54 /* CharacterCodes._6 */: case 55 /* CharacterCodes._7 */: case 56 /* CharacterCodes._8 */: case 57 /* CharacterCodes._9 */: value += scanNumber(); return (token = 11) /* SyntaxKind.NumericLiteral */; // literals and unknown symbols default: // is a literal? Read the full word. while (pos < len && isUnknownContentCharacter(code)) { pos++; code = text.charCodeAt(pos); } if (tokenOffset !== pos) { value = text.substring(tokenOffset, pos); // keywords: true, false, null switch (value) { case 'true': return (token = 8) /* SyntaxKind.TrueKeyword */; case 'false': return (token = 9) /* SyntaxKind.FalseKeyword */; case 'null': return (token = 7) /* SyntaxKind.NullKeyword */; } return (token = 16) /* SyntaxKind.Unknown */; } // some value += String.fromCharCode(code); pos++; return (token = 16) /* SyntaxKind.Unknown */; } } function isUnknownContentCharacter(code) { if (isWhitespace(code) || isLineBreak(code)) { return false; } switch (code) { case 125 /* CharacterCodes.closeBrace */: case 93 /* CharacterCodes.closeBracket */: case 123 /* CharacterCodes.openBrace */: case 91 /* CharacterCodes.openBracket */: case 34 /* CharacterCodes.doubleQuote */: case 58 /* CharacterCodes.colon */: case 44 /* CharacterCodes.comma */: case 47 /* CharacterCodes.slash */: return false; } return true; } function scanNextNonTrivia() { let result; do { result = scanNext(); } while (result >= 12 /* SyntaxKind.LineCommentTrivia */ && result <= 15 /* SyntaxKind.Trivia */); return result; } return { setPosition: setPosition, getPosition: () => pos, scan: ignoreTrivia ? scanNextNonTrivia : scanNext, getToken: () => token, getTokenValue: () => value, getTokenOffset: () => tokenOffset, getTokenLength: () => pos - tokenOffset, getTokenError: () => scanError, }; } function isWhitespace(ch) { return ( ch === 32 /* CharacterCodes.space */ || ch === 9 /* CharacterCodes.tab */ || ch === 11 /* CharacterCodes.verticalTab */ || ch === 12 /* CharacterCodes.formFeed */ || ch === 160 /* CharacterCodes.nonBreakingSpace */ || ch === 5760 /* CharacterCodes.ogham */ || (ch >= 8192 /* CharacterCodes.enQuad */ && ch <= 8203) /* CharacterCodes.zeroWidthSpace */ || ch === 8239 /* CharacterCodes.narrowNoBreakSpace */ || ch === 8287 /* CharacterCodes.mathematicalSpace */ || ch === 12288 /* CharacterCodes.ideographicSpace */ || ch === 65279 /* CharacterCodes.byteOrderMark */ ); } function isLineBreak(ch) { return ( ch === 10 /* CharacterCodes.lineFeed */ || ch === 13 /* CharacterCodes.carriageReturn */ || ch === 8232 /* CharacterCodes.lineSeparator */ || ch === 8233 /* CharacterCodes.paragraphSeparator */ ); } function isDigit(ch) { return ch >= 48 /* CharacterCodes._0 */ && ch <= 57 /* CharacterCodes._9 */; } /** * 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. */ export function getLocation(text, position) { const segments = []; // strings or numbers const earlyReturnException = new Object(); let previousNode = undefined; const previousNodeInst = { value: {}, offset: 0, length: 0, type: 'object', parent: undefined, }; let isAtPropertyKey = false; function setPreviousNode(value, offset, length, type) { previousNodeInst.value = value; previousNodeInst.offset = offset; previousNodeInst.length = length; previousNodeInst.type = type; previousNodeInst.colonOffset = undefined; previousNode = previousNodeInst; } try { visit(text, { onObjectBegin: (offset, length) => { if (position <= offset) { throw earlyReturnException; } previousNode = undefined; isAtPropertyKey = position > offset; segments.push(''); // push a placeholder (will be replaced) }, onObjectProperty: (name, offset, length) => { if (position < offset) { throw earlyReturnException; } setPreviousNode(name, offset, length, 'property'); segments[segments.length - 1] = name; if (position <= offset + length) { throw earlyReturnException; } }, onObjectEnd: (offset, length) => { if (position <= offset) { throw earlyReturnException; } previousNode = undefined; segments.pop(); }, onArrayBegin: (offset, length) => { if (position <= offset) { throw earlyReturnException; } previousNode = undefined; segments.push(0); }, onArrayEnd: (offset, length) => { if (position <= offset) { throw earlyReturnException; } previousNode = undefined; segments.pop(); }, onLiteralValue: (value, offset, length) => { if (position < offset) { throw earlyReturnException; } setPreviousNode(value, offset, length, getNodeType(value)); if (position <= offset + length) { throw earlyReturnException; } }, onSeparator: (sep, offset, length) => { if (position <= offset) { throw earlyReturnException; } if (sep === ':' && previousNode && previousNode.type === 'property') { previousNode.colonOffset = offset; isAtPropertyKey = false; previousNode = undefined; } else if (sep === ',') { const last = segments[segments.length - 1]; if (typeof last === 'number') { segments[segments.length - 1] = last + 1; } else { isAtPropertyKey = true; segments[segments.length - 1] = ''; } previousNode = undefined; } }, }); } catch (e) { if (e !== earlyReturnException) { throw e; } } return { path: segments, previousNode, isAtPropertyKey, matches: (pattern) => { let k = 0; for (let i = 0; k < pattern.length && i < segments.length; i++) { if (pattern[k] === segments[i] || pattern[k] === '*') { k++; } else if (pattern[k] !== '**') { return false; } } return k === pattern.length; }, }; } /** * 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. * Therefore always check the errors list to find out if the input was valid. */ export function parse(text, errors = [], options = ParseOptions.DEFAULT) { let currentProperty = null; let currentParent = []; const previousParents = []; function onValue(value) { if (Array.isArray(currentParent)) { currentParent.push(value); } else if (currentProperty !== null) { currentParent[currentProperty] = value; } } const visitor = { onObjectBegin: () => { const object = {}; onValue(object); previousParents.push(currentParent); currentParent = object; currentProperty = null; }, onObjectProperty: (name) => { currentProperty = name; }, onObjectEnd: () => { currentParent = previousParents.pop(); }, onArrayBegin: () => { const array = []; onValue(array); previousParents.push(currentParent); currentParent = array; currentProperty = null; }, onArrayEnd: () => { currentParent = previousParents.pop(); }, onLiteralValue: onValue, onError: (error, offset, length) => { errors.push({ error, offset, length }); }, }; visit(text, visitor, options); return currentParent[0]; } /** * 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. */ export function parseTree(text, errors = [], options = ParseOptions.DEFAULT) { let currentParent = { type: 'array', offset: -1, length: -1, children: [], parent: undefined }; // artificial root function ensurePropertyComplete(endOffset) { if (currentParent.type === 'property') { currentParent.length = endOffset - currentParent.offset; currentParent = currentParent.parent; } } function onValue(valueNode) { currentParent.children.push(valueNode); return valueNode; } const visitor = { onObjectBegin: (offset) => { currentParent = onValue({ type: 'object', offset, length: -1, parent: currentParent, children: [] }); }, onObjectProperty: (name, offset, length) => { currentParent = onValue({ type: 'property', offset, length: -1, parent: currentParent, children: [] }); currentParent.children.push({ type: 'string', value: name, offset, length, parent: currentParent }); }, onObjectEnd: (offset, length) => { currentParent.length = offset + length - currentParent.offset; currentParent = currentParent.parent; ensurePropertyComplete(offset + length); }, onArrayBegin: (offset, length) => { currentParent = onValue({ type: 'array', offset, length: -1, parent: currentParent, children: [] }); }, onArrayEnd: (offset, length) => { currentParent.length = offset + length - currentParent.offset; currentParent = currentParent.parent; ensurePropertyComplete(offset + length); }, onLiteralValue: (value, offset, length) => { onValue({ type: getNodeType(value), offset, length, parent: currentParent, value }); ensurePropertyComplete(offset + length); }, onSeparator: (sep, offset, length) => { if (currentParent.type === 'property') { if (sep === ':') { currentParent.colonOffset = offset; } else if (sep === ',') { ensurePropertyComplete(offset); } } }, onError: (error, offset, length) => { errors.push({ error, offset, length }); }, }; visit(text, visitor, options); const result = currentParent.children[0]; if (result) { delete result.parent; } return result; } /** * Finds the node at the given path in a JSON DOM. */ export function findNodeAtLocation(root, path) { if (!root) { return undefined; } let node = root; for (const segment of path) { if (typeof segment === 'string') { if (node.type !== 'object' || !Array.isArray(node.children)) { return undefined; } let found = false; for (const propertyNode of node.children) { if (Array.isArray(propertyNode.children) && propertyNode.children[0].value === segment) { node = propertyNode.children[1]; found = true; break; } } if (!found) { return undefined; } } else { const index = segment; if (node.type !== 'array' || index < 0 || !Array.isArray(node.children) || index >= node.children.length) { return undefined; } node = node.children[index]; } } return node; } /** * Gets the JSON path of the given JSON DOM node */ export function getNodePath(node) { if (!node.parent || !node.parent.children) { return []; } const path = getNodePath(node.parent); if (node.parent.type === 'property') { const key = node.parent.children[0].value; path.push(key); } else if (node.parent.type === 'array') { const index = node.parent.children.indexOf(node); if (index !== -1) { path.push(index); } } return path; } /** * Evaluates the JavaScript object of the given JSON DOM node */ export function getNodeValue(node) { switch (node.type) { case 'array': return node.children.map(getNodeValue); case 'object': { const obj = Object.create(null); for (const prop of node.children) { const valueNode = prop.children[1]; if (valueNode) { obj[prop.children[0].value] = getNodeValue(valueNode); } } return obj; } case 'null': case 'string': case 'number': case 'boolean': return node.value; default: return undefined; } } export function contains(node, offset, includeRightBound = false) { return ( (offset >= node.offset && offset < node.offset + node.length) || (includeRightBound && offset === node.offset + node.length) ); } /** * Finds the most inner node at the given offset. If includeRightBound is set, also finds nodes that end at the given offset. */ export function findNodeAtOffset(node, offset, includeRightBound = false) { if (contains(node, offset, includeRightBound)) { const children = node.children; if (Array.isArray(children)) { for (let i = 0; i < children.length && children[i].offset <= offset; i++) { const item = findNodeAtOffset(children[i], offset, includeRightBound); if (item) { return item; } } } return node; } return undefined; } /** * Parses the given text and invokes the visitor functions for each object, array and literal reached. */ export function visit(text, visitor, options = ParseOptions.DEFAULT) { const _scanner = createScanner(text, false); function toNoArgVisit(visitFunction) { return visitFunction ? () => visitFunction(_scanner.getTokenOffset(), _scanner.getTokenLength()) : () => true; } function toOneArgVisit(visitFunction) { return visitFunction ? (arg) => visitFunction(arg, _scanner.getTokenOffset(), _scanner.getTokenLength()) : () => true; } const 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); const disallowComments = options && options.disallowComments; const allowTrailingComma = options && options.allowTrailingComma; function scanNext() { while (true) { const token = _scanner.scan(); switch (_scanner.getTokenError()) { case 4 /* ScanError.InvalidUnicode */: handleError(14 /* ParseErrorCode.InvalidUnicode */); break; case 5 /* ScanError.InvalidEscapeCharacter */: handleError(15 /* ParseErrorCode.InvalidEscapeCharacter */); break; case 3 /* ScanError.UnexpectedEndOfNumber */: handleError(13 /* ParseErrorCode.UnexpectedEndOfNumber */); break; case 1 /* ScanError.UnexpectedEndOfComment */: if (!disallowComments) { handleError(11 /* ParseErrorCode.UnexpectedEndOfComment */); } break; case 2 /* ScanError.UnexpectedEndOfString */: handleError(12 /* ParseErrorCode.UnexpectedEndOfString */); break; case 6 /* ScanError.InvalidCharacter */: handleError(16 /* ParseErrorCode.InvalidCharacter */); break; } switch (token) { case 12 /* SyntaxKind.LineCommentTrivia */: case 13 /* SyntaxKind.BlockCommentTrivia */: if (disallowComments) { handleError(10 /* ParseErrorCode.InvalidCommentToken */); } else { onComment(); } break; case 16 /* SyntaxKind.Unknown */: handleError(1 /* ParseErrorCode.InvalidSymbol */); break; case 15 /* SyntaxKind.Trivia */: case 14 /* SyntaxKind.LineBreakTrivia */: break; default: return token; } } } function handleError(error, skipUntilAfter = [], skipUntil = []) { onError(error); if (skipUntilAfter.length + skipUntil.length > 0) { let token = _scanner.getToken(); while (token !== 17 /* SyntaxKind.EOF */) { if (skipUntilAfter.indexOf(token) !== -1) { scanNext(); break; } else if (skipUntil.indexOf(token) !== -1) { break; } token = scanNext(); } } } function parseString(isValue) { const value = _scanner.getTokenValue(); if (isValue) { onLiteralValue(value); } else { onObjectProperty(value); } scanNext(); return true; } function parseLiteral() { switch (_scanner.getToken()) { case 11 /* SyntaxKind.NumericLiteral */: { let value = 0; try { value = JSON.parse(_scanner.getTokenValue()); if (typeof value !== 'number') { handleError(2 /* ParseErrorCode.InvalidNumberFormat */); value = 0; } } catch (e) { handleError(2 /* ParseErrorCode.InvalidNumberFormat */); } onLiteralValue(value); break; } case 7 /* SyntaxKind.NullKeyword */: onLiteralValue(null); break; case 8 /* SyntaxKind.TrueKeyword */: onLiteralValue(true); break; case 9 /* SyntaxKind.FalseKeyword */: onLiteralValue(false); break; default: return false; } scanNext(); return true; } function parseProperty() { if (_scanner.getToken() !== 10 /* SyntaxKind.StringLiteral */) { handleError( 3 /* ParseErrorCode.PropertyNameExpected */, [], [2 /* SyntaxKind.CloseBraceToken */, 5 /* SyntaxKind.CommaToken */], ); return false; } parseString(false); if (_scanner.getToken() === 6 /* SyntaxKind.ColonToken */) { onSeparator(':'); scanNext(); // consume colon if (!parseValue()) { handleError( 4 /* ParseErrorCode.ValueExpected */, [], [2 /* SyntaxKind.CloseBraceToken */, 5 /* SyntaxKind.CommaToken */], ); } } else { handleError( 5 /* ParseErrorCode.ColonExpected */, [], [2 /* SyntaxKind.CloseBraceToken */, 5 /* SyntaxKind.CommaToken */], ); } return true; } function parseObject() { onObjectBegin(); scanNext(); // consume open brace let needsComma = false; while ( _scanner.getToken() !== 2 /* SyntaxKind.CloseBraceToken */ && _scanner.getToken() !== 17 /* SyntaxKind.EOF */ ) { if (_scanner.getToken() === 5 /* SyntaxKind.CommaToken */) { if (!needsComma) { handleError(4 /* ParseErrorCode.ValueExpected */, [], []); } onSeparator(','); scanNext(); // consume comma if (_scanner.getToken() === 2 /* SyntaxKind.CloseBraceToken */ && allowTrailingComma) { break; } } else if (needsComma) { handleError(6 /* ParseErrorCode.CommaExpected */, [], []); } if (!parseProperty()) { handleError( 4 /* ParseErrorCode.ValueExpected */, [], [2 /* SyntaxKind.CloseBraceToken */, 5 /* SyntaxKind.CommaToken */], ); } needsComma = true; } onObjectEnd(); if (_scanner.getToken() !== 2 /* SyntaxKind.CloseBraceToken */) { handleError(7 /* ParseErrorCode.CloseBraceExpected */, [2 /* SyntaxKind.CloseBraceToken */], []); } else { scanNext(); // consume close brace } return true; } function parseArray() { onArrayBegin(); scanNext(); // consume open bracket let needsComma = false; while ( _scanner.getToken() !== 4 /* SyntaxKind.CloseBracketToken */ && _scanner.getToken() !== 17 /* SyntaxKind.EOF */ ) { if (_scanner.getToken() === 5 /* SyntaxKind.CommaToken */) { if (!needsComma) { handleError(4 /* ParseErrorCode.ValueExpected */, [], []); } onSeparator(','); scanNext(); // consume comma if (_scanner.getToken() === 4 /* SyntaxKind.CloseBracketToken */ && allowTrailingComma) { break; } } else if (needsComma) { handleError(6 /* ParseErrorCode.CommaExpected */, [], []); } if (!parseValue()) { handleError( 4 /* ParseErrorCode.ValueExpected */, [], [4 /* SyntaxKind.CloseBracketToken */, 5 /* SyntaxKind.CommaToken */], ); } needsComma = true; } onArrayEnd(); if (_scanner.getToken() !== 4 /* SyntaxKind.CloseBracketToken */) { handleError(8 /* ParseErrorCode.CloseBracketExpected */, [4 /* SyntaxKind.CloseBracketToken */], []); } else { scanNext(); // consume close bracket } return true; } function parseValue() { switch (_scanner.getToken()) { case 3 /* SyntaxKind.OpenBracketToken */: return parseArray(); case 1 /* SyntaxKind.OpenBraceToken */: return parseObject(); case 10 /* SyntaxKind.StringLiteral */: return parseString(true); default: return parseLiteral(); } } scanNext(); if (_scanner.getToken() === 17 /* SyntaxKind.EOF */) { if (options.allowEmptyContent) { return true; } handleError(4 /* ParseErrorCode.ValueExpected */, [], []); return false; } if (!parseValue()) { handleError(4 /* ParseErrorCode.ValueExpected */, [], []); return false; } if (_scanner.getToken() !== 17 /* SyntaxKind.EOF */) { handleError(9 /* ParseErrorCode.EndOfFileExpected */, [], []); } return true; } /** * Takes JSON with JavaScript-style comments and remove * them. Optionally replaces every none-newline character * of comments with a replaceCharacter */ export function stripComments(text, replaceCh) { const _scanner = createScanner(text); const parts = []; let kind; let offset = 0; let pos; do { pos = _scanner.getPosition(); kind = _scanner.scan(); switch (kind) { case 12 /* SyntaxKind.LineCommentTrivia */: case 13 /* SyntaxKind.BlockCommentTrivia */: case 17 /* SyntaxKind.EOF */: if (offset !== pos) { parts.push(text.substring(offset, pos)); } if (replaceCh !== undefined) { parts.push(_scanner.getTokenValue().replace(/[^\r\n]/g, replaceCh)); } offset = _scanner.getPosition(); break; } } while (kind !== 17 /* SyntaxKind.EOF */); return parts.join(''); } export function getNodeType(value) { switch (typeof value) { case 'boolean': return 'boolean'; case 'number': return 'number'; case 'string': return 'string'; case 'object': { if (!value) { return 'null'; } else if (Array.isArray(value)) { return 'array'; } return 'object'; } default: return 'null'; } }