WXL
3 天以前 3bd962a6d7f61239c020e2dbbeb7341e5b842dd1
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
/*
    MIT License http://www.opensource.org/licenses/mit-license.php
*/
 
"use strict";
 
/** @typedef {import("../util/fs").JsonValue} JsonValue */
 
// Inspired by https://github.com/npm/json-parse-even-better-errors
 
// Remove byte order marker. This catches EF BB BF (the UTF-8 BOM)
// because the buffer-to-string conversion in `fs.readFileSync()`
// translates it to FEFF, the UTF-16 BOM.
/**
 * @param {string | Buffer} txt text
 * @returns {string} text without BOM
 */
const stripBOM = (txt) => String(txt).replace(/^\uFEFF/, "");
 
class JSONParseError extends SyntaxError {
    /**
     * @param {Error} err err
     * @param {EXPECTED_ANY} raw raw
     * @param {string} txt text
     * @param {number=} context context
     * @param {EXPECTED_FUNCTION=} caller caller
     */
    constructor(err, raw, txt, context = 20, caller = parseJson) {
        let originalMessage = err.message;
        /** @type {string} */
        let message;
        /** @type {number} */
        let position;
 
        if (typeof raw !== "string") {
            message = `Cannot parse ${Array.isArray(raw) && raw.length === 0 ? "an empty array" : String(raw)}`;
            position = 0;
        } else if (!txt) {
            message = `${originalMessage} while parsing empty string`;
            position = 0;
        } else {
            // Node 20 puts single quotes around the token and a comma after it
            const UNEXPECTED_TOKEN = /^Unexpected token '?(.)'?(,)? /i;
            const badTokenMatch = originalMessage.match(UNEXPECTED_TOKEN);
            const badIndexMatch = originalMessage.match(/ position\s+(\d+)/i);
 
            if (badTokenMatch) {
                const h = badTokenMatch[1].charCodeAt(0).toString(16).toUpperCase();
                const hex = `0x${h.length % 2 ? "0" : ""}${h}`;
 
                originalMessage = originalMessage.replace(
                    UNEXPECTED_TOKEN,
                    `Unexpected token ${JSON.stringify(badTokenMatch[1])} (${hex})$2 `
                );
            }
 
            /** @type {number | undefined} */
            let errIdx;
 
            if (badIndexMatch) {
                errIdx = Number(badIndexMatch[1]);
            } else if (
                // doesn't happen in Node 22+
                /^Unexpected end of JSON.*/i.test(originalMessage)
            ) {
                errIdx = txt.length - 1;
            }
 
            if (errIdx === undefined) {
                message = `${originalMessage} while parsing '${txt.slice(0, context * 2)}'`;
                position = 0;
            } else {
                const start = errIdx <= context ? 0 : errIdx - context;
                const end =
                    errIdx + context >= txt.length ? txt.length : errIdx + context;
                const slice = `${start ? "..." : ""}${txt.slice(start, end)}${end === txt.length ? "" : "..."}`;
 
                message = `${originalMessage} while parsing ${txt === slice ? "" : "near "}${JSON.stringify(slice)}`;
                position = errIdx;
            }
        }
 
        super(message);
 
        this.name = "JSONParseError";
        this.systemError = err;
        this.position = position;
 
        Error.captureStackTrace(this, caller || this.constructor);
    }
}
 
/**
 * @template [R=JsonValue]
 * @callback ParseJsonFn
 * @param {string} raw text
 * @param {(this: EXPECTED_ANY, key: string, value: EXPECTED_ANY) => EXPECTED_ANY=} reviver reviver
 * @returns {R} parsed JSON
 */
 
/** @type {ParseJsonFn} */
const parseJson = (raw, reviver) => {
    const txt = stripBOM(raw);
 
    try {
        return JSON.parse(txt, reviver);
    } catch (err) {
        throw new JSONParseError(/** @type {Error} */ (err), raw, txt);
    }
};
 
module.exports = parseJson;