'use strict' ; import * as assert from 'assert' ; import { SyntaxKind , createScanner , parse , getLocation , Node , ParseError , parseTree , ParseErrorCode , ParseOptions , Segment , findNodeAtLocation , getNodeValue , getNodePath , ScanError , visit , JSONVisitor , JSONPath } from '../main' ; function assertKinds ( text , ... kinds ) { var scanner = createScanner ( text ) ; var kind ; while ( ( kind = scanner . scan ( ) ) !== SyntaxKind . EOF ) { assert . strictEqual ( kind , kinds . shift ( ) ) ; assert . strictEqual ( scanner . getTokenError ( ) , ScanError . None , text ) ; } assert . strictEqual ( kinds . length , 0 ) ; } function assertScanError ( text , scanError , ... kinds ) { var scanner = createScanner ( text ) ; scanner . scan ( ) ; assert . strictEqual ( scanner . getToken ( ) , kinds . shift ( ) ) ; assert . strictEqual ( scanner . getTokenError ( ) , scanError ) ; var kind ; while ( ( kind = scanner . scan ( ) ) !== SyntaxKind . EOF ) { assert . strictEqual ( kind , kinds . shift ( ) ) ; } assert . strictEqual ( kinds . length , 0 ) ; } function assertValidParse ( input , expected , options ? ) { var errors = [ ] ; var actual = parse ( input , errors , options ) ; assert . deepStrictEqual ( [ ] , errors ) ; assert . deepStrictEqual ( actual , expected ) ; } function assertInvalidParse ( input , expected , options ? ) { var errors = [ ] ; var actual = parse ( input , errors , options ) ; assert ( errors . length > 0 ) ; assert . deepStrictEqual ( actual , expected ) ; } function assertTree ( input , expected , expectedErrors = [ ] ) { var errors = [ ] ; var actual = parseTree ( input , errors ) ; assert . deepStrictEqual ( errors , expectedErrors ) ; let checkParent = ( node ) => { if ( node ?. children ) { for ( let child of node . children ) { assert . strictEqual ( node , child . parent ) ; delete ( < any > child ) . parent ; checkParent ( child ) ; } } } ; checkParent ( actual ) ; assert . deepStrictEqual ( actual , expected , JSON . stringify ( actual ) ) ; } interface VisitorCallback { id : keyof JSONVisitor ; text : string ; startLine : number ; startCharacter : number ; arg ? : any ; path ? : JSONPath ; } interface VisitorError extends ParseError { startLine : number ; startCharacter : number ; } function assertVisit ( input , expected , expectedErrors = [ ] , disallowComments = false ) { let errors = [ ] ; let actuals = [ ] ; let noArgHalder = ( id ) => ( offset , length , startLine , startCharacter ) => actuals . push ( { id , text : input . substr ( offset , length ) , startLine , startCharacter } ) ; let noArgHalderWithPath = ( id ) => ( offset , length , startLine , startCharacter , pathSupplier ) => actuals . push ( { id , text : input . substr ( offset , length ) , startLine , startCharacter , path : pathSupplier ( ) } ) ; let oneArgHalder = ( id ) => ( arg , offset , length , startLine , startCharacter ) => actuals . push ( { id , text : input . substr ( offset , length ) , startLine , startCharacter , arg } ) ; let oneArgHalderWithPath = ( id ) => ( arg , offset , length , startLine , startCharacter , pathSupplier ) => actuals . push ( { id , text : input . substr ( offset , length ) , startLine , startCharacter , arg , path : pathSupplier ( ) } ) ; visit ( input , { onObjectBegin : noArgHalderWithPath ( 'onObjectBegin' ) , onObjectProperty : oneArgHalderWithPath ( 'onObjectProperty' ) , onObjectEnd : noArgHalder ( 'onObjectEnd' ) , onArrayBegin : noArgHalderWithPath ( 'onArrayBegin' ) , onArrayEnd : noArgHalder ( 'onArrayEnd' ) , onLiteralValue : oneArgHalderWithPath ( 'onLiteralValue' ) , onSeparator : oneArgHalder ( 'onSeparator' ) , onComment : noArgHalder ( 'onComment' ) , onError : ( error , offset , length , startLine , startCharacter ) => { errors . push ( { error , offset , length , startLine , startCharacter } ) ; } } , { disallowComments } ) ; assert . deepStrictEqual ( errors , expectedErrors ) ; assert . deepStrictEqual ( actuals , expected , JSON . stringify ( actuals ) ) ; } function assertNodeAtLocation ( input , segments , expected ) { let actual = input && findNodeAtLocation ( input , segments ) ; assert . deepEqual ( actual ? getNodeValue ( actual ) : void 0 , expected ) ; if ( actual ) { assert . deepStrictEqual ( getNodePath ( actual ) , segments ) ; } } function assertLocation ( input , expectedSegments , expectedNodeType , expectedCompleteProperty ) { var offset = input . indexOf ( '|' ) ; input = input . substring ( 0 , offset ) + input . substring ( offset + 1 , input . length ) ; var actual = getLocation ( input , offset ) ; assert ( actual ) ; assert . deepStrictEqual ( actual . path , expectedSegments , input ) ; assert . strictEqual ( actual . previousNode && actual . previousNode . type , expectedNodeType , input ) ; assert . strictEqual ( actual . isAtPropertyKey , expectedCompleteProperty , input ) ; } function assertMatchesLocation ( input , matchingSegments , expectedResult = true ) { var offset = input . indexOf ( '|' ) ; input = input . substring ( 0 , offset ) + input . substring ( offset + 1 , input . length ) ; var actual = getLocation ( input , offset ) ; assert ( actual ) ; assert . strictEqual ( actual . matches ( matchingSegments ) , expectedResult ) ; } suite ( 'JSON' , ( ) => { test ( 'tokens' , ( ) => { assertKinds ( '{' , SyntaxKind . OpenBraceToken ) ; assertKinds ( '}' , SyntaxKind . CloseBraceToken ) ; assertKinds ( '[' , SyntaxKind . OpenBracketToken ) ; assertKinds ( ']' , SyntaxKind . CloseBracketToken ) ; assertKinds ( ':' , SyntaxKind . ColonToken ) ; assertKinds ( ',' , SyntaxKind . CommaToken ) ; } ) ; test ( 'comments' , ( ) => { assertKinds ( '// this is a comment' , SyntaxKind . LineCommentTrivia ) ; assertKinds ( '// this is a comment\n' , SyntaxKind . LineCommentTrivia , SyntaxKind . LineBreakTrivia ) ; assertKinds ( '/* this is a comment*/' , SyntaxKind . BlockCommentTrivia ) ; assertKinds ( '/* this is a \r\ncomment*/' , SyntaxKind . BlockCommentTrivia ) ; assertKinds ( '/* this is a \ncomment*/' , SyntaxKind . BlockCommentTrivia ) ; assertScanError ( '/* this is a' , ScanError . UnexpectedEndOfComment , SyntaxKind . BlockCommentTrivia ) ; assertScanError ( '/* this is a \ncomment' , ScanError . UnexpectedEndOfComment , SyntaxKind . BlockCommentTrivia ) ; assertKinds ( '/ ttt' , SyntaxKind . Unknown , SyntaxKind . Trivia , SyntaxKind . Unknown ) ; } ) ; test ( 'strings' , ( ) => { assertKinds ( '"test"' , SyntaxKind . StringLiteral ) ; assertKinds ( '"\\""' , SyntaxKind . StringLiteral ) ; assertKinds ( '"\\/"' , SyntaxKind . StringLiteral ) ; assertKinds ( '"\\b"' , SyntaxKind . StringLiteral ) ; assertKinds ( '"\\f"' , SyntaxKind . StringLiteral ) ; assertKinds ( '"\\n"' , SyntaxKind . StringLiteral ) ; assertKinds ( '"\\r"' , SyntaxKind . StringLiteral ) ; assertKinds ( '"\\t"' , SyntaxKind . StringLiteral ) ; assertKinds ( '"\u88ff"' , SyntaxKind . StringLiteral ) ; assertKinds ( '"​\u2028"' , SyntaxKind . StringLiteral ) ; assertScanError ( '"\\v"' , ScanError . InvalidEscapeCharacter , SyntaxKind . StringLiteral ) ; assertScanError ( '"test' , ScanError . UnexpectedEndOfString , SyntaxKind . StringLiteral ) ; assertScanError ( '"test\n"' , ScanError . UnexpectedEndOfString , SyntaxKind . StringLiteral , SyntaxKind . LineBreakTrivia , SyntaxKind . StringLiteral ) ; assertScanError ( '"\t"' , ScanError . InvalidCharacter , SyntaxKind . StringLiteral ) ; assertScanError ( '"\t "' , ScanError . InvalidCharacter , SyntaxKind . StringLiteral ) ; assertScanError ( '"\0 "' , ScanError . InvalidCharacter , SyntaxKind . StringLiteral ) ; } ) ; test ( 'numbers' , ( ) => { assertKinds ( '0' , SyntaxKind . NumericLiteral ) ; assertKinds ( '0.1' , SyntaxKind . NumericLiteral ) ; assertKinds ( '-0.1' , SyntaxKind . NumericLiteral ) ; assertKinds ( '-1' , SyntaxKind . NumericLiteral ) ; assertKinds ( '1' , SyntaxKind . NumericLiteral ) ; assertKinds ( '123456789' , SyntaxKind . NumericLiteral ) ; assertKinds ( '10' , SyntaxKind . NumericLiteral ) ; assertKinds ( '90' , SyntaxKind . NumericLiteral ) ; assertKinds ( '90E+123' , SyntaxKind . NumericLiteral ) ; assertKinds ( '90e+123' , SyntaxKind . NumericLiteral ) ; assertKinds ( '90e-123' , SyntaxKind . NumericLiteral ) ; assertKinds ( '90E-123' , SyntaxKind . NumericLiteral ) ; assertKinds ( '90E123' , SyntaxKind . NumericLiteral ) ; assertKinds ( '90e123' , SyntaxKind . NumericLiteral ) ; assertKinds ( '01' , SyntaxKind . NumericLiteral , SyntaxKind . NumericLiteral ) ; assertKinds ( '-01' , SyntaxKind . NumericLiteral , SyntaxKind . NumericLiteral ) ; assertKinds ( '-' , SyntaxKind . Unknown ) ; assertKinds ( '.0' , SyntaxKind . Unknown ) ; } ) ; test ( 'keywords: true, false, null' , ( ) => { assertKinds ( 'true' , SyntaxKind . TrueKeyword ) ; assertKinds ( 'false' , SyntaxKind . FalseKeyword ) ; assertKinds ( 'null' , SyntaxKind . NullKeyword ) ; assertKinds ( 'true false null' , SyntaxKind . TrueKeyword , SyntaxKind . Trivia , SyntaxKind . FalseKeyword , SyntaxKind . Trivia , SyntaxKind . NullKeyword ) ; assertKinds ( 'nulllll' , SyntaxKind . Unknown ) ; assertKinds ( 'True' , SyntaxKind . Unknown ) ; assertKinds ( 'foo-bar' , SyntaxKind . Unknown ) ; assertKinds ( 'foo bar' , SyntaxKind . Unknown , SyntaxKind . Trivia , SyntaxKind . Unknown ) ; assertKinds ( 'false//hello' , SyntaxKind . FalseKeyword , SyntaxKind . LineCommentTrivia ) ; } ) ; test ( 'trivia' , ( ) => { assertKinds ( ' ' , SyntaxKind . Trivia ) ; assertKinds ( '  \t  ' , SyntaxKind . Trivia ) ; assertKinds ( '  \t  \n  \t  ' , SyntaxKind . Trivia , SyntaxKind . LineBreakTrivia , SyntaxKind . Trivia ) ; assertKinds ( '\r\n' , SyntaxKind . LineBreakTrivia ) ; assertKinds ( '\r' , SyntaxKind . LineBreakTrivia ) ; assertKinds ( '\n' , SyntaxKind . LineBreakTrivia ) ; assertKinds ( '\n\r' , SyntaxKind . LineBreakTrivia , SyntaxKind . LineBreakTrivia ) ; assertKinds ( '\n   \n' , SyntaxKind . LineBreakTrivia , SyntaxKind . Trivia , SyntaxKind . LineBreakTrivia ) ; } ) ; test ( 'parse: literals' , ( ) => { assertValidParse ( 'true' , true ) ; assertValidParse ( 'false' , false ) ; assertValidParse ( 'null' , null ) ; assertValidParse ( '"foo"' , 'foo' ) ; assertValidParse ( '"\\"-\\\\-\\/-\\b-\\f-\\n-\\r-\\t"' , '"-\\-/-\b-\f-\n-\r-\t' ) ; assertValidParse ( '"\\u00DC"' , 'Ü' ) ; assertValidParse ( '9' , 9 ) ; assertValidParse ( '-9' , - 9 ) ; assertValidParse ( '0.129' , 0.129 ) ; assertValidParse ( '23e3' , 23e3 ) ; assertValidParse ( '1.2E+3' , 1.2E+3 ) ; assertValidParse ( '1.2E-3' , 1.2E-3 ) ; assertValidParse ( '1.2E-3 // comment' , 1.2E-3 ) ; } ) ; test ( 'parse: objects' , ( ) => { assertValidParse ( '{}' , { } ) ; assertValidParse ( '{ "foo": true }' , { foo : true } ) ; assertValidParse ( '{ "bar": 8, "xoo": "foo" }' , { bar : 8 , xoo : 'foo' } ) ; assertValidParse ( '{ "hello": [], "world": {} }' , { hello : [ ] , world : { } } ) ; assertValidParse ( '{ "a": false, "b": true, "c": [ 7.4 ] }' , { a : false , b : true , c : [ 7.4 ] } ) ; assertValidParse ( '{ "lineComment": "//", "blockComment": ["/*", "*/"], "brackets": [ ["{", "}"], ["[", "]"], ["(", ")"] ] }' , { lineComment : '//' , blockComment : [ '/*' , '*/' ] , brackets : [ [ '{' , '}' ] , [ '[' , ']' ] , [ '(' , ')' ] ] } ) ; assertValidParse ( '{ "hello": [], "world": {} }' , { hello : [ ] , world : { } } ) ; assertValidParse ( '{ "hello": { "again": { "inside": 5 }, "world": 1 }}' , { hello : { again : { inside : 5 } , world : 1 } } ) ; assertValidParse ( '{ "foo": /*hello*/true }' , { foo : true } ) ; assertValidParse ( '{ "": true }' , { '' : true } ) ; } ) ; test ( 'parse: arrays' , ( ) => { assertValidParse ( '[]' , [ ] ) ; assertValidParse ( '[ [],  [ [] ]]' , [ [ ] , [ [ ] ] ] ) ; assertValidParse ( '[ 1, 2, 3 ]' , [ 1 , 2 , 3 ] ) ; assertValidParse ( '[ { "a": null } ]' , [ { a : null } ] ) ; } ) ; test ( 'parse: objects with errors' , ( ) => { assertInvalidParse ( '{,}' , { } ) ; assertInvalidParse ( '{ "foo": true, }' , { foo : true } ) ; assertInvalidParse ( '{ "bar": 8 "xoo": "foo" }' , { bar : 8 , xoo : 'foo' } ) ; assertInvalidParse ( '{ ,"bar": 8 }' , { bar : 8 } ) ; assertInvalidParse ( '{ ,"bar": 8, "foo" }' , { bar : 8 } ) ; assertInvalidParse ( '{ "bar": 8, "foo": }' , { bar : 8 } ) ; assertInvalidParse ( '{ 8, "foo": 9 }' , { foo : 9 } ) ; } ) ; test ( 'parse: array with errors' , ( ) => { assertInvalidParse ( '[,]' , [ ] ) ; assertInvalidParse ( '[ 1 2, 3 ]' , [ 1 , 2 , 3 ] ) ; assertInvalidParse ( '[ ,1, 2, 3 ]' , [ 1 , 2 , 3 ] ) ; assertInvalidParse ( '[ ,1, 2, 3, ]' , [ 1 , 2 , 3 ] ) ; } ) ; test ( 'parse: errors' , ( ) => { assertInvalidParse ( '' , undefined ) ; assertInvalidParse ( '1,1' , 1 ) ; } ) ; test ( 'parse: disallow comments' , ( ) => { let options = { disallowComments : true } ; assertValidParse ( '[ 1, 2, null, "foo" ]' , [ 1 , 2 , null , 'foo' ] , options ) ; assertValidParse ( '{ "hello": [], "world": {} }' , { hello : [ ] , world : { } } , options ) ; assertInvalidParse ( '{ "foo": /*comment*/ true }' , { foo : true } , options ) ; } ) ; test ( 'parse: trailing comma' , ( ) => { let options = { allowTrailingComma : true } ; assertValidParse ( '{ "hello": [], }' , { hello : [ ] } , options ) ; assertValidParse ( '{ "hello": [] }' , { hello : [ ] } , options ) ; assertValidParse ( '{ "hello": [], "world": {}, }' , { hello : [ ] , world : { } } , options ) ; assertValidParse ( '{ "hello": [], "world": {} }' , { hello : [ ] , world : { } } , options ) ; assertValidParse ( '[ 1, 2, ]' , [ 1 , 2 ] , options ) ; assertValidParse ( '[ 1, 2 ]' , [ 1 , 2 ] , options ) ; assertInvalidParse ( '{ "hello": [], }' , { hello : [ ] } ) ; assertInvalidParse ( '{ "hello": [], "world": {}, }' , { hello : [ ] , world : { } } ) ; assertInvalidParse ( '[ 1, 2, ]' , [ 1 , 2 ] ) ; } ) ; test ( 'location: properties' , ( ) => { assertLocation ( '|{ "foo": "bar" }' , [ ] , void 0 , false ) ; assertLocation ( '{| "foo": "bar" }' , [ '' ] , void 0 , true ) ; assertLocation ( '{ |"foo": "bar" }' , [ 'foo' ] , 'property' , true ) ; assertLocation ( '{ "foo|": "bar" }' , [ 'foo' ] , 'property' , true ) ; assertLocation ( '{ "foo"|: "bar" }' , [ 'foo' ] , 'property' , true ) ; assertLocation ( '{ "foo": "bar"| }' , [ 'foo' ] , 'string' , false ) ; assertLocation ( '{ "foo":| "bar" }' , [ 'foo' ] , void 0 , false ) ; assertLocation ( '{ "foo": {"bar|": 1, "car": 2 } }' , [ 'foo' , 'bar' ] , 'property' , true ) ; assertLocation ( '{ "foo": {"bar": 1|, "car": 3 } }' , [ 'foo' , 'bar' ] , 'number' , false ) ; assertLocation ( '{ "foo": {"bar": 1,| "car": 4 } }' , [ 'foo' , '' ] , void 0 , true ) ; assertLocation ( '{ "foo": {"bar": 1, "ca|r": 5 } }' , [ 'foo' , 'car' ] , 'property' , true ) ; assertLocation ( '{ "foo": {"bar": 1, "car": 6| } }' , [ 'foo' , 'car' ] , 'number' , false ) ; assertLocation ( '{ "foo": {"bar": 1, "car": 7 }| }' , [ 'foo' ] , void 0 , false ) ; assertLocation ( '{ "foo": {"bar": 1, "car": 8 },| "goo": {} }' , [ '' ] , void 0 , true ) ; assertLocation ( '{ "foo": {"bar": 1, "car": 9 }, "go|o": {} }' , [ 'goo' ] , 'property' , true ) ; assertLocation ( '{ "dep": {"bar": 1, "car": |' , [ 'dep' , 'car' ] , void 0 , false ) ; assertLocation ( '{ "dep": {"bar": 1,, "car": |' , [ 'dep' , 'car' ] , void 0 , false ) ; assertLocation ( '{ "dep": {"bar": "na", "dar": "ma", "car": | } }' , [ 'dep' , 'car' ] , void 0 , false ) ; } ) ; test ( 'location: arrays' , ( ) => { assertLocation ( '|["foo", null ]' , [ ] , void 0 , false ) ; assertLocation ( '[|"foo", null ]' , [ 0 ] , 'string' , false ) ; assertLocation ( '["foo"|, null ]' , [ 0 ] , 'string' , false ) ; assertLocation ( '["foo",| null ]' , [ 1 ] , void 0 , false ) ; assertLocation ( '["foo", |null ]' , [ 1 ] , 'null' , false ) ; assertLocation ( '["foo", null,| ]' , [ 2 ] , void 0 , false ) ; assertLocation ( '["foo", null,,| ]' , [ 3 ] , void 0 , false ) ; assertLocation ( '[["foo", null,, ],|' , [ 1 ] , void 0 , false ) ; } ) ; test ( 'tree: literals' , ( ) => { assertTree ( 'true' , { type : 'boolean' , offset : 0 , length : 4 , value : true } ) ; assertTree ( 'false' , { type : 'boolean' , offset : 0 , length : 5 , value : false } ) ; assertTree ( 'null' , { type : 'null' , offset : 0 , length : 4 , value : null } ) ; assertTree ( '23' , { type : 'number' , offset : 0 , length : 2 , value : 23 } ) ; assertTree ( '-1.93e-19' , { type : 'number' , offset : 0 , length : 9 , value : - 1.93e-19 } ) ; assertTree ( '"hello"' , { type : 'string' , offset : 0 , length : 7 , value : 'hello' } ) ; } ) ; test ( 'tree: arrays' , ( ) => { assertTree ( '[]' , { type : 'array' , offset : 0 , length : 2 , children : [ ] } ) ; assertTree ( '[ 1 ]' , { type : 'array' , offset : 0 , length : 5 , children : [ { type : 'number' , offset : 2 , length : 1 , value : 1 } ] } ) ; assertTree ( '[ 1,"x"]' , { type : 'array' , offset : 0 , length : 8 , children : [ { type : 'number' , offset : 2 , length : 1 , value : 1 } , { type : 'string' , offset : 4 , length : 3 , value : 'x' } ] } ) ; assertTree ( '[[]]' , { type : 'array' , offset : 0 , length : 4 , children : [ { type : 'array' , offset : 1 , length : 2 , children : [ ] } ] } ) ; } ) ; test ( 'tree: objects' , ( ) => { assertTree ( '{ }' , { type : 'object' , offset : 0 , length : 3 , children : [ ] } ) ; assertTree ( '{ "val": 1 }' , { type : 'object' , offset : 0 , length : 12 , children : [ { type : 'property' , offset : 2 , length : 8 , colonOffset : 7 , children : [ { type : 'string' , offset : 2 , length : 5 , value : 'val' } , { type : 'number' , offset : 9 , length : 1 , value : 1 } ] } ] } ) ; assertTree ( '{"id": "$", "v": [ null, null] }' , { type : 'object' , offset : 0 , length : 32 , children : [ { type : 'property' , offset : 1 , length : 9 , colonOffset : 5 , children : [ { type : 'string' , offset : 1 , length : 4 , value : 'id' } , { type : 'string' , offset : 7 , length : 3 , value : '$' } ] } , { type : 'property' , offset : 12 , length : 18 , colonOffset : 15 , children : [ { type : 'string' , offset : 12 , length : 3 , value : 'v' } , { type : 'array' , offset : 17 , length : 13 , children : [ { type : 'null' , offset : 19 , length : 4 , value : null } , { type : 'null' , offset : 25 , length : 4 , value : null } ] } ] } ] } ) ; assertTree ( '{  "id": { "foo": { } } , }' , { type : 'object' , offset : 0 , length : 27 , children : [ { type : 'property' , offset : 3 , length : 20 , colonOffset : 7 , children : [ { type : 'string' , offset : 3 , length : 4 , value : 'id' } , { type : 'object' , offset : 9 , length : 14 , children : [ { type : 'property' , offset : 11 , length : 10 , colonOffset : 16 , children : [ { type : 'string' , offset : 11 , length : 5 , value : 'foo' } , { type : 'object' , offset : 18 , length : 3 , children : [ ] } ] } ] } ] } ] } , [ { error : ParseErrorCode . PropertyNameExpected , offset : 26 , length : 1 } , { error : ParseErrorCode . ValueExpected , offset : 26 , length : 1 } ] ) ; } ) ; test ( 'visit: object' , ( ) => { assertVisit ( '{ }' , [ { id : 'onObjectBegin' , text : '{' , startLine : 0 , startCharacter : 0 , path : [ ] } , { id : 'onObjectEnd' , text : '}' , startLine : 0 , startCharacter : 2 } , ] ) ; assertVisit ( '{ "foo": "bar" }' , [ { id : 'onObjectBegin' , text : '{' , startLine : 0 , startCharacter : 0 , path : [ ] } , { id : 'onObjectProperty' , text : '"foo"' , startLine : 0 , startCharacter : 2 , arg : 'foo' , path : [ ] } , { id : 'onSeparator' , text : ':' , startLine : 0 , startCharacter : 7 , arg : ':' } , { id : 'onLiteralValue' , text : '"bar"' , startLine : 0 , startCharacter : 9 , arg : 'bar' , path : [ 'foo' ] } , { id : 'onObjectEnd' , text : '}' , startLine : 0 , startCharacter : 15 } , ] ) ; assertVisit ( '{ "foo": { "goo": 3 } }' , [ { id : 'onObjectBegin' , text : '{' , startLine : 0 , startCharacter : 0 , path : [ ] } , { id : 'onObjectProperty' , text : '"foo"' , startLine : 0 , startCharacter : 2 , arg : 'foo' , path : [ ] } , { id : 'onSeparator' , text : ':' , startLine : 0 , startCharacter : 7 , arg : ':' } , { id : 'onObjectBegin' , text : '{' , startLine : 0 , startCharacter : 9 , path : [ 'foo' ] } , { id : 'onObjectProperty' , text : '"goo"' , startLine : 0 , startCharacter : 11 , arg : 'goo' , path : [ 'foo' ] } , { id : 'onSeparator' , text : ':' , startLine : 0 , startCharacter : 16 , arg : ':' } , { id : 'onLiteralValue' , text : '3' , startLine : 0 , startCharacter : 18 , arg : 3 , path : [ 'foo' , 'goo' ] } , { id : 'onObjectEnd' , text : '}' , startLine : 0 , startCharacter : 20 } , { id : 'onObjectEnd' , text : '}' , startLine : 0 , startCharacter : 22 } , ] ) ; } ) ; test ( 'visit: array' , ( ) => { assertVisit ( '[]' , [ { id : 'onArrayBegin' , text : '[' , startLine : 0 , startCharacter : 0 , path : [ ] } , { id : 'onArrayEnd' , text : ']' , startLine : 0 , startCharacter : 1 } , ] ) ; assertVisit ( '[ true, null, [] ]' , [ { id : 'onArrayBegin' , text : '[' , startLine : 0 , startCharacter : 0 , path : [ ] } , { id : 'onLiteralValue' , text : 'true' , startLine : 0 , startCharacter : 2 , arg : true , path : [ 0 ] } , { id : 'onSeparator' , text : ',' , startLine : 0 , startCharacter : 6 , arg : ',' } , { id : 'onLiteralValue' , text : 'null' , startLine : 0 , startCharacter : 8 , arg : null , path : [ 1 ] } , { id : 'onSeparator' , text : ',' , startLine : 0 , startCharacter : 12 , arg : ',' } , { id : 'onArrayBegin' , text : '[' , startLine : 0 , startCharacter : 14 , path : [ 2 ] } , { id : 'onArrayEnd' , text : ']' , startLine : 0 , startCharacter : 15 } , { id : 'onArrayEnd' , text : ']' , startLine : 0 , startCharacter : 17 } , ] ) ; assertVisit ( '[\r\n0,\r\n1,\r\n2\r\n]' , [ { id : 'onArrayBegin' , text : '[' , startLine : 0 , startCharacter : 0 , path : [ ] } , { id : 'onLiteralValue' , text : '0' , startLine : 1 , startCharacter : 0 , arg : 0 , path : [ 0 ] } , { id : 'onSeparator' , text : ',' , startLine : 1 , startCharacter : 1 , arg : ',' } , { id : 'onLiteralValue' , text : '1' , startLine : 2 , startCharacter : 0 , arg : 1 , path : [ 1 ] } , { id : 'onSeparator' , text : ',' , startLine : 2 , startCharacter : 1 , arg : ',' } , { id : 'onLiteralValue' , text : '2' , startLine : 3 , startCharacter : 0 , arg : 2 , path : [ 2 ] } , { id : 'onArrayEnd' , text : ']' , startLine : 4 , startCharacter : 0 } , ] ) ; } ) ; test ( 'visit: object & array' , ( ) => { assertVisit ( '[ { "p1": [ { "p11": 1, "p12": [ true, [ false, 2 ] ] } ] } ]' , [ { id : 'onArrayBegin' , text : '[' , startLine : 0 , startCharacter : 0 , path : [ ] } , { id : 'onObjectBegin' , text : '{' , startLine : 0 , startCharacter : 2 , path : [ 0 ] } , { id : 'onObjectProperty' , text : '"p1"' , startLine : 0 , startCharacter : 4 , arg : 'p1' , path : [ 0 ] } , { id : 'onSeparator' , text : ':' , startLine : 0 , startCharacter : 8 , arg : ':' } , { id : 'onArrayBegin' , text : '[' , startLine : 0 , startCharacter : 10 , path : [ 0 , 'p1' ] } , { id : 'onObjectBegin' , text : '{' , startLine : 0 , startCharacter : 12 , path : [ 0 , 'p1' , 0 ] } , { id : 'onObjectProperty' , text : '"p11"' , startLine : 0 , startCharacter : 14 , arg : 'p11' , path : [ 0 , 'p1' , 0 ] } , { id : 'onSeparator' , text : ':' , startLine : 0 , startCharacter : 19 , arg : ':' } , { id : 'onLiteralValue' , text : '1' , startLine : 0 , startCharacter : 21 , arg : 1 , path : [ 0 , 'p1' , 0 , 'p11' ] } , { id : 'onSeparator' , text : ',' , startLine : 0 , startCharacter : 22 , arg : ',' } , { id : 'onObjectProperty' , text : '"p12"' , startLine : 0 , startCharacter : 24 , arg : 'p12' , path : [ 0 , 'p1' , 0 ] } , { id : 'onSeparator' , text : ':' , startLine : 0 , startCharacter : 29 , arg : ':' } , { id : 'onArrayBegin' , text : '[' , startLine : 0 , startCharacter : 31 , path : [ 0 , 'p1' , 0 , 'p12' ] } , { id : 'onLiteralValue' , text : 'true' , startLine : 0 , startCharacter : 33 , arg : true , path : [ 0 , 'p1' , 0 , 'p12' , 0 ] } , { id : 'onSeparator' , text : ',' , startLine : 0 , startCharacter : 37 , arg : ',' } , { id : 'onArrayBegin' , text : '[' , startLine : 0 , startCharacter : 39 , path : [ 0 , 'p1' , 0 , 'p12' , 1 ] } , { id : 'onLiteralValue' , text : 'false' , startLine : 0 , startCharacter : 41 , arg : false , path : [ 0 , 'p1' , 0 , 'p12' , 1 , 0 ] } , { id : 'onSeparator' , text : ',' , startLine : 0 , startCharacter : 46 , arg : ',' } , { id : 'onLiteralValue' , text : '2' , startLine : 0 , startCharacter : 48 , arg : 2 , path : [ 0 , 'p1' , 0 , 'p12' , 1 , 1 ] } , { id : 'onArrayEnd' , text : ']' , startLine : 0 , startCharacter : 50 } , { id : 'onArrayEnd' , text : ']' , startLine : 0 , startCharacter : 52 } , { id : 'onObjectEnd' , text : '}' , startLine : 0 , startCharacter : 54 } , { id : 'onArrayEnd' , text : ']' , startLine : 0 , startCharacter : 56 } , { id : 'onObjectEnd' , text : '}' , startLine : 0 , startCharacter : 58 } , { id : 'onArrayEnd' , text : ']' , startLine : 0 , startCharacter : 60 } , ] ) ; } ) ; test ( 'visit: comment' , ( ) => { assertVisit ( '/* g */ { "foo": //f\n"bar" }' , [ { id : 'onComment' , text : '/* g */' , startLine : 0 , startCharacter : 0 } , { id : 'onObjectBegin' , text : '{' , startLine : 0 , startCharacter : 8 , path : [ ] } , { id : 'onObjectProperty' , text : '"foo"' , startLine : 0 , startCharacter : 10 , arg : 'foo' , path : [ ] } , { id : 'onSeparator' , text : ':' , startLine : 0 , startCharacter : 15 , arg : ':' } , { id : 'onComment' , text : '//f' , startLine : 0 , startCharacter : 17 } , { id : 'onLiteralValue' , text : '"bar"' , startLine : 1 , startCharacter : 0 , arg : 'bar' , path : [ 'foo' ] } , { id : 'onObjectEnd' , text : '}' , startLine : 1 , startCharacter : 6 } , ] ) ; assertVisit ( '/* g\r\n */ { "foo": //f\n"bar" }' , [ { id : 'onComment' , text : '/* g\r\n */' , startLine : 0 , startCharacter : 0 } , { id : 'onObjectBegin' , text : '{' , startLine : 1 , startCharacter : 4 , path : [ ] } , { id : 'onObjectProperty' , text : '"foo"' , startLine : 1 , startCharacter : 6 , arg : 'foo' , path : [ ] } , { id : 'onSeparator' , text : ':' , startLine : 1 , startCharacter : 11 , arg : ':' } , { id : 'onComment' , text : '//f' , startLine : 1 , startCharacter : 13 } , { id : 'onLiteralValue' , text : '"bar"' , startLine : 2 , startCharacter : 0 , arg : 'bar' , path : [ 'foo' ] } , { id : 'onObjectEnd' , text : '}' , startLine : 2 , startCharacter : 6 } , ] ) ; assertVisit ( '/* g\n */ { "foo": //f\n"bar"\n}' , [ { id : 'onObjectBegin' , text : '{' , startLine : 1 , startCharacter : 4 , path : [ ] } , { id : 'onObjectProperty' , text : '"foo"' , startLine : 1 , startCharacter : 6 , arg : 'foo' , path : [ ] } , { id : 'onSeparator' , text : ':' , startLine : 1 , startCharacter : 11 , arg : ':' } , { id : 'onLiteralValue' , text : '"bar"' , startLine : 2 , startCharacter : 0 , arg : 'bar' , path : [ 'foo' ] } , { id : 'onObjectEnd' , text : '}' , startLine : 3 , startCharacter : 0 } , ] , [ { error : ParseErrorCode . InvalidCommentToken , offset : 0 , length : 8 , startLine : 0 , startCharacter : 0 } , { error : ParseErrorCode . InvalidCommentToken , offset : 18 , length : 3 , startLine : 1 , startCharacter : 13 } , ] , true ) ; } ) ; test ( 'visit: malformed' , ( ) => { assertVisit ( '[ { "a", "b": [] 1, "c" [ 1 2 ], true: "d":, "e": }, 2, 3 4 ]' , [ { id : 'onArrayBegin' , text : '[' , startLine : 0 , startCharacter : 0 , path : [ ] } , { id : 'onObjectBegin' , text : "{" , startLine : 0 , startCharacter : 2 , path : [ 0 ] } , { id : 'onObjectProperty' , text : '"a"' , startLine : 0 , startCharacter : 4 , arg : 'a' , path : [ 0 ] } , { id : 'onSeparator' , text : ',' , startLine : 0 , startCharacter : 7 , arg : ',' } , { id : 'onObjectProperty' , text : '"b"' , startLine : 0 , startCharacter : 9 , arg : 'b' , path : [ 0 ] } , { id : 'onSeparator' , text : ':' , startLine : 0 , startCharacter : 12 , arg : ':' } , { id : 'onArrayBegin' , text : '[' , startLine : 0 , startCharacter : 14 , path : [ 0 , 'b' ] } , { id : 'onArrayEnd' , text : ']' , startLine : 0 , startCharacter : 15 } , { id : 'onSeparator' , text : ',' , startLine : 0 , startCharacter : 18 , arg : ',' } , { id : 'onObjectProperty' , text : '"c"' , startLine : 0 , startCharacter : 20 , arg : 'c' , path : [ 0 ] } , { id : 'onSeparator' , text : ',' , startLine : 0 , startCharacter : 31 , arg : ',' } , { id : 'onSeparator' , text : ',' , startLine : 0 , startCharacter : 43 , arg : ',' } , { id : 'onObjectProperty' , text : '"e"' , startLine : 0 , startCharacter : 45 , arg : 'e' , path : [ 0 ] } , { id : 'onSeparator' , text : ':' , startLine : 0 , startCharacter : 48 , arg : ':' } , { id : 'onObjectEnd' , text : '}' , startLine : 0 , startCharacter : 50 } , { id : 'onSeparator' , text : ',' , startLine : 0 , startCharacter : 51 , arg : ',' } , { id : 'onLiteralValue' , text : '2' , startLine : 0 , startCharacter : 53 , arg : 2 , path : [ 1 ] } , { id : 'onSeparator' , text : ',' , startLine : 0 , startCharacter : 54 , arg : ',' } , { id : 'onLiteralValue' , text : '3' , startLine : 0 , startCharacter : 56 , arg : 3 , path : [ 2 ] } , { id : 'onLiteralValue' , text : '4' , startLine : 0 , startCharacter : 58 , arg : 4 , path : [ 3 ] } , { id : 'onArrayEnd' , text : ']' , startLine : 0 , startCharacter : 60 } , ] , [ { error : ParseErrorCode . ColonExpected , offset : 7 , length : 1 , startLine : 0 , startCharacter : 7 } , { error : ParseErrorCode . CommaExpected , offset : 17 , length : 1 , startLine : 0 , startCharacter : 17 } , { error : ParseErrorCode . PropertyNameExpected , offset : 17 , length : 1 , startLine : 0 , startCharacter : 17 } , { error : ParseErrorCode . ValueExpected , offset : 18 , length : 1 , startLine : 0 , startCharacter : 18 } , { error : ParseErrorCode . ColonExpected , offset : 24 , length : 1 , startLine : 0 , startCharacter : 24 } , { error : ParseErrorCode . PropertyNameExpected , offset : 33 , length : 4 , startLine : 0 , startCharacter : 33 } , { error : ParseErrorCode . ValueExpected , offset : 43 , length : 1 , startLine : 0 , startCharacter : 43 } , { error : ParseErrorCode . ValueExpected , offset : 50 , length : 1 , startLine : 0 , startCharacter : 50 } , { error : ParseErrorCode . CommaExpected , offset : 58 , length : 1 , startLine : 0 , startCharacter : 58 } , ] ) ; } ) ; test ( 'visit: incomplete' , ( ) => { assertVisit ( '{"prop1":"foo","prop2":"foo2","prop3":{"prp1":{""}}}' , [ { id : 'onObjectBegin' , text : "{" , startLine : 0 , startCharacter : 0 , path : [ ] } , { id : 'onObjectProperty' , text : '"prop1"' , startLine : 0 , startCharacter : 1 , arg : 'prop1' , path : [ ] } , { id : 'onSeparator' , text : ":" , startLine : 0 , startCharacter : 8 , arg : ":" } , { id : 'onLiteralValue' , text : '"foo"' , startLine : 0 , startCharacter : 9 , arg : 'foo' , path : [ 'prop1' ] } , { id : 'onSeparator' , text : "," , startLine : 0 , startCharacter : 14 , arg : "," } , { id : 'onObjectProperty' , text : '"prop2"' , startLine : 0 , startCharacter : 15 , arg : 'prop2' , path : [ ] } , { id : 'onSeparator' , text : ":" , startLine : 0 , startCharacter : 22 , arg : ":" } , { id : 'onLiteralValue' , text : '"foo2"' , startLine : 0 , startCharacter : 23 , arg : 'foo2' , path : [ 'prop2' ] } , { id : 'onSeparator' , text : "," , startLine : 0 , startCharacter : 29 , arg : "," } , { id : 'onObjectProperty' , text : '"prop3"' , startLine : 0 , startCharacter : 30 , arg : 'prop3' , path : [ ] } , { id : 'onSeparator' , text : ":" , startLine : 0 , startCharacter : 37 , arg : ":" } , { id : 'onObjectBegin' , text : "{" , startLine : 0 , startCharacter : 38 , path : [ 'prop3' ] } , { id : 'onObjectProperty' , text : '"prp1"' , startLine : 0 , startCharacter : 39 , arg : 'prp1' , path : [ 'prop3' ] } , { id : 'onSeparator' , text : ":" , startLine : 0 , startCharacter : 45 , arg : ':' } , { id : 'onObjectBegin' , text : "{" , startLine : 0 , startCharacter : 46 , path : [ 'prop3' , 'prp1' ] } , { id : 'onObjectProperty' , text : '""' , startLine : 0 , startCharacter : 47 , arg : '' , path : [ 'prop3' , 'prp1' ] } , { id : 'onObjectEnd' , text : "}" , startLine : 0 , startCharacter : 49 } , { id : 'onObjectEnd' , text : "}" , startLine : 0 , startCharacter : 50 } , { id : 'onObjectEnd' , text : "}" , startLine : 0 , startCharacter : 51 } , ] , [ { error : ParseErrorCode . ColonExpected , offset : 49 , length : 1 , startLine : 0 , startCharacter : 49 } , ] ) ; assertTree ( '{"prop1":"foo","prop2":"foo2","prop3":{"prp1":{""}}}' , { type : 'object' , offset : 0 , length : 52 , children : [ { type : 'property' , offset : 1 , length : 13 , children : [ { type : 'string' , value : 'prop1' , offset : 1 , length : 7 } , { type : 'string' , offset : 9 , length : 5 , value : 'foo' } ] , colonOffset : 8 } , { type : 'property' , offset : 15 , length : 14 , children : [ { type : 'string' , value : 'prop2' , offset : 15 , length : 7 } , { type : 'string' , offset : 23 , length : 6 , value : 'foo2' } ] , colonOffset : 22 } , { type : 'property' , offset : 30 , length : 21 , children : [ { type : 'string' , value : 'prop3' , offset : 30 , length : 7 } , { type : 'object' , offset : 38 , length : 13 , children : [ { type : 'property' , offset : 39 , length : 11 , children : [ { type : 'string' , value : 'prp1' , offset : 39 , length : 6 } , { type : 'object' , offset : 46 , length : 4 , children : [ { type : 'property' , offset : 47 , length : 3 , children : [ { type : 'string' , value : '' , offset : 47 , length : 2 } , ] } ] } ] , colonOffset : 45 } ] } ] , colonOffset : 37 } ] } , [ { error : ParseErrorCode . ColonExpected , offset : 49 , length : 1 } ] ) ; } ) ; test ( 'tree: find location' , ( ) => { let root = parseTree ( '{ "key1": { "key11": [ "val111", "val112" ] }, "key2": [ { "key21": false, "key22": 221 }, null, [{}] ], "key3": { "key31":, "key32": 32 } }' ) ; assertNodeAtLocation ( root , [ 'key1' ] , { key11 : [ 'val111' , 'val112' ] } ) ; assertNodeAtLocation ( root , [ 'key1' , 'key11' ] , [ 'val111' , 'val112' ] ) ; assertNodeAtLocation ( root , [ 'key1' , 'key11' , 0 ] , 'val111' ) ; assertNodeAtLocation ( root , [ 'key1' , 'key11' , 1 ] , 'val112' ) ; assertNodeAtLocation ( root , [ 'key1' , 'key11' , 2 ] , void 0 ) ; assertNodeAtLocation ( root , [ 'key2' , 0 , 'key21' ] , false ) ; assertNodeAtLocation ( root , [ 'key2' , 0 , 'key22' ] , 221 ) ; assertNodeAtLocation ( root , [ 'key2' , 1 ] , null ) ; assertNodeAtLocation ( root , [ 'key2' , 2 ] , [ { } ] ) ; assertNodeAtLocation ( root , [ 'key2' , 2 , 0 ] , { } ) ; assertNodeAtLocation ( root , [ 'key3' , 'key31' , 'key311' ] , undefined ) ; assertNodeAtLocation ( root , [ 'key3' , 'key32' ] , 32 ) ; } ) ; test ( 'location: matches' , ( ) => { assertMatchesLocation ( '{ "dependencies": { | } }' , [ 'dependencies' ] ) ; assertMatchesLocation ( '{ "dependencies": { "fo| } }' , [ 'dependencies' ] ) ; assertMatchesLocation ( '{ "dependencies": { "fo|" } }' , [ 'dependencies' ] ) ; assertMatchesLocation ( '{ "dependencies": { "fo|": 1 } }' , [ 'dependencies' ] ) ; assertMatchesLocation ( '{ "dependencies": { "fo|": 1 } }' , [ 'dependencies' ] ) ; assertMatchesLocation ( '{ "dependencies": { "fo": | } }' , [ 'dependencies' , '*' ] ) ; } ) ; } ) ;