-
-
Notifications
You must be signed in to change notification settings - Fork 36
/
Copy pathindex.js
118 lines (94 loc) · 3.04 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
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;
#input;
#jsonParseError;
#message;
#codeFrame;
#rawCodeFrame;
constructor(messageOrOptions) {
// JSONError constructor used accept string
// TODO[>=9]: Remove this `if` on next major version
if (typeof messageOrOptions === 'string') {
super();
this.#message = messageOrOptions;
} else {
const {jsonParseError, fileName, input} = messageOrOptions;
// We cannot pass message to `super()`, otherwise the message accessor will be overridden.
// https://262.ecma-international.org/14.0/#sec-error-message
super(undefined, {cause: jsonParseError});
this.#input = input;
this.#jsonParseError = jsonParseError;
this.fileName = fileName;
}
Error.captureStackTrace?.(this, JSONError);
}
get message() {
this.#message ??= `${addCodePointToUnexpectedToken(this.#jsonParseError.message)}${this.#input === '' ? ' while parsing empty string' : ''}`;
const {codeFrame} = this;
return `${this.#message}${this.fileName ? ` in ${this.fileName}` : ''}${codeFrame ? `\n\n${codeFrame}\n` : ''}`;
}
set message(message) {
this.#message = message;
}
#getCodeFrame(highlightCode) {
// TODO[>=9]: Remove this `if` on next major version
if (!this.#jsonParseError) {
return;
}
const input = this.#input;
const location = getErrorLocation(input, this.#jsonParseError.message);
if (!location) {
return;
}
return codeFrameColumns(input, {start: location}, {highlightCode});
}
get codeFrame() {
this.#codeFrame ??= this.#getCodeFrame(/* highlightCode */ true);
return this.#codeFrame;
}
get rawCodeFrame() {
this.#rawCodeFrame ??= this.#getCodeFrame(/* highlightCode */ false);
return this.#rawCodeFrame;
}
}
const getErrorLocation = (string, message) => {
const match = message.match(/in JSON at position (?<index>\d+)(?: \(line (?<line>\d+) column (?<column>\d+)\))?$/);
if (!match) {
return;
}
let {index, line, column} = match.groups;
if (line && column) {
return {line: Number(line), column: Number(column)};
}
index = Number(index);
// The error location can be out of bounds.
if (index === string.length) {
const {line, column} = indexToPosition(string, string.length - 1, {oneBased: true});
return {line, column: column + 1};
}
return indexToPosition(string, index, {oneBased: true});
};
const addCodePointToUnexpectedToken = message => message.replace(
// TODO[engine:node@>=20]: The token always quoted after Node.js 20
/(?<=^Unexpected token )(?<quote>')?(.)\k<quote>/,
(_, _quote, token) => `"${token}"(${getCodePoint(token)})`,
);
export default function parseJson(string, reviver, fileName) {
if (typeof reviver === 'string') {
fileName = reviver;
reviver = undefined;
}
try {
return JSON.parse(string, reviver);
} catch (error) {
throw new JSONError({
jsonParseError: error,
fileName,
input: string,
});
}
}