diff --git a/index.js b/index.js index 04206ee..771ccd4 100644 --- a/index.js +++ b/index.js @@ -1,7 +1,8 @@ -import fallback from 'json-parse-even-better-errors'; import {codeFrameColumns} from '@babel/code-frame'; import indexToPosition from 'index-to-position'; +const getCodePoint = character => `\\u{${character.codePointAt(0).toString(16)}}`; + export class JSONError extends Error { name = 'JSONError'; fileName; @@ -32,7 +33,7 @@ const generateCodeFrame = (string, location, highlightCode = true) => codeFrameColumns(string, {start: location}, {highlightCode}); const getErrorLocation = (string, message) => { - const match = message.match(/in JSON at position (?\d+)(?: \(line (?\d+) column (?\d+)\))? while parsing/); + const match = message.match(/in JSON at position (?\d+)(?: \(line (?\d+) column (?\d+)\))?$/); if (!match) { return; @@ -55,9 +56,15 @@ const getErrorLocation = (string, message) => { return indexToPosition(string, index, {oneBased: true}); }; -export default function parseJson(string, reviver, filename) { +const addCodePointToUnexpectedToken = message => message.replace( + // TODO[engine:node@>=20]: The token always quoted after Node.js 20 + /(?<=^Unexpected token )(?')?(.)\k/, + (_, _quote, token) => `"${token}"(${getCodePoint(token)})`, +); + +export default function parseJson(string, reviver, fileName) { if (typeof reviver === 'string') { - filename = reviver; + fileName = reviver; reviver = undefined; } @@ -68,20 +75,18 @@ export default function parseJson(string, reviver, filename) { message = error.message; } - try { - fallback(string, reviver); - } catch (error) { - message = error.message; + let location; + if (string) { + location = getErrorLocation(string, message); + message = addCodePointToUnexpectedToken(message); + } else { + message += ' while parsing empty string'; } - message = message.replaceAll('\n', ''); const jsonError = new JSONError(message); - if (filename) { - jsonError.fileName = filename; - } + jsonError.fileName = fileName; - const location = getErrorLocation(string, message); if (location) { jsonError.codeFrame = generateCodeFrame(string, location); jsonError.rawCodeFrame = generateCodeFrame(string, location, /* highlightCode */ false); diff --git a/package.json b/package.json index 0b25d10..e002b10 100644 --- a/package.json +++ b/package.json @@ -39,8 +39,7 @@ ], "dependencies": { "@babel/code-frame": "^7.22.13", - "index-to-position": "^0.1.1", - "json-parse-even-better-errors": "^3.0.0", + "index-to-position": "^0.1.2", "type-fest": "^4.7.1" }, "devDependencies": { diff --git a/test.js b/test.js index fff1022..68cde9d 100644 --- a/test.js +++ b/test.js @@ -8,14 +8,14 @@ const NODE_JS_VERSION = Number(process.versions.node.split('.')[0]); const errorMessageRegex = (() => { if (NODE_JS_VERSION < 20) { - return /Unexpected token "}"/; + return /Unexpected token "}"\(\\u{7d}\) in JSON at position 16/; } if (NODE_JS_VERSION < 21) { - return /Expected double-quoted property name in JSON at position 16 while parsing/; + return /Expected double-quoted property name in JSON at position 16/; } - return /Expected double-quoted property name in JSON at position 16 \(line 3 column 1\) while parsing/; + return /Expected double-quoted property name in JSON at position 16 \(line 3 column 1\)/; })(); const errorMessageRegexWithFileName = new RegExp(errorMessageRegex.source + '.*in foo\\.json'); const INVALID_JSON_STRING = outdent` @@ -103,6 +103,29 @@ test('empty string', t => { parseJson(''); } catch (error) { t.true(error instanceof JSONError); + t.is(error.message, 'Unexpected end of JSON input while parsing empty string'); t.is(error.rawCodeFrame, undefined); } + + try { + parseJson(' '); + } catch (error) { + t.true(error instanceof JSONError); + t.is(error.message, 'Unexpected end of JSON input'); + t.is(error.rawCodeFrame, undefined); + } +}); + +test('Unexpected tokens', t => { + try { + parseJson('a'); + } catch (error) { + t.true(error instanceof JSONError); + const firstLine = error.message.split('\n')[0]; + if (NODE_JS_VERSION === 18) { + t.is(firstLine, 'Unexpected token "a"(\\u{61}) in JSON at position 0'); + } else { + t.is(firstLine, 'Unexpected token "a"(\\u{61}), "a" is not valid JSON'); + } + } });