ata-validator 1.53.0 → 1.54.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/README.md +7 -0
- package/compat.js +13 -2
- package/lib/codegen-engine.js +2 -1
- package/lib/dialect.js +36 -1
- package/lib/draft7.js +17 -28
- package/lib/enrich-site.js +29 -2
- package/lib/error-runtime.js +6 -2
- package/lib/formats-source.js +43 -66
- package/lib/formats.js +37 -231
- package/lib/interpreter-compat.js +144 -0
- package/lib/interpreter-core.js +46 -142
- package/lib/js-compiler-errors.js +52 -16
- package/lib/js-compiler.js +537 -65
- package/lib/metaschemas.js +5 -3
- package/lib/plan-compiler.js +24 -7
- package/lib/regex-linear.js +81 -1
- package/lib/safe-regex.js +14 -11
- package/lib/schema-scan.js +19 -28
- package/lib/tier0.js +1 -0
- package/lib/unique-items.js +16 -5
- package/lib/uri-core.js +155 -0
- package/lib/validator-core.js +20 -11
- package/lib/version.js +1 -1
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -298,6 +298,13 @@ for.
|
|
|
298
298
|
process is not a cost anyone notices, and the runtime API is the simpler thing to reach
|
|
299
299
|
for. Speed is the same either way once warm.
|
|
300
300
|
|
|
301
|
+
**On Node 22 or later, a process that starts often can keep V8's compiled code between
|
|
302
|
+
starts.** Point `NODE_COMPILE_CACHE` at a directory that survives the restart, or call
|
|
303
|
+
`module.enableCompileCache()` in your entry. Measured on an Apple M4 Pro, Node 25.2.1:
|
|
304
|
+
loading ata goes from 4.9 to 2.7 ms, and a fresh process reaches its first validated
|
|
305
|
+
document in 5.3 ms instead of 7.8. This is Node's feature, it covers every module the
|
|
306
|
+
process loads, and ata does not turn it on for you.
|
|
307
|
+
|
|
301
308
|
**In a browser, on an edge runtime, or anywhere cold starts are charged, compile.** This
|
|
302
309
|
is where the difference is the whole story, and it is also where `new Function` is often
|
|
303
310
|
blocked outright, which the compiled module does not need. With a bundler, adding `@ata-project/unplugin`
|
package/compat.js
CHANGED
|
@@ -21,6 +21,9 @@ const { resolvePointer } = require('./lib/pointer.js');
|
|
|
21
21
|
// `strictTuples` and `strictRequired` are accepted and ignored.
|
|
22
22
|
|
|
23
23
|
const { Validator } = require('./index');
|
|
24
|
+
// The interpreted engine's coercion where each type is checked and its
|
|
25
|
+
// discriminator, which only compat asks for (see lib/interpreter-compat.js).
|
|
26
|
+
require('./lib/interpreter-core')._setCompat(require('./lib/interpreter-compat'));
|
|
24
27
|
const { METASCHEMAS } = require('./lib/metaschemas');
|
|
25
28
|
const { normalizeExclusiveBounds } = require('./lib/draft7');
|
|
26
29
|
const { createShaper, asReferenceKeyword } = require('./lib/compat-errors');
|
|
@@ -680,8 +683,16 @@ function fragmentKey(key) {
|
|
|
680
683
|
// What a schema holds anywhere below it, read from the walk normalization
|
|
681
684
|
// makes of it anyway (lib/schema-scan.js), which remembers it per object.
|
|
682
685
|
const _scan = require('./lib/schema-scan');
|
|
683
|
-
const F_BEYOND =
|
|
684
|
-
|
|
686
|
+
const F_REF = 1 << 16, F_BEYOND = 1 << 17, F_DISCR = 1 << 18, F_DEFAULT = 1 << 19, F_ALLOF = 1 << 20, F_FORMAT = 1 << 21;
|
|
687
|
+
_scan._setExtraKeys(new Map([
|
|
688
|
+
['$ref', F_REF | F_BEYOND], ['$dynamicRef', F_BEYOND], ['allOf', F_ALLOF | F_BEYOND],
|
|
689
|
+
['anyOf', F_BEYOND], ['oneOf', F_BEYOND], ['not', F_BEYOND], ['if', F_BEYOND],
|
|
690
|
+
['then', F_BEYOND], ['else', F_BEYOND], ['additionalProperties', F_BEYOND],
|
|
691
|
+
['patternProperties', F_BEYOND], ['prefixItems', F_BEYOND], ['additionalItems', F_BEYOND],
|
|
692
|
+
['contains', F_BEYOND], ['dependentSchemas', F_BEYOND], ['dependencies', F_BEYOND],
|
|
693
|
+
['propertyNames', F_BEYOND], ['unevaluatedProperties', F_BEYOND], ['unevaluatedItems', F_BEYOND],
|
|
694
|
+
['discriminator', F_DISCR], ['default', F_DEFAULT], ['format', F_FORMAT],
|
|
695
|
+
]));
|
|
685
696
|
const features = _scan.scan;
|
|
686
697
|
|
|
687
698
|
const _metaValidators = new Map();
|
package/lib/codegen-engine.js
CHANGED
|
@@ -69,7 +69,8 @@ function richBuilder(combRich, root, presentFallback, fallback, plain, cleanPlai
|
|
|
69
69
|
return (data) => {
|
|
70
70
|
const r = combRich(data);
|
|
71
71
|
if (r.valid || !r.errors || !r.errors.length) return presentFallback(fallback, data);
|
|
72
|
-
|
|
72
|
+
// `mo`: the list came out in the reader's order (see compileToJSCombined).
|
|
73
|
+
const out = r.mo === true || !_needsOrdering(r.errors) ? r.errors : _sortErrors(root, r.errors);
|
|
73
74
|
// A collapsed oneOf or anyOf comes out plain (no docUrl) and is enriched
|
|
74
75
|
// here, its branch errors with it, as presentErrors would.
|
|
75
76
|
for (let i = 0; i < out.length; i++) {
|
package/lib/dialect.js
CHANGED
|
@@ -28,4 +28,39 @@ function isV1Dialect(schema) {
|
|
|
28
28
|
return V1_DIALECTS.has(uri);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
// The earlier dialects by their meta-schema URIs. draftCode is asked of every
|
|
32
|
+
// schema, so it lives here, in a file every validator loads, and the
|
|
33
|
+
// normalizers in draft7.js load only for a schema that declares one of these.
|
|
34
|
+
// draft-06 is read through the draft-07 rules (see draft7.js).
|
|
35
|
+
const DRAFT7_SCHEMAS = new Set([
|
|
36
|
+
'http://json-schema.org/draft-07/schema#',
|
|
37
|
+
'http://json-schema.org/draft-07/schema',
|
|
38
|
+
'http://json-schema.org/draft-06/schema#',
|
|
39
|
+
'http://json-schema.org/draft-06/schema',
|
|
40
|
+
]);
|
|
41
|
+
const DRAFT6_SCHEMAS = new Set([
|
|
42
|
+
'http://json-schema.org/draft-06/schema#',
|
|
43
|
+
'http://json-schema.org/draft-06/schema',
|
|
44
|
+
]);
|
|
45
|
+
// draft-04 is read through the draft-06 rules plus its own: `id` for `$id`,
|
|
46
|
+
// and no `const`, `contains` or `propertyNames` (see draft7.js).
|
|
47
|
+
const DRAFT4_SCHEMAS = new Set([
|
|
48
|
+
'http://json-schema.org/draft-04/schema#',
|
|
49
|
+
'http://json-schema.org/draft-04/schema',
|
|
50
|
+
]);
|
|
51
|
+
const DRAFT2019_SCHEMAS = new Set([
|
|
52
|
+
'https://json-schema.org/draft/2019-09/schema',
|
|
53
|
+
'https://json-schema.org/draft/2019-09/schema#',
|
|
54
|
+
]);
|
|
55
|
+
|
|
56
|
+
// 0 for 2020-12 and later, 1 draft-07, 2 draft-06, 3 2019-09, 4 draft-04.
|
|
57
|
+
function draftCode(uri) {
|
|
58
|
+
if (typeof uri !== 'string') return 0;
|
|
59
|
+
if (DRAFT4_SCHEMAS.has(uri)) return 4;
|
|
60
|
+
if (DRAFT6_SCHEMAS.has(uri)) return 2;
|
|
61
|
+
if (DRAFT7_SCHEMAS.has(uri)) return 1;
|
|
62
|
+
if (DRAFT2019_SCHEMAS.has(uri)) return 3;
|
|
63
|
+
return 0;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = { isV1Dialect, V1_DIALECTS, DRAFT7_SCHEMAS, DRAFT6_SCHEMAS, DRAFT4_SCHEMAS, DRAFT2019_SCHEMAS, draftCode };
|
package/lib/draft7.js
CHANGED
|
@@ -6,16 +6,7 @@
|
|
|
6
6
|
// this, a draft-06 schema was read under 2020-12 and its tuple `items`,
|
|
7
7
|
// `additionalItems` and `dependencies` were not applied: 30 cases of the
|
|
8
8
|
// official draft-06 suite accepted documents they must reject.
|
|
9
|
-
const DRAFT7_SCHEMAS =
|
|
10
|
-
'http://json-schema.org/draft-07/schema#',
|
|
11
|
-
'http://json-schema.org/draft-07/schema',
|
|
12
|
-
'http://json-schema.org/draft-06/schema#',
|
|
13
|
-
'http://json-schema.org/draft-06/schema',
|
|
14
|
-
])
|
|
15
|
-
const DRAFT6_SCHEMAS = new Set([
|
|
16
|
-
'http://json-schema.org/draft-06/schema#',
|
|
17
|
-
'http://json-schema.org/draft-06/schema',
|
|
18
|
-
])
|
|
9
|
+
const { DRAFT7_SCHEMAS, DRAFT6_SCHEMAS, DRAFT4_SCHEMAS, DRAFT2019_SCHEMAS, draftCode } = require('./dialect')
|
|
19
10
|
|
|
20
11
|
function isDraft7(schema) {
|
|
21
12
|
return !!(schema && typeof schema.$schema === 'string' && DRAFT7_SCHEMAS.has(schema.$schema))
|
|
@@ -28,9 +19,11 @@ function isDraft6(schema) {
|
|
|
28
19
|
// `force` applies the draft-07 rules to a document that declares no dialect
|
|
29
20
|
// of its own, which is how a retrieved schema inherits the root's draft.
|
|
30
21
|
// `draft6` says the document, or the root it inherits from, is draft-06.
|
|
31
|
-
|
|
22
|
+
// `draft4`: draft-04, read as draft-06 plus its own differences (below).
|
|
23
|
+
function normalizeDraft7(schema, force, draft6, draft4) {
|
|
32
24
|
if (!force && !isDraft7(schema)) return schema
|
|
33
|
-
|
|
25
|
+
const d4 = draft4 === true || (typeof schema.$schema === 'string' && DRAFT4_SCHEMAS.has(schema.$schema))
|
|
26
|
+
_normalize(schema, d4 || draft6 === true || isDraft6(schema), d4)
|
|
34
27
|
return schema
|
|
35
28
|
}
|
|
36
29
|
|
|
@@ -38,13 +31,21 @@ function normalizeDraft7(schema, force, draft6) {
|
|
|
38
31
|
// definitions are addressable by pointer, annotations are inert.
|
|
39
32
|
const REF_SIBLINGS_KEPT = new Set(['$ref', '$defs', 'definitions', '$schema', '$comment', 'title', 'description', 'examples', 'default', 'readOnly', 'writeOnly'])
|
|
40
33
|
|
|
41
|
-
function _normalize(schema, d6) {
|
|
34
|
+
function _normalize(schema, d6, d4) {
|
|
42
35
|
if (typeof schema !== 'object' || schema === null) return
|
|
43
36
|
|
|
44
37
|
// draft-06 has no conditional: `if`, `then` and `else` are unknown keywords
|
|
45
38
|
// there, so they are dropped rather than applied.
|
|
46
39
|
if (d6) { delete schema.if; delete schema.then; delete schema.else }
|
|
47
40
|
|
|
41
|
+
// draft-04 names a schema's identifier `id`, and has none of draft-06's
|
|
42
|
+
// `const`, `contains` or `propertyNames`: unknown keywords there.
|
|
43
|
+
if (d4) {
|
|
44
|
+
delete schema.const; delete schema.contains; delete schema.propertyNames
|
|
45
|
+
if (typeof schema.id === 'string' && schema.$id === undefined && typeof schema.$ref !== 'string') schema.$id = schema.id
|
|
46
|
+
delete schema.id
|
|
47
|
+
}
|
|
48
|
+
|
|
48
49
|
// In draft-07 an object with `$ref` is that reference and nothing else:
|
|
49
50
|
// sibling keywords, `$id` included, are ignored. Dropping them here gives
|
|
50
51
|
// every engine the same reading without each having to know the draft.
|
|
@@ -97,7 +98,7 @@ function _normalize(schema, d6) {
|
|
|
97
98
|
for (const k in m) {
|
|
98
99
|
if (!Object.prototype.hasOwnProperty.call(m, k)) continue
|
|
99
100
|
const v = m[k]
|
|
100
|
-
if (typeof v === 'object' && v !== null) _normalize(v, d6)
|
|
101
|
+
if (typeof v === 'object' && v !== null) _normalize(v, d6, d4)
|
|
101
102
|
}
|
|
102
103
|
}
|
|
103
104
|
}
|
|
@@ -108,7 +109,7 @@ function _normalize(schema, d6) {
|
|
|
108
109
|
if (Array.isArray(a)) {
|
|
109
110
|
for (let i = 0; i < a.length; i++) {
|
|
110
111
|
const v = a[i]
|
|
111
|
-
if (typeof v === 'object' && v !== null) _normalize(v, d6)
|
|
112
|
+
if (typeof v === 'object' && v !== null) _normalize(v, d6, d4)
|
|
112
113
|
}
|
|
113
114
|
}
|
|
114
115
|
}
|
|
@@ -116,7 +117,7 @@ function _normalize(schema, d6) {
|
|
|
116
117
|
// Recurse into single sub-schemas
|
|
117
118
|
for (const key of D7_SINGLE_SUBS) {
|
|
118
119
|
const v = schema[key]
|
|
119
|
-
if (typeof v === 'object' && v !== null) _normalize(v, d6)
|
|
120
|
+
if (typeof v === 'object' && v !== null) _normalize(v, d6, d4)
|
|
120
121
|
}
|
|
121
122
|
}
|
|
122
123
|
// The schema-bearing keywords _normalize walks, built once rather than per node.
|
|
@@ -313,10 +314,6 @@ function _stripFormat(schema, seen) {
|
|
|
313
314
|
// carries the anchor, and a plain `$ref: "#"` when it does not, which is what
|
|
314
315
|
// 2019-09 says it means then. The outward search for the outermost anchored
|
|
315
316
|
// resource is the same rule in both drafts.
|
|
316
|
-
const DRAFT2019_SCHEMAS = new Set([
|
|
317
|
-
'https://json-schema.org/draft/2019-09/schema',
|
|
318
|
-
'https://json-schema.org/draft/2019-09/schema#',
|
|
319
|
-
])
|
|
320
317
|
const RECURSIVE_ANCHOR = '__ata_recursive_anchor'
|
|
321
318
|
|
|
322
319
|
function isDraft2019(schema) {
|
|
@@ -368,12 +365,4 @@ function _normalize2019(node, resourceRoot, isRoot) {
|
|
|
368
365
|
// The draft a `$schema` value names, for the normalizers: 2 draft-06,
|
|
369
366
|
// 1 draft-07, 3 2019-09, 0 anything else. Takes the string so the caller
|
|
370
367
|
// reads the property once.
|
|
371
|
-
function draftCode(uri) {
|
|
372
|
-
if (typeof uri !== 'string') return 0
|
|
373
|
-
if (DRAFT6_SCHEMAS.has(uri)) return 2
|
|
374
|
-
if (DRAFT7_SCHEMAS.has(uri)) return 1
|
|
375
|
-
if (DRAFT2019_SCHEMAS.has(uri)) return 3
|
|
376
|
-
return 0
|
|
377
|
-
}
|
|
378
|
-
|
|
379
368
|
module.exports = { draftCode, isDraft7, isDraft6, isDraft2019, normalizeDraft2019, normalizeDraft7, normalizeNullable, normalizeExclusiveBounds, stripFormatAssertions }
|
package/lib/enrich-site.js
CHANGED
|
@@ -16,9 +16,31 @@ const { reprValue, expectedFor, detailFor, rankFor, MISSING, DOC_BASE } = requir
|
|
|
16
16
|
// output. A module of its own because only the code generator reaches it, and
|
|
17
17
|
// ata-validator/lite, which enriches through `enrich`, should not carry it.
|
|
18
18
|
const NO_EXPECTED = {};
|
|
19
|
+
// `detail` (see detailFor in lib/enrich-error.js), worked out at the site as
|
|
20
|
+
// far as the site allows: the whole string when it depends only on the
|
|
21
|
+
// params, its fixed head when only the received value is left to append, and
|
|
22
|
+
// neither when it depends on the value (type) or the params are not fixed.
|
|
23
|
+
const DETAIL_AT_RUN = 0;
|
|
24
|
+
const DETAIL_FIXED = 1;
|
|
25
|
+
const DETAIL_HEAD = 2;
|
|
26
|
+
const RANGE_KEYWORDS = new Set(['minimum', 'maximum', 'exclusiveMinimum', 'exclusiveMaximum', 'minLength', 'maxLength']);
|
|
27
|
+
function siteDetail (keyword, params, expected) {
|
|
28
|
+
if (params === undefined || keyword === 'type') return [DETAIL_AT_RUN, undefined];
|
|
29
|
+
switch (keyword) {
|
|
30
|
+
case 'required': return [DETAIL_FIXED, `missing required property "${params.missingProperty}"`];
|
|
31
|
+
case 'additionalProperties': return [DETAIL_FIXED, `unknown property "${params.additionalProperty}"`];
|
|
32
|
+
case 'unevaluatedProperties': return [DETAIL_FIXED, `unevaluated property "${params.unevaluatedProperty}"`];
|
|
33
|
+
case 'format': return [DETAIL_HEAD, `not a valid ${params.format}: `];
|
|
34
|
+
}
|
|
35
|
+
if (RANGE_KEYWORDS.has(keyword) || expected) return [DETAIL_HEAD, `expected ${expected}, found `];
|
|
36
|
+
return [DETAIL_FIXED, undefined];
|
|
37
|
+
}
|
|
38
|
+
|
|
19
39
|
function errorSite (keyword, schemaPath, params, format) {
|
|
20
40
|
const dependent = keyword === 'required' && typeof schemaPath === 'string' && schemaPath.endsWith('/dependentRequired');
|
|
21
41
|
const code = (dependent && 'ATA7005') || codeFor(keyword, format) || 'ATA9001';
|
|
42
|
+
const expected = params === undefined ? NO_EXPECTED : expectedFor({ keyword, params });
|
|
43
|
+
const [detailKind, detail] = siteDetail(keyword, params, expected);
|
|
22
44
|
return {
|
|
23
45
|
code,
|
|
24
46
|
keyword,
|
|
@@ -26,7 +48,9 @@ function errorSite (keyword, schemaPath, params, format) {
|
|
|
26
48
|
docUrl: DOC_BASE + code,
|
|
27
49
|
rank: rankFor(keyword),
|
|
28
50
|
headline: (CODES[code] && CODES[code].headline) || 'validation failed',
|
|
29
|
-
expected
|
|
51
|
+
expected,
|
|
52
|
+
detailKind,
|
|
53
|
+
detail,
|
|
30
54
|
};
|
|
31
55
|
}
|
|
32
56
|
|
|
@@ -49,7 +73,10 @@ function makeRich (site, path, params, message, value, root, spPrefix) {
|
|
|
49
73
|
};
|
|
50
74
|
const sugg = suggestFor(out, root, at === MISSING ? undefined : at);
|
|
51
75
|
if (sugg) out.suggestion = sugg;
|
|
52
|
-
const
|
|
76
|
+
const kind = site.detailKind;
|
|
77
|
+
const detail = kind === DETAIL_FIXED ? site.detail
|
|
78
|
+
: kind === DETAIL_HEAD ? site.detail + out.received
|
|
79
|
+
: detailFor({ keyword: site.keyword, params }, out, at);
|
|
53
80
|
if (detail !== undefined) out.detail = detail;
|
|
54
81
|
out.rank = site.rank;
|
|
55
82
|
return out;
|
package/lib/error-runtime.js
CHANGED
|
@@ -23,6 +23,8 @@ const FILES = [
|
|
|
23
23
|
'safe-regex',
|
|
24
24
|
'unique-items',
|
|
25
25
|
'formats',
|
|
26
|
+
// The URI walk formats.js loads; left out with formats (see OPTIONAL).
|
|
27
|
+
'uri-core',
|
|
26
28
|
];
|
|
27
29
|
// The parts a build can leave out when none of its schemas needs them. The
|
|
28
30
|
// decision is made on the schemas the runtime walks (as embedded, see
|
|
@@ -91,8 +93,10 @@ function errorRuntimeSource(format, version, features, schemaMode) {
|
|
|
91
93
|
if (hit !== undefined) return hit;
|
|
92
94
|
const defs = [];
|
|
93
95
|
for (const name of schemaMode ? FILES.concat(SCHEMA_FILES) : FILES) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
+
// uri-core goes with formats: only formats.js requires it.
|
|
97
|
+
const part = name === 'uri-core' ? 'formats' : name;
|
|
98
|
+
const src = OPTIONAL[part] !== undefined && !want(part)
|
|
99
|
+
? (name === 'uri-core' ? 'module.exports = {};' : OPTIONAL[part].stub)
|
|
96
100
|
: dropCommentLines(fs.readFileSync(path.join(__dirname, name + '.js'), 'utf8'), name);
|
|
97
101
|
// The files' own require('./x') calls go to the table under another name:
|
|
98
102
|
// a tool that reads the runtime without scope (knip, a lint rule) took a
|
package/lib/formats-source.js
CHANGED
|
@@ -6,44 +6,11 @@
|
|
|
6
6
|
// checks the interpreted engine calls, so a bundle without the code generator
|
|
7
7
|
// (ata-validator/lite) does not carry them.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
// The three patterns below are read from lib/formats.js when a source that
|
|
10
|
+
// needs one is written, not when this file loads: a compile with no uri,
|
|
11
|
+
// uri-template or duration format then does not load formats.js at all.
|
|
12
|
+
const _formats = () => require('./formats');
|
|
10
13
|
|
|
11
|
-
const uriCharsSource = (v, from) =>
|
|
12
|
-
`for(let _ri=${from};_ri<${v}.length;_ri++){const _rc=${v}.charCodeAt(_ri);` +
|
|
13
|
-
'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;' +
|
|
14
|
-
`if(_rc===37){const _h1=${v}.charCodeAt(_ri+1),_h2=${v}.charCodeAt(_ri+2);` +
|
|
15
|
-
'if(!((_h1>=48&&_h1<=57)||(_h1>=97&&_h1<=102)||(_h1>=65&&_h1<=70))||' +
|
|
16
|
-
'!((_h2>=48&&_h2<=57)||(_h2>=97&&_h2<=102)||(_h2>=65&&_h2<=70)))return false;_ri+=2}}';
|
|
17
|
-
const uriAuthoritySource = (v, start) =>
|
|
18
|
-
`if(${v}.charCodeAt(${start})===47&&${v}.charCodeAt(${start}+1)===47){` +
|
|
19
|
-
`let _ae=${v}.length;` +
|
|
20
|
-
`for(let _ai=${start}+2;_ai<${v}.length;_ai++){const _ac=${v}.charCodeAt(_ai);` +
|
|
21
|
-
'if(_ac===47||_ac===63||_ac===35){_ae=_ai;break}}' +
|
|
22
|
-
`const _au=${v}.slice(${start}+2,_ae);const _aat=_au.lastIndexOf('@');` +
|
|
23
|
-
'if(_aat!==-1&&/[[\\]]/.test(_au.slice(0,_aat)))return false;' +
|
|
24
|
-
'const _hp=_aat===-1?_au:_au.slice(_aat+1);' +
|
|
25
|
-
'if(_hp.charCodeAt(0)===91){const _cl=_hp.indexOf("]");' +
|
|
26
|
-
'if(_cl===-1)return false;const _rs=_hp.slice(_cl+1);' +
|
|
27
|
-
'if(_rs!==""){if(_rs.charCodeAt(0)!==58||!/^[0-9]*$/.test(_rs.slice(1)))return false}}' +
|
|
28
|
-
'else{const _co=_hp.lastIndexOf(":");' +
|
|
29
|
-
'if(_co!==-1){if(_hp.indexOf(":")!==_co)return false;' +
|
|
30
|
-
'if(!/^[0-9]*$/.test(_hp.slice(_co+1)))return false}}}';
|
|
31
|
-
const iriCharsSource = (v, from) =>
|
|
32
|
-
`for(let _ii=${from};_ii<${v}.length;_ii++){const _ic=${v}.charCodeAt(_ii);` +
|
|
33
|
-
'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;' +
|
|
34
|
-
`if(_ic===37){const _j1=${v}.charCodeAt(_ii+1),_j2=${v}.charCodeAt(_ii+2);` +
|
|
35
|
-
'if(!((_j1>=48&&_j1<=57)||(_j1>=97&&_j1<=102)||(_j1>=65&&_j1<=70))||' +
|
|
36
|
-
'!((_j2>=48&&_j2<=57)||(_j2>=97&&_j2<=102)||(_j2>=65&&_j2<=70)))return false;_ii+=2}}';
|
|
37
|
-
function iriSource (v, isStr) {
|
|
38
|
-
return guard(v, isStr, `const _n=${v}.length;if(_n===0)return false;` +
|
|
39
|
-
`const _f=${v}.charCodeAt(0);if(!((_f>=97&&_f<=122)||(_f>=65&&_f<=90)))return false;` +
|
|
40
|
-
`let _co=-1;for(let _i=1;_i<_n;_i++){const _c=${v}.charCodeAt(_i);if(_c===58){_co=_i;break}` +
|
|
41
|
-
`if(!((_c>=48&&_c<=57)||(_c>=97&&_c<=122)||(_c>=65&&_c<=90)||_c===43||_c===45||_c===46))return false}` +
|
|
42
|
-
`if(_co===-1)return false;` +
|
|
43
|
-
iriCharsSource(v, '_co+1') +
|
|
44
|
-
uriAuthoritySource(v, '_co+1'));
|
|
45
|
-
}
|
|
46
|
-
function iriReferenceSource (v, isStr) { return guard(v, isStr, iriCharsSource(v, '0')); }
|
|
47
14
|
function idnEmailSource (v, isStr) {
|
|
48
15
|
const inner =
|
|
49
16
|
`const _at=${v}.lastIndexOf('@');` +
|
|
@@ -57,6 +24,7 @@ function idnEmailSource (v, isStr) {
|
|
|
57
24
|
`else{` +
|
|
58
25
|
`if(_dm.charCodeAt(0)===46||_dm.charCodeAt(_dm.length-1)===46)return false;` +
|
|
59
26
|
`if(_dm.indexOf('..')!==-1)return false;` +
|
|
27
|
+
`if(_dm.charCodeAt(0)===45||_dm.charCodeAt(_dm.length-1)===45||_dm.indexOf('-.')!==-1||_dm.indexOf('.-')!==-1)return false;` +
|
|
60
28
|
`for(let _di=0;_di<_dm.length;_di++){const _dc=_dm.charCodeAt(_di);` +
|
|
61
29
|
`if(_dc<=32||_dc===127||_dc===64)return false}` +
|
|
62
30
|
`}` +
|
|
@@ -180,31 +148,34 @@ function timeSource (v, isStr) {
|
|
|
180
148
|
'if(_u!==1439)return false}');
|
|
181
149
|
}
|
|
182
150
|
|
|
183
|
-
// The
|
|
184
|
-
// once per compiled function
|
|
185
|
-
//
|
|
186
|
-
// tests/test_uri_helper_parity.js holds
|
|
187
|
-
// built from the walk's own boundaries.
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
151
|
+
// The URI family: the walk in lib/uri-core.js, emitted as its factory's source
|
|
152
|
+
// and built once per compiled function (uriHelperSource, hoisted under `name`),
|
|
153
|
+
// so a standalone module carries the same walk the interpreted engine runs.
|
|
154
|
+
// tests/test_uri_helper_parity.js holds the two to one answer over a corpus
|
|
155
|
+
// built from the walk's own boundaries. `uri` keeps the regex for the common
|
|
156
|
+
// shape in front of the walk; the others go straight to it with their mode.
|
|
157
|
+
// The walk takes the IPv6 check for a bracketed host as its argument, and
|
|
158
|
+
// here that is ipv6Source as a function, so the module carries one copy.
|
|
159
|
+
// The factory's text with the spaces around punctuation taken out, about
|
|
160
|
+
// 40% shorter, since it lands in every generated function and standalone
|
|
161
|
+
// module that checks a URI. No string literal inside holds a space, and every
|
|
162
|
+
// statement ends in a semicolon or a brace, so the joins are safe;
|
|
163
|
+
// tests/test_uri_helper_parity.js runs this text against the function.
|
|
164
|
+
let _uriSrc = null
|
|
165
|
+
const uriWalkText = () => _uriSrc !== null ? _uriSrc : (_uriSrc = require('./uri-core').__ataUri.toString().replace(/\s*([=,(){};<>+\-&|?:!])\s*/g, '$1'))
|
|
166
|
+
const uriWalkSource = () => `(${uriWalkText()})(function(_s){${ipv6Source('_s', true)}return true})`
|
|
167
|
+
// `_uri(s)` is the uri check, `_urif(s, mode)` the other three (the
|
|
168
|
+
// expression in front for an iri), `_uriw(s, mode)` the walk alone.
|
|
196
169
|
const uriHelperSource = (name) =>
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
function
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
uriAuthoritySource(v, '_co+1'));
|
|
207
|
-
}
|
|
170
|
+
'const _ufa=/' + _formats().URI_FAST.source + '/;const ' + name + 'w=' + uriWalkSource() + ';' +
|
|
171
|
+
'function ' + name + '(_s){if(_ufa.test(_s))return true;return ' + name + 'w(_s,1)}' +
|
|
172
|
+
'function ' + name + 'f(_s,_m){if((_m&1)===1&&_ufa.test(_s))return true;return ' + name + 'w(_s,_m)}'
|
|
173
|
+
// The inline forms, for a caller with no function to hoist into: the walk is
|
|
174
|
+
// built in place, which costs a factory per call, so the generators hoist.
|
|
175
|
+
function uriSource (v, isStr) { return guard(v, isStr, `if(!/${_formats().URI_FAST.source}/.test(${v})&&!${uriWalkSource()}(${v},1))return false`) }
|
|
176
|
+
function uriReferenceSource (v, isStr) { return guard(v, isStr, `if(!${uriWalkSource()}(${v},0))return false`) }
|
|
177
|
+
function iriSource (v, isStr) { return guard(v, isStr, `if(!/${_formats().URI_FAST.source}/.test(${v})&&!${uriWalkSource()}(${v},3))return false`) }
|
|
178
|
+
function iriReferenceSource (v, isStr) { return guard(v, isStr, `if(!${uriWalkSource()}(${v},2))return false`) }
|
|
208
179
|
|
|
209
180
|
function ipv6Source (v, isStr) {
|
|
210
181
|
return guard(v, isStr, `const _n=${v}.length;if(_n<2||_n>45)return false;let _end=_n,_g=0;` +
|
|
@@ -245,6 +216,9 @@ function uriTemplateSource (v, isStr) {
|
|
|
245
216
|
`const _c=${v}.charCodeAt(_i);` +
|
|
246
217
|
`if(_c===125)return false;` +
|
|
247
218
|
`if(_c!==123&&(_c<=32||_c===127))return false;` +
|
|
219
|
+
`if(_c===37){const _h1=${v}.charCodeAt(_i+1),_h2=${v}.charCodeAt(_i+2);` +
|
|
220
|
+
'if(!((_h1>=48&&_h1<=57)||(_h1>=97&&_h1<=102)||(_h1>=65&&_h1<=70))||' +
|
|
221
|
+
'!((_h2>=48&&_h2<=57)||(_h2>=97&&_h2<=102)||(_h2>=65&&_h2<=70)))return false;_i+=3;continue}' +
|
|
248
222
|
`if(_c!==123){_i++;continue}` +
|
|
249
223
|
// `_ute`, not `_e`: this source is spliced into a generated function whose
|
|
250
224
|
// error accumulator is `_e`, and a `const _e` here shadowed it, so
|
|
@@ -254,7 +228,7 @@ function uriTemplateSource (v, isStr) {
|
|
|
254
228
|
`if(_ute===-1)return false;` +
|
|
255
229
|
`const _x=${v}.slice(_i+1,_ute);` +
|
|
256
230
|
`if(_x===''||_x.indexOf('{')!==-1)return false;` +
|
|
257
|
-
`if(!${URI_TEMPLATE_EXPR.toString()}.test(_x))return false;` +
|
|
231
|
+
`if(!${_formats().URI_TEMPLATE_EXPR.toString()}.test(_x))return false;` +
|
|
258
232
|
`_i=_ute+1` +
|
|
259
233
|
`}`;
|
|
260
234
|
return isStr ? `{${inner}}` : `if(typeof ${v}==='string'){${inner}}`;
|
|
@@ -296,7 +270,10 @@ function emailSource (v, isStr) {
|
|
|
296
270
|
`const _n0=${v}.length;let _at;` +
|
|
297
271
|
`if(${v}.charCodeAt(0)===34){` +
|
|
298
272
|
`_at=${v}.lastIndexOf('@');` +
|
|
299
|
-
`if(_at<2||_at===_n0-1||_at>64||${v}.charCodeAt(_at-1)!==34)return false
|
|
273
|
+
`if(_at<2||_at===_n0-1||_at>64||${v}.charCodeAt(_at-1)!==34)return false;` +
|
|
274
|
+
`for(let _qi=1;_qi<_at-1;_qi++){const _qc=${v}.charCodeAt(_qi);` +
|
|
275
|
+
`if(_qc===92){const _qp=${v}.charCodeAt(_qi+1);if(_qp<32||_qp>126||_qi+1>=_at-1)return false;_qi++;continue}` +
|
|
276
|
+
`if(_qc<32||_qc>126||_qc===34)return false}` +
|
|
300
277
|
`}else{` +
|
|
301
278
|
`if(${v}.charCodeAt(0)===46)return false;` +
|
|
302
279
|
`_at=-1;let _pd=false;` +
|
|
@@ -310,14 +287,14 @@ function emailSource (v, isStr) {
|
|
|
310
287
|
`const _dm=${v}.slice(_at+1);` +
|
|
311
288
|
`if(_dm.charCodeAt(_dm.length-1)!==93||_dm.length<=2)return false;` +
|
|
312
289
|
`const _in=_dm.slice(1,-1);` +
|
|
313
|
-
`if(_in.
|
|
290
|
+
`if((_in.charCodeAt(0)|32)===105&&(_in.charCodeAt(1)|32)===112&&(_in.charCodeAt(2)|32)===118&&_in.charCodeAt(3)===54&&_in.charCodeAt(4)===58){const _i6=_in.slice(5);${ipv6Source('_i6', true)}}` +
|
|
314
291
|
`else{${ipv4Source('_in', true)}}` +
|
|
315
292
|
`}` +
|
|
316
293
|
`else{${hostnameRangeSource(v, '_at+1', `${v}.length`)}}`;
|
|
317
294
|
return isStr ? `{${inner}}` : `if(typeof ${v}==='string'){${inner}}`;
|
|
318
295
|
}
|
|
319
296
|
function durationSource (v, isStr) {
|
|
320
|
-
const inner = `if(!${DURATION_RE.toString()}.test(${v}))return false`;
|
|
297
|
+
const inner = `if(!${_formats().DURATION_RE.toString()}.test(${v}))return false`;
|
|
321
298
|
return isStr ? inner : `if(typeof ${v}==='string'&&${inner.slice(3)}`;
|
|
322
299
|
}
|
|
323
|
-
module.exports = { uriHelperSource,
|
|
300
|
+
module.exports = { uriHelperSource, uriSource, uriReferenceSource, iriSource, iriReferenceSource, idnEmailSource, dateSource, ipv4Source, dateTimeSource, ipv6Source, hostnameSource, uriSource, uuidSource, timeSource, noReservedSource, jsonPointerSource, relativeJsonPointerSource, uriTemplateSource, emailSource, durationSource };
|