Explaining Errors
Last phase ended with our formatter throwing Unexpected token and a line number that meant nothing. A tool that fails that way is worse than no tool. This phase is about catching the throw and turning it into something you'd actually want to read: what went wrong, and where to look.
Don't let the throw escape
The first move is the cheapest one. Wrap the parse in try / catch so a bad input becomes a result you can hand back, not a crash.
Guess which of the two calls below comes back with ok: true before you run it.
console.log;
console.log;
Now every call returns an object. ok: true carries the formatted output; ok: false carries the error message. Nothing throws past our function. That alone makes this safe to wire into a page - but the message is still raw browser text.
Where did it break?
Modern JavaScript engines tuck a character position into the error message - often as position N or (line L column C). The exact wording differs between browsers, which is annoying, but the number is gold. If we can pull a position out, we can show the reader the spot.
const broken = '{"name":"Ada", "age":}'; // value missing after age
try catch
Run it and read the raw message your browser produced. Somewhere in there is a number telling you how many characters in the problem is. findPosition below digs that number out with a regular expression - it's given, since it's a one-liner. The real work is lineAndColumn: translating a raw character count into a line and column a human can count to.
Your turn. Fill in lineAndColumn and hit Run: the checks underneath tell you whether it works. My version is in the next block whenever you want it.
// --- checks: fix your function until this prints "All good." ---
const single = '{"name":"Ada", "age":}';
const r1 = ;
if
const multi = '{\n "name": "Ada",\n "age":\n}';
const r2 = ;
if
console.log;
Stuck? text.lastIndexOf("\n") returns -1 when there's no newline at all. Whatever formula you use for "distance from the last newline" needs to keep working with that -1, for both single-line and multi-line text.
One way to write it
const broken = '{"name":"Ada", "age":}';
const pos = 21; // pretend the message gave us this
console.log;
console.log;
findPosition reads the number out of whatever the engine said. lineAndColumn counts newlines before that point to work out the line, then measures the distance from the last newline to get the column. For a one-line input the line is always 1 and the column is what you care about.
If you computed the column as position - lastNewline - 1, that's a reasonable instinct - it's the plain 0-based offset into the line, which is how most string methods think. But editors and humans count columns starting at 1, so that version lands one character before the real spot. The caret in the next section still points close enough to be useful even when it's off by one, which is exactly why an off-by-one bug like that can slip through unnoticed for a while.
A caret that points at the spot
Numbers are fine, but the kindest thing a formatter can do is draw an arrow at the broken character. We slice the text around the position and put a ^ underneath.
Guess which character the caret lands under before you run it - count 21 characters into broken by hand if you want to check yourself.
const broken = '{"name":"Ada", "age":}';
console.log;
Run it and you get the line with a caret sitting under the character the parser choked on. For broken JSON that's usually right at - or one past - the real mistake, which is close enough to find it by eye.
Wiring it into one clear formatter
Now we fold parsing, error catching, position-finding, and the caret into a single function that returns a clean result either way.
// A good one and three classic breakages.
console.log;
console.log;
console.log; // missing value
console.log;
console.log; // missing comma
console.log;
console.log; // single quotes
Run the whole thing. The valid object formats cleanly. The three broken ones each come back with a message and, where the engine gave us a position, a caret under the trouble spot. Three of the most common JSON mistakes - missing value, missing comma, single quotes - now produce a result you can act on instead of a stack trace.
Your formatter no longer crashes on bad input, and it explains what it found. Next we ask a harder question: the JSON parsed fine, but is it the right data? That's the shape check in Phase 3.