UNPKG

2.5 kBJavaScriptView Raw
1import {codeFrameColumns} from '@babel/code-frame';
2import indexToPosition from 'index-to-position';
3
4const getCodePoint = character => `\\u{${character.codePointAt(0).toString(16)}}`;
5
6export class JSONError extends Error {
7 name = 'JSONError';
8 fileName;
9 codeFrame;
10 rawCodeFrame;
11 #message;
12
13 constructor(message) {
14 // We cannot pass message to `super()`, otherwise the message accessor will be overridden.
15 // https://262.ecma-international.org/14.0/#sec-error-message
16 super();
17
18 this.#message = message;
19 Error.captureStackTrace?.(this, JSONError);
20 }
21
22 get message() {
23 const {fileName, codeFrame} = this;
24 return `${this.#message}${fileName ? ` in ${fileName}` : ''}${codeFrame ? `\n\n${codeFrame}\n` : ''}`;
25 }
26
27 set message(message) {
28 this.#message = message;
29 }
30}
31
32const generateCodeFrame = (string, location, highlightCode = true) =>
33 codeFrameColumns(string, {start: location}, {highlightCode});
34
35const getErrorLocation = (string, message) => {
36 const match = message.match(/in JSON at position (?<index>\d+)(?: \(line (?<line>\d+) column (?<column>\d+)\))?$/);
37
38 if (!match) {
39 return;
40 }
41
42 let {index, line, column} = match.groups;
43
44 if (line && column) {
45 return {line: Number(line), column: Number(column)};
46 }
47
48 index = Number(index);
49
50 // The error location can be out of bounds.
51 if (index === string.length) {
52 const {line, column} = indexToPosition(string, string.length - 1, {oneBased: true});
53 return {line, column: column + 1};
54 }
55
56 return indexToPosition(string, index, {oneBased: true});
57};
58
59const addCodePointToUnexpectedToken = message => message.replace(
60 // TODO[engine:node@>=20]: The token always quoted after Node.js 20
61 /(?<=^Unexpected token )(?<quote>')?(.)\k<quote>/,
62 (_, _quote, token) => `"${token}"(${getCodePoint(token)})`,
63);
64
65export default function parseJson(string, reviver, fileName) {
66 if (typeof reviver === 'string') {
67 fileName = reviver;
68 reviver = undefined;
69 }
70
71 let message;
72 try {
73 return JSON.parse(string, reviver);
74 } catch (error) {
75 message = error.message;
76 }
77
78 let location;
79 if (string) {
80 location = getErrorLocation(string, message);
81 message = addCodePointToUnexpectedToken(message);
82 } else {
83 message += ' while parsing empty string';
84 }
85
86 const jsonError = new JSONError(message);
87
88 jsonError.fileName = fileName;
89
90 if (location) {
91 jsonError.codeFrame = generateCodeFrame(string, location);
92 jsonError.rawCodeFrame = generateCodeFrame(string, location, /* highlightCode */ false);
93 }
94
95 throw jsonError;
96}