@bhsd/common 1.1.0 → 1.3.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 +20 -3
- package/dist/index.js +85 -8
- package/dist/index.mjs +358 -15
- package/dist/json_parse.js +344 -0
- package/package.json +9 -4
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
declare interface JsonError {
|
|
2
|
+
severity: 'error' | 'warning';
|
|
2
3
|
message: string;
|
|
3
|
-
line: number
|
|
4
|
-
column: number
|
|
5
|
-
|
|
4
|
+
line: number;
|
|
5
|
+
column: number;
|
|
6
|
+
endLine?: number;
|
|
7
|
+
endColumn?: number;
|
|
8
|
+
from: number;
|
|
9
|
+
to?: number;
|
|
10
|
+
/** @deprecated */
|
|
11
|
+
position: number;
|
|
6
12
|
}
|
|
7
13
|
export type RegexGetter<T = string> = (s: T) => RegExp;
|
|
8
14
|
export declare const wmf = "wiktionary|wiki(?:pedia|books|news|quote|source|versity|voyage)";
|
|
@@ -39,6 +45,17 @@ export declare function getRegex<T extends object>(f: RegexGetter<T>): RegexGett
|
|
|
39
45
|
* @deprecated 需要改为使用`getRegex`
|
|
40
46
|
*/
|
|
41
47
|
export declare const getObjRegex: typeof getRegex;
|
|
48
|
+
/**
|
|
49
|
+
* 按行分割字符串并记录每行的起止位置
|
|
50
|
+
* @param str 字符串
|
|
51
|
+
*/
|
|
52
|
+
export declare const splitLines: (str: string) => [string, number, number][];
|
|
53
|
+
/**
|
|
54
|
+
* 使用`JSON.parse()`诊断JSON字符串中的语法错误
|
|
55
|
+
* @param str JSON字符串
|
|
56
|
+
* @param force 是否强制诊断
|
|
57
|
+
*/
|
|
58
|
+
export declare const lintJSONNative: (str: string, force?: boolean) => JsonError[];
|
|
42
59
|
/**
|
|
43
60
|
* 诊断JSON字符串中的语法错误
|
|
44
61
|
* @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,99 @@ 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
|
+
const offsetToPosition = (offset) => {
|
|
95
|
+
lines ??= (0, exports.splitLines)(str);
|
|
96
|
+
const line = lines.findIndex(([, , end]) => offset <= end) + 1;
|
|
97
|
+
return {
|
|
98
|
+
line,
|
|
99
|
+
column: offset - lines[line - 1][1] + 1,
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
for (const error of errors) {
|
|
103
|
+
const { line, column, from, to } = error;
|
|
104
|
+
if (from === null || from === undefined) {
|
|
105
|
+
if (line) {
|
|
106
|
+
lines ??= (0, exports.splitLines)(str);
|
|
107
|
+
error.column ??= 1;
|
|
108
|
+
error.from = lines[line - 1][1] + (error.column - 1);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
error.from = 0;
|
|
112
|
+
error.line = 1;
|
|
113
|
+
error.column = 1;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
else if (!line || !column) {
|
|
117
|
+
Object.assign(error, offsetToPosition(from));
|
|
118
|
+
}
|
|
119
|
+
if (to !== undefined) {
|
|
120
|
+
({ line: error.endLine, column: error.endColumn } = offsetToPosition(to));
|
|
121
|
+
}
|
|
122
|
+
error.position = error.from;
|
|
123
|
+
}
|
|
124
|
+
return errors;
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* 使用`JSON.parse()`诊断JSON字符串中的语法错误
|
|
128
|
+
* @param str JSON字符串
|
|
129
|
+
* @param force 是否强制诊断
|
|
130
|
+
*/
|
|
131
|
+
const lintJSONNative = (str, force) => {
|
|
132
|
+
if (force || str.trim()) {
|
|
133
|
+
try {
|
|
134
|
+
JSON.parse(str);
|
|
135
|
+
}
|
|
136
|
+
catch (e) {
|
|
137
|
+
const { message } = e, line = mt2num(/\bline (\d+)/u.exec(message)), column = mt2num(/\bcolumn (\d+)/u.exec(message)), from = mt2num(/\bposition (\d+)/u.exec(message));
|
|
138
|
+
return formatJsonError(str, [{ message, line, column, from, severity: 'error' }]);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return [];
|
|
142
|
+
};
|
|
143
|
+
exports.lintJSONNative = lintJSONNative;
|
|
76
144
|
/**
|
|
77
145
|
* 诊断JSON字符串中的语法错误
|
|
78
146
|
* @param str JSON字符串
|
|
79
147
|
*/
|
|
80
148
|
const lintJSON = (str) => {
|
|
149
|
+
if (!str.trim()) {
|
|
150
|
+
return [];
|
|
151
|
+
}
|
|
152
|
+
let errors;
|
|
81
153
|
try {
|
|
82
|
-
|
|
83
|
-
JSON.parse(str);
|
|
84
|
-
}
|
|
154
|
+
(0, json_parse_js_1.json_parse)(str);
|
|
85
155
|
}
|
|
86
156
|
catch (e) {
|
|
87
|
-
if (e instanceof
|
|
88
|
-
const {
|
|
89
|
-
|
|
157
|
+
if (!(e instanceof Error)) {
|
|
158
|
+
const { warnings, ...error } = e;
|
|
159
|
+
if (error.message) {
|
|
160
|
+
warnings.push(error);
|
|
161
|
+
}
|
|
162
|
+
errors = formatJsonError(str, warnings);
|
|
163
|
+
if (error.message) {
|
|
164
|
+
return errors;
|
|
165
|
+
}
|
|
90
166
|
}
|
|
91
167
|
}
|
|
92
|
-
|
|
168
|
+
const nativeErrors = (0, exports.lintJSONNative)(str);
|
|
169
|
+
return errors ? [...nativeErrors, ...errors] : nativeErrors;
|
|
93
170
|
};
|
|
94
171
|
exports.lintJSON = lintJSON;
|
package/dist/index.mjs
CHANGED
|
@@ -1,14 +1,293 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
// src/json_parse.ts
|
|
2
|
+
var escapee = {
|
|
3
|
+
'"': '"',
|
|
4
|
+
"\\": "\\",
|
|
5
|
+
"/": "/",
|
|
6
|
+
b: "\b",
|
|
7
|
+
f: "\f",
|
|
8
|
+
n: "\n",
|
|
9
|
+
r: "\r",
|
|
10
|
+
t: " "
|
|
11
|
+
};
|
|
12
|
+
var spaces = /* @__PURE__ */ new Set([" ", " ", "\n", "\r"]);
|
|
13
|
+
var stringify = (c) => {
|
|
14
|
+
if (c === "") {
|
|
15
|
+
return "end of input";
|
|
16
|
+
}
|
|
17
|
+
return c === '"' ? `'"'` : JSON.stringify(c);
|
|
18
|
+
};
|
|
19
|
+
var json_parse = /* @__PURE__ */ (() => {
|
|
20
|
+
let at;
|
|
21
|
+
let ch;
|
|
22
|
+
let text;
|
|
23
|
+
let warnings;
|
|
24
|
+
const prepareError = (e, from, to) => {
|
|
25
|
+
if (from === void 0) {
|
|
26
|
+
e.from = at - 1;
|
|
27
|
+
} else {
|
|
28
|
+
e.from = from;
|
|
29
|
+
e.to = to ?? at - 1;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
const warn = (message, from, to) => {
|
|
33
|
+
const warning = {
|
|
34
|
+
message,
|
|
35
|
+
severity: "warning"
|
|
36
|
+
};
|
|
37
|
+
prepareError(warning, from, to);
|
|
38
|
+
warnings.push(warning);
|
|
39
|
+
};
|
|
40
|
+
const error = (message, from, to) => {
|
|
41
|
+
const e = {
|
|
42
|
+
warnings,
|
|
43
|
+
message,
|
|
44
|
+
severity: "error"
|
|
45
|
+
};
|
|
46
|
+
prepareError(e, from, to);
|
|
47
|
+
throw e;
|
|
48
|
+
};
|
|
49
|
+
const next = (c) => {
|
|
50
|
+
if (c && c !== ch) {
|
|
51
|
+
error(`Expected ${stringify(c)} instead of ${stringify(ch)}`);
|
|
52
|
+
}
|
|
53
|
+
ch = text.charAt(at);
|
|
54
|
+
at++;
|
|
55
|
+
return ch;
|
|
56
|
+
};
|
|
57
|
+
const number = () => {
|
|
58
|
+
let string2 = "";
|
|
59
|
+
const from = at - 1;
|
|
60
|
+
if (ch === "-") {
|
|
61
|
+
string2 = "-";
|
|
62
|
+
next();
|
|
63
|
+
}
|
|
64
|
+
if (ch === "0") {
|
|
65
|
+
string2 += ch;
|
|
66
|
+
next();
|
|
67
|
+
if (ch >= "0" && ch <= "9") {
|
|
68
|
+
error("Bad number");
|
|
69
|
+
}
|
|
70
|
+
} else if (ch >= "1" && ch <= "9") {
|
|
71
|
+
while (ch >= "0" && ch <= "9") {
|
|
72
|
+
string2 += ch;
|
|
73
|
+
next();
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
error("No number after minus sign");
|
|
77
|
+
}
|
|
78
|
+
if (ch !== "." && ch !== "e" && ch !== "E") {
|
|
79
|
+
const value3 = Number(string2);
|
|
80
|
+
if (!Number.isSafeInteger(value3)) {
|
|
81
|
+
warn("Not a safe integer", from);
|
|
82
|
+
}
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (ch === ".") {
|
|
86
|
+
string2 += ".";
|
|
87
|
+
next();
|
|
88
|
+
if (ch < "0" || ch > "9") {
|
|
89
|
+
error("Unterminated fractional number");
|
|
90
|
+
}
|
|
91
|
+
while (ch >= "0" && ch <= "9") {
|
|
92
|
+
string2 += ch;
|
|
93
|
+
next();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (ch === "e" || ch === "E") {
|
|
97
|
+
string2 += ch;
|
|
98
|
+
next();
|
|
99
|
+
if (ch === "-" || ch === "+") {
|
|
100
|
+
string2 += ch;
|
|
101
|
+
next();
|
|
102
|
+
}
|
|
103
|
+
while (ch >= "0" && ch <= "9") {
|
|
104
|
+
string2 += ch;
|
|
105
|
+
next();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const value2 = Number(string2);
|
|
109
|
+
if (!Number.isFinite(value2)) {
|
|
110
|
+
error("Bad number");
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
const string = () => {
|
|
114
|
+
let value2 = "";
|
|
115
|
+
if (ch === '"') {
|
|
116
|
+
while (next()) {
|
|
117
|
+
if (ch === '"') {
|
|
118
|
+
next();
|
|
119
|
+
return value2;
|
|
120
|
+
}
|
|
121
|
+
const from = at - 1;
|
|
122
|
+
if (ch === "\\") {
|
|
123
|
+
next();
|
|
124
|
+
if (ch === "u") {
|
|
125
|
+
let i = 0;
|
|
126
|
+
let uffff = 0;
|
|
127
|
+
for (; i < 4; i++) {
|
|
128
|
+
const hex = parseInt(next(), 16);
|
|
129
|
+
if (!isFinite(hex)) {
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
uffff = uffff * 16 + hex;
|
|
133
|
+
}
|
|
134
|
+
if (i < 4) {
|
|
135
|
+
error("Bad unicode escape", from);
|
|
136
|
+
}
|
|
137
|
+
value2 += String.fromCharCode(uffff);
|
|
138
|
+
} else if (typeof escapee[ch] === "string") {
|
|
139
|
+
value2 += escapee[ch];
|
|
140
|
+
} else {
|
|
141
|
+
error("Bad escaped character", from, at);
|
|
142
|
+
}
|
|
143
|
+
} else if (ch < " ") {
|
|
144
|
+
error("Bad control character", from, at);
|
|
145
|
+
} else {
|
|
146
|
+
value2 += ch;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
} else {
|
|
150
|
+
error(`Expected '"' instead of ${JSON.stringify(ch)}`);
|
|
151
|
+
}
|
|
152
|
+
return error("Unterminated string");
|
|
153
|
+
};
|
|
154
|
+
const white = () => {
|
|
155
|
+
while (ch && spaces.has(ch)) {
|
|
156
|
+
next();
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
const word = () => {
|
|
160
|
+
switch (ch) {
|
|
161
|
+
case "t":
|
|
162
|
+
next();
|
|
163
|
+
next("r");
|
|
164
|
+
next("u");
|
|
165
|
+
next("e");
|
|
166
|
+
return;
|
|
167
|
+
case "f":
|
|
168
|
+
next();
|
|
169
|
+
next("a");
|
|
170
|
+
next("l");
|
|
171
|
+
next("s");
|
|
172
|
+
next("e");
|
|
173
|
+
return;
|
|
174
|
+
case "n":
|
|
175
|
+
next();
|
|
176
|
+
next("u");
|
|
177
|
+
next("l");
|
|
178
|
+
next("l");
|
|
179
|
+
return;
|
|
180
|
+
default:
|
|
181
|
+
error(`Unexpected ${JSON.stringify(ch)}`);
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
const array = () => {
|
|
185
|
+
next();
|
|
186
|
+
white();
|
|
187
|
+
if (ch === "]") {
|
|
188
|
+
next();
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
let from;
|
|
192
|
+
while (ch) {
|
|
193
|
+
if (ch === "]") {
|
|
194
|
+
error("Trailing comma in array", from, from + 1);
|
|
195
|
+
}
|
|
196
|
+
value();
|
|
197
|
+
white();
|
|
198
|
+
if (ch === "]") {
|
|
199
|
+
next();
|
|
200
|
+
return;
|
|
201
|
+
} else if (ch === ",") {
|
|
202
|
+
from = at - 1;
|
|
203
|
+
next();
|
|
204
|
+
white();
|
|
205
|
+
} else {
|
|
206
|
+
error(`Expected "," or "]" instead of ${stringify(ch)}`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
error("Unterminated array");
|
|
210
|
+
};
|
|
211
|
+
const object = () => {
|
|
212
|
+
const keys = /* @__PURE__ */ new Set();
|
|
213
|
+
next();
|
|
214
|
+
white();
|
|
215
|
+
if (ch === "}") {
|
|
216
|
+
next();
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
let from;
|
|
220
|
+
while (ch) {
|
|
221
|
+
if (ch === "}") {
|
|
222
|
+
error("Trailing comma in object", from, from + 1);
|
|
223
|
+
}
|
|
224
|
+
from = at;
|
|
225
|
+
const key = string();
|
|
226
|
+
const to = at - 2;
|
|
227
|
+
white();
|
|
228
|
+
next(":");
|
|
229
|
+
if (keys.has(key)) {
|
|
230
|
+
warn(`Duplicate key ${stringify(key)}`, from, to);
|
|
231
|
+
} else {
|
|
232
|
+
keys.add(key);
|
|
233
|
+
}
|
|
234
|
+
value();
|
|
235
|
+
white();
|
|
236
|
+
if (ch === "}") {
|
|
237
|
+
next();
|
|
238
|
+
return;
|
|
239
|
+
} else if (ch === ",") {
|
|
240
|
+
from = at - 1;
|
|
241
|
+
next();
|
|
242
|
+
white();
|
|
243
|
+
} else {
|
|
244
|
+
error(`Expected "," or "}" instead of ${stringify(ch)}`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
error(`Expected '"'`);
|
|
248
|
+
};
|
|
249
|
+
const value = () => {
|
|
250
|
+
white();
|
|
251
|
+
switch (ch) {
|
|
252
|
+
case "{":
|
|
253
|
+
return object();
|
|
254
|
+
case "[":
|
|
255
|
+
return array();
|
|
256
|
+
case '"':
|
|
257
|
+
return string();
|
|
258
|
+
case "-":
|
|
259
|
+
return number();
|
|
260
|
+
default:
|
|
261
|
+
return ch >= "0" && ch <= "9" ? number() : word();
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
return (source) => {
|
|
265
|
+
text = source;
|
|
266
|
+
warnings = [];
|
|
267
|
+
at = 0;
|
|
268
|
+
ch = " ";
|
|
269
|
+
value();
|
|
270
|
+
white();
|
|
271
|
+
if (ch) {
|
|
272
|
+
error("Syntax error");
|
|
273
|
+
} else if (warnings.length > 0) {
|
|
274
|
+
throw { warnings };
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
})();
|
|
278
|
+
|
|
279
|
+
// src/index.ts
|
|
280
|
+
var wmf = "wiktionary|wiki(?:pedia|books|news|quote|source|versity|voyage)";
|
|
281
|
+
var rawurldecode = (str) => decodeURIComponent(str.replace(/%(?![\da-f]{2})/giu, "%25"));
|
|
282
|
+
var numToHex = (d) => Math.round(d * 255).toString(16).padStart(2, "0");
|
|
283
|
+
var regex = /* @__PURE__ */ (() => {
|
|
5
284
|
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
285
|
return {
|
|
7
286
|
full: new RegExp(String.raw`(^|[^\p{L}\p{N}_])(${hexColor}|${rgbColor}|${hslColor})`, "giu"),
|
|
8
287
|
rgb: new RegExp(String.raw`(^|[^\p{L}\p{N}_])(${hexColor}|${rgbColor})`, "giu")
|
|
9
288
|
};
|
|
10
289
|
})();
|
|
11
|
-
|
|
290
|
+
var splitColors = (str, hsl = true) => {
|
|
12
291
|
const pieces = [], re = regex[hsl ? "full" : "rgb"];
|
|
13
292
|
re.lastIndex = 0;
|
|
14
293
|
let mt = re.exec(str), lastIndex = 0;
|
|
@@ -26,7 +305,7 @@ const splitColors = (str, hsl = true) => {
|
|
|
26
305
|
}
|
|
27
306
|
return pieces;
|
|
28
307
|
};
|
|
29
|
-
|
|
308
|
+
var sanitizeInlineStyle = (style) => style.replace(/[{}]/gu, (p) => p === "{" ? "{" : "}");
|
|
30
309
|
function getRegex(f) {
|
|
31
310
|
const map = /* @__PURE__ */ new Map(), weakMap = /* @__PURE__ */ new WeakMap();
|
|
32
311
|
return (s) => {
|
|
@@ -41,28 +320,92 @@ function getRegex(f) {
|
|
|
41
320
|
return re;
|
|
42
321
|
};
|
|
43
322
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
323
|
+
var getObjRegex = getRegex;
|
|
324
|
+
var splitLines = (str) => {
|
|
325
|
+
const lines = [];
|
|
326
|
+
let start = 0;
|
|
327
|
+
for (const line of str.split("\n")) {
|
|
328
|
+
const end = start + line.length;
|
|
329
|
+
lines.push([line, start, end]);
|
|
330
|
+
start = end + 1;
|
|
331
|
+
}
|
|
332
|
+
return lines;
|
|
333
|
+
};
|
|
334
|
+
var mt2num = (mt) => mt && Number(mt[1]);
|
|
335
|
+
var formatJsonError = (str, errors) => {
|
|
336
|
+
let lines;
|
|
337
|
+
const offsetToPosition = (offset) => {
|
|
338
|
+
lines ??= splitLines(str);
|
|
339
|
+
const line = lines.findIndex(([, , end]) => offset <= end) + 1;
|
|
340
|
+
return {
|
|
341
|
+
line,
|
|
342
|
+
column: offset - lines[line - 1][1] + 1
|
|
343
|
+
};
|
|
344
|
+
};
|
|
345
|
+
for (const error of errors) {
|
|
346
|
+
const { line, column, from, to } = error;
|
|
347
|
+
if (from === null || from === void 0) {
|
|
348
|
+
if (line) {
|
|
349
|
+
lines ??= splitLines(str);
|
|
350
|
+
error.column ??= 1;
|
|
351
|
+
error.from = lines[line - 1][1] + (error.column - 1);
|
|
352
|
+
} else {
|
|
353
|
+
error.from = 0;
|
|
354
|
+
error.line = 1;
|
|
355
|
+
error.column = 1;
|
|
356
|
+
}
|
|
357
|
+
} else if (!line || !column) {
|
|
358
|
+
Object.assign(error, offsetToPosition(from));
|
|
359
|
+
}
|
|
360
|
+
if (to !== void 0) {
|
|
361
|
+
({ line: error.endLine, column: error.endColumn } = offsetToPosition(to));
|
|
362
|
+
}
|
|
363
|
+
error.position = error.from;
|
|
364
|
+
}
|
|
365
|
+
return errors;
|
|
366
|
+
};
|
|
367
|
+
var lintJSONNative = (str, force) => {
|
|
368
|
+
if (force || str.trim()) {
|
|
369
|
+
try {
|
|
49
370
|
JSON.parse(str);
|
|
371
|
+
} catch (e) {
|
|
372
|
+
const { message } = e, line = mt2num(/\bline (\d+)/u.exec(message)), column = mt2num(/\bcolumn (\d+)/u.exec(message)), from = mt2num(/\bposition (\d+)/u.exec(message));
|
|
373
|
+
return formatJsonError(str, [{ message, line, column, from, severity: "error" }]);
|
|
50
374
|
}
|
|
375
|
+
}
|
|
376
|
+
return [];
|
|
377
|
+
};
|
|
378
|
+
var lintJSON = (str) => {
|
|
379
|
+
if (!str.trim()) {
|
|
380
|
+
return [];
|
|
381
|
+
}
|
|
382
|
+
let errors;
|
|
383
|
+
try {
|
|
384
|
+
json_parse(str);
|
|
51
385
|
} catch (e) {
|
|
52
|
-
if (e instanceof
|
|
53
|
-
const {
|
|
54
|
-
|
|
386
|
+
if (!(e instanceof Error)) {
|
|
387
|
+
const { warnings, ...error } = e;
|
|
388
|
+
if (error.message) {
|
|
389
|
+
warnings.push(error);
|
|
390
|
+
}
|
|
391
|
+
errors = formatJsonError(str, warnings);
|
|
392
|
+
if (error.message) {
|
|
393
|
+
return errors;
|
|
394
|
+
}
|
|
55
395
|
}
|
|
56
396
|
}
|
|
57
|
-
|
|
397
|
+
const nativeErrors = lintJSONNative(str);
|
|
398
|
+
return errors ? [...nativeErrors, ...errors] : nativeErrors;
|
|
58
399
|
};
|
|
59
400
|
export {
|
|
60
401
|
getObjRegex,
|
|
61
402
|
getRegex,
|
|
62
403
|
lintJSON,
|
|
404
|
+
lintJSONNative,
|
|
63
405
|
numToHex,
|
|
64
406
|
rawurldecode,
|
|
65
407
|
sanitizeInlineStyle,
|
|
66
408
|
splitColors,
|
|
409
|
+
splitLines,
|
|
67
410
|
wmf
|
|
68
411
|
};
|
|
@@ -0,0 +1,344 @@
|
|
|
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
|
+
const escapee = {
|
|
33
|
+
'"': '"',
|
|
34
|
+
"\\": "\\",
|
|
35
|
+
"/": "/",
|
|
36
|
+
b: "\b",
|
|
37
|
+
f: "\f",
|
|
38
|
+
n: "\n",
|
|
39
|
+
r: "\r",
|
|
40
|
+
t: "\t",
|
|
41
|
+
};
|
|
42
|
+
const spaces = new Set([" ", "\t", "\n", "\r"]);
|
|
43
|
+
const stringify = (c) => {
|
|
44
|
+
if (c === "") {
|
|
45
|
+
return "end of input";
|
|
46
|
+
}
|
|
47
|
+
return c === '"' ? `'"'` : JSON.stringify(c);
|
|
48
|
+
};
|
|
49
|
+
exports.json_parse = (() => {
|
|
50
|
+
// This is a function that can parse a JSON text.
|
|
51
|
+
// It is a simple, recursive descent parser.
|
|
52
|
+
// It does not use eval or regular expressions,
|
|
53
|
+
// so it can be used as a model for implementing a JSON parser in other languages.
|
|
54
|
+
// We are defining the function inside of another function to avoid creating global variables.
|
|
55
|
+
let at; // The index of the current character
|
|
56
|
+
let ch; // The current character
|
|
57
|
+
let text;
|
|
58
|
+
let warnings;
|
|
59
|
+
const prepareError = (e, from, to) => {
|
|
60
|
+
if (from === undefined) {
|
|
61
|
+
e.from = at - 1;
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
e.from = from;
|
|
65
|
+
e.to = to ?? at - 1;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
const warn = (message, from, to) => {
|
|
69
|
+
// Log warning when something is wrong.
|
|
70
|
+
const warning = {
|
|
71
|
+
message,
|
|
72
|
+
severity: "warning",
|
|
73
|
+
};
|
|
74
|
+
prepareError(warning, from, to);
|
|
75
|
+
warnings.push(warning);
|
|
76
|
+
};
|
|
77
|
+
const error = (message, from, to) => {
|
|
78
|
+
// Call error when something is wrong.
|
|
79
|
+
const e = {
|
|
80
|
+
warnings,
|
|
81
|
+
message,
|
|
82
|
+
severity: "error",
|
|
83
|
+
};
|
|
84
|
+
prepareError(e, from, to);
|
|
85
|
+
throw e;
|
|
86
|
+
};
|
|
87
|
+
const next = (c) => {
|
|
88
|
+
// If a c parameter is provided, verify that it matches the current character.
|
|
89
|
+
if (c && c !== ch) {
|
|
90
|
+
error(`Expected ${stringify(c)} instead of ${stringify(ch)}`);
|
|
91
|
+
}
|
|
92
|
+
// Get the next character. When there are no more characters, return the empty string.
|
|
93
|
+
ch = text.charAt(at);
|
|
94
|
+
at++;
|
|
95
|
+
return ch;
|
|
96
|
+
};
|
|
97
|
+
const number = () => {
|
|
98
|
+
// Parse a number value.
|
|
99
|
+
let string = "";
|
|
100
|
+
const from = at - 1;
|
|
101
|
+
if (ch === "-") {
|
|
102
|
+
string = "-";
|
|
103
|
+
next();
|
|
104
|
+
}
|
|
105
|
+
if (ch === "0") {
|
|
106
|
+
string += ch;
|
|
107
|
+
next();
|
|
108
|
+
if (ch >= "0" && ch <= "9") {
|
|
109
|
+
error("Bad number");
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
else if (ch >= "1" && ch <= "9") {
|
|
113
|
+
while (ch >= "0" && ch <= "9") {
|
|
114
|
+
string += ch;
|
|
115
|
+
next();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
error("No number after minus sign");
|
|
120
|
+
}
|
|
121
|
+
if (ch !== "." && ch !== "e" && ch !== "E") {
|
|
122
|
+
const value = Number(string);
|
|
123
|
+
if (!Number.isSafeInteger(value)) {
|
|
124
|
+
warn("Not a safe integer", from);
|
|
125
|
+
}
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (ch === ".") {
|
|
129
|
+
string += ".";
|
|
130
|
+
next();
|
|
131
|
+
if (ch < "0" || ch > "9") {
|
|
132
|
+
error("Unterminated fractional number");
|
|
133
|
+
}
|
|
134
|
+
while (ch >= "0" && ch <= "9") {
|
|
135
|
+
string += ch;
|
|
136
|
+
next();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (ch === "e" || ch === "E") {
|
|
140
|
+
string += ch;
|
|
141
|
+
next();
|
|
142
|
+
// @ts-expect-error `ch` modified
|
|
143
|
+
if (ch === "-" || ch === "+") {
|
|
144
|
+
string += ch;
|
|
145
|
+
next();
|
|
146
|
+
}
|
|
147
|
+
while (ch >= "0" && ch <= "9") {
|
|
148
|
+
string += ch;
|
|
149
|
+
next();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
const value = Number(string);
|
|
153
|
+
if (!Number.isFinite(value)) {
|
|
154
|
+
error("Bad number");
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
const string = () => {
|
|
158
|
+
// Parse a string value.
|
|
159
|
+
let value = "";
|
|
160
|
+
// When parsing for string values, we must look for " and \ characters.
|
|
161
|
+
if (ch === '"') {
|
|
162
|
+
while (next()) {
|
|
163
|
+
if (ch === '"') {
|
|
164
|
+
next();
|
|
165
|
+
return value;
|
|
166
|
+
}
|
|
167
|
+
const from = at - 1;
|
|
168
|
+
if (ch === "\\") {
|
|
169
|
+
next();
|
|
170
|
+
if (ch === "u") {
|
|
171
|
+
let i = 0;
|
|
172
|
+
let uffff = 0;
|
|
173
|
+
for (; i < 4; i++) {
|
|
174
|
+
const hex = parseInt(next(), 16);
|
|
175
|
+
if (!isFinite(hex)) {
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
uffff = uffff * 16 + hex;
|
|
179
|
+
}
|
|
180
|
+
if (i < 4) {
|
|
181
|
+
error("Bad unicode escape", from);
|
|
182
|
+
}
|
|
183
|
+
value += String.fromCharCode(uffff);
|
|
184
|
+
}
|
|
185
|
+
else if (typeof escapee[ch] === "string") {
|
|
186
|
+
value += escapee[ch];
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
error("Bad escaped character", from, at);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
else if (ch < " ") {
|
|
193
|
+
error("Bad control character", from, at);
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
value += ch;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
error(`Expected '"' instead of ${JSON.stringify(ch)}`);
|
|
202
|
+
}
|
|
203
|
+
return error("Unterminated string");
|
|
204
|
+
};
|
|
205
|
+
const white = () => {
|
|
206
|
+
// Skip whitespace.
|
|
207
|
+
while (ch && spaces.has(ch)) {
|
|
208
|
+
next();
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
const word = () => {
|
|
212
|
+
// true, false, or null.
|
|
213
|
+
switch (ch) {
|
|
214
|
+
case "t":
|
|
215
|
+
next();
|
|
216
|
+
next("r");
|
|
217
|
+
next("u");
|
|
218
|
+
next("e");
|
|
219
|
+
return;
|
|
220
|
+
case "f":
|
|
221
|
+
next();
|
|
222
|
+
next("a");
|
|
223
|
+
next("l");
|
|
224
|
+
next("s");
|
|
225
|
+
next("e");
|
|
226
|
+
return;
|
|
227
|
+
case "n":
|
|
228
|
+
next();
|
|
229
|
+
next("u");
|
|
230
|
+
next("l");
|
|
231
|
+
next("l");
|
|
232
|
+
return;
|
|
233
|
+
default:
|
|
234
|
+
error(`Unexpected ${JSON.stringify(ch)}`);
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
const array = () => {
|
|
238
|
+
// Parse an array value.
|
|
239
|
+
next();
|
|
240
|
+
white();
|
|
241
|
+
if (ch === "]") {
|
|
242
|
+
next();
|
|
243
|
+
return; // empty array
|
|
244
|
+
}
|
|
245
|
+
let from;
|
|
246
|
+
while (ch) {
|
|
247
|
+
if (ch === "]") {
|
|
248
|
+
error("Trailing comma in array", from, from + 1);
|
|
249
|
+
}
|
|
250
|
+
value();
|
|
251
|
+
white();
|
|
252
|
+
if (ch === "]") {
|
|
253
|
+
next();
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
else if (ch === ",") {
|
|
257
|
+
from = at - 1;
|
|
258
|
+
next();
|
|
259
|
+
white();
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
error(`Expected "," or "]" instead of ${stringify(ch)}`);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
error("Unterminated array");
|
|
266
|
+
};
|
|
267
|
+
const object = () => {
|
|
268
|
+
// Parse an object value.
|
|
269
|
+
const keys = new Set();
|
|
270
|
+
next();
|
|
271
|
+
white();
|
|
272
|
+
if (ch === "}") {
|
|
273
|
+
next();
|
|
274
|
+
return; // empty object
|
|
275
|
+
}
|
|
276
|
+
let from;
|
|
277
|
+
while (ch) {
|
|
278
|
+
if (ch === "}") {
|
|
279
|
+
error("Trailing comma in object", from, from + 1);
|
|
280
|
+
}
|
|
281
|
+
from = at;
|
|
282
|
+
const key = string();
|
|
283
|
+
const to = at - 2;
|
|
284
|
+
white();
|
|
285
|
+
next(":");
|
|
286
|
+
if (keys.has(key)) {
|
|
287
|
+
warn(`Duplicate key ${stringify(key)}`, from, to);
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
keys.add(key);
|
|
291
|
+
}
|
|
292
|
+
value();
|
|
293
|
+
white();
|
|
294
|
+
if (ch === "}") {
|
|
295
|
+
next();
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
else if (ch === ",") {
|
|
299
|
+
from = at - 1;
|
|
300
|
+
next();
|
|
301
|
+
white();
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
error(`Expected "," or "}" instead of ${stringify(ch)}`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
error(`Expected '"'`);
|
|
308
|
+
};
|
|
309
|
+
const value = () => {
|
|
310
|
+
// Parse a JSON value. It could be an object, an array, a string, a number,
|
|
311
|
+
// or a word.
|
|
312
|
+
white();
|
|
313
|
+
switch (ch) {
|
|
314
|
+
case "{":
|
|
315
|
+
return object();
|
|
316
|
+
case "[":
|
|
317
|
+
return array();
|
|
318
|
+
case '"':
|
|
319
|
+
return string();
|
|
320
|
+
case "-":
|
|
321
|
+
return number();
|
|
322
|
+
default:
|
|
323
|
+
return ch >= "0" && ch <= "9"
|
|
324
|
+
? number()
|
|
325
|
+
: word();
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
// Return the json_parse function. It will have access to all of the above
|
|
329
|
+
// functions and variables.
|
|
330
|
+
return (source) => {
|
|
331
|
+
text = source;
|
|
332
|
+
warnings = [];
|
|
333
|
+
at = 0;
|
|
334
|
+
ch = " ";
|
|
335
|
+
value();
|
|
336
|
+
white();
|
|
337
|
+
if (ch) {
|
|
338
|
+
error("Syntax error");
|
|
339
|
+
}
|
|
340
|
+
else if (warnings.length > 0) {
|
|
341
|
+
throw { warnings };
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
})();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bhsd/common",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.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": {
|