@sussudio/base
Version:
Internal APIs for VS Code's utilities and user interface building blocks.
1,013 lines (1,012 loc) • 29.4 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* 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';
}
}