From d037c66e8c009ae8863855ad8ecf91421f78be8c Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sat, 24 Oct 2026 09:53:48 -0700 Subject: [PATCH] Report JSON.parse syntax errors with V8's messages and positions JSON.parse's SyntaxError messages were shaped like V8's but differed from Node's in several ways: - Every positional message now ends with V8's "(line L column C)", where \n, \r and \r\n end lines. - Positions are UTF-16 offsets, as in Node, not byte offsets. - An object or array cut off after '{', a property name, a value or an array element reports the token V8 expected ("Expected property name or '}'", "Expected ':' after property name", "Expected ',' or '}' after property value", "Expected ',' or ']' after array element") instead of "Unexpected end of JSON input". - After a comma in an object the message is "Expected double-quoted property name". - An unterminated string reports the end of the input, and a truncated \u escape is a "Bad Unicode escape" there. - A leading zero followed by a digit is an "Unexpected number". - A misspelled literal names the first differing character, or the end of the input. - An unexpected character is named with the source as V8 does: whole when it is under 21 units, otherwise ten units of context on each side; the sources "NaN", "Infinity", "undefined" and "[object Object]" name only themselves; a quote or a number there is an "Unexpected string" or "Unexpected number". The new corpus program 7993-json-parse-error-messages pins 76 inputs against Node, including non-ASCII text before the error, line breaks, and long sources with split surrogate pairs at the context edges. Co-Authored-By: Claude Opus 5.5 --- .../7993-json-parse-error-messages.ts.json | 6 + packages/runtime/src/scr_dyn.h | 4 +- packages/runtime/src/scr_json.c | 206 ++++++++++++++---- packages/runtime/test/json.test.ts | 33 ++- tests/corpus/1004-json-parse-errors.ts | 8 +- .../corpus/7993-json-parse-error-messages.ts | 30 +++ .../7993-json-parse-error-messages.ts.json | 1 + tests/harness/dyncheck.test.ts | 12 +- 8 files changed, 229 insertions(+), 71 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7993-json-parse-error-messages.ts.json create mode 100644 tests/corpus/7993-json-parse-error-messages.ts create mode 100644 tests/harness/corpus-costs/7993-json-parse-error-messages.ts.json diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7993-json-parse-error-messages.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7993-json-parse-error-messages.ts.json new file mode 100644 index 000000000..4fec854a7 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/7993-json-parse-error-messages.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/7993-json-parse-error-messages.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index 82fe1a6d9..d3997ebd9 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -23,8 +23,8 @@ * Ownership: the checked-dynamic tree owns its children (strings, items, entry keys+values); * releasing the root releases the tree recursively. scr_json_parse borrows * the text and returns +1 — or THROWS a catchable string shaped like Node's - * V8 messages ("Unexpected end of JSON input"; approximate fidelity, see - * SEMANTICS.md) and returns NULL. scr_dyn_obj_get returns a BORROWED + * V8 messages ("Unexpected end of JSON input", "... in JSON at position + * (line column )") and returns NULL. scr_dyn_obj_get returns a BORROWED * member (the compiler's builders retain what they extract themselves). */ diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index 49af072ed..45ba766a1 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -9,10 +9,10 @@ * arrays, objects (later duplicate keys win, like JS), and the * four JSON whitespace characters. Syntax errors THROW catchable * SyntaxError instances (the depth cap a RangeError, like V8's) whose - * messages are shaped like Node's V8 texts ("Unexpected end of JSON - * input", "Unexpected token 'x', \"...\" is not valid JSON") — - * APPROXIMATE message fidelity by design (SEMANTICS.md; e.name is exact, - * e.message is ours), pinned by the runtime C tests. + * messages are Node's V8 texts ("Unexpected end of JSON input", + * "Unexpected token 'x', \"...\" is not valid JSON", "... in JSON at + * position (line column )"), reported where V8 + * reports them. * - scr_dyn_check_fail is the shared failure path of the compiler-emitted * dynCheck builders: a TypeError instance carrying "expected at * , got ", thrown through the exception cell. @@ -11115,26 +11115,142 @@ static void scr_json_throw(const ScrJsonP *p, const char *msg) { scr_throw_error_msg(SCR_ERR_SYNTAX, msg, strlen(msg)); } +/* V8's location of a JSON error at byte `pos`: the UTF-16 offset (the + * text is WTF-8; a four-byte sequence is two units), and the 1-based line + * and column, where \r, \n and \r\n end a line. */ +static void scr_json_location(const ScrJsonP *p, size_t pos, size_t *unit, size_t *line, + size_t *column) { + const unsigned char *s = (const unsigned char *)p->s; + size_t units = 0, ln = 1, line_start = 0; + for (size_t i = 0; i < pos; i++) { + unsigned char c = s[i]; + if ((c & 0xC0) == 0x80) continue; + if (c == '\r' && i + 1 < pos && s[i + 1] == '\n') { + i++; + units++; + c = '\n'; + } + units += c >= 0xF0 ? 2 : 1; + if (c == '\r' || c == '\n') { + ln++; + line_start = units; + } + } + *unit = units; + *line = ln; + *column = 1 + units - line_start; +} + /* Errors stay out of line so the scanners that report them keep their - * registers for the common path. */ -static __attribute__((noinline, cold)) void scr_json_throw_pos(const ScrJsonP *p, const char *what, - size_t pos) { + * registers for the common path. V8's positional messages: + * " in JSON at position (line column )". */ +static __attribute__((noinline, cold)) void scr_json_throw_located(const ScrJsonP *p, + const char *what, + const char *where, size_t pos) { if (p->quiet) return; - char buf[128]; - int n = snprintf(buf, sizeof buf, "%s in JSON at position %zu", what, pos); + size_t unit, line, column; + scr_json_location(p, pos, &unit, &line, &column); + char buf[192]; + int n = snprintf(buf, sizeof buf, "%s%s at position %zu (line %zu column %zu)", what, where, unit, + line, column); scr_throw_error_msg(SCR_ERR_SYNTAX, buf, (size_t)n); } -/* V8-flavored bad-token message with a short snippet of the input around - * the offending character. Approximate fidelity (documented). */ -static void scr_json_throw_token(const ScrJsonP *p) { +static __attribute__((noinline, cold)) void scr_json_throw_pos(const ScrJsonP *p, const char *what, + size_t pos) { + scr_json_throw_located(p, what, " in JSON", pos); +} + +/* Appends the text's UTF-16 units [from, to) as WTF-8: a surrogate pair the + * range cuts contributes its half as a lone surrogate, as V8's substring. */ +static size_t scr_json_unit_slice(const ScrJsonP *p, size_t from, size_t to, char *out) { + const unsigned char *s = (const unsigned char *)p->s; + size_t n = 0, unit = 0, i = 0; + while (i < p->len && unit < to) { + unsigned char c = s[i]; + size_t width = c < 0x80 ? 1 : c < 0xE0 ? 2 : c < 0xF0 ? 3 : 4; + if (i + width > p->len) width = p->len - i; + if (width == 4) { + uint32_t cp = ((uint32_t)(c & 0x07) << 18) | ((uint32_t)(s[i + 1] & 0x3F) << 12) | + ((uint32_t)(s[i + 2] & 0x3F) << 6) | (uint32_t)(s[i + 3] & 0x3F); + bool high = unit >= from && unit < to, low = unit + 1 >= from && unit + 1 < to; + if (high && low) { + memcpy(out + n, s + i, 4); + n += 4; + } else if (high) { + n += scr_wtf8_put(out + n, 0xD800 + ((cp - 0x10000) >> 10)); + } else if (low) { + n += scr_wtf8_put(out + n, 0xDC00 + ((cp - 0x10000) & 0x3FF)); + } + unit += 2; + } else { + if (unit >= from) { + memcpy(out + n, s + i, width); + n += width; + } + unit++; + } + i += width; + } + return n; +} + +/* V8's report of an unexpected token at byte `pos`: the end of the input, + * a string or a number, or any other character, named with the source (a + * short one whole, a longer one as up to ten units on each side), except + * that the sources "NaN", "Infinity", "undefined" and "[object Object]" + * name only themselves. */ +static __attribute__((noinline, cold)) void scr_json_throw_unexpected(const ScrJsonP *p, + size_t pos) { if (p->quiet) return; - size_t start = p->pos > 8 ? p->pos - 8 : 0; - size_t take = p->len - start < 16 ? p->len - start : 16; - char buf[192]; - int n = snprintf(buf, sizeof buf, "Unexpected token '%c', %s\"%.*s\"%s is not valid JSON", - p->s[p->pos], start > 0 ? "..." : "", (int)take, p->s + start, - start + take < p->len ? "..." : ""); + if (pos >= p->len) { + scr_json_throw(p, "Unexpected end of JSON input"); + return; + } + char c = p->s[pos]; + if (c == '"') { + scr_json_throw_pos(p, "Unexpected string", pos); + return; + } + if (c == '-' || (c >= '0' && c <= '9')) { + scr_json_throw_pos(p, "Unexpected number", pos); + return; + } + static const char *const special[] = {"NaN", "Infinity", "undefined", "[object Object]"}; + char buf[256]; + int n; + for (size_t k = 0; k < sizeof special / sizeof special[0]; k++) { + if (p->len == strlen(special[k]) && memcmp(p->s, special[k], p->len) == 0) { + n = snprintf(buf, sizeof buf, "\"%s\" is not valid JSON", special[k]); + scr_throw_error_msg(SCR_ERR_SYNTAX, buf, (size_t)n); + return; + } + } + size_t unit, line, column, total; + scr_json_location(p, pos, &unit, &line, &column); + scr_json_location(p, p->len, &total, &line, &column); + char token[8]; + size_t token_len = scr_json_unit_slice(p, unit, unit + 1, token); + enum { CONTEXT = 10 }; + size_t from = 0, to = total; + const char *before = "", *after = ""; + if (total >= 2 * CONTEXT + 1) { + if (unit < CONTEXT) { + to = unit + CONTEXT; + after = "..."; + } else if (unit < total - CONTEXT) { + from = unit - CONTEXT; + to = unit + CONTEXT; + before = after = "..."; + } else { + from = unit - CONTEXT; + before = "..."; + } + } + char source[4 * 2 * CONTEXT + 8]; + size_t source_len = scr_json_unit_slice(p, from, to, source); + n = snprintf(buf, sizeof buf, "Unexpected token '%.*s', %s\"%.*s\"%s is not valid JSON", + (int)token_len, token, before, (int)source_len, source, after); scr_throw_error_msg(SCR_ERR_SYNTAX, buf, (size_t)n); } @@ -11159,12 +11275,13 @@ static void scr_json_put_cp(ScrJsonBuf *b, uint32_t cp) { /* Four hex digits at pos, or -1 (throws). */ static int32_t scr_json_hex4(ScrJsonP *p) { - if (p->len - p->pos < 4) { - scr_json_throw(p, "Unexpected end of JSON input"); - return -1; - } uint32_t v = 0; for (int i = 0; i < 4; i++) { + if (p->pos + (size_t)i >= p->len) { + /* V8 reports the escape at the end of the input. */ + scr_json_throw_pos(p, "Bad Unicode escape", p->len); + return -1; + } char c = p->s[p->pos + (size_t)i]; uint32_t digit; if (c >= '0' && c <= '9') digit = (uint32_t)(c - '0'); @@ -11205,7 +11322,7 @@ static int scr_json_string_span(ScrJsonP *p, const char **span, return -1; } } - scr_json_throw_pos(p, "Unterminated string", p->pos); + scr_json_throw_pos(p, "Unterminated string", p->len); return -1; } @@ -11221,7 +11338,7 @@ static ScrStr *scr_json_string_slow(ScrJsonP *p, size_t prefix) { for (;;) { if (p->pos >= p->len) { scr_jb_dispose(&b); - scr_json_throw_pos(p, "Unterminated string", open); + scr_json_throw_pos(p, "Unterminated string", p->len); return NULL; } unsigned char c = (unsigned char)p->s[p->pos]; @@ -11325,6 +11442,12 @@ static bool scr_json_number_value(ScrJsonP *p, double *out) { } if (s[pos] == '0') { pos++; + /* A leading zero followed by a digit: V8's unexpected number there. */ + if (pos < len && s[pos] >= '0' && s[pos] <= '9') { + p->pos = pos; + scr_json_throw_pos(p, "Unexpected number", pos); + return false; + } } else { while (pos < len && s[pos] >= '0' && s[pos] <= '9') { if (ndig < 15) { @@ -11405,7 +11528,10 @@ static bool scr_json_lit(ScrJsonP *p, const char *word, size_t n) { p->pos += n; return true; } - scr_json_throw_token(p); + /* V8 names the first character that differs, or the end of the input. */ + size_t i = 1; + while (p->pos + i < p->len && i < n && p->s[p->pos + i] == word[i]) i++; + scr_json_throw_unexpected(p, p->pos + i); return false; } @@ -11465,7 +11591,7 @@ static ScrDyn *scr_json_array(ScrJsonP *p) { scr_json_ws(p); if (p->pos >= p->len) { scr_dyn_release(arr); - scr_json_throw(p, "Unexpected end of JSON input"); + scr_json_throw_pos(p, "Expected ',' or ']' after array element", p->pos); return NULL; } if (p->s[p->pos] == ',') { @@ -11595,18 +11721,20 @@ static ScrDyn *scr_json_object(ScrJsonP *p) { } uint32_t shape = 0; /* the empty layout */ bool shaped = true; /* obj holds exactly the keys on shape's path */ + bool first = true; for (;;) { scr_json_ws(p); - if (p->pos >= p->len) { + if (p->pos >= p->len || p->s[p->pos] != '"') { + /* V8: the first member may also close the object; after a comma + * only a property name may follow. */ scr_dyn_release(obj); - scr_json_throw(p, "Unexpected end of JSON input"); - return NULL; - } - if (p->s[p->pos] != '"') { - scr_dyn_release(obj); - scr_json_throw_pos(p, "Expected property name or '}'", p->pos); + scr_json_throw_pos(p, + first ? "Expected property name or '}'" + : "Expected double-quoted property name", + p->pos); return NULL; } + first = false; /* Keep the key borrowed until insertion determines whether it already * exists or can reuse a recycled slot. Escaped keys own their decoded * string until the value has been parsed and inserted. */ @@ -11644,8 +11772,7 @@ static ScrDyn *scr_json_object(ScrJsonP *p) { if (p->pos >= p->len || p->s[p->pos] != ':') { scr_str_release(decoded_key); scr_dyn_release(obj); - if (p->pos >= p->len) scr_json_throw(p, "Unexpected end of JSON input"); - else scr_json_throw_pos(p, "Expected ':' after property name", p->pos); + scr_json_throw_pos(p, "Expected ':' after property name", p->pos); return NULL; } p->pos++; /* ':' */ @@ -11672,7 +11799,7 @@ static ScrDyn *scr_json_object(ScrJsonP *p) { scr_json_ws(p); if (p->pos >= p->len) { scr_dyn_release(obj); - scr_json_throw(p, "Unexpected end of JSON input"); + scr_json_throw_pos(p, "Expected ',' or '}' after property value", p->pos); return NULL; } if (p->s[p->pos] == ',') { @@ -11730,7 +11857,7 @@ static ScrDyn *scr_json_value(ScrJsonP *p) { return scr_dyn_new_null(); } if (c == '-' || (c >= '0' && c <= '9')) return scr_json_number(p); - scr_json_throw_token(p); + scr_json_throw_unexpected(p, p->pos); return NULL; } @@ -11750,10 +11877,7 @@ ScrDyn *scr_json_parse(ScrStr *text) { scr_json_ws(&p); if (p.pos < p.len) { scr_dyn_release(d); - char buf[96]; - int n = snprintf(buf, sizeof buf, - "Unexpected non-whitespace character after JSON at position %zu", p.pos); - scr_throw_error_msg(SCR_ERR_SYNTAX, buf, (size_t)n); + scr_json_throw_located(&p, "Unexpected non-whitespace character after JSON", "", p.pos); return NULL; } return d; diff --git a/packages/runtime/test/json.test.ts b/packages/runtime/test/json.test.ts index 3315cb415..14aaed199 100644 --- a/packages/runtime/test/json.test.ts +++ b/packages/runtime/test/json.test.ts @@ -53,9 +53,8 @@ beforeAll(async () => { // The C side parses/inspects dyn values as checks and routes each expected // failure through scr_exc_print_uncaught. Parse failures are SyntaxError // INSTANCES (the depth cap a RangeError, dynCheck failures TypeErrors), so -// every uncaught line renders "name: message" — e.name is Node-exact while -// the V8-flavored message wording stays approximate (SEMANTICS.md): these -// lines pin OUR rendering, not Node's. +// every uncaught line renders "name: message"; the JSON syntax error +// messages are Node's (V8's wording, positions and line/column). test("json runtime: parser, dyn, dynCheck failure path, stringify buffer", async () => { const { stdout, stderr } = await execFileAsync(bin); expect(stdout).toMatch(/^(\d+)\/\1 checks passed\n$/); @@ -63,21 +62,21 @@ test("json runtime: parser, dyn, dynCheck failure path, stringify buffer", async [ `Uncaught SyntaxError: Unexpected end of JSON input`, `Uncaught SyntaxError: Unexpected end of JSON input`, - `Uncaught SyntaxError: Expected property name or '}' in JSON at position 1`, + `Uncaught SyntaxError: Expected property name or '}' in JSON at position 1 (line 1 column 2)`, + `Uncaught SyntaxError: Unexpected end of JSON input`, + `Uncaught SyntaxError: Expected ',' or ']' after array element in JSON at position 3 (line 1 column 4)`, + `Uncaught SyntaxError: Expected ':' after property name in JSON at position 4 (line 1 column 5)`, + `Uncaught SyntaxError: Expected double-quoted property name in JSON at position 7 (line 1 column 8)`, + `Uncaught SyntaxError: Expected property name or '}' in JSON at position 1 (line 1 column 2)`, + `Uncaught SyntaxError: Unterminated string in JSON at position 13 (line 1 column 14)`, + `Uncaught SyntaxError: Bad escaped character in JSON at position 6 (line 1 column 7)`, + `Uncaught SyntaxError: Bad control character in string literal in JSON at position 6 (line 1 column 7)`, + `Uncaught SyntaxError: Bad Unicode escape in JSON at position 5 (line 1 column 6)`, + `Uncaught SyntaxError: No number after minus sign in JSON at position 1 (line 1 column 2)`, + `Uncaught SyntaxError: Unterminated fractional number in JSON at position 2 (line 1 column 3)`, + `Uncaught SyntaxError: Exponent part is missing a number in JSON at position 3 (line 1 column 4)`, + `Uncaught SyntaxError: Unexpected non-whitespace character after JSON at position 2 (line 1 column 3)`, `Uncaught SyntaxError: Unexpected end of JSON input`, - `Uncaught SyntaxError: Expected ',' or ']' after array element in JSON at position 3`, - `Uncaught SyntaxError: Expected ':' after property name in JSON at position 4`, - `Uncaught SyntaxError: Expected property name or '}' in JSON at position 7`, - `Uncaught SyntaxError: Expected property name or '}' in JSON at position 1`, - `Uncaught SyntaxError: Unterminated string in JSON at position 0`, - `Uncaught SyntaxError: Bad escaped character in JSON at position 6`, - `Uncaught SyntaxError: Bad control character in string literal in JSON at position 6`, - `Uncaught SyntaxError: Bad Unicode escape in JSON at position 5`, - `Uncaught SyntaxError: No number after minus sign in JSON at position 1`, - `Uncaught SyntaxError: Unterminated fractional number in JSON at position 2`, - `Uncaught SyntaxError: Exponent part is missing a number in JSON at position 3`, - `Uncaught SyntaxError: Unexpected non-whitespace character after JSON at position 2`, - `Uncaught SyntaxError: Unexpected token 'n', "nul" is not valid JSON`, `Uncaught RangeError: Maximum call stack size exceeded`, `Uncaught TypeError: expected number at $.items[2].price, got string`, `Uncaught TypeError: expected object at $, got undefined`, diff --git a/tests/corpus/1004-json-parse-errors.ts b/tests/corpus/1004-json-parse-errors.ts index e65a9752e..aad0817d7 100644 --- a/tests/corpus/1004-json-parse-errors.ts +++ b/tests/corpus/1004-json-parse-errors.ts @@ -1,9 +1,7 @@ // JSON.parse syntax errors are CATCHABLE, exactly like Node's SyntaxError. -// The message text itself is unobservable here (the supported catch form is -// bindingless) and its V8-flavored shape is deliberately approximate — -// exact-message assertions live in the runtime C tests (test_json.c), per -// SEMANTICS.md. This program only proves the throw/catch/recover behavior -// matches Node for every syntax-error class. +// This program proves the throw/catch/recover behavior matches Node for +// every syntax-error class; 7993-json-parse-error-messages pins the +// messages themselves. function attempt(raw: string, label: string): void { try { diff --git a/tests/corpus/7993-json-parse-error-messages.ts b/tests/corpus/7993-json-parse-error-messages.ts new file mode 100644 index 000000000..76605e501 --- /dev/null +++ b/tests/corpus/7993-json-parse-error-messages.ts @@ -0,0 +1,30 @@ +// JSON.parse syntax error messages, exactly as Node's V8 writes them: the +// expected token at the end of the input inside objects and arrays, the +// position as a UTF-16 offset with its line and column (\n, \r and \r\n +// end lines), the unexpected character named with the source (whole when +// short, ten units of context on each side when longer), and the special +// sources that name only themselves. +const inputs: string[] = [ + "", " ", "[", "[{", "{", '{"a"', '{"a":', '{"a":1', '{"a":1,', '{"a":1,}', "{,", "{1:2}", "{'a':1}", + '{"a" 1}', '{"a":1 "b":2}', "[1", "[1,", "[1,]", "[1 2]", "[,1]", '"abc', '"a\\', '"\\u12', '"\\u12x4"', + '"\\u12"', '"\\x"', "tru", "nul", "fals", "t1", 'n"', "-", "-x", "1.", "1.e", "1e", "1e+", "01", "-01", + "[01]", "+1", "1 2", "{} x", "[]]", "\u0001", '"\u0001"', '{"a":[1,{"b":', '[{"a":1},', '{"a":{"b":{}', + "[[[", " [ \n { \n", "\n\n{", "\r\n\r\n{", "\r\r{", '{"a":tru}', "NaN", "Infinity", "undefined", + "[object Object]", " NaN", "-Infinity", "[-]", + // Positions count UTF-16 units: non-ASCII text before the error. + '{"é":1 x}', '["😀" 1]', '{"😀😀":', '"é\u0001"', "é", + // Long sources: the unexpected character with its context. + '{"name":"some long value","other":x}', '[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,]', + 'x{"name":"some long value","other":1}', '{"name":"some long value","other":1}x', '{"a":"0123456789abcdef"x}', + '["😀😀😀😀😀😀", x, "😀😀😀😀😀😀"]', '["😀😀😀😀😀😀",x]', '{"k":[true,false,null,"s",1.5e3,-2] ]', + '\n\n {\n "a": [1, 2,\n 3 4]\n }', +]; +for (const text of inputs) { + try { + JSON.parse(text); + console.log(JSON.stringify(text), "parsed"); + } catch (e) { + const err = e as Error; + console.log(JSON.stringify(text), err.name, JSON.stringify(err.message)); + } +} diff --git a/tests/harness/corpus-costs/7993-json-parse-error-messages.ts.json b/tests/harness/corpus-costs/7993-json-parse-error-messages.ts.json new file mode 100644 index 000000000..85179832c --- /dev/null +++ b/tests/harness/corpus-costs/7993-json-parse-error-messages.ts.json @@ -0,0 +1 @@ +{ "plain": 3600 } diff --git a/tests/harness/dyncheck.test.ts b/tests/harness/dyncheck.test.ts index 69e97dc6d..a0c43cd0c 100644 --- a/tests/harness/dyncheck.test.ts +++ b/tests/harness/dyncheck.test.ts @@ -191,11 +191,11 @@ for (const text of inputs) { expect(r.stderr).toBe(""); expect(r.stdout.split("\n")).toEqual([ "SyntaxError: Unexpected end of JSON input (same)", - "SyntaxError: Expected ',' or '}' after property value in JSON at position 19 (same)", - "SyntaxError: Bad escaped character in JSON at position 11 (same)", - "SyntaxError: Unexpected non-whitespace character after JSON at position 22 (same)", - 'SyntaxError: Unexpected token \']\', ...","x":[1,],"qty":"... is not valid JSON (same)', - "SyntaxError: Bad Unicode escape in JSON at position 21 (same)", + "SyntaxError: Unexpected number in JSON at position 19 (line 1 column 20) (same)", + "SyntaxError: Bad escaped character in JSON at position 11 (line 1 column 12) (same)", + "SyntaxError: Unexpected non-whitespace character after JSON at position 22 (line 1 column 23) (same)", + 'SyntaxError: Unexpected token \']\', ..."a","x":[1,],"qty":1}"... is not valid JSON (same)', + "SyntaxError: Bad Unicode escape in JSON at position 21 (line 1 column 22) (same)", "RangeError: Maximum call stack size exceeded (same)", "TypeError: expected number at $[0].qty, got undefined (plain ok)", "TypeError: expected number at $[1].qty, got boolean (plain ok)", @@ -268,7 +268,7 @@ try { "err TypeError: expected number at $.id, got undefined", "err TypeError: expected number at $.id, got null", "err TypeError: expected object at $, got array", - "err SyntaxError: Unexpected end of JSON input", + "err SyntaxError: Expected ',' or '}' after property value in JSON at position 39 (line 1 column 40)", "ok Infinity \uFFFD 0 ", "ok 7 reordered 0 ", "err expected number at $[1].qty, got string",