| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659 | /*--------------------------------------------------------------------------------------------- *  Copyright (c) Microsoft Corporation. All rights reserved. *  Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/'use strict';import { createScanner } from './scanner';var ParseOptions;(function (ParseOptions) {    ParseOptions.DEFAULT = {        allowTrailingComma: false    };})(ParseOptions || (ParseOptions = {}));/** * 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) => {            ensurePropertyComplete(offset + length); // in case of a missing value for a property: make sure property is complete            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 (let 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 && propertyNode.children.length === 2) {                    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 (let 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);    // Important: Only pass copies of this to visitor functions to prevent accidental modification, and    // to not affect visitor functions which stored a reference to a previous JSONPath    const _jsonPath = [];    // Depth of onXXXBegin() callbacks suppressed. onXXXEnd() decrements this if it isn't 0 already.    // Callbacks are only called when this value is 0.    let suppressedCallbacks = 0;    function toNoArgVisit(visitFunction) {        return visitFunction ? () => suppressedCallbacks === 0 && visitFunction(_scanner.getTokenOffset(), _scanner.getTokenLength(), _scanner.getTokenStartLine(), _scanner.getTokenStartCharacter()) : () => true;    }    function toOneArgVisit(visitFunction) {        return visitFunction ? (arg) => suppressedCallbacks === 0 && visitFunction(arg, _scanner.getTokenOffset(), _scanner.getTokenLength(), _scanner.getTokenStartLine(), _scanner.getTokenStartCharacter()) : () => true;    }    function toOneArgVisitWithPath(visitFunction) {        return visitFunction ? (arg) => suppressedCallbacks === 0 && visitFunction(arg, _scanner.getTokenOffset(), _scanner.getTokenLength(), _scanner.getTokenStartLine(), _scanner.getTokenStartCharacter(), () => _jsonPath.slice()) : () => true;    }    function toBeginVisit(visitFunction) {        return visitFunction ?            () => {                if (suppressedCallbacks > 0) {                    suppressedCallbacks++;                }                else {                    let cbReturn = visitFunction(_scanner.getTokenOffset(), _scanner.getTokenLength(), _scanner.getTokenStartLine(), _scanner.getTokenStartCharacter(), () => _jsonPath.slice());                    if (cbReturn === false) {                        suppressedCallbacks = 1;                    }                }            }            : () => true;    }    function toEndVisit(visitFunction) {        return visitFunction ?            () => {                if (suppressedCallbacks > 0) {                    suppressedCallbacks--;                }                if (suppressedCallbacks === 0) {                    visitFunction(_scanner.getTokenOffset(), _scanner.getTokenLength(), _scanner.getTokenStartLine(), _scanner.getTokenStartCharacter());                }            }            : () => true;    }    const onObjectBegin = toBeginVisit(visitor.onObjectBegin), onObjectProperty = toOneArgVisitWithPath(visitor.onObjectProperty), onObjectEnd = toEndVisit(visitor.onObjectEnd), onArrayBegin = toBeginVisit(visitor.onArrayBegin), onArrayEnd = toEndVisit(visitor.onArrayEnd), onLiteralValue = toOneArgVisitWithPath(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);            // add property name afterwards            _jsonPath.push(value);        }        scanNext();        return true;    }    function parseLiteral() {        switch (_scanner.getToken()) {            case 11 /* SyntaxKind.NumericLiteral */:                const tokenValue = _scanner.getTokenValue();                let value = Number(tokenValue);                if (isNaN(value)) {                    handleError(2 /* ParseErrorCode.InvalidNumberFormat */);                    value = 0;                }                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 */]);        }        _jsonPath.pop(); // remove processed property name        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 isFirstElement = true;        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 (isFirstElement) {                _jsonPath.push(0);                isFirstElement = false;            }            else {                _jsonPath[_jsonPath.length - 1]++;            }            if (!parseValue()) {                handleError(4 /* ParseErrorCode.ValueExpected */, [], [4 /* SyntaxKind.CloseBracketToken */, 5 /* SyntaxKind.CommaToken */]);            }            needsComma = true;        }        onArrayEnd();        if (!isFirstElement) {            _jsonPath.pop(); // remove array index        }        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) {    let _scanner = createScanner(text), parts = [], kind, offset = 0, 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';    }}
 |