UNPKG

alm

Version:

The best IDE for TypeScript

1,097 lines (1,007 loc) 36.5 kB
/** * From https://github.com/Microsoft/node-jsonc-parser/blob/master/src/main.ts * * Just removed vscode-nls dependency 🌹 */ /** BAS the function that was provided by vscode-nls */ const localize = (a: string, b: string) => b; /** * Rest is copy paste 🌹 */ export enum ScanError { None, UnexpectedEndOfComment, UnexpectedEndOfString, UnexpectedEndOfNumber, InvalidUnicode, InvalidEscapeCharacter } export enum SyntaxKind { Unknown = 0, OpenBraceToken, CloseBraceToken, OpenBracketToken, CloseBracketToken, CommaToken, ColonToken, NullKeyword, TrueKeyword, FalseKeyword, StringLiteral, NumericLiteral, LineCommentTrivia, BlockCommentTrivia, LineBreakTrivia, Trivia, EOF } /** * The scanner object, representing a JSON scanner at a position in the input string. */ export interface JSONScanner { /** * Sets the scan position to a new offset. A call to 'scan' is needed to get the first token. */ setPosition(pos: number); /** * Read the next token. Returns the tolen code. */ scan(): SyntaxKind; /** * Returns the current scan position, which is after the last read token. */ getPosition(): number; /** * Returns the last read token. */ getToken(): SyntaxKind; /** * Returns the last read token value. The value for strings is the decoded string content. For numbers its of type number, for boolean it's true or false. */ getTokenValue(): string; /** * The start offset of the last read token. */ getTokenOffset(): number; /** * The length of the last read token. */ getTokenLength(): number; /** * An error code of the last scan. */ getTokenError(): ScanError; } /** * Creates a JSON scanner on the given text. * If ignoreTrivia is set, whitespaces or comments are ignored. */ export function createScanner(text: string, ignoreTrivia: boolean = false): JSONScanner { let pos = 0, len = text.length, value: string = '', tokenOffset = 0, token: SyntaxKind = SyntaxKind.Unknown, scanError: ScanError = ScanError.None; function scanHexDigits(count: number, exact?: boolean): number { let digits = 0; let value = 0; while (digits < count || !exact) { let ch = text.charCodeAt(pos); if (ch >= CharacterCodes._0 && ch <= CharacterCodes._9) { value = value * 16 + ch - CharacterCodes._0; } else if (ch >= CharacterCodes.A && ch <= CharacterCodes.F) { value = value * 16 + ch - CharacterCodes.A + 10; } else if (ch >= CharacterCodes.a && ch <= CharacterCodes.f) { value = value * 16 + ch - CharacterCodes.a + 10; } else { break; } pos++; digits++; } if (digits < count) { value = -1; } return value; } function setPosition(newPosition: number) { pos = newPosition; value = ''; tokenOffset = 0; token = SyntaxKind.Unknown; scanError = ScanError.None; } function scanNumber(): string { let start = pos; if (text.charCodeAt(pos) === CharacterCodes._0) { pos++; } else { pos++; while (pos < text.length && isDigit(text.charCodeAt(pos))) { pos++; } } if (pos < text.length && text.charCodeAt(pos) === CharacterCodes.dot) { pos++; if (pos < text.length && isDigit(text.charCodeAt(pos))) { pos++; while (pos < text.length && isDigit(text.charCodeAt(pos))) { pos++; } } else { scanError = ScanError.UnexpectedEndOfNumber; return text.substring(start, pos); } } let end = pos; if (pos < text.length && (text.charCodeAt(pos) === CharacterCodes.E || text.charCodeAt(pos) === CharacterCodes.e)) { pos++; if (pos < text.length && text.charCodeAt(pos) === CharacterCodes.plus || text.charCodeAt(pos) === 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 = ScanError.UnexpectedEndOfNumber; } } return text.substring(start, end); } function scanString(): string { let result = '', start = pos; while (true) { if (pos >= len) { result += text.substring(start, pos); scanError = ScanError.UnexpectedEndOfString; break; } let ch = text.charCodeAt(pos); if (ch === CharacterCodes.doubleQuote) { result += text.substring(start, pos); pos++; break; } if (ch === CharacterCodes.backslash) { result += text.substring(start, pos); pos++; if (pos >= len) { scanError = ScanError.UnexpectedEndOfString; break; } ch = text.charCodeAt(pos++); switch (ch) { case CharacterCodes.doubleQuote: result += '\"'; break; case CharacterCodes.backslash: result += '\\'; break; case CharacterCodes.slash: result += '/'; break; case CharacterCodes.b: result += '\b'; break; case CharacterCodes.f: result += '\f'; break; case CharacterCodes.n: result += '\n'; break; case CharacterCodes.r: result += '\r'; break; case CharacterCodes.t: result += '\t'; break; case CharacterCodes.u: let ch = scanHexDigits(4, true); if (ch >= 0) { result += String.fromCharCode(ch); } else { scanError = ScanError.InvalidUnicode; } break; default: scanError = ScanError.InvalidEscapeCharacter; } start = pos; continue; } if (isLineBreak(ch)) { result += text.substring(start, pos); scanError = ScanError.UnexpectedEndOfString; break; } pos++; } return result; } function scanNext(): SyntaxKind { value = ''; scanError = ScanError.None; tokenOffset = pos; if (pos >= len) { // at the end tokenOffset = len; return token = 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 = SyntaxKind.Trivia; } // trivia: newlines if (isLineBreak(code)) { pos++; value += String.fromCharCode(code); if (code === CharacterCodes.carriageReturn && text.charCodeAt(pos) === CharacterCodes.lineFeed) { pos++; value += '\n'; } return token = SyntaxKind.LineBreakTrivia; } switch (code) { // tokens: []{}:, case CharacterCodes.openBrace: pos++; return token = SyntaxKind.OpenBraceToken; case CharacterCodes.closeBrace: pos++; return token = SyntaxKind.CloseBraceToken; case CharacterCodes.openBracket: pos++; return token = SyntaxKind.OpenBracketToken; case CharacterCodes.closeBracket: pos++; return token = SyntaxKind.CloseBracketToken; case CharacterCodes.colon: pos++; return token = SyntaxKind.ColonToken; case CharacterCodes.comma: pos++; return token = SyntaxKind.CommaToken; // strings case CharacterCodes.doubleQuote: pos++; value = scanString(); return token = SyntaxKind.StringLiteral; // comments case CharacterCodes.slash: let start = pos - 1; // Single-line comment if (text.charCodeAt(pos + 1) === CharacterCodes.slash) { pos += 2; while (pos < len) { if (isLineBreak(text.charCodeAt(pos))) { break; } pos++; } value = text.substring(start, pos); return token = SyntaxKind.LineCommentTrivia; } // Multi-line comment if (text.charCodeAt(pos + 1) === CharacterCodes.asterisk) { pos += 2; let safeLength = len - 1; // For lookahead. let commentClosed = false; while (pos < safeLength) { let ch = text.charCodeAt(pos); if (ch === CharacterCodes.asterisk && text.charCodeAt(pos + 1) === CharacterCodes.slash) { pos += 2; commentClosed = true; break; } pos++; } if (!commentClosed) { pos++; scanError = ScanError.UnexpectedEndOfComment; } value = text.substring(start, pos); return token = SyntaxKind.BlockCommentTrivia; } // just a single slash value += String.fromCharCode(code); pos++; return token = SyntaxKind.Unknown; // numbers case CharacterCodes.minus: value += String.fromCharCode(code); pos++; if (pos === len || !isDigit(text.charCodeAt(pos))) { return token = SyntaxKind.Unknown; } // found a minus, followed by a number so // we fall through to proceed with scanning // numbers case CharacterCodes._0: case CharacterCodes._1: case CharacterCodes._2: case CharacterCodes._3: case CharacterCodes._4: case CharacterCodes._5: case CharacterCodes._6: case CharacterCodes._7: case CharacterCodes._8: case CharacterCodes._9: value += scanNumber(); return token = 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 = SyntaxKind.TrueKeyword; case 'false': return token = SyntaxKind.FalseKeyword; case 'null': return token = SyntaxKind.NullKeyword; } return token = SyntaxKind.Unknown; } // some value += String.fromCharCode(code); pos++; return token = SyntaxKind.Unknown; } } function isUnknownContentCharacter(code: CharacterCodes) { if (isWhiteSpace(code) || isLineBreak(code)) { return false; } switch (code) { case CharacterCodes.closeBrace: case CharacterCodes.closeBracket: case CharacterCodes.openBrace: case CharacterCodes.openBracket: case CharacterCodes.doubleQuote: case CharacterCodes.colon: case CharacterCodes.comma: return false; } return true; } function scanNextNonTrivia(): SyntaxKind { let result: SyntaxKind; do { result = scanNext(); } while (result >= SyntaxKind.LineCommentTrivia && result <= 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: number): boolean { return ch === CharacterCodes.space || ch === CharacterCodes.tab || ch === CharacterCodes.verticalTab || ch === CharacterCodes.formFeed || ch === CharacterCodes.nonBreakingSpace || ch === CharacterCodes.ogham || ch >= CharacterCodes.enQuad && ch <= CharacterCodes.zeroWidthSpace || ch === CharacterCodes.narrowNoBreakSpace || ch === CharacterCodes.mathematicalSpace || ch === CharacterCodes.ideographicSpace || ch === CharacterCodes.byteOrderMark; } function isLineBreak(ch: number): boolean { return ch === CharacterCodes.lineFeed || ch === CharacterCodes.carriageReturn || ch === CharacterCodes.lineSeparator || ch === CharacterCodes.paragraphSeparator; } function isDigit(ch: number): boolean { return ch >= CharacterCodes._0 && ch <= CharacterCodes._9; } function isLetter(ch: number): boolean { return ch >= CharacterCodes.a && ch <= CharacterCodes.z || ch >= CharacterCodes.A && ch <= CharacterCodes.Z; } enum CharacterCodes { nullCharacter = 0, maxAsciiCharacter = 0x7F, lineFeed = 0x0A, // \n carriageReturn = 0x0D, // \r lineSeparator = 0x2028, paragraphSeparator = 0x2029, // REVIEW: do we need to support this? The scanner doesn't, but our IText does. This seems // like an odd disparity? (Or maybe it's completely fine for them to be different). nextLine = 0x0085, // Unicode 3.0 space characters space = 0x0020, // " " nonBreakingSpace = 0x00A0, // enQuad = 0x2000, emQuad = 0x2001, enSpace = 0x2002, emSpace = 0x2003, threePerEmSpace = 0x2004, fourPerEmSpace = 0x2005, sixPerEmSpace = 0x2006, figureSpace = 0x2007, punctuationSpace = 0x2008, thinSpace = 0x2009, hairSpace = 0x200A, zeroWidthSpace = 0x200B, narrowNoBreakSpace = 0x202F, ideographicSpace = 0x3000, mathematicalSpace = 0x205F, ogham = 0x1680, _ = 0x5F, $ = 0x24, _0 = 0x30, _1 = 0x31, _2 = 0x32, _3 = 0x33, _4 = 0x34, _5 = 0x35, _6 = 0x36, _7 = 0x37, _8 = 0x38, _9 = 0x39, a = 0x61, b = 0x62, c = 0x63, d = 0x64, e = 0x65, f = 0x66, g = 0x67, h = 0x68, i = 0x69, j = 0x6A, k = 0x6B, l = 0x6C, m = 0x6D, n = 0x6E, o = 0x6F, p = 0x70, q = 0x71, r = 0x72, s = 0x73, t = 0x74, u = 0x75, v = 0x76, w = 0x77, x = 0x78, y = 0x79, z = 0x7A, A = 0x41, B = 0x42, C = 0x43, D = 0x44, E = 0x45, F = 0x46, G = 0x47, H = 0x48, I = 0x49, J = 0x4A, K = 0x4B, L = 0x4C, M = 0x4D, N = 0x4E, O = 0x4F, P = 0x50, Q = 0x51, R = 0x52, S = 0x53, T = 0x54, U = 0x55, V = 0x56, W = 0x57, X = 0x58, Y = 0x59, Z = 0x5a, ampersand = 0x26, // & asterisk = 0x2A, // * at = 0x40, // @ backslash = 0x5C, // \ bar = 0x7C, // | caret = 0x5E, // ^ closeBrace = 0x7D, // } closeBracket = 0x5D, // ] closeParen = 0x29, // ) colon = 0x3A, // : comma = 0x2C, // , dot = 0x2E, // . doubleQuote = 0x22, // " equals = 0x3D, // = exclamation = 0x21, // ! greaterThan = 0x3E, // > lessThan = 0x3C, // < minus = 0x2D, // - openBrace = 0x7B, // { openBracket = 0x5B, // [ openParen = 0x28, // ( percent = 0x25, // % plus = 0x2B, // + question = 0x3F, // ? semicolon = 0x3B, // ; singleQuote = 0x27, // ' slash = 0x2F, // / tilde = 0x7E, // ~ backspace = 0x08, // \b formFeed = 0x0C, // \f byteOrderMark = 0xFEFF, tab = 0x09, // \t verticalTab = 0x0B, // \v } /** * Takes JSON with JavaScript-style comments and remove * them. Optionally replaces every none-newline character * of comments with a replaceCharacter */ export function stripComments(text: string, replaceCh?: string): string { let _scanner = createScanner(text), parts: string[] = [], kind: SyntaxKind, offset = 0, pos: number; do { pos = _scanner.getPosition(); kind = _scanner.scan(); switch (kind) { case SyntaxKind.LineCommentTrivia: case SyntaxKind.BlockCommentTrivia: case SyntaxKind.EOF: if (offset !== pos) { parts.push(text.substring(offset, pos)); } if (replaceCh !== void 0) { parts.push(_scanner.getTokenValue().replace(/[^\r\n]/g, replaceCh)); } offset = _scanner.getPosition(); break; } } while (kind !== SyntaxKind.EOF); return parts.join(''); } export enum ParseErrorCode { InvalidSymbol, InvalidNumberFormat, PropertyNameExpected, ValueExpected, ColonExpected, CommaExpected, CloseBraceExpected, CloseBracketExpected, EndOfFileExpected } export function getParseErrorMessage(errorCode: ParseErrorCode): string { switch (errorCode) { case ParseErrorCode.InvalidSymbol: return localize('error.invalidSymbol', 'Invalid symbol'); case ParseErrorCode.InvalidNumberFormat: return localize('error.invalidNumberFormat', 'Invalid number format'); case ParseErrorCode.PropertyNameExpected: return localize('error.propertyNameExpected', 'Property name expected'); case ParseErrorCode.ValueExpected: return localize('error.valueExpected', 'Value expected'); case ParseErrorCode.ColonExpected: return localize('error.colonExpected', 'Colon expected'); case ParseErrorCode.CommaExpected: return localize('error.commaExpected', 'Comma expected'); case ParseErrorCode.CloseBraceExpected: return localize('error.closeBraceExpected', 'Closing brace expected'); case ParseErrorCode.CloseBracketExpected: return localize('error.closeBracketExpected', 'Closing bracket expected'); case ParseErrorCode.EndOfFileExpected: return localize('error.endOfFileExpected', 'End of file expected'); default: return ''; } } export type NodeType = "object" | "array" | "property" | "string" | "number" | "null"; export interface Node { type: NodeType; value: any; offset: number; length: number; columnOffset?: number; } export interface Location { previousNode?: Node; segments: string[]; matches: (segments: string[]) => boolean; completeProperty: boolean; } /** * For a given offset, evaluate the location in the JSON document. Each segment in a location is either a property names or an array accessors. */ export function getLocation(text: string, position: number): Location { let segments: string[] = []; let earlyReturnException = new Object(); let previousNode: Node = void 0; const previousNodeInst: Node = { value: void 0, offset: void 0, length: void 0, type: void 0 }; let completeProperty = false; let hasComma = false; function setPreviousNode(value: string, offset: number, length: number, type: NodeType) { previousNodeInst.value = value; previousNodeInst.offset = offset; previousNodeInst.length = length; previousNodeInst.type = type; previousNodeInst.columnOffset = void 0; previousNode = previousNodeInst; } try { visit(text, { onObjectBegin: (offset: number, length: number) => { if (position <= offset) { throw earlyReturnException; } previousNode = void 0; completeProperty = position > offset; hasComma = false; }, onObjectProperty: (name: string, offset: number, length: number) => { if (position < offset) { throw earlyReturnException; } setPreviousNode(name, offset, length, 'property'); hasComma = false; segments.push(name); if (position <= offset + length) { throw earlyReturnException; } }, onObjectEnd: (offset: number, length: number) => { if (position <= offset) { throw earlyReturnException; } previousNode = void 0; if (!hasComma) { segments.pop(); } hasComma = false; }, onArrayBegin: (offset: number, length: number) => { if (position <= offset) { throw earlyReturnException; } previousNode = void 0; segments.push('[0]'); hasComma = false; }, onArrayEnd: (offset: number, length: number) => { if (position <= offset) { throw earlyReturnException; } previousNode = void 0; if (!hasComma) { segments.pop(); } hasComma = false; }, onLiteralValue: (value: any, offset: number, length: number) => { if (position < offset) { throw earlyReturnException; } setPreviousNode(value, offset, length, value === null ? 'null' : (typeof value === 'string' ? 'string' : 'number')); if (position <= offset + length) { throw earlyReturnException; } }, onSeparator: (sep: string, offset: number, length: number) => { if (position <= offset) { throw earlyReturnException; } if (sep === ':' && previousNode.type === 'property') { previousNode.columnOffset = offset; completeProperty = false; previousNode = void 0; } else if (sep === ',') { let last = segments.pop(); if (last[0] === '[' && last[last.length - 1] === ']') { segments.push('[' + (parseInt(last.substr(1, last.length - 2)) + 1) + ']'); } else { completeProperty = true; } previousNode = void 0; hasComma = true; } } }); } catch (e) { if (e !== earlyReturnException) { throw e; } } return { segments, previousNode, completeProperty, matches: (pattern: string[]) => { 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; } }; } export interface ParseOptions { disallowComments?: boolean; } /** * Parses the given text and returns the object the JSON content represents. On invalid input, the parser tries to be as fault lolerant 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: string, errors: { error: ParseErrorCode; }[] = [], options?: ParseOptions): any { let currentProperty: string = null; let currentParent: any = []; let previousParents: any[] = []; function onValue(value: any) { if (Array.isArray(currentParent)) { (<any[]>currentParent).push(value); } else if (currentProperty) { currentParent[currentProperty] = value; } } let visitor = { onObjectBegin: () => { let object = {}; onValue(object); previousParents.push(currentParent); currentParent = object; currentProperty = null; }, onObjectProperty: (name: string) => { currentProperty = name; }, onObjectEnd: () => { currentParent = previousParents.pop(); }, onArrayBegin: () => { let array = []; onValue(array); previousParents.push(currentParent); currentParent = array; currentProperty = null; }, onArrayEnd: () => { currentParent = previousParents.pop(); }, onLiteralValue: onValue, onError: (error: ParseErrorCode) => { errors.push({ error: error }); } }; visit(text, visitor, options); return currentParent[0]; } /** * Parses the given text and invokes the visitor functions for each object, array and literal reached. */ export function visit(text: string, visitor: JSONVisitor, options?: ParseOptions): any { let _scanner = createScanner(text, false); function toNoArgVisit(visitFunction: (offset: number, length: number) => void): () => void { return visitFunction ? () => visitFunction(_scanner.getTokenOffset(), _scanner.getTokenLength()) : () => true; } function toOneArgVisit<T>(visitFunction: (arg: T, offset: number, length: number) => void): (arg: T) => void { return visitFunction ? (arg: T) => visitFunction(arg, _scanner.getTokenOffset(), _scanner.getTokenLength()) : () => true; } let 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), onError = toOneArgVisit(visitor.onError); let disallowComments = options && options.disallowComments; function scanNext(): SyntaxKind { while (true) { let token = _scanner.scan(); switch (token) { case SyntaxKind.LineCommentTrivia: case SyntaxKind.BlockCommentTrivia: if (disallowComments) { handleError(ParseErrorCode.InvalidSymbol); } break; case SyntaxKind.Unknown: handleError(ParseErrorCode.InvalidSymbol); break; case SyntaxKind.Trivia: case SyntaxKind.LineBreakTrivia: break; default: return token; } } } function handleError(error: ParseErrorCode, skipUntilAfter: SyntaxKind[] = [], skipUntil: SyntaxKind[] = []): void { onError(error); if (skipUntilAfter.length + skipUntil.length > 0) { let token = _scanner.getToken(); while (token !== SyntaxKind.EOF) { if (skipUntilAfter.indexOf(token) !== -1) { scanNext(); break; } else if (skipUntil.indexOf(token) !== -1) { break; } token = scanNext(); } } } function parseString(isValue: boolean): boolean { if (_scanner.getToken() !== SyntaxKind.StringLiteral) { return false; } let value = _scanner.getTokenValue(); if (isValue) { onLiteralValue(value); } else { onObjectProperty(value); } scanNext(); return true; } function parseLiteral(): boolean { switch (_scanner.getToken()) { case SyntaxKind.NumericLiteral: let value = 0; try { value = JSON.parse(_scanner.getTokenValue()); if (typeof value !== 'number') { handleError(ParseErrorCode.InvalidNumberFormat); value = 0; } } catch (e) { handleError(ParseErrorCode.InvalidNumberFormat); } onLiteralValue(value); break; case SyntaxKind.NullKeyword: onLiteralValue(null); break; case SyntaxKind.TrueKeyword: onLiteralValue(true); break; case SyntaxKind.FalseKeyword: onLiteralValue(false); break; default: return false; } scanNext(); return true; } function parseProperty(): boolean { if (!parseString(false)) { handleError(ParseErrorCode.PropertyNameExpected, [], [SyntaxKind.CloseBraceToken, SyntaxKind.CommaToken]); return false; } if (_scanner.getToken() === SyntaxKind.ColonToken) { onSeparator(':'); scanNext(); // consume colon if (!parseValue()) { handleError(ParseErrorCode.ValueExpected, [], [SyntaxKind.CloseBraceToken, SyntaxKind.CommaToken]); } } else { handleError(ParseErrorCode.ColonExpected, [], [SyntaxKind.CloseBraceToken, SyntaxKind.CommaToken]); } return true; } function parseObject(): boolean { if (_scanner.getToken() !== SyntaxKind.OpenBraceToken) { return false; } onObjectBegin(); scanNext(); // consume open brace let needsComma = false; while (_scanner.getToken() !== SyntaxKind.CloseBraceToken && _scanner.getToken() !== SyntaxKind.EOF) { if (_scanner.getToken() === SyntaxKind.CommaToken) { if (!needsComma) { handleError(ParseErrorCode.ValueExpected, [], []); } onSeparator(','); scanNext(); // consume comma } else if (needsComma) { handleError(ParseErrorCode.CommaExpected, [], []); } if (!parseProperty()) { handleError(ParseErrorCode.ValueExpected, [], [SyntaxKind.CloseBraceToken, SyntaxKind.CommaToken]); } needsComma = true; } onObjectEnd(); if (_scanner.getToken() !== SyntaxKind.CloseBraceToken) { handleError(ParseErrorCode.CloseBraceExpected, [SyntaxKind.CloseBraceToken], []); } else { scanNext(); // consume close brace } return true; } function parseArray(): boolean { if (_scanner.getToken() !== SyntaxKind.OpenBracketToken) { return false; } onArrayBegin(); scanNext(); // consume open bracket let needsComma = false; while (_scanner.getToken() !== SyntaxKind.CloseBracketToken && _scanner.getToken() !== SyntaxKind.EOF) { if (_scanner.getToken() === SyntaxKind.CommaToken) { if (!needsComma) { handleError(ParseErrorCode.ValueExpected, [], []); } onSeparator(','); scanNext(); // consume comma } else if (needsComma) { handleError(ParseErrorCode.CommaExpected, [], []); } if (!parseValue()) { handleError(ParseErrorCode.ValueExpected, [], [SyntaxKind.CloseBracketToken, SyntaxKind.CommaToken]); } needsComma = true; } onArrayEnd(); if (_scanner.getToken() !== SyntaxKind.CloseBracketToken) { handleError(ParseErrorCode.CloseBracketExpected, [SyntaxKind.CloseBracketToken], []); } else { scanNext(); // consume close bracket } return true; } function parseValue(): boolean { return parseArray() || parseObject() || parseString(true) || parseLiteral(); } scanNext(); if (!parseValue()) { handleError(ParseErrorCode.ValueExpected, [], []); return false; } if (_scanner.getToken() !== SyntaxKind.EOF) { handleError(ParseErrorCode.EndOfFileExpected, [], []); } return true; } export interface JSONVisitor { /** * Invoked when an open brace is encountered and an object is started. The offset and length represent the location of the open brace. */ onObjectBegin?: (offset: number, length: number) => void; /** * Invoked when a property is encountered. The offset and length represent the location of the property name. */ onObjectProperty?: (property: string, offset: number, length: number) => void; /** * Invoked when a closing brace is encountered and an object is completed. The offset and length represent the location of the closing brace. */ onObjectEnd?: (offset: number, length: number) => void; /** * Invoked when an open bracket is encountered. The offset and length represent the location of the open bracket. */ onArrayBegin?: (offset: number, length: number) => void; /** * Invoked when a closing bracket is encountered. The offset and length represent the location of the closing bracket. */ onArrayEnd?: (offset: number, length: number) => void; /** * Invoked when a literal value is encountered. The offset and length represent the location of the literal value. */ onLiteralValue?: (value: any, offset: number, length: number) => void; /** * Invoked when a comma or colon separator is encountered. The offset and length represent the location of the separator. */ onSeparator?: (charcter: string, offset: number, length: number) => void; /** * Invoked on an error. */ onError?: (error: ParseErrorCode, offset: number, length: number) => void; }