Skip to content

Commit a653ade

Browse files
fix: no json-better-parser-error (#20828)
1 parent 5c43068 commit a653ade

11 files changed

Lines changed: 428 additions & 34 deletions

.github/dependabot.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ updates:
2222
- "major"
2323
exclude-patterns:
2424
- "eslint-scope"
25-
- "json-parse-even-better-errors"
2625
- "strip-ansi"
2726
- "rimraf"
2827
- package-ecosystem: "github-actions"

cspell.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,8 @@
336336
"WHATWG",
337337
"publicpath",
338338
"Uninstantiated",
339-
"sccs"
339+
"sccs",
340+
"FEFF"
340341
],
341342
"ignoreRegExpList": [
342343
"/Author.+/",
@@ -359,6 +360,7 @@
359360
"**/**/*.snap",
360361
"test/cases/json/weird-properties/globals.json",
361362
"test/JavascriptParser.unittest.js",
363+
"test/test262-cases/**",
362364
"**/*.svg",
363365
"*.log",
364366
"**/*.wasm",

declarations.d.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -228,15 +228,6 @@ declare module "webpack-sources" {
228228
} from "webpack-sources/types";
229229
}
230230

231-
declare module "json-parse-even-better-errors" {
232-
function parseJson(
233-
text: string,
234-
reviver?: (this: any, key: string, value: any) => any,
235-
context?: number
236-
): any;
237-
export = parseJson;
238-
}
239-
240231
type RecursiveArrayOrRecord<T> =
241232
| { [index: string]: RecursiveArrayOrRecord<T> }
242233
| Array<RecursiveArrayOrRecord<T>>

lib/Compiler.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
"use strict";
77

8-
const parseJson = require("json-parse-even-better-errors");
98
const asyncLib = require("neo-async");
109
const {
1110
AsyncParallelHook,
@@ -31,6 +30,7 @@ const { Logger } = require("./logging/Logger");
3130
const { dirname, join, mkdirp } = require("./util/fs");
3231
const { makePathsRelative } = require("./util/identifier");
3332
const memoize = require("./util/memoize");
33+
const parseJson = require("./util/parseJson");
3434
const { isSourceEqual } = require("./util/source");
3535
const webpack = require(".");
3636

@@ -1200,9 +1200,9 @@ ${other}`);
12001200
if (err) return callback(err);
12011201

12021202
try {
1203-
this.records = parseJson(
1204-
/** @type {Buffer} */ (content).toString("utf8")
1205-
);
1203+
this.records =
1204+
/** @type {Records} */
1205+
(parseJson(/** @type {Buffer} */ (content).toString("utf8")));
12061206
} catch (parseErr) {
12071207
return callback(
12081208
new Error(

lib/DllReferencePlugin.js

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55

66
"use strict";
77

8-
const parseJson = require("json-parse-even-better-errors");
98
const DelegatedModuleFactoryPlugin = require("./DelegatedModuleFactoryPlugin");
109
const ExternalModuleFactoryPlugin = require("./ExternalModuleFactoryPlugin");
1110
const WebpackError = require("./WebpackError");
1211
const DelegatedSourceDependency = require("./dependencies/DelegatedSourceDependency");
13-
const makePathsRelative = require("./util/identifier").makePathsRelative;
12+
const { makePathsRelative } = require("./util/identifier");
13+
const parseJson = require("./util/parseJson");
1414

1515
/** @typedef {import("../declarations/WebpackOptions").Externals} Externals */
1616
/** @typedef {import("../declarations/plugins/DllReferencePlugin").DllReferencePluginOptions} DllReferencePluginOptions */
@@ -80,9 +80,12 @@ class DllReferencePlugin {
8080
// Catch errors parsing the manifest so that blank
8181
// or malformed manifest files don't kill the process.
8282
try {
83-
data.data = parseJson(
84-
/** @type {Buffer} */ (result).toString("utf8")
85-
);
83+
data.data =
84+
/** @type {DllReferencePluginOptionsManifest} */
85+
(
86+
/** @type {unknown} */
87+
(parseJson(/** @type {Buffer} */ (result).toString("utf8")))
88+
);
8689
} catch (parseErr) {
8790
// Store the error in the params so that it can
8891
// be added as a compilation error later on.

lib/NormalModule.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
"use strict";
77

88
const querystring = require("querystring");
9-
const parseJson = require("json-parse-even-better-errors");
109
const { getContext, runLoaders } = require("loader-runner");
1110
const {
1211
AsyncSeriesBailHook,
@@ -53,6 +52,7 @@ const {
5352
} = require("./util/identifier");
5453
const makeSerializable = require("./util/makeSerializable");
5554
const memoize = require("./util/memoize");
55+
const parseJson = require("./util/parseJson");
5656

5757
/** @typedef {import("enhanced-resolve").ResolveContext} ResolveContext */
5858
/** @typedef {import("enhanced-resolve").ResolveRequest} ResolveRequest */
@@ -726,7 +726,9 @@ class NormalModule extends Module {
726726
if (typeof options === "string") {
727727
if (options.startsWith("{") && options.endsWith("}")) {
728728
try {
729-
options = parseJson(options);
729+
options =
730+
/** @type {LoaderItem["options"]} */
731+
(parseJson(options));
730732
} catch (err) {
731733
throw new Error(
732734
`Cannot parse string options: ${/** @type {Error} */ (err).message}`,

lib/json/JsonParser.js

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
const Parser = require("../Parser");
99
const JsonExportsDependency = require("../dependencies/JsonExportsDependency");
10-
const memoize = require("../util/memoize");
10+
const parseJson = require("../util/parseJson");
1111
const JsonData = require("./JsonData");
1212

1313
/** @typedef {import("../../declarations/plugins/JsonModulesPluginParser").JsonModulesPluginParserOptions} JsonModulesPluginParserOptions */
@@ -17,17 +17,14 @@ const JsonData = require("./JsonData");
1717
/** @typedef {import("../Parser").PreparsedAst} PreparsedAst */
1818
/** @typedef {import("../util/fs").JsonValue} JsonValue */
1919

20+
/** @typedef {(input: string) => Buffer | JsonValue} ParseFn */
21+
2022
/**
2123
* Defines the function returning type used by this module.
2224
* @template T
2325
* @typedef {import("../util/memoize").FunctionReturning<T>} FunctionReturning
2426
*/
2527

26-
/** @typedef {(input: string) => Buffer | JsonValue} ParseFn */
27-
28-
/** @type {FunctionReturning<ParseFn>} */
29-
const getParseJson = memoize(() => require("json-parse-even-better-errors"));
30-
3128
class JsonParser extends Parser {
3229
/**
3330
* Creates an instance of JsonParser.
@@ -50,11 +47,9 @@ class JsonParser extends Parser {
5047
source = source.toString("utf8");
5148
}
5249

53-
/** @type {ParseFn} */
50+
/** @type {typeof parseJson} */
5451
const parseFn =
55-
typeof this.options.parse === "function"
56-
? this.options.parse
57-
: getParseJson();
52+
typeof this.options.parse === "function" ? this.options.parse : parseJson;
5853
/** @type {Buffer | JsonValue | undefined} */
5954
let data;
6055
try {

lib/util/parseJson.js

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
MIT License http://www.opensource.org/licenses/mit-license.php
3+
*/
4+
5+
"use strict";
6+
7+
/** @typedef {import("../util/fs").JsonValue} JsonValue */
8+
9+
// Inspired by https://github.com/npm/json-parse-even-better-errors
10+
11+
// Remove byte order marker. This catches EF BB BF (the UTF-8 BOM)
12+
// because the buffer-to-string conversion in `fs.readFileSync()`
13+
// translates it to FEFF, the UTF-16 BOM.
14+
/**
15+
* @param {string | Buffer} txt text
16+
* @returns {string} text without BOM
17+
*/
18+
const stripBOM = (txt) => String(txt).replace(/^\uFEFF/, "");
19+
20+
class JSONParseError extends SyntaxError {
21+
/**
22+
* @param {Error} err err
23+
* @param {EXPECTED_ANY} raw raw
24+
* @param {string} txt text
25+
* @param {number=} context context
26+
* @param {EXPECTED_FUNCTION=} caller caller
27+
*/
28+
constructor(err, raw, txt, context = 20, caller = parseJson) {
29+
let originalMessage = err.message;
30+
/** @type {string} */
31+
let message;
32+
/** @type {number} */
33+
let position;
34+
35+
if (typeof raw !== "string") {
36+
message = `Cannot parse ${Array.isArray(raw) && raw.length === 0 ? "an empty array" : String(raw)}`;
37+
position = 0;
38+
} else if (!txt) {
39+
message = `${originalMessage} while parsing empty string`;
40+
position = 0;
41+
} else {
42+
// Node 20 puts single quotes around the token and a comma after it
43+
const UNEXPECTED_TOKEN = /^Unexpected token '?(.)'?(,)? /i;
44+
const badTokenMatch = originalMessage.match(UNEXPECTED_TOKEN);
45+
const badIndexMatch = originalMessage.match(/ position\s+(\d+)/i);
46+
47+
if (badTokenMatch) {
48+
const h = badTokenMatch[1].charCodeAt(0).toString(16).toUpperCase();
49+
const hex = `0x${h.length % 2 ? "0" : ""}${h}`;
50+
51+
originalMessage = originalMessage.replace(
52+
UNEXPECTED_TOKEN,
53+
`Unexpected token ${JSON.stringify(badTokenMatch[1])} (${hex})$2 `
54+
);
55+
}
56+
57+
/** @type {number | undefined} */
58+
let errIdx;
59+
60+
if (badIndexMatch) {
61+
errIdx = Number(badIndexMatch[1]);
62+
} else if (
63+
// doesn't happen in Node 22+
64+
/^Unexpected end of JSON.*/i.test(originalMessage)
65+
) {
66+
errIdx = txt.length - 1;
67+
}
68+
69+
if (errIdx === undefined) {
70+
message = `${originalMessage} while parsing '${txt.slice(0, context * 2)}'`;
71+
position = 0;
72+
} else {
73+
const start = errIdx <= context ? 0 : errIdx - context;
74+
const end =
75+
errIdx + context >= txt.length ? txt.length : errIdx + context;
76+
const slice = `${start ? "..." : ""}${txt.slice(start, end)}${end === txt.length ? "" : "..."}`;
77+
78+
message = `${originalMessage} while parsing ${txt === slice ? "" : "near "}${JSON.stringify(slice)}`;
79+
position = errIdx;
80+
}
81+
}
82+
83+
super(message);
84+
85+
this.name = "JSONParseError";
86+
this.systemError = err;
87+
this.position = position;
88+
89+
Error.captureStackTrace(this, caller || this.constructor);
90+
}
91+
}
92+
93+
/**
94+
* @template [R=JsonValue]
95+
* @callback ParseJsonFn
96+
* @param {string} raw text
97+
* @param {(this: EXPECTED_ANY, key: string, value: EXPECTED_ANY) => EXPECTED_ANY=} reviver reviver
98+
* @returns {R} parsed JSON
99+
*/
100+
101+
/** @type {ParseJsonFn} */
102+
const parseJson = (raw, reviver) => {
103+
const txt = stripBOM(raw);
104+
105+
try {
106+
return JSON.parse(txt, reviver);
107+
} catch (err) {
108+
throw new JSONParseError(/** @type {Error} */ (err), raw, txt);
109+
}
110+
};
111+
112+
module.exports = parseJson;

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,6 @@
102102
"events": "^3.2.0",
103103
"glob-to-regexp": "^0.4.1",
104104
"graceful-fs": "^4.2.11",
105-
"json-parse-even-better-errors": "^2.3.1",
106105
"loader-runner": "^4.3.1",
107106
"mime-db": "^1.54.0",
108107
"neo-async": "^2.6.2",

0 commit comments

Comments
 (0)