@bhsd/common 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  declare interface JsonError {
2
+ severity: 'error' | 'warning';
2
3
  message: string;
3
- line: number | null;
4
- column: number | null;
5
- position: number | null;
4
+ line: number;
5
+ column: number;
6
+ position: number;
6
7
  }
7
8
  export type RegexGetter<T = string> = (s: T) => RegExp;
8
9
  export declare const wmf = "wiktionary|wiki(?:pedia|books|news|quote|source|versity|voyage)";
@@ -39,6 +40,17 @@ export declare function getRegex<T extends object>(f: RegexGetter<T>): RegexGett
39
40
  * @deprecated 需要改为使用`getRegex`
40
41
  */
41
42
  export declare const getObjRegex: typeof getRegex;
43
+ /**
44
+ * 按行分割字符串并记录每行的起止位置
45
+ * @param str 字符串
46
+ */
47
+ export declare const splitLines: (str: string) => [string, number, number][];
48
+ /**
49
+ * 使用`JSON.parse()`诊断JSON字符串中的语法错误
50
+ * @param str JSON字符串
51
+ * @param force 是否强制诊断
52
+ */
53
+ export declare const lintJSONNative: (str: string, force?: boolean) => JsonError[];
42
54
  /**
43
55
  * 诊断JSON字符串中的语法错误
44
56
  * @param str JSON字符串
package/dist/index.js CHANGED
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.lintJSON = exports.getObjRegex = exports.sanitizeInlineStyle = exports.splitColors = exports.numToHex = exports.rawurldecode = exports.wmf = void 0;
3
+ exports.lintJSON = exports.lintJSONNative = exports.splitLines = exports.getObjRegex = exports.sanitizeInlineStyle = exports.splitColors = exports.numToHex = exports.rawurldecode = exports.wmf = void 0;
4
4
  exports.getRegex = getRegex;
5
+ const json_parse_js_1 = require("./json_parse.js");
5
6
  exports.wmf = 'wiktionary|wiki(?:pedia|books|news|quote|source|versity|voyage)';
6
7
  /**
7
8
  * PHP的`rawurldecode`函数的JavaScript实现
@@ -72,23 +73,89 @@ function getRegex(f) {
72
73
  * @deprecated 需要改为使用`getRegex`
73
74
  */
74
75
  exports.getObjRegex = getRegex;
76
+ /**
77
+ * 按行分割字符串并记录每行的起止位置
78
+ * @param str 字符串
79
+ */
80
+ const splitLines = (str) => {
81
+ const lines = [];
82
+ let start = 0;
83
+ for (const line of str.split('\n')) {
84
+ const end = start + line.length;
85
+ lines.push([line, start, end]);
86
+ start = end + 1;
87
+ }
88
+ return lines;
89
+ };
90
+ exports.splitLines = splitLines;
75
91
  const mt2num = (mt) => mt && Number(mt[1]);
92
+ const formatJsonError = (str, errors) => {
93
+ let lines;
94
+ for (const error of errors) {
95
+ const { line, column, position } = error;
96
+ if (position === null || position === undefined) {
97
+ if (line) {
98
+ lines ??= (0, exports.splitLines)(str);
99
+ error.column ??= 1;
100
+ error.position = lines[line - 1][1] + (error.column - 1);
101
+ }
102
+ else {
103
+ error.position = 0;
104
+ error.line = 1;
105
+ error.column = 1;
106
+ }
107
+ }
108
+ else if (!line || !column) {
109
+ lines ??= (0, exports.splitLines)(str);
110
+ error.line = lines.findIndex(([, , end]) => position <= end) + 1;
111
+ error.column = position - lines[error.line - 1][1] + 1;
112
+ }
113
+ }
114
+ return errors;
115
+ };
116
+ /**
117
+ * 使用`JSON.parse()`诊断JSON字符串中的语法错误
118
+ * @param str JSON字符串
119
+ * @param force 是否强制诊断
120
+ */
121
+ const lintJSONNative = (str, force) => {
122
+ if (force || str.trim()) {
123
+ try {
124
+ JSON.parse(str);
125
+ }
126
+ catch (e) {
127
+ const { message } = e, line = mt2num(/\bline (\d+)/u.exec(message)), column = mt2num(/\bcolumn (\d+)/u.exec(message)), position = mt2num(/\bposition (\d+)/u.exec(message));
128
+ return formatJsonError(str, [{ message, line, column, position, severity: 'error' }]);
129
+ }
130
+ }
131
+ return [];
132
+ };
133
+ exports.lintJSONNative = lintJSONNative;
76
134
  /**
77
135
  * 诊断JSON字符串中的语法错误
78
136
  * @param str JSON字符串
79
137
  */
80
138
  const lintJSON = (str) => {
139
+ if (!str.trim()) {
140
+ return [];
141
+ }
142
+ let errors;
81
143
  try {
82
- if (str.trim()) {
83
- JSON.parse(str);
84
- }
144
+ (0, json_parse_js_1.json_parse)(str);
85
145
  }
86
146
  catch (e) {
87
- if (e instanceof SyntaxError) {
88
- const { message } = e, line = mt2num(/\bline (\d+)/u.exec(message)), column = mt2num(/\bcolumn (\d+)/u.exec(message)), position = mt2num(/\bposition (\d+)/u.exec(message));
89
- return [{ message, line, column, position }];
147
+ if (!(e instanceof Error)) {
148
+ const { warnings, ...error } = e;
149
+ if (error.message) {
150
+ warnings.push(error);
151
+ }
152
+ errors = formatJsonError(str, warnings);
153
+ if (error.message) {
154
+ return errors;
155
+ }
90
156
  }
91
157
  }
92
- return [];
158
+ const nativeErrors = (0, exports.lintJSONNative)(str);
159
+ return errors ? [...nativeErrors, ...errors] : nativeErrors;
93
160
  };
94
161
  exports.lintJSON = lintJSON;
package/dist/index.mjs CHANGED
@@ -1,14 +1,278 @@
1
- const wmf = "wiktionary|wiki(?:pedia|books|news|quote|source|versity|voyage)";
2
- const rawurldecode = (str) => decodeURIComponent(str.replace(/%(?![\da-f]{2})/giu, "%25"));
3
- const numToHex = (d) => Math.round(d * 255).toString(16).padStart(2, "0");
4
- const regex = /* @__PURE__ */ (() => {
1
+ // src/json_parse.ts
2
+ var json_parse = /* @__PURE__ */ (() => {
3
+ let at;
4
+ let ch;
5
+ const escapee = {
6
+ '"': '"',
7
+ "\\": "\\",
8
+ "/": "/",
9
+ b: "\b",
10
+ f: "\f",
11
+ n: "\n",
12
+ r: "\r",
13
+ t: " "
14
+ };
15
+ const spaces = /* @__PURE__ */ new Set([" ", " ", "\n", "\r"]);
16
+ let text;
17
+ let warnings;
18
+ const stringify = (c) => {
19
+ if (c === "") {
20
+ return "end of input";
21
+ }
22
+ return c === '"' ? `'"'` : JSON.stringify(c);
23
+ };
24
+ const warn = (m) => {
25
+ warnings.push({
26
+ message: m,
27
+ position: at - 1,
28
+ severity: "warning"
29
+ });
30
+ };
31
+ const error = (m) => {
32
+ throw {
33
+ warnings,
34
+ message: m,
35
+ position: at - 1,
36
+ severity: "error"
37
+ };
38
+ };
39
+ const next = (c) => {
40
+ if (c && c !== ch) {
41
+ error(`Expected ${stringify(c)} instead of ${stringify(ch)}`);
42
+ }
43
+ ch = text.charAt(at);
44
+ at += 1;
45
+ return ch;
46
+ };
47
+ const number = () => {
48
+ let value2;
49
+ let string2 = "";
50
+ if (ch === "-") {
51
+ string2 = "-";
52
+ next();
53
+ }
54
+ if (ch === "0") {
55
+ string2 += ch;
56
+ next();
57
+ if (ch >= "0" && ch <= "9") {
58
+ error("Bad number");
59
+ }
60
+ } else if (ch >= "1" && ch <= "9") {
61
+ while (ch >= "0" && ch <= "9") {
62
+ string2 += ch;
63
+ next();
64
+ }
65
+ } else {
66
+ error("No number after minus sign");
67
+ }
68
+ if (ch !== "." && ch !== "e" && ch !== "E") {
69
+ value2 = Number(string2);
70
+ if (!Number.isSafeInteger(value2)) {
71
+ warn("Not a safe integer");
72
+ }
73
+ }
74
+ if (ch === ".") {
75
+ string2 += ".";
76
+ next();
77
+ if (ch < "0" || ch > "9") {
78
+ error("Unterminated fractional number");
79
+ }
80
+ while (ch >= "0" && ch <= "9") {
81
+ string2 += ch;
82
+ next();
83
+ }
84
+ }
85
+ if (ch === "e" || ch === "E") {
86
+ string2 += ch;
87
+ next();
88
+ if (ch === "-" || ch === "+") {
89
+ string2 += ch;
90
+ next();
91
+ }
92
+ while (ch >= "0" && ch <= "9") {
93
+ string2 += ch;
94
+ next();
95
+ }
96
+ }
97
+ value2 ??= Number(string2);
98
+ if (!Number.isFinite(value2)) {
99
+ error("Bad number");
100
+ }
101
+ };
102
+ const string = () => {
103
+ let hex;
104
+ let i;
105
+ let value2 = "";
106
+ let uffff;
107
+ if (ch === '"') {
108
+ while (next()) {
109
+ if (ch === '"') {
110
+ next();
111
+ return value2;
112
+ }
113
+ if (ch === "\\") {
114
+ next();
115
+ if (ch === "u") {
116
+ uffff = 0;
117
+ for (i = 0; i < 4; i++) {
118
+ hex = parseInt(next(), 16);
119
+ if (!isFinite(hex)) {
120
+ break;
121
+ }
122
+ uffff = uffff * 16 + hex;
123
+ }
124
+ if (i < 4) {
125
+ error("Bad unicode escape");
126
+ }
127
+ value2 += String.fromCharCode(uffff);
128
+ } else if (typeof escapee[ch] === "string") {
129
+ value2 += escapee[ch];
130
+ } else {
131
+ error("Bad escaped character");
132
+ }
133
+ } else if (ch < " ") {
134
+ error("Bad control character");
135
+ } else {
136
+ value2 += ch;
137
+ }
138
+ }
139
+ } else {
140
+ error(`Expected '"' instead of ${JSON.stringify(ch)}`);
141
+ }
142
+ return error("Unterminated string");
143
+ };
144
+ const white = () => {
145
+ while (ch && spaces.has(ch)) {
146
+ next();
147
+ }
148
+ };
149
+ const word = () => {
150
+ switch (ch) {
151
+ case "t":
152
+ next();
153
+ next("r");
154
+ next("u");
155
+ next("e");
156
+ return;
157
+ case "f":
158
+ next();
159
+ next("a");
160
+ next("l");
161
+ next("s");
162
+ next("e");
163
+ return;
164
+ case "n":
165
+ next();
166
+ next("u");
167
+ next("l");
168
+ next("l");
169
+ return;
170
+ default:
171
+ error(`Unexpected ${JSON.stringify(ch)}`);
172
+ }
173
+ };
174
+ const array = () => {
175
+ next("[");
176
+ white();
177
+ if (ch === "]") {
178
+ next();
179
+ return;
180
+ }
181
+ while (ch) {
182
+ if (ch === "]") {
183
+ error("Trailing comma in array");
184
+ }
185
+ value();
186
+ white();
187
+ if (ch === "]") {
188
+ next();
189
+ return;
190
+ } else if (ch === ",") {
191
+ next();
192
+ white();
193
+ } else {
194
+ error(`Expected "," or "]" instead of ${stringify(ch)}`);
195
+ }
196
+ }
197
+ error("Unterminated array");
198
+ };
199
+ const object = () => {
200
+ let key;
201
+ const keys = /* @__PURE__ */ new Set();
202
+ next("{");
203
+ white();
204
+ if (ch === "}") {
205
+ next();
206
+ return;
207
+ }
208
+ while (ch) {
209
+ if (ch === "}") {
210
+ error("Trailing comma in object");
211
+ }
212
+ key = string();
213
+ white();
214
+ next(":");
215
+ if (keys.has(key)) {
216
+ warn(`Duplicate key ${stringify(key)}`);
217
+ } else {
218
+ keys.add(key);
219
+ }
220
+ value();
221
+ white();
222
+ if (ch === "}") {
223
+ next();
224
+ return;
225
+ } else if (ch === ",") {
226
+ next();
227
+ white();
228
+ } else {
229
+ error(`Expected "," or "}" instead of ${stringify(ch)}`);
230
+ }
231
+ }
232
+ error(`Expected '"'`);
233
+ };
234
+ const value = () => {
235
+ white();
236
+ switch (ch) {
237
+ case "{":
238
+ return object();
239
+ case "[":
240
+ return array();
241
+ case '"':
242
+ return string();
243
+ case "-":
244
+ return number();
245
+ default:
246
+ return ch >= "0" && ch <= "9" ? number() : word();
247
+ }
248
+ };
249
+ return (source) => {
250
+ text = source;
251
+ warnings = [];
252
+ at = 0;
253
+ ch = " ";
254
+ value();
255
+ white();
256
+ if (ch) {
257
+ error("Syntax error");
258
+ } else if (warnings.length > 0) {
259
+ throw { warnings };
260
+ }
261
+ };
262
+ })();
263
+
264
+ // src/index.ts
265
+ var wmf = "wiktionary|wiki(?:pedia|books|news|quote|source|versity|voyage)";
266
+ var rawurldecode = (str) => decodeURIComponent(str.replace(/%(?![\da-f]{2})/giu, "%25"));
267
+ var numToHex = (d) => Math.round(d * 255).toString(16).padStart(2, "0");
268
+ var regex = /* @__PURE__ */ (() => {
5
269
  const hexColor = String.raw`#(?:[\da-f]{3,4}|(?:[\da-f]{2}){3,4})(?![\p{L}\p{N}_])`, rgbValue = String.raw`(?:\d*\.)?\d+%?`, hue = String.raw`(?:\d*\.)?\d+(?:deg|grad|rad|turn)?`, rgbColor = String.raw`rgba?\(\s*(?:${String.raw`${new Array(3).fill(rgbValue).join(String.raw`\s+`)}(?:\s*\/\s*${rgbValue})?`}|${String.raw`${new Array(3).fill(rgbValue).join(String.raw`\s*,\s*`)}(?:\s*,\s*${rgbValue})?`})\s*\)`, hslColor = String.raw`hsla?\(\s*(?:${String.raw`${hue}\s+${rgbValue}\s+${rgbValue}(?:\s*\/\s*${rgbValue})?`}|${String.raw`${hue}${String.raw`\s*,\s*(?:\d*\.)?\d+%`.repeat(2)}(?:\s*,\s*${rgbValue})?`})\s*\)`;
6
270
  return {
7
271
  full: new RegExp(String.raw`(^|[^\p{L}\p{N}_])(${hexColor}|${rgbColor}|${hslColor})`, "giu"),
8
272
  rgb: new RegExp(String.raw`(^|[^\p{L}\p{N}_])(${hexColor}|${rgbColor})`, "giu")
9
273
  };
10
274
  })();
11
- const splitColors = (str, hsl = true) => {
275
+ var splitColors = (str, hsl = true) => {
12
276
  const pieces = [], re = regex[hsl ? "full" : "rgb"];
13
277
  re.lastIndex = 0;
14
278
  let mt = re.exec(str), lastIndex = 0;
@@ -26,7 +290,7 @@ const splitColors = (str, hsl = true) => {
26
290
  }
27
291
  return pieces;
28
292
  };
29
- const sanitizeInlineStyle = (style) => style.replace(/[{}]/gu, (p) => p === "{" ? "{" : "}");
293
+ var sanitizeInlineStyle = (style) => style.replace(/[{}]/gu, (p) => p === "{" ? "{" : "}");
30
294
  function getRegex(f) {
31
295
  const map = /* @__PURE__ */ new Map(), weakMap = /* @__PURE__ */ new WeakMap();
32
296
  return (s) => {
@@ -41,28 +305,82 @@ function getRegex(f) {
41
305
  return re;
42
306
  };
43
307
  }
44
- const getObjRegex = getRegex;
45
- const mt2num = (mt) => mt && Number(mt[1]);
46
- const lintJSON = (str) => {
47
- try {
48
- if (str.trim()) {
49
- JSON.parse(str);
308
+ var getObjRegex = getRegex;
309
+ var splitLines = (str) => {
310
+ const lines = [];
311
+ let start = 0;
312
+ for (const line of str.split("\n")) {
313
+ const end = start + line.length;
314
+ lines.push([line, start, end]);
315
+ start = end + 1;
316
+ }
317
+ return lines;
318
+ };
319
+ var mt2num = (mt) => mt && Number(mt[1]);
320
+ var formatJsonError = (str, errors) => {
321
+ let lines;
322
+ for (const error of errors) {
323
+ const { line, column, position } = error;
324
+ if (position === null || position === void 0) {
325
+ if (line) {
326
+ lines ??= splitLines(str);
327
+ error.column ??= 1;
328
+ error.position = lines[line - 1][1] + (error.column - 1);
329
+ } else {
330
+ error.position = 0;
331
+ error.line = 1;
332
+ error.column = 1;
333
+ }
334
+ } else if (!line || !column) {
335
+ lines ??= splitLines(str);
336
+ error.line = lines.findIndex(([, , end]) => position <= end) + 1;
337
+ error.column = position - lines[error.line - 1][1] + 1;
50
338
  }
51
- } catch (e) {
52
- if (e instanceof SyntaxError) {
339
+ }
340
+ return errors;
341
+ };
342
+ var lintJSONNative = (str, force) => {
343
+ if (force || str.trim()) {
344
+ try {
345
+ JSON.parse(str);
346
+ } catch (e) {
53
347
  const { message } = e, line = mt2num(/\bline (\d+)/u.exec(message)), column = mt2num(/\bcolumn (\d+)/u.exec(message)), position = mt2num(/\bposition (\d+)/u.exec(message));
54
- return [{ message, line, column, position }];
348
+ return formatJsonError(str, [{ message, line, column, position, severity: "error" }]);
55
349
  }
56
350
  }
57
351
  return [];
58
352
  };
353
+ var lintJSON = (str) => {
354
+ if (!str.trim()) {
355
+ return [];
356
+ }
357
+ let errors;
358
+ try {
359
+ json_parse(str);
360
+ } catch (e) {
361
+ if (!(e instanceof Error)) {
362
+ const { warnings, ...error } = e;
363
+ if (error.message) {
364
+ warnings.push(error);
365
+ }
366
+ errors = formatJsonError(str, warnings);
367
+ if (error.message) {
368
+ return errors;
369
+ }
370
+ }
371
+ }
372
+ const nativeErrors = lintJSONNative(str);
373
+ return errors ? [...nativeErrors, ...errors] : nativeErrors;
374
+ };
59
375
  export {
60
376
  getObjRegex,
61
377
  getRegex,
62
378
  lintJSON,
379
+ lintJSONNative,
63
380
  numToHex,
64
381
  rawurldecode,
65
382
  sanitizeInlineStyle,
66
383
  splitColors,
384
+ splitLines,
67
385
  wmf
68
386
  };
@@ -0,0 +1,328 @@
1
+ "use strict";
2
+ /*
3
+ json_parse.js
4
+ 2016-05-02
5
+
6
+ Public Domain.
7
+
8
+ NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
9
+
10
+ This file creates a json_parse function.
11
+
12
+ json_parse(text)
13
+ This method parses a JSON text and throws a SyntaxError exception.
14
+
15
+ This is a reference implementation. You are free to copy, modify, or redistribute.
16
+
17
+ Original source:
18
+ https://github.com/douglascrockford/JSON-js/blob/107fc93c94aa3a9c7b48548631593ecf3aac60d2/json_parse.js
19
+
20
+ Modifications:
21
+ - Only returns errors and warnings instead of the parsed value.
22
+ - Better agreement with JSON specification.
23
+ - Warnings for duplicate object keys and unsafe integers.
24
+
25
+ This code should be minified before deployment.
26
+ See http://javascript.crockford.com/jsmin.html
27
+
28
+ USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO NOT CONTROL.
29
+ */
30
+ Object.defineProperty(exports, "__esModule", { value: true });
31
+ exports.json_parse = void 0;
32
+ exports.json_parse = (() => {
33
+ // This is a function that can parse a JSON text.
34
+ // It is a simple, recursive descent parser.
35
+ // It does not use eval or regular expressions,
36
+ // so it can be used as a model for implementing a JSON parser in other languages.
37
+ // We are defining the function inside of another function to avoid creating global variables.
38
+ let at; // The index of the current character
39
+ let ch; // The current character
40
+ const escapee = {
41
+ '"': '"',
42
+ "\\": "\\",
43
+ "/": "/",
44
+ b: "\b",
45
+ f: "\f",
46
+ n: "\n",
47
+ r: "\r",
48
+ t: "\t",
49
+ };
50
+ const spaces = new Set([" ", "\t", "\n", "\r"]);
51
+ let text;
52
+ let warnings;
53
+ const stringify = (c) => {
54
+ if (c === "") {
55
+ return "end of input";
56
+ }
57
+ return c === '"' ? `'"'` : JSON.stringify(c);
58
+ };
59
+ const warn = (m) => {
60
+ // Log warning when something is wrong.
61
+ warnings.push({
62
+ message: m,
63
+ position: at - 1,
64
+ severity: "warning",
65
+ });
66
+ };
67
+ const error = (m) => {
68
+ // Call error when something is wrong.
69
+ throw {
70
+ warnings,
71
+ message: m,
72
+ position: at - 1,
73
+ severity: "error",
74
+ };
75
+ };
76
+ const next = (c) => {
77
+ // If a c parameter is provided, verify that it matches the current character.
78
+ if (c && c !== ch) {
79
+ error(`Expected ${stringify(c)} instead of ${stringify(ch)}`);
80
+ }
81
+ // Get the next character. When there are no more characters, return the empty string.
82
+ ch = text.charAt(at);
83
+ at += 1;
84
+ return ch;
85
+ };
86
+ const number = () => {
87
+ // Parse a number value.
88
+ let value;
89
+ let string = "";
90
+ if (ch === "-") {
91
+ string = "-";
92
+ next();
93
+ }
94
+ if (ch === "0") {
95
+ string += ch;
96
+ next();
97
+ if (ch >= "0" && ch <= "9") {
98
+ error("Bad number");
99
+ }
100
+ }
101
+ else if (ch >= "1" && ch <= "9") {
102
+ while (ch >= "0" && ch <= "9") {
103
+ string += ch;
104
+ next();
105
+ }
106
+ }
107
+ else {
108
+ error("No number after minus sign");
109
+ }
110
+ if (ch !== "." && ch !== "e" && ch !== "E") {
111
+ value = Number(string);
112
+ if (!Number.isSafeInteger(value)) {
113
+ warn("Not a safe integer");
114
+ }
115
+ }
116
+ if (ch === ".") {
117
+ string += ".";
118
+ next();
119
+ if (ch < "0" || ch > "9") {
120
+ error("Unterminated fractional number");
121
+ }
122
+ while (ch >= "0" && ch <= "9") {
123
+ string += ch;
124
+ next();
125
+ }
126
+ }
127
+ if (ch === "e" || ch === "E") {
128
+ string += ch;
129
+ next();
130
+ // @ts-expect-error `ch` modified
131
+ if (ch === "-" || ch === "+") {
132
+ string += ch;
133
+ next();
134
+ }
135
+ while (ch >= "0" && ch <= "9") {
136
+ string += ch;
137
+ next();
138
+ }
139
+ }
140
+ value ??= Number(string);
141
+ if (!Number.isFinite(value)) {
142
+ error("Bad number");
143
+ }
144
+ };
145
+ const string = () => {
146
+ // Parse a string value.
147
+ let hex;
148
+ let i;
149
+ let value = "";
150
+ let uffff;
151
+ // When parsing for string values, we must look for " and \ characters.
152
+ if (ch === '"') {
153
+ while (next()) {
154
+ if (ch === '"') {
155
+ next();
156
+ return value;
157
+ }
158
+ if (ch === "\\") {
159
+ next();
160
+ if (ch === "u") {
161
+ uffff = 0;
162
+ for (i = 0; i < 4; i++) {
163
+ hex = parseInt(next(), 16);
164
+ if (!isFinite(hex)) {
165
+ break;
166
+ }
167
+ uffff = uffff * 16 + hex;
168
+ }
169
+ if (i < 4) {
170
+ error("Bad unicode escape");
171
+ }
172
+ value += String.fromCharCode(uffff);
173
+ }
174
+ else if (typeof escapee[ch] === "string") {
175
+ value += escapee[ch];
176
+ }
177
+ else {
178
+ error("Bad escaped character");
179
+ }
180
+ }
181
+ else if (ch < " ") {
182
+ error("Bad control character");
183
+ }
184
+ else {
185
+ value += ch;
186
+ }
187
+ }
188
+ }
189
+ else {
190
+ error(`Expected '"' instead of ${JSON.stringify(ch)}`);
191
+ }
192
+ return error("Unterminated string");
193
+ };
194
+ const white = () => {
195
+ // Skip whitespace.
196
+ while (ch && spaces.has(ch)) {
197
+ next();
198
+ }
199
+ };
200
+ const word = () => {
201
+ // true, false, or null.
202
+ switch (ch) {
203
+ case "t":
204
+ next();
205
+ next("r");
206
+ next("u");
207
+ next("e");
208
+ return;
209
+ case "f":
210
+ next();
211
+ next("a");
212
+ next("l");
213
+ next("s");
214
+ next("e");
215
+ return;
216
+ case "n":
217
+ next();
218
+ next("u");
219
+ next("l");
220
+ next("l");
221
+ return;
222
+ default:
223
+ error(`Unexpected ${JSON.stringify(ch)}`);
224
+ }
225
+ };
226
+ const array = () => {
227
+ // Parse an array value.
228
+ next("[");
229
+ white();
230
+ if (ch === "]") {
231
+ next();
232
+ return; // empty array
233
+ }
234
+ while (ch) {
235
+ if (ch === "]") {
236
+ error("Trailing comma in array");
237
+ }
238
+ value();
239
+ white();
240
+ if (ch === "]") {
241
+ next();
242
+ return;
243
+ }
244
+ else if (ch === ",") {
245
+ next();
246
+ white();
247
+ }
248
+ else {
249
+ error(`Expected "," or "]" instead of ${stringify(ch)}`);
250
+ }
251
+ }
252
+ error("Unterminated array");
253
+ };
254
+ const object = () => {
255
+ // Parse an object value.
256
+ let key;
257
+ const keys = new Set();
258
+ next("{");
259
+ white();
260
+ if (ch === "}") {
261
+ next();
262
+ return; // empty object
263
+ }
264
+ while (ch) {
265
+ if (ch === "}") {
266
+ error("Trailing comma in object");
267
+ }
268
+ key = string();
269
+ white();
270
+ next(":");
271
+ if (keys.has(key)) {
272
+ warn(`Duplicate key ${stringify(key)}`);
273
+ }
274
+ else {
275
+ keys.add(key);
276
+ }
277
+ value();
278
+ white();
279
+ if (ch === "}") {
280
+ next();
281
+ return;
282
+ }
283
+ else if (ch === ",") {
284
+ next();
285
+ white();
286
+ }
287
+ else {
288
+ error(`Expected "," or "}" instead of ${stringify(ch)}`);
289
+ }
290
+ }
291
+ error(`Expected '"'`);
292
+ };
293
+ const value = () => {
294
+ // Parse a JSON value. It could be an object, an array, a string, a number,
295
+ // or a word.
296
+ white();
297
+ switch (ch) {
298
+ case "{":
299
+ return object();
300
+ case "[":
301
+ return array();
302
+ case '"':
303
+ return string();
304
+ case "-":
305
+ return number();
306
+ default:
307
+ return ch >= "0" && ch <= "9"
308
+ ? number()
309
+ : word();
310
+ }
311
+ };
312
+ // Return the json_parse function. It will have access to all of the above
313
+ // functions and variables.
314
+ return (source) => {
315
+ text = source;
316
+ warnings = [];
317
+ at = 0;
318
+ ch = " ";
319
+ value();
320
+ white();
321
+ if (ch) {
322
+ error("Syntax error");
323
+ }
324
+ else if (warnings.length > 0) {
325
+ throw { warnings };
326
+ }
327
+ };
328
+ })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bhsd/common",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "homepage": "https://github.com/bhsd-harry/common#readme",
5
5
  "bugs": {
6
6
  "url": "https://github.com/bhsd-harry/common/issues"
@@ -8,7 +8,9 @@
8
8
  "license": "MIT",
9
9
  "author": "Bhsd",
10
10
  "files": [
11
- "/dist/"
11
+ "/dist/*.js",
12
+ "/dist/index.mjs",
13
+ "/dist/index.d.ts"
12
14
  ],
13
15
  "exports": {
14
16
  ".": {
@@ -21,13 +23,15 @@
21
23
  "sideEffects": false,
22
24
  "scripts": {
23
25
  "prepublishOnly": "npm run build",
24
- "build": "tsc && esbuild src/index.ts --charset=utf8 --target=es2024 --format=esm --outfile=dist/index.mjs",
26
+ "build": "tsc && esbuild src/index.ts --charset=utf8 --target=es2024 --bundle --format=esm --outfile=dist/index.mjs && cp dist/index.d.ts dist/index.d.mts",
25
27
  "lint:ts": "tsc --noEmit && eslint --cache .",
26
- "lint": "npm run lint:ts"
28
+ "lint": "npm run lint:ts",
29
+ "test": "mocha"
27
30
  },
28
31
  "devDependencies": {
29
32
  "@bhsd/code-standard": "^1.3.2",
30
33
  "@stylistic/eslint-plugin": "^5.5.0",
34
+ "@types/mocha": "^10.0.10",
31
35
  "@typescript-eslint/eslint-plugin": "^8.46.4",
32
36
  "@typescript-eslint/parser": "^8.46.4",
33
37
  "esbuild": "^0.25.12",
@@ -39,6 +43,7 @@
39
43
  "eslint-plugin-promise": "^7.2.1",
40
44
  "eslint-plugin-regexp": "^2.10.0",
41
45
  "eslint-plugin-unicorn": "^62.0.0",
46
+ "mocha": "^11.7.5",
42
47
  "typescript": "^5.9.3"
43
48
  },
44
49
  "engines": {