@@ -370,4 +370,117 @@ class JSON5ParseErrorsTest {
370370 exception.lineNumber shouldBe 1
371371 exception.columnNumber shouldBe 2 // Position of the "1"
372372 }
373+
374+ @Test
375+ @DisplayName(" Object: should throw for invalid keys" )
376+ fun `object invalid keys` () {
377+ val ex1 = shouldThrow<JSON5Exception > { JSON5 .parse(" {null: 1}" ) }
378+ ex1.message shouldContain " Expected property name or '}'" // Lexer sees 'n', expects identifier or string key
379+ ex1.lineNumber shouldBe 1
380+ ex1.columnNumber shouldBe 2 // 'n'
381+
382+ val ex2 = shouldThrow<JSON5Exception > { JSON5 .parse(" {true: 1}" ) }
383+ ex2.message shouldContain " Expected property name or '}'"
384+ ex2.lineNumber shouldBe 1
385+ ex2.columnNumber shouldBe 2 // 't'
386+
387+ val ex3 = shouldThrow<JSON5Exception > { JSON5 .parse(" {123: 1}" ) }
388+ ex3.message shouldContain " Expected property name or '}'"
389+ ex3.lineNumber shouldBe 1
390+ ex3.columnNumber shouldBe 2 // '1'
391+ }
392+
393+ @Test
394+ @DisplayName(" Object: should throw for comma issues" )
395+ fun `object comma issues` () {
396+ val ex1 = shouldThrow<JSON5Exception > { JSON5 .parse(" {a:1,,}" ) }
397+ ex1.message shouldContain " Expected property name or '}'" // Expects a key after comma
398+ ex1.lineNumber shouldBe 1
399+ ex1.columnNumber shouldBe 6
400+
401+ val ex2 = shouldThrow<JSON5Exception > { JSON5 .parse(" {,a:1}" ) }
402+ ex2.message shouldContain " Expected property name or '}'" // Cannot start with comma
403+ ex2.lineNumber shouldBe 1
404+ ex2.columnNumber shouldBe 2
405+ }
406+
407+ @Test
408+ @DisplayName(" Object: should throw for structure issues" )
409+ fun `object structure issues` () {
410+ val ex1 = shouldThrow<JSON5Exception > { JSON5 .parse(" {a:1 b:2}" ) } // Missing comma
411+ ex1.message shouldContain " Expected ',' or '}'" // Expects comma or }
412+ ex1.lineNumber shouldBe 1
413+ ex1.columnNumber shouldBe 6
414+
415+ val ex2 = shouldThrow<JSON5Exception > { JSON5 .parse(" {\" a\" :1 \" b\" :2}" ) } // Missing comma, string keys
416+ ex2.message shouldContain " Expected ',' or '}'" // Expects comma or }
417+ ex2.lineNumber shouldBe 1
418+ ex2.columnNumber shouldBe 8
419+
420+ val ex3 = shouldThrow<JSON5Exception > { JSON5 .parse(" {a:}" ) } // Missing value
421+ ex3.message shouldContain " Unexpected punctuator" // Expects a value
422+ ex3.lineNumber shouldBe 1
423+ ex3.columnNumber shouldBe 4
424+
425+ val ex4 = shouldThrow<JSON5Exception > { JSON5 .parse(" {a:1, :2}" ) } // Missing key
426+ ex4.message shouldContain " Expected property name or '}'" // Expects a key
427+ ex4.lineNumber shouldBe 1
428+ ex4.columnNumber shouldBe 7
429+ }
430+
431+ @Test
432+ @DisplayName(" Array: should throw for comma issues" )
433+ fun `array comma issues` () {
434+ val ex1 = shouldThrow<JSON5Exception > { JSON5 .parse(" [1,2,,]" ) }
435+ ex1.message shouldContain " Unexpected punctuator" // Expects a value after comma
436+ ex1.lineNumber shouldBe 1
437+ ex1.columnNumber shouldBe 6
438+
439+ val ex2 = shouldThrow<JSON5Exception > { JSON5 .parse(" [,1,2]" ) }
440+ ex2.message shouldContain " Unexpected punctuator" // Cannot start with comma
441+ ex2.lineNumber shouldBe 1
442+ ex2.columnNumber shouldBe 2
443+
444+ val ex3 = shouldThrow<JSON5Exception > { JSON5 .parse(" [1,,2]" ) } // Elision not allowed by spec
445+ ex3.message shouldContain " Unexpected punctuator" // Expects value, finds comma
446+ ex3.lineNumber shouldBe 1
447+ ex3.columnNumber shouldBe 4
448+ }
449+
450+ @Test
451+ @DisplayName(" Array: should throw for structure issues" )
452+ fun `array structure issues` () {
453+ val ex1 = shouldThrow<JSON5Exception > { JSON5 .parse(" [1 2]" ) } // Missing comma
454+ ex1.message shouldContain " Expected ',' or ']'" // Expects comma or ]
455+ ex1.lineNumber shouldBe 1
456+ ex1.columnNumber shouldBe 4
457+ }
458+
459+ @Test
460+ @DisplayName(" String: should throw for unterminated strings" )
461+ fun `string unterminated` () {
462+ val ex1 = shouldThrow<JSON5Exception > { JSON5 .parse(" \" abc" ) }
463+ ex1.message shouldContain " invalid end of input"
464+ ex1.lineNumber shouldBe 1 // The line where the string started
465+ // Column could be end of line or where EOF is effectively seen
466+ // ex1.columnNumber shouldBe 4
467+
468+ val ex2 = shouldThrow<JSON5Exception > { JSON5 .parse(" 'abc" ) }
469+ ex2.message shouldContain " invalid end of input"
470+ ex2.lineNumber shouldBe 1
471+ // ex2.columnNumber shouldBe 4
472+ }
473+
474+ @Test
475+ @DisplayName(" String: should throw for invalid unescaped newline" )
476+ fun `string invalid unescaped newline` () {
477+ val jsonStringWithUnescapedLF = " 'abc\n def'" // Kotlin makes this a literal LF
478+ val exception = shouldThrow<JSON5Exception > {
479+ JSON5 .parse(jsonStringWithUnescapedLF)
480+ }
481+ exception.message shouldContain " invalid character '\\ x0a'" // LF
482+ exception.lineNumber shouldBe 2 // Error is on the first line where string starts
483+ exception.columnNumber shouldBe 1 // After 'abc'
484+ }
485+
373486}
0 commit comments