1 | import {codeFrameColumns} from '@babel/code-frame';
|
2 | import indexToPosition from 'index-to-position';
|
3 |
|
4 | const getCodePoint = character => `\\u{${character.codePointAt(0).toString(16)}}`;
|
5 |
|
6 | export class JSONError extends Error {
|
7 | name = 'JSONError';
|
8 | fileName;
|
9 | codeFrame;
|
10 | rawCodeFrame;
|
11 | #message;
|
12 |
|
13 | constructor(message) {
|
14 |
|
15 |
|
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 |
|
32 | const generateCodeFrame = (string, location, highlightCode = true) =>
|
33 | codeFrameColumns(string, {start: location}, {highlightCode});
|
34 |
|
35 | const 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 |
|
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 |
|
59 | const addCodePointToUnexpectedToken = message => message.replace(
|
60 |
|
61 | /(?<=^Unexpected token )(?<quote>')?(.)\k<quote>/,
|
62 | (_, _quote, token) => `"${token}"(${getCodePoint(token)})`,
|
63 | );
|
64 |
|
65 | export 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 | }
|