Files
scriptc/tests/corpus/1004-json-parse-errors.ts
T
Malte UblandClaude Opus 5.5 d037c66e8c 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 <noreply@anthropic.com>
2026-10-24 09:55:05 -07:00

58 lines
1.7 KiB
TypeScript

// JSON.parse syntax errors are CATCHABLE, exactly like Node's SyntaxError.
// 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 {
const v = JSON.parse(raw) as number;
console.log("ok", label, v);
} catch {
console.log("caught", label);
}
}
attempt("42", "valid");
attempt("", "empty input");
attempt(" ", "whitespace only");
attempt("{oops", "bad token");
attempt("[1,2,", "unterminated array");
attempt('{"a":1', "unterminated object");
attempt('{"a"}', "missing colon");
attempt('{"a":1,}', "trailing comma");
attempt("[1 2]", "missing comma");
attempt('"unterminated', "unterminated string");
attempt('"bad \\q escape"', "bad escape");
attempt("01", "leading zero");
attempt("1.", "bare fraction dot");
attempt("1e", "missing exponent");
attempt("-", "lone minus");
attempt("truthy", "bad literal");
attempt("1 2", "trailing content");
attempt("NaN", "NaN is not JSON");
attempt("undefined", "undefined is not JSON");
attempt("'single'", "single quotes");
// Parse failures propagate through call chains to the nearest catch and
// leave earlier work intact.
function parseOrZero(raw: string): number {
try {
return JSON.parse(raw) as number;
} catch {
return 0;
}
}
console.log(parseOrZero("5") + parseOrZero("{nope") + parseOrZero("6"));
// finally runs on the exception path of a failing parse.
try {
try {
const v = JSON.parse("[") as number[];
console.log("unreachable", v.length);
} finally {
console.log("finally ran");
}
} catch {
console.log("caught after finally");
}