ata-validator 1.14.0 → 1.15.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/aot.d.ts +5 -0
- package/build.d.ts +10 -1
- package/lib/formats.js +404 -30
- package/lib/interpreter.js +9 -4
- package/lib/js-compiler.js +26 -13
- package/lib/version.js +1 -1
- package/package.json +8 -8
package/aot.d.ts
CHANGED
|
@@ -6,6 +6,11 @@ export interface StandaloneOptions {
|
|
|
6
6
|
format?: 'esm' | 'cjs';
|
|
7
7
|
/** Use stub error functions for the smallest output. Default: false. */
|
|
8
8
|
abortEarly?: boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Also export `parse(data)`: validate, then return a copy of the input
|
|
11
|
+
* holding only the properties the schema declares. Default: false.
|
|
12
|
+
*/
|
|
13
|
+
parse?: boolean;
|
|
9
14
|
[key: string]: unknown;
|
|
10
15
|
}
|
|
11
16
|
|
package/build.d.ts
CHANGED
|
@@ -97,6 +97,14 @@ export interface ToStandaloneModuleOptions {
|
|
|
97
97
|
formats?: Record<string, (value: string) => boolean>;
|
|
98
98
|
/** See {@link BundleStandaloneOptions.formatMode}. */
|
|
99
99
|
formatMode?: 'embed' | 'inject';
|
|
100
|
+
/**
|
|
101
|
+
* Also export `parse(data)`: validate, then return a copy of the input
|
|
102
|
+
* holding only the properties the schema declares. Off by default because
|
|
103
|
+
* it adds to the emitted module's size. Emitted only where the copy is
|
|
104
|
+
* provably exact; a schema using `$ref`, a composition, `patternProperties`,
|
|
105
|
+
* an `additionalProperties` schema or an array of objects gets no `parse`.
|
|
106
|
+
*/
|
|
107
|
+
parse?: boolean;
|
|
100
108
|
}
|
|
101
109
|
|
|
102
110
|
/** Bundle multiple schemas into one self-contained module (no ata-validator runtime). */
|
|
@@ -105,5 +113,6 @@ export function bundleStandalone(schemas: unknown[], options?: BundleStandaloneO
|
|
|
105
113
|
/** Like {@link bundleStandalone} but deduplicates shared bodies for smaller output. */
|
|
106
114
|
export function bundleCompact(schemas: unknown[], options?: BundleStandaloneOptions): string;
|
|
107
115
|
|
|
108
|
-
/** Emit a self-contained `validate`/`isValid` module string for a single
|
|
116
|
+
/** Emit a self-contained `validate`/`isValid` module string for a single
|
|
117
|
+
* schema, plus `parse` when {@link ToStandaloneModuleOptions.parse} is set. */
|
|
109
118
|
export function toStandaloneModule(schema: unknown, options?: ToStandaloneModuleOptions): string | null;
|
package/lib/formats.js
CHANGED
|
@@ -18,7 +18,10 @@ function date (s) {
|
|
|
18
18
|
}
|
|
19
19
|
const m = (s.charCodeAt(5) - 48) * 10 + (s.charCodeAt(6) - 48);
|
|
20
20
|
const d = (s.charCodeAt(8) - 48) * 10 + (s.charCodeAt(9) - 48);
|
|
21
|
-
|
|
21
|
+
if (m < 1 || m > 12 || d < 1) return false;
|
|
22
|
+
const y = (s.charCodeAt(0) - 48) * 1000 + (s.charCodeAt(1) - 48) * 100 +
|
|
23
|
+
(s.charCodeAt(2) - 48) * 10 + (s.charCodeAt(3) - 48);
|
|
24
|
+
return d <= daysInMonth(y, m);
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
// RFC 3339 date-time, read once. The form this replaces ran a regular
|
|
@@ -71,7 +74,7 @@ function dateTime (s) {
|
|
|
71
74
|
const mi0 = s.charCodeAt(14) - 48, mi1 = s.charCodeAt(15) - 48;
|
|
72
75
|
if ((mi0 >>> 0) > 5 || (mi1 >>> 0) > 9) return false;
|
|
73
76
|
const se0 = s.charCodeAt(17) - 48, se1 = s.charCodeAt(18) - 48;
|
|
74
|
-
if ((se0 >>> 0) >
|
|
77
|
+
if ((se0 >>> 0) > 6 || (se1 >>> 0) > 9 || se0 * 10 + se1 > 60) return false;
|
|
75
78
|
|
|
76
79
|
let i = 19;
|
|
77
80
|
if (s.charCodeAt(i) === 46) {
|
|
@@ -86,14 +89,27 @@ function dateTime (s) {
|
|
|
86
89
|
}
|
|
87
90
|
|
|
88
91
|
const c = s.charCodeAt(i);
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if (
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
92
|
+
const sec = se0 * 10 + se1;
|
|
93
|
+
const hh = h0 * 10 + h1, mi = mi0 * 10 + mi1;
|
|
94
|
+
let offMin = 0;
|
|
95
|
+
if (c === 90 || c === 122) {
|
|
96
|
+
if (i !== n - 1) return false;
|
|
97
|
+
} else {
|
|
98
|
+
if (c !== 43 && c !== 45) return false;
|
|
99
|
+
if (n - i !== 6) return false;
|
|
100
|
+
if (!isDigit(s.charCodeAt(i + 1)) || !isDigit(s.charCodeAt(i + 2))) return false;
|
|
101
|
+
if (s.charCodeAt(i + 3) !== 58) return false;
|
|
102
|
+
if (!isDigit(s.charCodeAt(i + 4)) || !isDigit(s.charCodeAt(i + 5))) return false;
|
|
103
|
+
const oh = twoDigits(s, i + 1), om = twoDigits(s, i + 4);
|
|
104
|
+
if (oh > 23 || om > 59) return false;
|
|
105
|
+
offMin = (c === 43 ? 1 : -1) * (oh * 60 + om);
|
|
106
|
+
}
|
|
107
|
+
// Same rule as time(): second 60 is the leap second and exists only at
|
|
108
|
+
// 23:59:60 UTC, whatever offset the string is written in.
|
|
109
|
+
if (sec === 60) {
|
|
110
|
+
const utc = ((hh * 60 + mi - offMin) % 1440 + 1440) % 1440;
|
|
111
|
+
if (utc !== 1439) return false;
|
|
112
|
+
}
|
|
97
113
|
return true;
|
|
98
114
|
}
|
|
99
115
|
|
|
@@ -225,7 +241,7 @@ function uri (s) {
|
|
|
225
241
|
c === 43 || c === 45 || c === 46)) return false;
|
|
226
242
|
}
|
|
227
243
|
if (colon === -1) return false;
|
|
228
|
-
return
|
|
244
|
+
return uriChars(s, colon + 1) && uriAuthority(s, colon + 1);
|
|
229
245
|
}
|
|
230
246
|
|
|
231
247
|
// The characters a URI cannot hold: C0 controls, space, DEL, and the rest of
|
|
@@ -238,6 +254,176 @@ function uri (s) {
|
|
|
238
254
|
// than the engine on the common case, which is why it is not the first answer.
|
|
239
255
|
const NON_PRINTABLE = /[^\u0021-\u007e]/;
|
|
240
256
|
|
|
257
|
+
|
|
258
|
+
// RFC 3986 allows these ASCII characters in a URI and nothing else: the
|
|
259
|
+
// unreserved set, the delimiters, and "%" when it introduces two hex digits.
|
|
260
|
+
// The printable characters missing from that list are the ones this rejects:
|
|
261
|
+
// " < > \ ^ ` { | }, plus everything below 0x21 and above 0x7e. The old check
|
|
262
|
+
// only looked for whitespace and non-printables, so a backslash or a lone
|
|
263
|
+
// percent sign passed as a URI.
|
|
264
|
+
function uriChar (c) {
|
|
265
|
+
if (c < 33 || c > 126) return false;
|
|
266
|
+
return !(c === 34 || c === 60 || c === 62 || c === 92 ||
|
|
267
|
+
c === 94 || c === 96 || c === 123 || c === 124 || c === 125);
|
|
268
|
+
}
|
|
269
|
+
function isHex (c) {
|
|
270
|
+
return (c >= 48 && c <= 57) || (c >= 97 && c <= 102) || (c >= 65 && c <= 70);
|
|
271
|
+
}
|
|
272
|
+
function uriChars (s, from) {
|
|
273
|
+
for (let i = from; i < s.length; i++) {
|
|
274
|
+
const c = s.charCodeAt(i);
|
|
275
|
+
if (!uriChar(c)) return false;
|
|
276
|
+
if (c === 37) { // '%'
|
|
277
|
+
if (!isHex(s.charCodeAt(i + 1)) || !isHex(s.charCodeAt(i + 2))) return false;
|
|
278
|
+
i += 2;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
const uriCharsSource = (v, from) =>
|
|
284
|
+
`for(let _ri=${from};_ri<${v}.length;_ri++){const _rc=${v}.charCodeAt(_ri);` +
|
|
285
|
+
'if(_rc<33||_rc>126||_rc===34||_rc===60||_rc===62||_rc===92||_rc===94||_rc===96||_rc===123||_rc===124||_rc===125)return false;' +
|
|
286
|
+
`if(_rc===37){const _h1=${v}.charCodeAt(_ri+1),_h2=${v}.charCodeAt(_ri+2);` +
|
|
287
|
+
'if(!((_h1>=48&&_h1<=57)||(_h1>=97&&_h1<=102)||(_h1>=65&&_h1<=70))||' +
|
|
288
|
+
'!((_h2>=48&&_h2<=57)||(_h2>=97&&_h2<=102)||(_h2>=65&&_h2<=70)))return false;_ri+=2}}';
|
|
289
|
+
|
|
290
|
+
// The authority sits between "//" and the next "/", "?" or "#". A bracketed
|
|
291
|
+
// host must close, and a port is digits.
|
|
292
|
+
function uriAuthority (s, start) {
|
|
293
|
+
if (s.charCodeAt(start) !== 47 || s.charCodeAt(start + 1) !== 47) return true;
|
|
294
|
+
let end = s.length;
|
|
295
|
+
for (let i = start + 2; i < s.length; i++) {
|
|
296
|
+
const c = s.charCodeAt(i);
|
|
297
|
+
if (c === 47 || c === 63 || c === 35) { end = i; break; }
|
|
298
|
+
}
|
|
299
|
+
const auth = s.slice(start + 2, end);
|
|
300
|
+
const at = auth.lastIndexOf('@');
|
|
301
|
+
// Brackets belong to the host, so one before the "@" is not userinfo.
|
|
302
|
+
if (at !== -1 && /[[\]]/.test(auth.slice(0, at))) return false;
|
|
303
|
+
const hostPort = at === -1 ? auth : auth.slice(at + 1);
|
|
304
|
+
if (hostPort.charCodeAt(0) === 91) { // '['
|
|
305
|
+
const close = hostPort.indexOf(']');
|
|
306
|
+
if (close === -1) return false;
|
|
307
|
+
const rest = hostPort.slice(close + 1);
|
|
308
|
+
if (rest === '') return true;
|
|
309
|
+
if (rest.charCodeAt(0) !== 58) return false;
|
|
310
|
+
return /^[0-9]*$/.test(rest.slice(1));
|
|
311
|
+
}
|
|
312
|
+
const colon = hostPort.lastIndexOf(':');
|
|
313
|
+
if (colon === -1) return true;
|
|
314
|
+
// A host holding more than one colon is an IPv6 address, and those must be
|
|
315
|
+
// bracketed. Without that rule the last group reads as a port number.
|
|
316
|
+
if (hostPort.indexOf(':') !== colon) return false;
|
|
317
|
+
return /^[0-9]*$/.test(hostPort.slice(colon + 1));
|
|
318
|
+
}
|
|
319
|
+
const uriAuthoritySource = (v, start) =>
|
|
320
|
+
`if(${v}.charCodeAt(${start})===47&&${v}.charCodeAt(${start}+1)===47){` +
|
|
321
|
+
`let _ae=${v}.length;` +
|
|
322
|
+
`for(let _ai=${start}+2;_ai<${v}.length;_ai++){const _ac=${v}.charCodeAt(_ai);` +
|
|
323
|
+
'if(_ac===47||_ac===63||_ac===35){_ae=_ai;break}}' +
|
|
324
|
+
`const _au=${v}.slice(${start}+2,_ae);const _aat=_au.lastIndexOf('@');` +
|
|
325
|
+
'if(_aat!==-1&&/[[\\]]/.test(_au.slice(0,_aat)))return false;' +
|
|
326
|
+
'const _hp=_aat===-1?_au:_au.slice(_aat+1);' +
|
|
327
|
+
'if(_hp.charCodeAt(0)===91){const _cl=_hp.indexOf("]");' +
|
|
328
|
+
'if(_cl===-1)return false;const _rs=_hp.slice(_cl+1);' +
|
|
329
|
+
'if(_rs!==""){if(_rs.charCodeAt(0)!==58||!/^[0-9]*$/.test(_rs.slice(1)))return false}}' +
|
|
330
|
+
'else{const _co=_hp.lastIndexOf(":");' +
|
|
331
|
+
'if(_co!==-1){if(_hp.indexOf(":")!==_co)return false;' +
|
|
332
|
+
'if(!/^[0-9]*$/.test(_hp.slice(_co+1)))return false}}}';
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
// An IRI is a URI that also admits non-ASCII. The ASCII rules are identical,
|
|
336
|
+
// so the same character test runs with everything above 0x7e allowed through.
|
|
337
|
+
function iriChar (c) { return c > 126 ? c !== 127 : uriChar(c); }
|
|
338
|
+
function iriChars (s, from) {
|
|
339
|
+
for (let i = from; i < s.length; i++) {
|
|
340
|
+
const c = s.charCodeAt(i);
|
|
341
|
+
if (!iriChar(c)) return false;
|
|
342
|
+
if (c === 37) {
|
|
343
|
+
if (!isHex(s.charCodeAt(i + 1)) || !isHex(s.charCodeAt(i + 2))) return false;
|
|
344
|
+
i += 2;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return true;
|
|
348
|
+
}
|
|
349
|
+
const iriCharsSource = (v, from) =>
|
|
350
|
+
`for(let _ii=${from};_ii<${v}.length;_ii++){const _ic=${v}.charCodeAt(_ii);` +
|
|
351
|
+
'if(_ic===127||(_ic<127&&(_ic<33||_ic===34||_ic===60||_ic===62||_ic===92||_ic===94||_ic===96||_ic===123||_ic===124||_ic===125)))return false;' +
|
|
352
|
+
`if(_ic===37){const _j1=${v}.charCodeAt(_ii+1),_j2=${v}.charCodeAt(_ii+2);` +
|
|
353
|
+
'if(!((_j1>=48&&_j1<=57)||(_j1>=97&&_j1<=102)||(_j1>=65&&_j1<=70))||' +
|
|
354
|
+
'!((_j2>=48&&_j2<=57)||(_j2>=97&&_j2<=102)||(_j2>=65&&_j2<=70)))return false;_ii+=2}}';
|
|
355
|
+
|
|
356
|
+
function iri (s) {
|
|
357
|
+
const n = s.length;
|
|
358
|
+
if (n === 0) return false;
|
|
359
|
+
const first = s.charCodeAt(0);
|
|
360
|
+
if (!((first >= 97 && first <= 122) || (first >= 65 && first <= 90))) return false;
|
|
361
|
+
let colon = -1;
|
|
362
|
+
for (let i = 1; i < n; i++) {
|
|
363
|
+
const c = s.charCodeAt(i);
|
|
364
|
+
if (c === 58) { colon = i; break; }
|
|
365
|
+
if (!(isDigit(c) || (c >= 97 && c <= 122) || (c >= 65 && c <= 90) ||
|
|
366
|
+
c === 43 || c === 45 || c === 46)) return false;
|
|
367
|
+
}
|
|
368
|
+
if (colon === -1) return false;
|
|
369
|
+
return iriChars(s, colon + 1) && uriAuthority(s, colon + 1);
|
|
370
|
+
}
|
|
371
|
+
function iriSource (v, isStr) {
|
|
372
|
+
return guard(v, isStr, `const _n=${v}.length;if(_n===0)return false;` +
|
|
373
|
+
`const _f=${v}.charCodeAt(0);if(!((_f>=97&&_f<=122)||(_f>=65&&_f<=90)))return false;` +
|
|
374
|
+
`let _co=-1;for(let _i=1;_i<_n;_i++){const _c=${v}.charCodeAt(_i);if(_c===58){_co=_i;break}` +
|
|
375
|
+
`if(!((_c>=48&&_c<=57)||(_c>=97&&_c<=122)||(_c>=65&&_c<=90)||_c===43||_c===45||_c===46))return false}` +
|
|
376
|
+
`if(_co===-1)return false;` +
|
|
377
|
+
iriCharsSource(v, '_co+1') +
|
|
378
|
+
uriAuthoritySource(v, '_co+1'));
|
|
379
|
+
}
|
|
380
|
+
function iriReference (s) { return iriChars(s, 0); }
|
|
381
|
+
function iriReferenceSource (v, isStr) { return guard(v, isStr, iriCharsSource(v, '0')); }
|
|
382
|
+
|
|
383
|
+
// An internationalised mailbox differs from a mailbox only in admitting
|
|
384
|
+
// non-ASCII on both sides. The separator stays the ASCII "@": a fullwidth one
|
|
385
|
+
// is a character in the local part, not a separator.
|
|
386
|
+
function idnEmail (s) {
|
|
387
|
+
const at = s.lastIndexOf('@');
|
|
388
|
+
if (at <= 0 || at === s.length - 1) return false;
|
|
389
|
+
const local = s.slice(0, at);
|
|
390
|
+
const domain = s.slice(at + 1);
|
|
391
|
+
if (local.charCodeAt(0) === 34) {
|
|
392
|
+
return local.length >= 2 && local.charCodeAt(local.length - 1) === 34 && domain.length > 0;
|
|
393
|
+
}
|
|
394
|
+
if (local.charCodeAt(0) === 46 || local.charCodeAt(local.length - 1) === 46) return false;
|
|
395
|
+
if (local.indexOf('..') !== -1) return false;
|
|
396
|
+
if (domain.charCodeAt(0) === 91) {
|
|
397
|
+
return domain.charCodeAt(domain.length - 1) === 93 && domain.length > 2;
|
|
398
|
+
}
|
|
399
|
+
if (domain.charCodeAt(0) === 46 || domain.charCodeAt(domain.length - 1) === 46) return false;
|
|
400
|
+
if (domain.indexOf('..') !== -1) return false;
|
|
401
|
+
for (let i = 0; i < domain.length; i++) {
|
|
402
|
+
const c = domain.charCodeAt(i);
|
|
403
|
+
if (c <= 32 || c === 127 || c === 64) return false;
|
|
404
|
+
}
|
|
405
|
+
return true;
|
|
406
|
+
}
|
|
407
|
+
function idnEmailSource (v, isStr) {
|
|
408
|
+
const inner =
|
|
409
|
+
`const _at=${v}.lastIndexOf('@');` +
|
|
410
|
+
`if(_at<=0||_at===${v}.length-1)return false;` +
|
|
411
|
+
`const _lp=${v}.slice(0,_at),_dm=${v}.slice(_at+1);` +
|
|
412
|
+
`if(_lp.charCodeAt(0)===34){if(_lp.length<2||_lp.charCodeAt(_lp.length-1)!==34||_dm.length===0)return false}` +
|
|
413
|
+
`else{` +
|
|
414
|
+
`if(_lp.charCodeAt(0)===46||_lp.charCodeAt(_lp.length-1)===46)return false;` +
|
|
415
|
+
`if(_lp.indexOf('..')!==-1)return false;` +
|
|
416
|
+
`if(_dm.charCodeAt(0)===91){if(_dm.charCodeAt(_dm.length-1)!==93||_dm.length<=2)return false}` +
|
|
417
|
+
`else{` +
|
|
418
|
+
`if(_dm.charCodeAt(0)===46||_dm.charCodeAt(_dm.length-1)===46)return false;` +
|
|
419
|
+
`if(_dm.indexOf('..')!==-1)return false;` +
|
|
420
|
+
`for(let _di=0;_di<_dm.length;_di++){const _dc=_dm.charCodeAt(_di);` +
|
|
421
|
+
`if(_dc<=32||_dc===127||_dc===64)return false}` +
|
|
422
|
+
`}` +
|
|
423
|
+
`}`;
|
|
424
|
+
return isStr ? `{${inner}}` : `if(typeof ${v}==='string'){${inner}}`;
|
|
425
|
+
}
|
|
426
|
+
|
|
241
427
|
function noReserved (s, from) {
|
|
242
428
|
if (!NON_PRINTABLE.test(s)) return true;
|
|
243
429
|
const n = s.length;
|
|
@@ -297,8 +483,10 @@ function time (s) {
|
|
|
297
483
|
if (s.charCodeAt(2) !== 58 || s.charCodeAt(5) !== 58) return false;
|
|
298
484
|
const m1 = s.charCodeAt(3) - 48, m2 = s.charCodeAt(4) - 48;
|
|
299
485
|
if ((m1 >>> 0) > 5 || (m2 >>> 0) > 9) return false;
|
|
486
|
+
// Seconds reach 60 for the leap second, which the offset check below pins
|
|
487
|
+
// to 23:59:60 UTC; anything past that is not a clock reading.
|
|
300
488
|
const c1 = s.charCodeAt(6) - 48, c2 = s.charCodeAt(7) - 48;
|
|
301
|
-
if ((c1 >>> 0) >
|
|
489
|
+
if ((c1 >>> 0) > 6 || (c2 >>> 0) > 9 || c1 * 10 + c2 > 60) return false;
|
|
302
490
|
let i = 8;
|
|
303
491
|
if (i < n && s.charCodeAt(i) === 46) {
|
|
304
492
|
i++;
|
|
@@ -310,21 +498,37 @@ function time (s) {
|
|
|
310
498
|
}
|
|
311
499
|
if (i === start) return false;
|
|
312
500
|
}
|
|
313
|
-
|
|
501
|
+
// RFC 3339 full-time requires an offset; a bare wall clock is not a time.
|
|
502
|
+
if (i === n) return false;
|
|
503
|
+
const hh = h1 * 10 + h2, mm = m1 * 10 + m2, ss = c1 * 10 + c2;
|
|
314
504
|
const z = s.charCodeAt(i);
|
|
315
|
-
|
|
316
|
-
if (z
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
if (
|
|
320
|
-
if (
|
|
505
|
+
let offMin = 0;
|
|
506
|
+
if (z === 90 || z === 122) {
|
|
507
|
+
if (i !== n - 1) return false;
|
|
508
|
+
} else {
|
|
509
|
+
if (z !== 43 && z !== 45) return false;
|
|
510
|
+
if (n - i !== 6 || s.charCodeAt(i + 3) !== 58) return false;
|
|
511
|
+
for (let k = 1; k <= 5; k++) {
|
|
512
|
+
if (k === 3) continue;
|
|
513
|
+
if ((s.charCodeAt(i + k) - 48 >>> 0) > 9) return false;
|
|
514
|
+
}
|
|
515
|
+
const oh = (s.charCodeAt(i + 1) - 48) * 10 + (s.charCodeAt(i + 2) - 48);
|
|
516
|
+
const om = (s.charCodeAt(i + 4) - 48) * 10 + (s.charCodeAt(i + 5) - 48);
|
|
517
|
+
if (oh > 23 || om > 59) return false;
|
|
518
|
+
offMin = (z === 43 ? 1 : -1) * (oh * 60 + om);
|
|
519
|
+
}
|
|
520
|
+
// Second 60 exists only as the leap second, which is 23:59:60 in UTC. Any
|
|
521
|
+
// other clock reading with :60 is not a time.
|
|
522
|
+
if (ss === 60) {
|
|
523
|
+
const utc = ((hh * 60 + mm - offMin) % 1440 + 1440) % 1440;
|
|
524
|
+
if (utc !== 23 * 60 + 59) return false;
|
|
321
525
|
}
|
|
322
526
|
return true;
|
|
323
527
|
}
|
|
324
528
|
|
|
325
529
|
// Anything without a control character or whitespace.
|
|
326
530
|
function uriReference (s) {
|
|
327
|
-
return
|
|
531
|
+
return uriChars(s, 0);
|
|
328
532
|
}
|
|
329
533
|
|
|
330
534
|
// Source twins. `v` is the expression holding the string; when `isStr` is
|
|
@@ -333,7 +537,7 @@ function uriReference (s) {
|
|
|
333
537
|
function guard (v, isStr, body) { return isStr ? `{${body}}` : `if(typeof ${v}==='string'){${body}}`; }
|
|
334
538
|
|
|
335
539
|
function dateSource (v, isStr) {
|
|
336
|
-
return guard(v, isStr, `if(${v}.length!==10)return false;for(let _i=0;_i<10;_i++){const _c=${v}.charCodeAt(_i);if(_i===4||_i===7){if(_c!==45)return false}else if(_c<48||_c>57)return false}const _m=(${v}.charCodeAt(5)-48)*10+(${v}.charCodeAt(6)-48),_d=(${v}.charCodeAt(8)-48)*10+(${v}.charCodeAt(9)-48);if(_m<1||_m>12||_d<1||_d>
|
|
540
|
+
return guard(v, isStr, `if(${v}.length!==10)return false;for(let _i=0;_i<10;_i++){const _c=${v}.charCodeAt(_i);if(_i===4||_i===7){if(_c!==45)return false}else if(_c<48||_c>57)return false}const _m=(${v}.charCodeAt(5)-48)*10+(${v}.charCodeAt(6)-48),_d=(${v}.charCodeAt(8)-48)*10+(${v}.charCodeAt(9)-48);if(_m<1||_m>12||_d<1)return false;const _y=(${v}.charCodeAt(0)-48)*1000+(${v}.charCodeAt(1)-48)*100+(${v}.charCodeAt(2)-48)*10+(${v}.charCodeAt(3)-48);const _dim=_m===2?(((_y%4===0&&_y%100!==0)||_y%400===0)?29:28):((_m===4||_m===6||_m===9||_m===11)?30:31);if(_d>_dim)return false`);
|
|
337
541
|
}
|
|
338
542
|
|
|
339
543
|
function dateTimeSource (v, isStr) {
|
|
@@ -354,15 +558,20 @@ function dateTimeSource (v, isStr) {
|
|
|
354
558
|
`const _mi0=${v}.charCodeAt(14)-48,_mi1=${v}.charCodeAt(15)-48;` +
|
|
355
559
|
'if((_mi0>>>0)>5||(_mi1>>>0)>9)return false;' +
|
|
356
560
|
`const _se0=${v}.charCodeAt(17)-48,_se1=${v}.charCodeAt(18)-48;` +
|
|
357
|
-
'if((_se0>>>0)>
|
|
561
|
+
'if((_se0>>>0)>6||(_se1>>>0)>9||_se0*10+_se1>60)return false;' +
|
|
358
562
|
`let _i2=19;if(${v}.charCodeAt(19)===46){_i2=20;const _st=_i2;while(_i2<_n){const _c2=${v}.charCodeAt(_i2);if(_c2<48||_c2>57)break;_i2++}if(_i2===_st)return false}` +
|
|
359
|
-
`const _tz=${v}.charCodeAt(_i2);` +
|
|
563
|
+
`const _tz=${v}.charCodeAt(_i2);let _dtoff=0;` +
|
|
360
564
|
`if(_tz===90||_tz===122){if(_i2!==_n-1)return false}` +
|
|
361
565
|
`else{if(_tz!==43&&_tz!==45)return false;if(_n-_i2!==6)return false;` +
|
|
362
566
|
`const _oh=${v}.charCodeAt(_i2+1),_oh2=${v}.charCodeAt(_i2+2),_om=${v}.charCodeAt(_i2+4),_om2=${v}.charCodeAt(_i2+5);` +
|
|
363
567
|
`if(_oh<48||_oh>57||_oh2<48||_oh2>57||_om<48||_om>57||_om2<48||_om2>57)return false;` +
|
|
364
568
|
`if(${v}.charCodeAt(_i2+3)!==58)return false;` +
|
|
365
|
-
`
|
|
569
|
+
`const _ohv=(_oh-48)*10+(_oh2-48),_omv=(_om-48)*10+(_om2-48);` +
|
|
570
|
+
`if(_ohv>23||_omv>59)return false;` +
|
|
571
|
+
`_dtoff=(_tz===43?1:-1)*(_ohv*60+_omv)}` +
|
|
572
|
+
`if(_se0*10+_se1===60){` +
|
|
573
|
+
`const _dtu=(((_h0*10+_h1)*60+(_mi0*10+_mi1)-_dtoff)%1440+1440)%1440;` +
|
|
574
|
+
`if(_dtu!==1439)return false}`);
|
|
366
575
|
}
|
|
367
576
|
|
|
368
577
|
function hostnameSource (v, isStr) {
|
|
@@ -393,17 +602,24 @@ function timeSource (v, isStr) {
|
|
|
393
602
|
`const _m1=${v}.charCodeAt(3)-48,_m2=${v}.charCodeAt(4)-48;` +
|
|
394
603
|
'if((_m1>>>0)>5||(_m2>>>0)>9)return false;' +
|
|
395
604
|
`const _s1=${v}.charCodeAt(6)-48,_s2=${v}.charCodeAt(7)-48;` +
|
|
396
|
-
'if((_s1>>>0)>
|
|
605
|
+
'if((_s1>>>0)>6||(_s2>>>0)>9||_s1*10+_s2>60)return false;' +
|
|
397
606
|
`let _ti=8;if(_ti<_n&&${v}.charCodeAt(_ti)===46){_ti++;const _tf=_ti;` +
|
|
398
607
|
`while(_ti<_n){const _tc=${v}.charCodeAt(_ti)-48;if((_tc>>>0)>9)break;_ti++}` +
|
|
399
608
|
'if(_ti===_tf)return false}' +
|
|
400
|
-
'if(_ti
|
|
401
|
-
`const _tz=${v}.charCodeAt(_ti);` +
|
|
609
|
+
'if(_ti===_n)return false;' +
|
|
610
|
+
`const _tz=${v}.charCodeAt(_ti);let _off=0;` +
|
|
402
611
|
'if(_tz===90||_tz===122){if(_ti!==_n-1)return false}' +
|
|
403
612
|
'else{if(_tz!==43&&_tz!==45)return false;' +
|
|
404
613
|
`if(_n-_ti!==6||${v}.charCodeAt(_ti+3)!==58)return false;` +
|
|
405
614
|
`if((${v}.charCodeAt(_ti+1)-48>>>0)>9||(${v}.charCodeAt(_ti+2)-48>>>0)>9||` +
|
|
406
|
-
`(${v}.charCodeAt(_ti+4)-48>>>0)>9||(${v}.charCodeAt(_ti+5)-48>>>0)>9)return false
|
|
615
|
+
`(${v}.charCodeAt(_ti+4)-48>>>0)>9||(${v}.charCodeAt(_ti+5)-48>>>0)>9)return false;` +
|
|
616
|
+
`const _oh=(${v}.charCodeAt(_ti+1)-48)*10+(${v}.charCodeAt(_ti+2)-48),` +
|
|
617
|
+
`_om=(${v}.charCodeAt(_ti+4)-48)*10+(${v}.charCodeAt(_ti+5)-48);` +
|
|
618
|
+
'if(_oh>23||_om>59)return false;' +
|
|
619
|
+
'_off=(_tz===43?1:-1)*(_oh*60+_om)}' +
|
|
620
|
+
'if(_s1*10+_s2===60){' +
|
|
621
|
+
'const _u=(((_h1*10+_h2)*60+(_m1*10+_m2)-_off)%1440+1440)%1440;' +
|
|
622
|
+
'if(_u!==1439)return false}');
|
|
407
623
|
}
|
|
408
624
|
|
|
409
625
|
function uriSource (v, isStr) {
|
|
@@ -412,7 +628,8 @@ function uriSource (v, isStr) {
|
|
|
412
628
|
`let _co=-1;for(let _i=1;_i<_n;_i++){const _c=${v}.charCodeAt(_i);if(_c===58){_co=_i;break}` +
|
|
413
629
|
`if(!((_c>=48&&_c<=57)||(_c>=97&&_c<=122)||(_c>=65&&_c<=90)||_c===43||_c===45||_c===46))return false}` +
|
|
414
630
|
`if(_co===-1)return false;` +
|
|
415
|
-
|
|
631
|
+
uriCharsSource(v, '_co+1') +
|
|
632
|
+
uriAuthoritySource(v, '_co+1'));
|
|
416
633
|
}
|
|
417
634
|
|
|
418
635
|
function ipv6Source (v, isStr) {
|
|
@@ -440,4 +657,161 @@ function ipv4Source (v, isStr) {
|
|
|
440
657
|
return guard(v, isStr, `const _n=${v}.length;if(_n<7||_n>15)return false;let _o=0,_val=0,_dg=0;for(let _i=0;_i<=_n;_i++){const _c=_i<_n?${v}.charCodeAt(_i):46;if(_c===46){if(_dg===0||_val>255)return false;_o++;_val=0;_dg=0;if(_o>4)return false}else if(_c>=48&&_c<=57){if(_dg===1&&_val===0)return false;_val=_val*10+(_c-48);_dg++;if(_dg>3)return false}else return false}if(_o!==4)return false`);
|
|
441
658
|
}
|
|
442
659
|
|
|
443
|
-
|
|
660
|
+
|
|
661
|
+
// --- RFC 6901 JSON pointers -------------------------------------------------
|
|
662
|
+
// A pointer is empty or a run of "/"-prefixed tokens, and the only escape is
|
|
663
|
+
// "~" followed by 0 or 1. Everything else, including "#", is not a pointer:
|
|
664
|
+
// the fragment form belongs to a URI, not to this format.
|
|
665
|
+
function jsonPointer(s) {
|
|
666
|
+
if (s === '') return true;
|
|
667
|
+
if (s.charCodeAt(0) !== 47) return false; // '/'
|
|
668
|
+
for (let i = 0; i < s.length; i++) {
|
|
669
|
+
if (s.charCodeAt(i) !== 126) continue; // '~'
|
|
670
|
+
const n = s.charCodeAt(i + 1);
|
|
671
|
+
if (n !== 48 && n !== 49) return false; // '0' | '1'
|
|
672
|
+
}
|
|
673
|
+
return true;
|
|
674
|
+
}
|
|
675
|
+
function jsonPointerSource(v, isStr) {
|
|
676
|
+
const body = `{let _ok=${v}==='';if(!_ok&&${v}.charCodeAt(0)===47){_ok=true;for(let _i=0;_i<${v}.length;_i++){if(${v}.charCodeAt(_i)!==126)continue;const _n=${v}.charCodeAt(_i+1);if(_n!==48&&_n!==49){_ok=false;break}}}if(!_ok)return false}`;
|
|
677
|
+
return isStr ? body : `if(typeof ${v}==='string')${body}`;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// A relative pointer is a non-negative integer without leading zeros, then
|
|
681
|
+
// either "#" or a JSON pointer.
|
|
682
|
+
function relativeJsonPointer(s) {
|
|
683
|
+
let i = 0;
|
|
684
|
+
while (i < s.length) {
|
|
685
|
+
const c = s.charCodeAt(i);
|
|
686
|
+
if (c < 48 || c > 57) break;
|
|
687
|
+
i++;
|
|
688
|
+
}
|
|
689
|
+
if (i === 0) return false;
|
|
690
|
+
if (i > 1 && s.charCodeAt(0) === 48) return false; // leading zero
|
|
691
|
+
const rest = s.slice(i);
|
|
692
|
+
if (rest === '#') return true;
|
|
693
|
+
return jsonPointer(rest);
|
|
694
|
+
}
|
|
695
|
+
function relativeJsonPointerSource(v, isStr) {
|
|
696
|
+
const body = `{let _i=0;while(_i<${v}.length){const _c=${v}.charCodeAt(_i);if(_c<48||_c>57)break;_i++}let _ok=_i>0&&!(_i>1&&${v}.charCodeAt(0)===48);if(_ok){const _r=${v}.slice(_i);if(_r!=='#'){_ok=_r==='';if(!_ok&&_r.charCodeAt(0)===47){_ok=true;for(let _j=0;_j<_r.length;_j++){if(_r.charCodeAt(_j)!==126)continue;const _n=_r.charCodeAt(_j+1);if(_n!==48&&_n!==49){_ok=false;break}}}}}if(!_ok)return false}`;
|
|
697
|
+
return isStr ? body : `if(typeof ${v}==='string')${body}`;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// --- RFC 6570 URI templates -------------------------------------------------
|
|
701
|
+
// Everything outside braces must be a literal, and each "{...}" must hold one
|
|
702
|
+
// expression: an optional operator, then a comma-separated list of varspecs,
|
|
703
|
+
// each a possibly dotted name of varchars with an optional ":n" prefix length
|
|
704
|
+
// or "*" explode.
|
|
705
|
+
const URI_TEMPLATE_EXPR = /^(?:[+#./;?&=,!@|]?)(?:[A-Za-z0-9_%]|%[0-9A-Fa-f]{2})+(?:\.(?:[A-Za-z0-9_%]|%[0-9A-Fa-f]{2})+)*(?::[1-9][0-9]{0,3}|\*)?(?:,(?:[A-Za-z0-9_%]|%[0-9A-Fa-f]{2})+(?:\.(?:[A-Za-z0-9_%]|%[0-9A-Fa-f]{2})+)*(?::[1-9][0-9]{0,3}|\*)?)*$/;
|
|
706
|
+
function uriTemplate(s) {
|
|
707
|
+
let i = 0;
|
|
708
|
+
while (i < s.length) {
|
|
709
|
+
const c = s.charCodeAt(i);
|
|
710
|
+
if (c === 125) return false; // '}' with no opening brace
|
|
711
|
+
// A literal excludes the controls, the space and DEL.
|
|
712
|
+
if (c !== 123 && (c <= 32 || c === 127)) return false;
|
|
713
|
+
if (c !== 123) { i++; continue; } // not '{'
|
|
714
|
+
const end = s.indexOf('}', i + 1);
|
|
715
|
+
if (end === -1) return false;
|
|
716
|
+
const expr = s.slice(i + 1, end);
|
|
717
|
+
if (expr === '' || expr.indexOf('{') !== -1) return false;
|
|
718
|
+
if (!URI_TEMPLATE_EXPR.test(expr)) return false;
|
|
719
|
+
i = end + 1;
|
|
720
|
+
}
|
|
721
|
+
return true;
|
|
722
|
+
}
|
|
723
|
+
function uriTemplateSource (v, isStr) {
|
|
724
|
+
const inner =
|
|
725
|
+
`let _i=0;` +
|
|
726
|
+
`while(_i<${v}.length){` +
|
|
727
|
+
`const _c=${v}.charCodeAt(_i);` +
|
|
728
|
+
`if(_c===125)return false;` +
|
|
729
|
+
`if(_c!==123&&(_c<=32||_c===127))return false;` +
|
|
730
|
+
`if(_c!==123){_i++;continue}` +
|
|
731
|
+
`const _e=${v}.indexOf('}',_i+1);` +
|
|
732
|
+
`if(_e===-1)return false;` +
|
|
733
|
+
`const _x=${v}.slice(_i+1,_e);` +
|
|
734
|
+
`if(_x===''||_x.indexOf('{')!==-1)return false;` +
|
|
735
|
+
`if(!${URI_TEMPLATE_EXPR.toString()}.test(_x))return false;` +
|
|
736
|
+
`_i=_e+1` +
|
|
737
|
+
`}`;
|
|
738
|
+
return isStr ? `{${inner}}` : `if(typeof ${v}==='string'){${inner}}`;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// --- RFC 5321 mailbox -------------------------------------------------------
|
|
742
|
+
// Local part is either a quoted string or dot-separated atoms, so a leading,
|
|
743
|
+
// trailing or doubled dot is not a mailbox. Domain is a hostname or a bracketed
|
|
744
|
+
// address literal.
|
|
745
|
+
// Local-part characters: alphanumerics, the RFC 5322 atext punctuation, and
|
|
746
|
+
// the dot that separates atoms. Written as code ranges rather than a regular
|
|
747
|
+
// expression because this lands inside every emitted module that validates an
|
|
748
|
+
// email, and the expression cost more bytes there than the whole check.
|
|
749
|
+
function isEmailLocalChar (c) {
|
|
750
|
+
return (c >= 48 && c <= 57) || (c >= 97 && c <= 122) || (c >= 65 && c <= 90) ||
|
|
751
|
+
c === 46 || c === 33 || (c >= 35 && c <= 39) || c === 42 || c === 43 ||
|
|
752
|
+
c === 45 || c === 47 || c === 61 || c === 63 || (c >= 94 && c <= 96) ||
|
|
753
|
+
(c >= 123 && c <= 126);
|
|
754
|
+
}
|
|
755
|
+
const EMAIL_LOCAL_SRC = (v) => `((${v}>=48&&${v}<=57)||(${v}>=97&&${v}<=122)||(${v}>=65&&${v}<=90)||${v}===46||${v}===33||(${v}>=35&&${v}<=39)||${v}===42||${v}===43||${v}===45||${v}===47||${v}===61||${v}===63||(${v}>=94&&${v}<=96)||(${v}>=123&&${v}<=126))`;
|
|
756
|
+
function email(s) {
|
|
757
|
+
const at = s.lastIndexOf('@');
|
|
758
|
+
if (at <= 0 || at === s.length - 1) return false;
|
|
759
|
+
const local = s.slice(0, at);
|
|
760
|
+
const domain = s.slice(at + 1);
|
|
761
|
+
if (local.length > 64) return false;
|
|
762
|
+
if (local.charCodeAt(0) === 34) {
|
|
763
|
+
// quoted string: must close, and the quote may only end the local part
|
|
764
|
+
if (local.charCodeAt(local.length - 1) !== 34 || local.length < 2) return false;
|
|
765
|
+
} else {
|
|
766
|
+
if (local.charCodeAt(0) === 46 || local.charCodeAt(local.length - 1) === 46) return false;
|
|
767
|
+
if (local.indexOf('..') !== -1) return false;
|
|
768
|
+
for (let i = 0; i < local.length; i++) if (!isEmailLocalChar(local.charCodeAt(i))) return false;
|
|
769
|
+
}
|
|
770
|
+
if (domain.charCodeAt(0) === 91) { // '['
|
|
771
|
+
if (domain.charCodeAt(domain.length - 1) !== 93 || domain.length <= 2) return false;
|
|
772
|
+
const inner = domain.slice(1, -1);
|
|
773
|
+
// IPv6 literals are tagged; anything else in brackets is an IPv4 address.
|
|
774
|
+
if (inner.startsWith('IPv6:')) return ipv6(inner.slice(5));
|
|
775
|
+
return ipv4(inner);
|
|
776
|
+
}
|
|
777
|
+
return hostname(domain);
|
|
778
|
+
}
|
|
779
|
+
// Emitted rather than bound as a closure: a standalone module embeds format
|
|
780
|
+
// functions by their source text, so anything reaching a free variable would
|
|
781
|
+
// not survive the trip. The domain half reuses hostnameSource, which keeps
|
|
782
|
+
// one hostname implementation for both halves of this file.
|
|
783
|
+
function emailSource (v, isStr) {
|
|
784
|
+
const inner =
|
|
785
|
+
`const _at=${v}.lastIndexOf('@');` +
|
|
786
|
+
`if(_at<=0||_at===${v}.length-1)return false;` +
|
|
787
|
+
`const _lp=${v}.slice(0,_at);const _dm=${v}.slice(_at+1);` +
|
|
788
|
+
`if(_lp.length>64)return false;` +
|
|
789
|
+
`if(_lp.charCodeAt(0)===34){if(_lp.length<2||_lp.charCodeAt(_lp.length-1)!==34)return false}` +
|
|
790
|
+
`else{` +
|
|
791
|
+
`if(_lp.charCodeAt(0)===46||_lp.charCodeAt(_lp.length-1)===46)return false;` +
|
|
792
|
+
`if(_lp.indexOf('..')!==-1)return false;` +
|
|
793
|
+
`for(let _i=0;_i<_lp.length;_i++){const _lc=_lp.charCodeAt(_i);if(!${EMAIL_LOCAL_SRC('_lc')})return false}` +
|
|
794
|
+
`}` +
|
|
795
|
+
`if(_dm.charCodeAt(0)===91){` +
|
|
796
|
+
`if(_dm.charCodeAt(_dm.length-1)!==93||_dm.length<=2)return false;` +
|
|
797
|
+
`const _in=_dm.slice(1,-1);` +
|
|
798
|
+
`if(_in.startsWith('IPv6:')){const _i6=_in.slice(5);${ipv6Source('_i6', true)}}` +
|
|
799
|
+
`else{${ipv4Source('_in', true)}}` +
|
|
800
|
+
`}` +
|
|
801
|
+
`else{${hostnameSource('_dm', true)}}`;
|
|
802
|
+
return isStr ? `{${inner}}` : `if(typeof ${v}==='string'){${inner}}`;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
|
|
806
|
+
// RFC 3339 appendix A duration. The units form a chain rather than a set: a
|
|
807
|
+
// year may be followed by a month and a month by a day, and the same on the
|
|
808
|
+
// time side, so "P1Y2D" and "PT1H2S" are not durations. Weeks stand alone and
|
|
809
|
+
// no unit takes a fraction.
|
|
810
|
+
const DURATION_RE = /^P(?:\d+W|(?:\d+Y(?:\d+M(?:\d+D)?)?|\d+M(?:\d+D)?|\d+D)(?:T(?:\d+H(?:\d+M(?:\d+S)?)?|\d+M(?:\d+S)?|\d+S))?|T(?:\d+H(?:\d+M(?:\d+S)?)?|\d+M(?:\d+S)?|\d+S))$/;
|
|
811
|
+
function duration (s) { return DURATION_RE.test(s); }
|
|
812
|
+
function durationSource (v, isStr) {
|
|
813
|
+
const inner = `if(!${DURATION_RE.toString()}.test(${v}))return false`;
|
|
814
|
+
return isStr ? inner : `if(typeof ${v}==='string'&&${inner.slice(3)}`;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
module.exports = { date, ipv4, dateTime, ipv6, hostname, uri, uriReference, uuid, time, noReserved, jsonPointer, relativeJsonPointer, uriTemplate, email, duration, uriChars, uriAuthority, uriCharsSource, uriAuthoritySource, iri, iriReference, idnEmail, iriSource, iriReferenceSource, idnEmailSource, dateSource, ipv4Source, dateTimeSource, ipv6Source, hostnameSource, uriSource, uuidSource, timeSource, noReservedSource, jsonPointerSource, relativeJsonPointerSource, uriTemplateSource, emailSource, durationSource };
|
package/lib/interpreter.js
CHANGED
|
@@ -249,19 +249,24 @@ function multipleOfOk(d, m) {
|
|
|
249
249
|
|
|
250
250
|
const _formats = require('./formats');
|
|
251
251
|
const FORMAT_CHECKS = {
|
|
252
|
-
email:
|
|
252
|
+
email: _formats.email,
|
|
253
253
|
date: _formats.date,
|
|
254
254
|
'date-time': _formats.dateTime,
|
|
255
255
|
time: _formats.time,
|
|
256
|
-
duration:
|
|
256
|
+
duration: _formats.duration,
|
|
257
257
|
uuid: _formats.uuid,
|
|
258
258
|
uri: _formats.uri,
|
|
259
259
|
'uri-reference': _formats.uriReference,
|
|
260
260
|
ipv4: _formats.ipv4,
|
|
261
261
|
ipv6: _formats.ipv6,
|
|
262
262
|
hostname: _formats.hostname,
|
|
263
|
-
regex: (s) => { try { new RegExp(s, 'u'); return true; } catch {
|
|
264
|
-
'json-pointer':
|
|
263
|
+
regex: (s) => { try { new RegExp(s, 'u'); return true; } catch { return false; } },
|
|
264
|
+
'json-pointer': _formats.jsonPointer,
|
|
265
|
+
'relative-json-pointer': _formats.relativeJsonPointer,
|
|
266
|
+
'uri-template': _formats.uriTemplate,
|
|
267
|
+
iri: _formats.iri,
|
|
268
|
+
'iri-reference': _formats.iriReference,
|
|
269
|
+
'idn-email': _formats.idnEmail,
|
|
265
270
|
};
|
|
266
271
|
|
|
267
272
|
// ---------- evaluation ----------
|
package/lib/js-compiler.js
CHANGED
|
@@ -2024,7 +2024,7 @@ function genCode(schema, v, lines, ctx, knownType) {
|
|
|
2024
2024
|
if (schema.format) {
|
|
2025
2025
|
const fc = FORMAT_CODEGEN[schema.format]
|
|
2026
2026
|
if (fc) {
|
|
2027
|
-
lines.push(fc(v, isStr))
|
|
2027
|
+
lines.push(fc(v, isStr, ctx))
|
|
2028
2028
|
} else if (ctx.userFormats && typeof ctx.userFormats[schema.format] === 'function') {
|
|
2029
2029
|
// User-supplied format checker: thread the function via closure and call at runtime.
|
|
2030
2030
|
const safeName = schema.format.replace(/[^a-zA-Z0-9_]/g, '_')
|
|
@@ -2959,24 +2959,37 @@ function genCode(schema, v, lines, ctx, knownType) {
|
|
|
2959
2959
|
}
|
|
2960
2960
|
}
|
|
2961
2961
|
|
|
2962
|
+
// Formats whose check is large enough that repeating it at every call site
|
|
2963
|
+
// costs more than the call. They are hoisted once per compiled function and
|
|
2964
|
+
// invoked by name: one real schema with four email fields grew by 20 KB when
|
|
2965
|
+
// the check was inlined at each of them.
|
|
2966
|
+
const EMAIL_HELPER = `function _em(_s){${_formats.emailSource('_s', true)}return true}`
|
|
2967
|
+
|
|
2962
2968
|
const FORMAT_CODEGEN = {
|
|
2963
|
-
email: (v, isStr) => {
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2969
|
+
email: (v, isStr, ctx) => {
|
|
2970
|
+
if (!ctx) return _formats.emailSource(v, isStr)
|
|
2971
|
+
hoistOnce(ctx, '_emHoisted', EMAIL_HELPER)
|
|
2972
|
+
return isStr ? `if(!_em(${v}))return false` : `if(typeof ${v}==='string'&&!_em(${v}))return false`
|
|
2973
|
+
},
|
|
2974
|
+
'json-pointer': _formats.jsonPointerSource,
|
|
2975
|
+
'relative-json-pointer': _formats.relativeJsonPointerSource,
|
|
2976
|
+
'uri-template': _formats.uriTemplateSource,
|
|
2977
|
+
iri: _formats.iriSource,
|
|
2978
|
+
'iri-reference': _formats.iriReferenceSource,
|
|
2979
|
+
'idn-email': _formats.idnEmailSource,
|
|
2980
|
+
regex: (v, isStr) => {
|
|
2981
|
+
const inner = `try{new RegExp(${v},'u')}catch(_er){return false}`
|
|
2982
|
+
return isStr ? `{${inner}}` : `if(typeof ${v}==='string'){${inner}}`
|
|
2968
2983
|
},
|
|
2969
2984
|
date: _formats.dateSource,
|
|
2970
2985
|
uuid: _formats.uuidSource,
|
|
2971
2986
|
'date-time': _formats.dateTimeSource,
|
|
2972
2987
|
time: _formats.timeSource,
|
|
2973
|
-
duration:
|
|
2974
|
-
? `if(!/^P(?:\\d+Y)?(?:\\d+M)?(?:\\d+W)?(?:\\d+D)?(?:T(?:\\d+H)?(?:\\d+M)?(?:\\d+(?:\\.\\d+)?S)?)?$/.test(${v})||${v}==='P'||${v}.endsWith('T'))return false`
|
|
2975
|
-
: `if(typeof ${v}==='string'&&(!/^P(?:\\d+Y)?(?:\\d+M)?(?:\\d+W)?(?:\\d+D)?(?:T(?:\\d+H)?(?:\\d+M)?(?:\\d+(?:\\.\\d+)?S)?)?$/.test(${v})||${v}==='P'||${v}.endsWith('T')))return false`,
|
|
2988
|
+
duration: _formats.durationSource,
|
|
2976
2989
|
uri: _formats.uriSource,
|
|
2977
2990
|
'uri-reference': (v, isStr) => isStr
|
|
2978
|
-
? `{${_formats.
|
|
2979
|
-
: `if(typeof ${v}==='string'){${_formats.
|
|
2991
|
+
? `{${_formats.uriCharsSource(v, '0')}}`
|
|
2992
|
+
: `if(typeof ${v}==='string'){${_formats.uriCharsSource(v, '0')}}`,
|
|
2980
2993
|
ipv4: _formats.ipv4Source,
|
|
2981
2994
|
ipv6: _formats.ipv6Source,
|
|
2982
2995
|
hostname: _formats.hostnameSource,
|
|
@@ -3522,7 +3535,7 @@ function genCodeE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
3522
3535
|
// let every later statement of the same check push the same error again.
|
|
3523
3536
|
if (fc) {
|
|
3524
3537
|
const ri = ctx.varCounter++
|
|
3525
|
-
const fmtCode = fc(v, isStr).replace(/return false/g, `{${failPush};break _fmt${ri}}`)
|
|
3538
|
+
const fmtCode = fc(v, isStr, ctx).replace(/return false/g, `{${failPush};break _fmt${ri}}`)
|
|
3526
3539
|
lines.push(`_fmt${ri}:{${fmtCode}}`)
|
|
3527
3540
|
} else if (ctx.userFormats && typeof ctx.userFormats[schema.format] === 'function') {
|
|
3528
3541
|
// User-supplied format checker on the error path. Same closure plumbing
|
|
@@ -4158,7 +4171,7 @@ function genCodeC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4158
4171
|
// Same rule as the error codegen: one violation, one error. The first
|
|
4159
4172
|
// failing statement pushes and leaves the labeled block.
|
|
4160
4173
|
const ri = ctx.varCounter++
|
|
4161
|
-
const code = fc(v, isStr).replace(/return false/g, `{${fail('format', 'format', `{format:'${esc(schema.format)}'}`, `'must match format "${esc(schema.format)}"'`)};break _fmt${ri}}`)
|
|
4174
|
+
const code = fc(v, isStr, ctx).replace(/return false/g, `{${fail('format', 'format', `{format:'${esc(schema.format)}'}`, `'must match format "${esc(schema.format)}"'`)};break _fmt${ri}}`)
|
|
4162
4175
|
lines.push(`_fmt${ri}:{${code}}`)
|
|
4163
4176
|
} else if (ctx.userFormats && typeof ctx.userFormats[schema.format] === 'function') {
|
|
4164
4177
|
const safeName = schema.format.replace(/[^a-zA-Z0-9_]/g, '_')
|
package/lib/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ata-validator",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.15.0",
|
|
4
4
|
"description": "JSON Schema validation with first-class TypeScript and zero runtime cost. AOT compile to per-schema ESM modules with zero validator dependency. Generic Validator<T> for TypeBox/Zod/Valibot composition. Optional runtime API. Standard Schema V1 compatible.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"module": "index.mjs",
|
|
@@ -122,13 +122,13 @@
|
|
|
122
122
|
"LICENSE"
|
|
123
123
|
],
|
|
124
124
|
"optionalDependencies": {
|
|
125
|
-
"@ata-validator/native-darwin-arm64": "1.
|
|
126
|
-
"@ata-validator/native-darwin-x64": "1.
|
|
127
|
-
"@ata-validator/native-linux-x64-gnu": "1.
|
|
128
|
-
"@ata-validator/native-linux-arm64-gnu": "1.
|
|
129
|
-
"@ata-validator/native-linux-x64-musl": "1.
|
|
130
|
-
"@ata-validator/native-linux-arm64-musl": "1.
|
|
131
|
-
"@ata-validator/native-win32-x64": "1.
|
|
125
|
+
"@ata-validator/native-darwin-arm64": "1.15.0",
|
|
126
|
+
"@ata-validator/native-darwin-x64": "1.15.0",
|
|
127
|
+
"@ata-validator/native-linux-x64-gnu": "1.15.0",
|
|
128
|
+
"@ata-validator/native-linux-arm64-gnu": "1.15.0",
|
|
129
|
+
"@ata-validator/native-linux-x64-musl": "1.15.0",
|
|
130
|
+
"@ata-validator/native-linux-arm64-musl": "1.15.0",
|
|
131
|
+
"@ata-validator/native-win32-x64": "1.15.0"
|
|
132
132
|
},
|
|
133
133
|
"peerDependencies": {
|
|
134
134
|
"yaml": "^2.0.0"
|