'use strict' ; import { createScanner } from './scanner' ; import { JSONPath , JSONVisitor , Location , Node , NodeType , ParseError , ParseErrorCode , ParseOptions , ScanError , Segment , SyntaxKind } from '../main' ; namespace ParseOptions { export const DEFAULT = { allowTrailingComma : false } ; } interface NodeImpl extends Node { type : NodeType ; value ? : any ; offset : number ; length : number ; colonOffset ? : number ; parent ? : NodeImpl ; children ? : NodeImpl [ ] ; } /**
 * 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 = [ ] ; 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 ( '' ) ; } , 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 ) ) { ( < any [ ] > 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 } ; 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 ) ; 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 = < number > 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 ) ; const _jsonPath = [ ] ; function toNoArgVisit ( visitFunction ? ) { return visitFunction ? ( ) => visitFunction ( _scanner . getTokenOffset ( ) , _scanner . getTokenLength ( ) , _scanner . getTokenStartLine ( ) , _scanner . getTokenStartCharacter ( ) ) : ( ) => true ; } function toNoArgVisitWithPath ( visitFunction ? ) { return visitFunction ? ( ) => visitFunction ( _scanner . getTokenOffset ( ) , _scanner . getTokenLength ( ) , _scanner . getTokenStartLine ( ) , _scanner . getTokenStartCharacter ( ) , ( ) => _jsonPath . slice ( ) ) : ( ) => true ; } function toOneArgVisit < T > ( visitFunction ? ) { return visitFunction ? ( arg ) => visitFunction ( arg , _scanner . getTokenOffset ( ) , _scanner . getTokenLength ( ) , _scanner . getTokenStartLine ( ) , _scanner . getTokenStartCharacter ( ) ) : ( ) => true ; } function toOneArgVisitWithPath < T > ( visitFunction ? ) { return visitFunction ? ( arg ) => visitFunction ( arg , _scanner . getTokenOffset ( ) , _scanner . getTokenLength ( ) , _scanner . getTokenStartLine ( ) , _scanner . getTokenStartCharacter ( ) , ( ) => _jsonPath . slice ( ) ) : ( ) => true ; } const onObjectBegin = toNoArgVisitWithPath ( visitor . onObjectBegin ) , onObjectProperty = toOneArgVisitWithPath ( visitor . onObjectProperty ) , onObjectEnd = toNoArgVisit ( visitor . onObjectEnd ) , onArrayBegin = toNoArgVisitWithPath ( visitor . onArrayBegin ) , onArrayEnd = toNoArgVisit ( 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 ScanError . InvalidUnicode : handleError ( ParseErrorCode . InvalidUnicode ) ; break ; case ScanError . InvalidEscapeCharacter : handleError ( ParseErrorCode . InvalidEscapeCharacter ) ; break ; case ScanError . UnexpectedEndOfNumber : handleError ( ParseErrorCode . UnexpectedEndOfNumber ) ; break ; case ScanError . UnexpectedEndOfComment : if ( ! disallowComments ) { handleError ( ParseErrorCode . UnexpectedEndOfComment ) ; } break ; case ScanError . UnexpectedEndOfString : handleError ( ParseErrorCode . UnexpectedEndOfString ) ; break ; case ScanError . InvalidCharacter : handleError ( ParseErrorCode . InvalidCharacter ) ; break ; } switch ( token ) { case SyntaxKind . LineCommentTrivia : case SyntaxKind . BlockCommentTrivia : if ( disallowComments ) { handleError ( ParseErrorCode . InvalidCommentToken ) ; } else { onComment ( ) ; } break ; case SyntaxKind . Unknown : handleError ( ParseErrorCode . InvalidSymbol ) ; break ; case SyntaxKind . Trivia : case 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 !== 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 ) ; _jsonPath . push ( value ) ; } scanNext ( ) ; return true ; } function parseLiteral ( ) { switch ( _scanner . getToken ( ) ) { case SyntaxKind . NumericLiteral : const tokenValue = _scanner . getTokenValue ( ) ; let value = Number ( tokenValue ) ; if ( isNaN ( value ) ) { handleError ( ParseErrorCode . InvalidNumberFormat ) ; value = 0 ; } 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 ( ) { if ( _scanner . getToken ( ) !== SyntaxKind . StringLiteral ) { handleError ( ParseErrorCode . PropertyNameExpected , [ ] , [ SyntaxKind . CloseBraceToken , SyntaxKind . CommaToken ] ) ; return false ; } parseString ( false ) ; if ( _scanner . getToken ( ) === SyntaxKind . ColonToken ) { onSeparator ( ':' ) ; scanNext ( ) ; if ( ! parseValue ( ) ) { handleError ( ParseErrorCode . ValueExpected , [ ] , [ SyntaxKind . CloseBraceToken , SyntaxKind . CommaToken ] ) ; } } else { handleError ( ParseErrorCode . ColonExpected , [ ] , [ SyntaxKind . CloseBraceToken , SyntaxKind . CommaToken ] ) ; } _jsonPath . pop ( ) ; return true ; } function parseObject ( ) { onObjectBegin ( ) ; scanNext ( ) ; let needsComma = false ; while ( _scanner . getToken ( ) !== SyntaxKind . CloseBraceToken && _scanner . getToken ( ) !== SyntaxKind . EOF ) { if ( _scanner . getToken ( ) === SyntaxKind . CommaToken ) { if ( ! needsComma ) { handleError ( ParseErrorCode . ValueExpected , [ ] , [ ] ) ; } onSeparator ( ',' ) ; scanNext ( ) ; if ( _scanner . getToken ( ) === SyntaxKind . CloseBraceToken && allowTrailingComma ) { break ; } } 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 ( ) ; } return true ; } function parseArray ( ) { onArrayBegin ( ) ; scanNext ( ) ; let isFirstElement = true ; let needsComma = false ; while ( _scanner . getToken ( ) !== SyntaxKind . CloseBracketToken && _scanner . getToken ( ) !== SyntaxKind . EOF ) { if ( _scanner . getToken ( ) === SyntaxKind . CommaToken ) { if ( ! needsComma ) { handleError ( ParseErrorCode . ValueExpected , [ ] , [ ] ) ; } onSeparator ( ',' ) ; scanNext ( ) ; if ( _scanner . getToken ( ) === SyntaxKind . CloseBracketToken && allowTrailingComma ) { break ; } } else if ( needsComma ) { handleError ( ParseErrorCode . CommaExpected , [ ] , [ ] ) ; } if ( isFirstElement ) { _jsonPath . push ( 0 ) ; isFirstElement = false ; } else { ( _jsonPath [ _jsonPath . length - 1 ] as number ) ++ ; } if ( ! parseValue ( ) ) { handleError ( ParseErrorCode . ValueExpected , [ ] , [ SyntaxKind . CloseBracketToken , SyntaxKind . CommaToken ] ) ; } needsComma = true ; } onArrayEnd ( ) ; if ( ! isFirstElement ) { _jsonPath . pop ( ) ; } if ( _scanner . getToken ( ) !== SyntaxKind . CloseBracketToken ) { handleError ( ParseErrorCode . CloseBracketExpected , [ SyntaxKind . CloseBracketToken ] , [ ] ) ; } else { scanNext ( ) ; } return true ; } function parseValue ( ) { switch ( _scanner . getToken ( ) ) { case SyntaxKind . OpenBracketToken : return parseArray ( ) ; case SyntaxKind . OpenBraceToken : return parseObject ( ) ; case SyntaxKind . StringLiteral : return parseString ( true ) ; default : return parseLiteral ( ) ; } } scanNext ( ) ; if ( _scanner . getToken ( ) === SyntaxKind . EOF ) { if ( options . allowEmptyContent ) { return true ; } handleError ( ParseErrorCode . ValueExpected , [ ] , [ ] ) ; return false ; } if ( ! parseValue ( ) ) { handleError ( ParseErrorCode . ValueExpected , [ ] , [ ] ) ; return false ; } if ( _scanner . getToken ( ) !== SyntaxKind . EOF ) { handleError ( 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 SyntaxKind . LineCommentTrivia : case SyntaxKind . BlockCommentTrivia : case 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 !== 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' ; } }