ata-validator 1.48.0 → 1.50.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 +45 -7
- package/aot.d.ts +40 -0
- package/aot.mjs +1 -1
- package/bin/ata.js +14 -1
- package/build.d.ts +1 -1
- package/error-runtime.d.ts +24 -0
- package/error-runtime.js +40 -0
- package/error-runtime.mjs +3 -0
- package/index.browser.mjs +14 -5
- package/index.edge.js +14 -0
- package/index.edge.mjs +13 -0
- package/index.js +8 -996
- package/lib/aot-build.js +211 -68
- package/lib/aot-impl.js +216 -8
- package/lib/codegen-engine.js +1024 -0
- package/lib/draft7.js +106 -8
- package/lib/enrich-error.js +42 -11
- package/lib/enrich-site.js +1 -1
- package/lib/error-code-of.js +22 -0
- package/lib/error-runtime-entry.js +85 -0
- package/lib/error-runtime.js +152 -0
- package/lib/error-sort-rank.js +61 -0
- package/lib/interpreter-core.js +1356 -0
- package/lib/interpreter.js +8 -1260
- package/lib/js-compiler-errors.js +6 -4
- package/lib/js-compiler.js +50 -2
- package/lib/metaschemas.js +21 -2
- package/lib/public-api.js +80 -0
- package/lib/regex-linear.js +194 -37
- package/lib/safe-regex.js +23 -9
- package/lib/schema-runtime-entry.js +21 -0
- package/lib/schema-strip.js +115 -0
- package/lib/tools.mjs +21 -0
- package/lib/validator-core.js +35 -20
- package/lib/version.js +1 -1
- package/lib/vocabularies.js +12 -0
- package/package.json +29 -9
|
@@ -9,7 +9,7 @@ const { compileFunction } = require('./compile-fn')
|
|
|
9
9
|
// the first call that compiles errors. Everything here was written as part
|
|
10
10
|
// of js-compiler.js and reads its helpers from that module's _internal.
|
|
11
11
|
const core = require('./js-compiler')
|
|
12
|
-
const { hasConstraints, assertEmitted, prepareForCodegen, sharedCodegenGate, codegenSafe, isSimplePN, hasUnresolvableRef, needsBaseTracking, _cpLen, sharedRt, DECLINE, bindRt, compileSafe, emitGuardState, withPlain, REF_NEUTRAL_SIBLINGS, resolveCrossSchemaRef, enumCondition, emitDeq, emitConstant, ownGuard, ownKeyExpr, multipleOfBad, compilePatternInline, useSafeEngine, reFlags, FORMAT_CODEGEN, PROTO_NAMES, setOwn, nestedGenCode, compileToJSCodegen, SKIP_VALUE_KEYS, skipString, CYCLE_DEPTH, cyclicDefNames, sharedDefNames, reFlagArg, hoistOnce, PTR_ESC_HELPER, unevalContributions, emitUq, uqSources, safeReClosure, fastPrefixCheck, replaceTopLevel, refTargetFor, genCode, addLocalAnnotations, addTopApplicatorAnnotations, protoIs, ANNOT_APPLICATORS, SIZE_SKIPS, emitRootGuard, emitGuardedRun } = core._internal
|
|
12
|
+
const { hasConstraints, assertEmitted, prepareForCodegen, sharedCodegenGate, codegenSafe, isSimplePN, hasUnresolvableRef, needsBaseTracking, _cpLen, sharedRt, DECLINE, bindRt, compileSafe, emitGuardState, withPlain, REF_NEUTRAL_SIBLINGS, resolveCrossSchemaRef, enumCondition, enumOperand, emitDeq, emitConstant, ownGuard, ownKeyExpr, multipleOfBad, compilePatternInline, useSafeEngine, reFlags, FORMAT_CODEGEN, PROTO_NAMES, setOwn, nestedGenCode, compileToJSCodegen, SKIP_VALUE_KEYS, skipString, CYCLE_DEPTH, cyclicDefNames, sharedDefNames, reFlagArg, hoistOnce, PTR_ESC_HELPER, unevalContributions, emitUq, uqSources, safeReClosure, fastPrefixCheck, replaceTopLevel, refTargetFor, genCode, addLocalAnnotations, addTopApplicatorAnnotations, protoIs, ANNOT_APPLICATORS, SIZE_SKIPS, emitRootGuard, emitGuardedRun } = core._internal
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
function emitPtrEsc(ctx) {
|
|
@@ -289,7 +289,7 @@ function compileToJSCodegenWithErrors(schema, schemaMap, userFormats, sourceOpts
|
|
|
289
289
|
if (hasUnresolvableRef(schema, eRootDefs, eAnchors, schemaMap, new Set())) return null
|
|
290
290
|
if (needsBaseTracking(schema, schemaMap, new Set())) return null
|
|
291
291
|
|
|
292
|
-
const ctx = { varCounter: 0, helperCode: [], rootDefs: eRootDefs, shared: [], refStack: new Set(), schemaMap: schemaMap || null, anchors: eAnchors, rootE: schema, rootSchema: inputSchema, userFormats: userFormats || null,
|
|
292
|
+
const ctx = { noOrdinal: !!(sourceOpts && sourceOpts.noOrdinal), varCounter: 0, helperCode: [], rootDefs: eRootDefs, shared: [], refStack: new Set(), schemaMap: schemaMap || null, anchors: eAnchors, rootE: schema, rootSchema: inputSchema, userFormats: userFormats || null,
|
|
293
293
|
// Custom format checkers referenced by the body, bound as
|
|
294
294
|
// closure parameters below the way the other entry points do.
|
|
295
295
|
closureVars: [], closureVals: [],
|
|
@@ -1143,7 +1143,8 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
1143
1143
|
|
|
1144
1144
|
// enum
|
|
1145
1145
|
if (schema.enum) {
|
|
1146
|
-
|
|
1146
|
+
const ev = enumOperand(ctx, schema.enum, v, lines)
|
|
1147
|
+
lines.push(`if(!(${enumCondition(ctx, schema.enum, ev)})){${fail('enum', 'enum', `{allowedValues:${JSON.stringify(schema.enum)}}`, "'must be equal to one of the allowed values'")}}`)
|
|
1147
1148
|
}
|
|
1148
1149
|
|
|
1149
1150
|
// const — use canonical (sorted-key) comparison for objects
|
|
@@ -2330,7 +2331,8 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
2330
2331
|
const emitEnumConst = () => {
|
|
2331
2332
|
// enum
|
|
2332
2333
|
if (schema.enum) {
|
|
2333
|
-
|
|
2334
|
+
const ev = enumOperand(ctx, schema.enum, v, lines)
|
|
2335
|
+
lines.push(`if(!(${enumCondition(ctx, schema.enum, ev)})){${fail('enum', 'enum', `{allowedValues:${JSON.stringify(schema.enum)}}`, "'must be equal to one of the allowed values'")}}`)
|
|
2334
2336
|
}
|
|
2335
2337
|
|
|
2336
2338
|
// const
|
package/lib/js-compiler.js
CHANGED
|
@@ -135,6 +135,21 @@ function emitConstant(ctx, value) {
|
|
|
135
135
|
return name
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
+
// The operand an `enum` test compares: `v` itself when it is a plain
|
|
139
|
+
// identifier, otherwise a local bound once. The test is a chain of `===`,
|
|
140
|
+
// one per member, and each used to repeat the access expression, so a
|
|
141
|
+
// 32-member enum on a required property read `_e3["tool"]` 32 times; on an
|
|
142
|
+
// array of items in several shapes those reads are polymorphic and cost
|
|
143
|
+
// 59 ns against 20 with the value in a local. The binding goes to `lines`
|
|
144
|
+
// ahead of the test.
|
|
145
|
+
const IDENT_RE = /^[A-Za-z_$][\w$]*$/
|
|
146
|
+
function enumOperand(ctx, vals, v, lines) {
|
|
147
|
+
if (IDENT_RE.test(v) || vals.length < 2) return v
|
|
148
|
+
const name = `_ev${ctx.varCounter++}`
|
|
149
|
+
lines.push(`const ${name}=${v}`)
|
|
150
|
+
return name
|
|
151
|
+
}
|
|
152
|
+
|
|
138
153
|
// One `enum` test: primitives compare with `===`, which needs no call, and
|
|
139
154
|
// object members go through structural equality.
|
|
140
155
|
function enumCondition(ctx, vals, v) {
|
|
@@ -1316,6 +1331,14 @@ function sharedDefNames (root, defs) {
|
|
|
1316
1331
|
}
|
|
1317
1332
|
return out
|
|
1318
1333
|
}
|
|
1334
|
+
// Raising this to inline small definitions at every site was measured and
|
|
1335
|
+
// rejected: SchemaStore's detekt applies one 200-character rule definition
|
|
1336
|
+
// at 293 sites, and inlining it took the verdict from 5287 ns to 452 but the
|
|
1337
|
+
// standalone module from 168 KB to 1692 KB and its compile to 33 ms. The
|
|
1338
|
+
// slowness of the shared function is megamorphic property reads (474 objects
|
|
1339
|
+
// of 72 shapes through one function); a for-in body for the shared function
|
|
1340
|
+
// was tried too and gave 1.5x, not enough to carry its complexity. Left as a
|
|
1341
|
+
// known trade: on that shape the shared function costs 27 ns a call.
|
|
1319
1342
|
const SHARED_DEF_MIN_CHARS = 120
|
|
1320
1343
|
|
|
1321
1344
|
function cyclicDefNames (defs) {
|
|
@@ -1986,8 +2009,32 @@ function sharedCodegenGate(schema, schemaMap) {
|
|
|
1986
2009
|
_gateSchema = schema; _gateMap = schemaMap; _gateOk = ok
|
|
1987
2010
|
return ok
|
|
1988
2011
|
}
|
|
2012
|
+
// `unevaluatedProperties` or `unevaluatedItems` beside a `$ref` back to the
|
|
2013
|
+
// document root: the generated evaluated-set tracking does not credit what
|
|
2014
|
+
// the root evaluates through that reference, so a tree whose branches say
|
|
2015
|
+
// `{ unevaluatedProperties: false, $ref: '#' }` rejected valid branches (a
|
|
2016
|
+
// wrong reject, found through the 2019-09 `$recursiveRef` tests, and the same
|
|
2017
|
+
// in 2020-12). The interpreted engine answers it. A text check first: almost
|
|
2018
|
+
// no schema has both.
|
|
2019
|
+
function unevaluatedBesideRootRef(schema) {
|
|
2020
|
+
const text = JSON.stringify(schema)
|
|
2021
|
+
if (!text.includes('"$ref":"#"') || !/"unevaluated(?:Properties|Items)"/.test(text)) return false
|
|
2022
|
+
let found = false
|
|
2023
|
+
const seen = new Set()
|
|
2024
|
+
const walk = (n) => {
|
|
2025
|
+
if (found || n === null || typeof n !== 'object' || seen.has(n)) return
|
|
2026
|
+
seen.add(n)
|
|
2027
|
+
if (Array.isArray(n)) { for (const x of n) walk(x); return }
|
|
2028
|
+
if (n.$ref === '#' && (n.unevaluatedProperties !== undefined || n.unevaluatedItems !== undefined)) { found = true; return }
|
|
2029
|
+
for (const [k, v] of Object.entries(n)) if (k !== 'enum' && k !== 'const' && k !== 'default' && k !== 'examples') walk(v)
|
|
2030
|
+
}
|
|
2031
|
+
walk(schema)
|
|
2032
|
+
return found
|
|
2033
|
+
}
|
|
2034
|
+
|
|
1989
2035
|
function sharedCodegenGateUncached(schema, schemaMap) {
|
|
1990
2036
|
if (bothDefsContainers(schema)) return false
|
|
2037
|
+
if (unevaluatedBesideRootRef(schema)) return false
|
|
1991
2038
|
if (hasSameInstanceRefCycle(schema)) return false
|
|
1992
2039
|
if (!codegenSafe(schema, schemaMap)) return false
|
|
1993
2040
|
if (needsBaseTracking(schema, schemaMap, new Set())) return false
|
|
@@ -3297,7 +3344,8 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
|
|
|
3297
3344
|
|
|
3298
3345
|
// enum
|
|
3299
3346
|
if (schema.enum) {
|
|
3300
|
-
|
|
3347
|
+
const ev = enumOperand(ctx, schema.enum, v, lines)
|
|
3348
|
+
lines.push(`if(!(${enumCondition(ctx, schema.enum, ev)}))return false`)
|
|
3301
3349
|
}
|
|
3302
3350
|
|
|
3303
3351
|
// const
|
|
@@ -5371,4 +5419,4 @@ Object.defineProperty(module.exports, '_expandDynamicScopes', { value: expandDyn
|
|
|
5371
5419
|
// For tests/test_custom_keyword_codegen.js, which holds these copies to the interpreter's.
|
|
5372
5420
|
Object.defineProperty(module.exports, '_kwTypeBits', { value: { kwTypeBit, kwDataBits, KW_T_ANY }, enumerable: false })
|
|
5373
5421
|
|
|
5374
|
-
module.exports._internal = { hasConstraints, assertEmitted, prepareForCodegen, sharedCodegenGate, codegenSafe, isSimplePN, hasUnresolvableRef, needsBaseTracking, _cpLen, sharedRt, DECLINE, bindRt, compileSafe, emitGuardState, withPlain, REF_NEUTRAL_SIBLINGS, resolveCrossSchemaRef, enumCondition, emitDeq, emitConstant, ownGuard, ownKeyExpr, multipleOfBad, compilePatternInline, useSafeEngine, reFlags, FORMAT_CODEGEN, PROTO_NAMES, setOwn, nestedGenCode, compileToJSCodegen, SKIP_VALUE_KEYS, skipString, CYCLE_DEPTH, cyclicDefNames, sharedDefNames, reFlagArg, hoistOnce, PTR_ESC_HELPER, unevalContributions, emitUq, uqSources, safeReClosure, fastPrefixCheck, replaceTopLevel, refTargetFor, genCode, addLocalAnnotations, addTopApplicatorAnnotations, protoIs, ANNOT_APPLICATORS, SIZE_SKIPS, emitRootGuard, emitGuardedRun }
|
|
5422
|
+
module.exports._internal = { hasConstraints, assertEmitted, prepareForCodegen, sharedCodegenGate, codegenSafe, isSimplePN, hasUnresolvableRef, needsBaseTracking, _cpLen, sharedRt, DECLINE, bindRt, compileSafe, emitGuardState, withPlain, REF_NEUTRAL_SIBLINGS, resolveCrossSchemaRef, enumCondition, enumOperand, emitDeq, emitConstant, ownGuard, ownKeyExpr, multipleOfBad, compilePatternInline, useSafeEngine, reFlags, FORMAT_CODEGEN, PROTO_NAMES, setOwn, nestedGenCode, compileToJSCodegen, SKIP_VALUE_KEYS, skipString, CYCLE_DEPTH, cyclicDefNames, sharedDefNames, reFlagArg, hoistOnce, PTR_ESC_HELPER, unevalContributions, emitUq, uqSources, safeReClosure, fastPrefixCheck, replaceTopLevel, refTargetFor, genCode, addLocalAnnotations, addTopApplicatorAnnotations, protoIs, ANNOT_APPLICATORS, SIZE_SKIPS, emitRootGuard, emitGuardedRun }
|
package/lib/metaschemas.js
CHANGED
|
@@ -2,8 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
// The official meta-schemas, vendored so a `$ref` to one of them resolves
|
|
4
4
|
// without a network. Draft 2020-12 is eight documents joined by $dynamicRef;
|
|
5
|
-
// draft-07
|
|
6
|
-
//
|
|
5
|
+
// draft-07 and draft-06 are one each; 2019-09 is seven. Sources:
|
|
6
|
+
// https://json-schema.org/draft/2020-12/schema and
|
|
7
|
+
// http://json-schema.org/draft-07/schema, fetched 2026-08-23, and
|
|
8
|
+
// http://json-schema.org/draft-06/schema and
|
|
9
|
+
// https://json-schema.org/draft/2019-09/schema, fetched 2026-10-10, unmodified.
|
|
10
|
+
// The 2019-09 documents are read through the same 2019-09 rewrite as a
|
|
11
|
+
// caller's schema (lib/draft7.js) once, at load, since they use
|
|
12
|
+
// `$recursiveRef`, which the engines read as `$dynamicRef`.
|
|
7
13
|
// Keyed by $id; index.js also registers the http/https and trailing-# spellings.
|
|
8
14
|
|
|
9
15
|
const METASCHEMAS = new Map([
|
|
@@ -16,6 +22,19 @@ const METASCHEMAS = new Map([
|
|
|
16
22
|
["https://json-schema.org/draft/2020-12/meta/content", {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://json-schema.org/draft/2020-12/meta/content","$dynamicAnchor":"meta","title":"Content vocabulary meta-schema","type":["object","boolean"],"properties":{"contentEncoding":{"type":"string"},"contentMediaType":{"type":"string"},"contentSchema":{"$dynamicRef":"#meta"}}}],
|
|
17
23
|
["https://json-schema.org/draft/2020-12/meta/unevaluated", {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://json-schema.org/draft/2020-12/meta/unevaluated","$dynamicAnchor":"meta","title":"Unevaluated applicator vocabulary meta-schema","type":["object","boolean"],"properties":{"unevaluatedItems":{"$dynamicRef":"#meta"},"unevaluatedProperties":{"$dynamicRef":"#meta"}}}],
|
|
18
24
|
["http://json-schema.org/draft-07/schema#", {"$schema":"http://json-schema.org/draft-07/schema#","$id":"http://json-schema.org/draft-07/schema#","title":"Core schema meta-schema","definitions":{"schemaArray":{"type":"array","minItems":1,"items":{"$ref":"#"}},"nonNegativeInteger":{"type":"integer","minimum":0},"nonNegativeIntegerDefault0":{"allOf":[{"$ref":"#/definitions/nonNegativeInteger"},{"default":0}]},"simpleTypes":{"enum":["array","boolean","integer","null","number","object","string"]},"stringArray":{"type":"array","items":{"type":"string"},"uniqueItems":true,"default":[]}},"type":["object","boolean"],"properties":{"$id":{"type":"string","format":"uri-reference"},"$schema":{"type":"string","format":"uri"},"$ref":{"type":"string","format":"uri-reference"},"$comment":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"default":true,"readOnly":{"type":"boolean","default":false},"writeOnly":{"type":"boolean","default":false},"examples":{"type":"array","items":true},"multipleOf":{"type":"number","exclusiveMinimum":0},"maximum":{"type":"number"},"exclusiveMaximum":{"type":"number"},"minimum":{"type":"number"},"exclusiveMinimum":{"type":"number"},"maxLength":{"$ref":"#/definitions/nonNegativeInteger"},"minLength":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"pattern":{"type":"string","format":"regex"},"additionalItems":{"$ref":"#"},"items":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/schemaArray"}],"default":true},"maxItems":{"$ref":"#/definitions/nonNegativeInteger"},"minItems":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"uniqueItems":{"type":"boolean","default":false},"contains":{"$ref":"#"},"maxProperties":{"$ref":"#/definitions/nonNegativeInteger"},"minProperties":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"required":{"$ref":"#/definitions/stringArray"},"additionalProperties":{"$ref":"#"},"definitions":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"properties":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"patternProperties":{"type":"object","additionalProperties":{"$ref":"#"},"propertyNames":{"format":"regex"},"default":{}},"dependencies":{"type":"object","additionalProperties":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/stringArray"}]}},"propertyNames":{"$ref":"#"},"const":true,"enum":{"type":"array","items":true,"minItems":1,"uniqueItems":true},"type":{"anyOf":[{"$ref":"#/definitions/simpleTypes"},{"type":"array","items":{"$ref":"#/definitions/simpleTypes"},"minItems":1,"uniqueItems":true}]},"format":{"type":"string"},"contentMediaType":{"type":"string"},"contentEncoding":{"type":"string"},"if":{"$ref":"#"},"then":{"$ref":"#"},"else":{"$ref":"#"},"allOf":{"$ref":"#/definitions/schemaArray"},"anyOf":{"$ref":"#/definitions/schemaArray"},"oneOf":{"$ref":"#/definitions/schemaArray"},"not":{"$ref":"#"}},"default":true}],
|
|
25
|
+
["http://json-schema.org/draft-06/schema#", {"$schema":"http://json-schema.org/draft-06/schema#","$id":"http://json-schema.org/draft-06/schema#","title":"Core schema meta-schema","definitions":{"schemaArray":{"type":"array","minItems":1,"items":{"$ref":"#"}},"nonNegativeInteger":{"type":"integer","minimum":0},"nonNegativeIntegerDefault0":{"allOf":[{"$ref":"#/definitions/nonNegativeInteger"},{"default":0}]},"simpleTypes":{"enum":["array","boolean","integer","null","number","object","string"]},"stringArray":{"type":"array","items":{"type":"string"},"uniqueItems":true,"default":[]}},"type":["object","boolean"],"properties":{"$id":{"type":"string","format":"uri-reference"},"$schema":{"type":"string","format":"uri"},"$ref":{"type":"string","format":"uri-reference"},"title":{"type":"string"},"description":{"type":"string"},"default":{},"examples":{"type":"array","items":{}},"multipleOf":{"type":"number","exclusiveMinimum":0},"maximum":{"type":"number"},"exclusiveMaximum":{"type":"number"},"minimum":{"type":"number"},"exclusiveMinimum":{"type":"number"},"maxLength":{"$ref":"#/definitions/nonNegativeInteger"},"minLength":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"pattern":{"type":"string","format":"regex"},"additionalItems":{"$ref":"#"},"items":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/schemaArray"}],"default":{}},"maxItems":{"$ref":"#/definitions/nonNegativeInteger"},"minItems":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"uniqueItems":{"type":"boolean","default":false},"contains":{"$ref":"#"},"maxProperties":{"$ref":"#/definitions/nonNegativeInteger"},"minProperties":{"$ref":"#/definitions/nonNegativeIntegerDefault0"},"required":{"$ref":"#/definitions/stringArray"},"additionalProperties":{"$ref":"#"},"definitions":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"properties":{"type":"object","additionalProperties":{"$ref":"#"},"default":{}},"patternProperties":{"type":"object","additionalProperties":{"$ref":"#"},"propertyNames":{"format":"regex"},"default":{}},"dependencies":{"type":"object","additionalProperties":{"anyOf":[{"$ref":"#"},{"$ref":"#/definitions/stringArray"}]}},"propertyNames":{"$ref":"#"},"const":{},"enum":{"type":"array","minItems":1,"uniqueItems":true},"type":{"anyOf":[{"$ref":"#/definitions/simpleTypes"},{"type":"array","items":{"$ref":"#/definitions/simpleTypes"},"minItems":1,"uniqueItems":true}]},"format":{"type":"string"},"allOf":{"$ref":"#/definitions/schemaArray"},"anyOf":{"$ref":"#/definitions/schemaArray"},"oneOf":{"$ref":"#/definitions/schemaArray"},"not":{"$ref":"#"}},"default":{}}],
|
|
26
|
+
["https://json-schema.org/draft/2019-09/schema", {"$schema":"https://json-schema.org/draft/2019-09/schema","$id":"https://json-schema.org/draft/2019-09/schema","$vocabulary":{"https://json-schema.org/draft/2019-09/vocab/core":true,"https://json-schema.org/draft/2019-09/vocab/applicator":true,"https://json-schema.org/draft/2019-09/vocab/validation":true,"https://json-schema.org/draft/2019-09/vocab/meta-data":true,"https://json-schema.org/draft/2019-09/vocab/format":false,"https://json-schema.org/draft/2019-09/vocab/content":true},"$recursiveAnchor":true,"title":"Core and Validation specifications meta-schema","allOf":[{"$ref":"meta/core"},{"$ref":"meta/applicator"},{"$ref":"meta/validation"},{"$ref":"meta/meta-data"},{"$ref":"meta/format"},{"$ref":"meta/content"}],"type":["object","boolean"],"properties":{"definitions":{"$comment":"While no longer an official keyword as it is replaced by $defs, this keyword is retained in the meta-schema to prevent incompatible extensions as it remains in common use.","type":"object","additionalProperties":{"$recursiveRef":"#"},"default":{}},"dependencies":{"$comment":"\"dependencies\" is no longer a keyword, but schema authors should avoid redefining it to facilitate a smooth transition to \"dependentSchemas\" and \"dependentRequired\"","type":"object","additionalProperties":{"anyOf":[{"$recursiveRef":"#"},{"$ref":"meta/validation#/$defs/stringArray"}]}}}}],
|
|
27
|
+
["https://json-schema.org/draft/2019-09/meta/core", {"$schema":"https://json-schema.org/draft/2019-09/schema","$id":"https://json-schema.org/draft/2019-09/meta/core","$recursiveAnchor":true,"title":"Core vocabulary meta-schema","type":["object","boolean"],"properties":{"$id":{"type":"string","format":"uri-reference","$comment":"Non-empty fragments not allowed.","pattern":"^[^#]*#?$"},"$schema":{"type":"string","format":"uri"},"$anchor":{"type":"string","pattern":"^[A-Za-z][-A-Za-z0-9.:_]*$"},"$ref":{"type":"string","format":"uri-reference"},"$recursiveRef":{"type":"string","format":"uri-reference"},"$recursiveAnchor":{"type":"boolean","default":false},"$vocabulary":{"type":"object","propertyNames":{"type":"string","format":"uri"},"additionalProperties":{"type":"boolean"}},"$comment":{"type":"string"},"$defs":{"type":"object","additionalProperties":{"$recursiveRef":"#"},"default":{}}}}],
|
|
28
|
+
["https://json-schema.org/draft/2019-09/meta/applicator", {"$schema":"https://json-schema.org/draft/2019-09/schema","$id":"https://json-schema.org/draft/2019-09/meta/applicator","$recursiveAnchor":true,"title":"Applicator vocabulary meta-schema","type":["object","boolean"],"properties":{"additionalItems":{"$recursiveRef":"#"},"unevaluatedItems":{"$recursiveRef":"#"},"items":{"anyOf":[{"$recursiveRef":"#"},{"$ref":"#/$defs/schemaArray"}]},"contains":{"$recursiveRef":"#"},"additionalProperties":{"$recursiveRef":"#"},"unevaluatedProperties":{"$recursiveRef":"#"},"properties":{"type":"object","additionalProperties":{"$recursiveRef":"#"},"default":{}},"patternProperties":{"type":"object","additionalProperties":{"$recursiveRef":"#"},"propertyNames":{"format":"regex"},"default":{}},"dependentSchemas":{"type":"object","additionalProperties":{"$recursiveRef":"#"}},"propertyNames":{"$recursiveRef":"#"},"if":{"$recursiveRef":"#"},"then":{"$recursiveRef":"#"},"else":{"$recursiveRef":"#"},"allOf":{"$ref":"#/$defs/schemaArray"},"anyOf":{"$ref":"#/$defs/schemaArray"},"oneOf":{"$ref":"#/$defs/schemaArray"},"not":{"$recursiveRef":"#"}},"$defs":{"schemaArray":{"type":"array","minItems":1,"items":{"$recursiveRef":"#"}}}}],
|
|
29
|
+
["https://json-schema.org/draft/2019-09/meta/validation", {"$schema":"https://json-schema.org/draft/2019-09/schema","$id":"https://json-schema.org/draft/2019-09/meta/validation","$recursiveAnchor":true,"title":"Validation vocabulary meta-schema","type":["object","boolean"],"properties":{"multipleOf":{"type":"number","exclusiveMinimum":0},"maximum":{"type":"number"},"exclusiveMaximum":{"type":"number"},"minimum":{"type":"number"},"exclusiveMinimum":{"type":"number"},"maxLength":{"$ref":"#/$defs/nonNegativeInteger"},"minLength":{"$ref":"#/$defs/nonNegativeIntegerDefault0"},"pattern":{"type":"string","format":"regex"},"maxItems":{"$ref":"#/$defs/nonNegativeInteger"},"minItems":{"$ref":"#/$defs/nonNegativeIntegerDefault0"},"uniqueItems":{"type":"boolean","default":false},"maxContains":{"$ref":"#/$defs/nonNegativeInteger"},"minContains":{"$ref":"#/$defs/nonNegativeInteger","default":1},"maxProperties":{"$ref":"#/$defs/nonNegativeInteger"},"minProperties":{"$ref":"#/$defs/nonNegativeIntegerDefault0"},"required":{"$ref":"#/$defs/stringArray"},"dependentRequired":{"type":"object","additionalProperties":{"$ref":"#/$defs/stringArray"}},"const":true,"enum":{"type":"array","items":true},"type":{"anyOf":[{"$ref":"#/$defs/simpleTypes"},{"type":"array","items":{"$ref":"#/$defs/simpleTypes"},"minItems":1,"uniqueItems":true}]}},"$defs":{"nonNegativeInteger":{"type":"integer","minimum":0},"nonNegativeIntegerDefault0":{"$ref":"#/$defs/nonNegativeInteger","default":0},"simpleTypes":{"enum":["array","boolean","integer","null","number","object","string"]},"stringArray":{"type":"array","items":{"type":"string"},"uniqueItems":true,"default":[]}}}],
|
|
30
|
+
["https://json-schema.org/draft/2019-09/meta/meta-data", {"$schema":"https://json-schema.org/draft/2019-09/schema","$id":"https://json-schema.org/draft/2019-09/meta/meta-data","$recursiveAnchor":true,"title":"Meta-data vocabulary meta-schema","type":["object","boolean"],"properties":{"title":{"type":"string"},"description":{"type":"string"},"default":true,"deprecated":{"type":"boolean","default":false},"readOnly":{"type":"boolean","default":false},"writeOnly":{"type":"boolean","default":false},"examples":{"type":"array","items":true}}}],
|
|
31
|
+
["https://json-schema.org/draft/2019-09/meta/format", {"$schema":"https://json-schema.org/draft/2019-09/schema","$id":"https://json-schema.org/draft/2019-09/meta/format","$recursiveAnchor":true,"title":"Format vocabulary meta-schema","type":["object","boolean"],"properties":{"format":{"type":"string"}}}],
|
|
32
|
+
["https://json-schema.org/draft/2019-09/meta/content", {"$schema":"https://json-schema.org/draft/2019-09/schema","$id":"https://json-schema.org/draft/2019-09/meta/content","$recursiveAnchor":true,"title":"Content vocabulary meta-schema","type":["object","boolean"],"properties":{"contentMediaType":{"type":"string"},"contentEncoding":{"type":"string"},"contentSchema":{"$recursiveRef":"#"}}}],
|
|
19
33
|
]);
|
|
20
34
|
|
|
35
|
+
{
|
|
36
|
+
const { isDraft2019, normalizeDraft2019 } = require('./draft7')
|
|
37
|
+
for (const [id, doc] of METASCHEMAS) if (isDraft2019(doc)) METASCHEMAS.set(id, normalizeDraft2019(JSON.parse(JSON.stringify(doc)), true))
|
|
38
|
+
}
|
|
39
|
+
|
|
21
40
|
module.exports = { METASCHEMAS };
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// The public names of the package, shared by index.js (with the code
|
|
4
|
+
// generator) and index.edge.js (without it), so the two entries export the
|
|
5
|
+
// same thing.
|
|
6
|
+
const core = require('./validator-core.js');
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
// The parts no Validator calls: TypeScript generation, the error renderers,
|
|
11
|
+
// the spec output format, the retry message for language models, the schema
|
|
12
|
+
// description and the suggestion helper. ata-validator/lite leaves them out.
|
|
13
|
+
// Each loads its module on first call. None of them is on the path to a first
|
|
14
|
+
// validated request, and together they were about a sixth of what requiring
|
|
15
|
+
// the package cost.
|
|
16
|
+
function toTypeScript (...a) { return require('./ts-gen').toTypeScript(...a); }
|
|
17
|
+
function renderPretty (...a) { return require('./render-pretty').renderPretty(...a); }
|
|
18
|
+
function renderCompact (...a) { return require('./render-compact').renderCompact(...a); }
|
|
19
|
+
function toOutput (...a) { return require('./output-format').toOutput(...a); }
|
|
20
|
+
function toRetryMessage (...a) { return require('./retry-message').toRetryMessage(...a); }
|
|
21
|
+
function describeSchema (...a) { return require('./describe-schema').describeSchema(...a); }
|
|
22
|
+
function renderJSON (...a) { return require('./render-json').renderJSON(...a); }
|
|
23
|
+
|
|
24
|
+
// Walk a JSON pointer (RFC 6901 escapes) into a data tree. Mirrors the helper
|
|
25
|
+
// inside lib/suggestions.js, kept local to avoid exporting an internal.
|
|
26
|
+
function _walkPointer (root, pointer) {
|
|
27
|
+
if (!pointer) return root;
|
|
28
|
+
const parts = pointer.replace(/^\//, '').split('/').map(s => s.replace(/~1/g, '/').replace(/~0/g, '~'));
|
|
29
|
+
let cur = root;
|
|
30
|
+
for (const p of parts) { if (cur == null) return undefined; cur = cur[p]; }
|
|
31
|
+
return cur;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Post-hoc suggestion enrichment for AOT-compiled validators. The standalone
|
|
35
|
+
// modules do not embed the suggestion engine, so consumers who want
|
|
36
|
+
// suggestions pass the error array through this after validation.
|
|
37
|
+
function attachSuggestions (errors, data) {
|
|
38
|
+
if (!errors) return errors;
|
|
39
|
+
const { suggestFor } = require('./suggestions');
|
|
40
|
+
const { reprValue } = require('./enrich-error');
|
|
41
|
+
for (const e of errors) {
|
|
42
|
+
if (!e || e.suggestion) continue;
|
|
43
|
+
let received = e.received;
|
|
44
|
+
if (received === undefined && data !== undefined) {
|
|
45
|
+
const ptr = e.instancePath != null ? e.instancePath : (e.path || '');
|
|
46
|
+
const raw = _walkPointer(data, ptr);
|
|
47
|
+
if (raw !== undefined || ptr === '') received = reprValue(raw);
|
|
48
|
+
}
|
|
49
|
+
const probe = received !== undefined && e.received === undefined
|
|
50
|
+
? Object.assign({}, e, { received })
|
|
51
|
+
: e;
|
|
52
|
+
const s = suggestFor(probe, data);
|
|
53
|
+
if (s) e.suggestion = s;
|
|
54
|
+
}
|
|
55
|
+
// AOT modules import nothing, so this is their only route to a frame. The
|
|
56
|
+
// caller holds the original object and ran no preprocessing through here.
|
|
57
|
+
require('./diagnostic-source').setDiagnosticSource(errors, { data, mutatesInput: false });
|
|
58
|
+
return errors;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
module.exports = {
|
|
62
|
+
Validator: core.Validator,
|
|
63
|
+
compile: core.compile,
|
|
64
|
+
validate: core.validate,
|
|
65
|
+
validateAsync: core.validateAsync,
|
|
66
|
+
parseAsync: core.parseAsync,
|
|
67
|
+
version: core.version,
|
|
68
|
+
createPaddedBuffer: core.createPaddedBuffer,
|
|
69
|
+
SIMDJSON_PADDING: core.SIMDJSON_PADDING,
|
|
70
|
+
parseJSON: core.parseJSON,
|
|
71
|
+
toTypeScript,
|
|
72
|
+
defineSchema: core.defineSchema,
|
|
73
|
+
renderPretty,
|
|
74
|
+
renderCompact,
|
|
75
|
+
toOutput,
|
|
76
|
+
toRetryMessage,
|
|
77
|
+
describeSchema,
|
|
78
|
+
renderJSON,
|
|
79
|
+
attachSuggestions, // internal: used by the renderers; not public API
|
|
80
|
+
};
|
package/lib/regex-linear.js
CHANGED
|
@@ -3,34 +3,56 @@
|
|
|
3
3
|
const { parse } = require('./safe-regex')
|
|
4
4
|
|
|
5
5
|
// True when the platform RegExp, a backtracking engine, also runs `src` in
|
|
6
|
-
// linear time, so it can take the pattern
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
6
|
+
// linear time, so it can take the pattern instead of the linear-time engine
|
|
7
|
+
// in safe-regex.js, which costs three to five times as much per character.
|
|
8
|
+
// Two rules, and a pattern passes on either:
|
|
9
|
+
//
|
|
10
|
+
// The simple rule, the original one: every unbounded quantifier is on one
|
|
11
|
+
// character, class or `.`; at most one of them, and then the pattern is
|
|
12
|
+
// anchored with `^` on every branch; a finite repeat may cover a group that
|
|
13
|
+
// holds no unbounded quantifier; few bounded choices in all. One loop whose
|
|
14
|
+
// body cannot be split two ways, exited at most once per position, with
|
|
15
|
+
// bounded work after it.
|
|
16
|
+
//
|
|
17
|
+
// The deterministic rule, for the patterns the simple one declines: dotted
|
|
18
|
+
// identifiers, `[^/]+/[^/]+`, version numbers. On the Glushkov construction
|
|
19
|
+
// the characters, classes and dots are positions. Around every unbounded
|
|
20
|
+
// loop the next character must decide the single way to continue: the
|
|
21
|
+
// positions that can follow a position inside a loop accept pairwise
|
|
22
|
+
// disjoint characters, and so do the positions a match can start at when a
|
|
23
|
+
// loop is among them; a position reaches a follower by one route only, since
|
|
24
|
+
// `(a+)+` reaches `a` from `a` through the inner loop and again through the
|
|
25
|
+
// outer one and that double route is what a backtracking engine explores
|
|
26
|
+
// exponentially; a loop body must not match the empty string, since `(a*)*`
|
|
27
|
+
// iterates emptily without bound. Ambiguity away from any loop, `(pwa|PWA)`,
|
|
28
|
+
// costs a bounded factor and is allowed, as the simple rule allows it. A
|
|
29
|
+
// bounded repeat is a loop only when its body holds an unbounded one, since
|
|
30
|
+
// `(\w+\s?){5}` splits five ways where `(?:[A-F0-9]{2}:){31}` counts. With
|
|
31
|
+
// an unbounded quantifier the pattern must be anchored with `^` on every
|
|
32
|
+
// branch, since an unanchored search retries from each position.
|
|
33
|
+
//
|
|
34
|
+
// Both rules allow only escapes both engines read alike and nothing outside
|
|
35
|
+
// the BMP, so both see the same UTF-16 units. tests/test_regex_native_linear.js
|
|
36
|
+
// holds them: every accepted pattern answers as the linear engine does and
|
|
37
|
+
// stays fast on inputs built to make a backtracking engine retry.
|
|
12
38
|
const NATIVE_ESCAPES = new Set(['d', 'D', 'w', 'W', 's', 'S', 'n', 'r', 't', 'f', 'v', '.', '\\', '/', '^', '$', '|', '?', '*', '+', '(', ')', '[', ']', '{', '}', '-', 'x', 'u'])
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
try { ast = parse(src) } catch { return false }
|
|
39
|
+
const MAX_BOUNDED_CHOICES = 64
|
|
40
|
+
|
|
41
|
+
const atom = (n) => n.t === 'char' || n.t === 'class' || n.t === 'any'
|
|
42
|
+
|
|
43
|
+
// Anchored at the start on every top-level branch.
|
|
44
|
+
function anchored (ast) {
|
|
45
|
+
let top = ast
|
|
46
|
+
while (top.t === 'group') top = top.child
|
|
47
|
+
const branches = top.t === 'alt' ? top.opts : [top]
|
|
48
|
+
const first = (n) => { while (n && n.t === 'group') n = n.child; return n && n.t === 'concat' ? first(n.parts[0]) : n }
|
|
49
|
+
return branches.every((b) => { const f = first(b); return f && f.t === 'bol' })
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function simpleRule (ast) {
|
|
28
53
|
// Per subtree: how many unbounded quantifiers it holds (`u`) and how many
|
|
29
54
|
// bounded choices a backtracking engine could try (`b`), or null where the
|
|
30
|
-
// shape is not provably linear.
|
|
31
|
-
// the group holds no unbounded quantifier: `(?:[A-F0-9]{2}:){31}` has
|
|
32
|
-
// nothing to retry, and its choices multiply through the nesting.
|
|
33
|
-
const atom = (n) => n.t === 'char' || n.t === 'class' || n.t === 'any'
|
|
55
|
+
// shape is not provably linear.
|
|
34
56
|
const info = (n) => {
|
|
35
57
|
switch (n.t) {
|
|
36
58
|
case 'star': case 'plus':
|
|
@@ -56,19 +78,154 @@ function nativeIsLinear (src) {
|
|
|
56
78
|
}
|
|
57
79
|
}
|
|
58
80
|
const r = info(ast)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
81
|
+
if (r === null || r.u > 1 || r.b > MAX_BOUNDED_CHOICES) return false
|
|
82
|
+
return r.u === 0 || anchored(ast)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// The characters a position accepts: 128 ASCII bits and one bit for "some
|
|
86
|
+
// code point at 128 or above", which keeps two classes with non-ASCII
|
|
87
|
+
// members apart only when one of them has none. Conservative in the right
|
|
88
|
+
// direction: an overlap that is not there only sends a pattern to the
|
|
89
|
+
// linear engine.
|
|
90
|
+
function charSet (node) {
|
|
91
|
+
const bits = new Uint8Array(128)
|
|
92
|
+
let high = 0
|
|
93
|
+
if (node.t === 'any') {
|
|
94
|
+
bits.fill(1); bits[10] = 0; bits[13] = 0; high = 1
|
|
95
|
+
} else if (node.t === 'char') {
|
|
96
|
+
if (node.c < 128) bits[node.c] = 1; else high = 1
|
|
97
|
+
} else {
|
|
98
|
+
for (const [lo, hi] of node.ranges) {
|
|
99
|
+
for (let c = Math.max(0, lo), top = Math.min(127, hi); c <= top; c++) bits[c] = 1
|
|
100
|
+
if (hi >= 128) high = 1
|
|
101
|
+
}
|
|
102
|
+
if (node.neg) { for (let c = 0; c < 128; c++) bits[c] ^= 1; high = 1 }
|
|
70
103
|
}
|
|
71
|
-
return
|
|
104
|
+
return { bits, high }
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function overlap (a, b) {
|
|
108
|
+
if (a.high && b.high) return true
|
|
109
|
+
for (let c = 0; c < 128; c++) if (a.bits[c] & b.bits[c]) return true
|
|
110
|
+
return false
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Whether a subtree holds an unbounded quantifier.
|
|
114
|
+
function hasUnbounded (n) {
|
|
115
|
+
switch (n.t) {
|
|
116
|
+
case 'star': case 'plus': return true
|
|
117
|
+
case 'repeat': return n.max === Infinity || hasUnbounded(n.child)
|
|
118
|
+
case 'quest': case 'group': return hasUnbounded(n.child)
|
|
119
|
+
case 'concat': return n.parts.some(hasUnbounded)
|
|
120
|
+
case 'alt': return n.opts.some(hasUnbounded)
|
|
121
|
+
default: return false
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function deterministicRule (ast) {
|
|
126
|
+
const sets = []
|
|
127
|
+
const follow = []
|
|
128
|
+
const inLoop = []
|
|
129
|
+
let ok = true
|
|
130
|
+
let unbounded = 0
|
|
131
|
+
let bounded = 0
|
|
132
|
+
let depth = 0
|
|
133
|
+
// Pairwise disjoint, required when a loop position is among them.
|
|
134
|
+
const disjoint = (ids) => {
|
|
135
|
+
const arr = Array.from(ids)
|
|
136
|
+
if (!arr.some((p) => inLoop[p])) return
|
|
137
|
+
for (let i = 0; i < arr.length && ok; i++) {
|
|
138
|
+
for (let j = i + 1; j < arr.length; j++) if (overlap(sets[arr[i]], sets[arr[j]])) { ok = false; return }
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const link = (from, to) => {
|
|
142
|
+
for (const p of to) {
|
|
143
|
+
if (follow[from].has(p)) { ok = false; return }
|
|
144
|
+
follow[from].add(p)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
const empty = () => ({ nullable: true, first: new Set(), last: new Set() })
|
|
148
|
+
const walk = (n) => {
|
|
149
|
+
if (!ok) return empty()
|
|
150
|
+
switch (n.t) {
|
|
151
|
+
case 'char': case 'class': case 'any': {
|
|
152
|
+
const id = sets.length
|
|
153
|
+
sets.push(charSet(n))
|
|
154
|
+
follow.push(new Set())
|
|
155
|
+
inLoop.push(depth > 0)
|
|
156
|
+
return { nullable: false, first: new Set([id]), last: new Set([id]) }
|
|
157
|
+
}
|
|
158
|
+
case 'bol': case 'eol': return empty()
|
|
159
|
+
case 'group': return walk(n.child)
|
|
160
|
+
case 'star': case 'plus': case 'quest': case 'repeat': {
|
|
161
|
+
const infinite = n.t === 'star' || n.t === 'plus' || (n.t === 'repeat' && n.max === Infinity)
|
|
162
|
+
const loops = infinite || (n.t === 'repeat' && n.max > 1 && hasUnbounded(n.child))
|
|
163
|
+
const optional = n.t === 'star' || n.t === 'quest' || (n.t === 'repeat' && n.min === 0)
|
|
164
|
+
if (loops) depth++
|
|
165
|
+
const c = walk(n.child)
|
|
166
|
+
if (loops) depth--
|
|
167
|
+
if (infinite) {
|
|
168
|
+
unbounded++
|
|
169
|
+
if (c.nullable) ok = false
|
|
170
|
+
} else if (n.t === 'quest') {
|
|
171
|
+
bounded += 1
|
|
172
|
+
} else {
|
|
173
|
+
bounded += (n.max - n.min)
|
|
174
|
+
if (!atom(n.child)) bounded += n.max
|
|
175
|
+
}
|
|
176
|
+
if (loops) for (const l of c.last) link(l, c.first)
|
|
177
|
+
return { nullable: c.nullable || optional, first: c.first, last: c.last }
|
|
178
|
+
}
|
|
179
|
+
case 'concat': {
|
|
180
|
+
let acc = empty()
|
|
181
|
+
for (const part of n.parts) {
|
|
182
|
+
const r = walk(part)
|
|
183
|
+
for (const l of acc.last) link(l, r.first)
|
|
184
|
+
const first = new Set(acc.first)
|
|
185
|
+
if (acc.nullable) for (const f of r.first) first.add(f)
|
|
186
|
+
const last = new Set(r.last)
|
|
187
|
+
if (r.nullable) for (const l of acc.last) last.add(l)
|
|
188
|
+
acc = { nullable: acc.nullable && r.nullable, first, last }
|
|
189
|
+
}
|
|
190
|
+
return acc
|
|
191
|
+
}
|
|
192
|
+
case 'alt': {
|
|
193
|
+
const rs = n.opts.map(walk)
|
|
194
|
+
const first = new Set()
|
|
195
|
+
const last = new Set()
|
|
196
|
+
for (const r of rs) { for (const f of r.first) first.add(f); for (const l of r.last) last.add(l) }
|
|
197
|
+
return { nullable: rs.some((r) => r.nullable), first, last }
|
|
198
|
+
}
|
|
199
|
+
default:
|
|
200
|
+
ok = false
|
|
201
|
+
return empty()
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
const root = walk(ast)
|
|
205
|
+
if (!ok) return false
|
|
206
|
+
disjoint(root.first)
|
|
207
|
+
for (let p = 0; p < sets.length && ok; p++) disjoint(follow[p])
|
|
208
|
+
if (!ok) return false
|
|
209
|
+
if (bounded > MAX_BOUNDED_CHOICES) return false
|
|
210
|
+
return unbounded === 0 || anchored(ast)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function nativeIsLinear (src) {
|
|
214
|
+
if (typeof src !== 'string' || src.length > 512) return false
|
|
215
|
+
for (let i = 0; i < src.length; i++) {
|
|
216
|
+
const c = src.charCodeAt(i)
|
|
217
|
+
if (c >= 0xd800 && c <= 0xdfff) return false
|
|
218
|
+
if (src[i] === '\\') {
|
|
219
|
+
const e = src[i + 1]
|
|
220
|
+
if (!NATIVE_ESCAPES.has(e)) return false
|
|
221
|
+
if (e === 'x' && !/^[0-9a-fA-F]{2}$/.test(src.slice(i + 2, i + 4))) return false
|
|
222
|
+
if (e === 'u' && !/^[0-9a-fA-F]{4}$/.test(src.slice(i + 2, i + 6))) return false
|
|
223
|
+
i++
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
let ast
|
|
227
|
+
try { ast = parse(src) } catch { return false }
|
|
228
|
+
return simpleRule(ast) || deterministicRule(ast)
|
|
72
229
|
}
|
|
73
230
|
|
|
74
231
|
module.exports = { nativeIsLinear }
|
package/lib/safe-regex.js
CHANGED
|
@@ -410,7 +410,8 @@ function __ataSafeRegex () {
|
|
|
410
410
|
id = states.length
|
|
411
411
|
let isMatch = false
|
|
412
412
|
for (let k = 0; k < pcs.length; k++) if (ops[pcs[k]] === OP_MATCH) { isMatch = true; break }
|
|
413
|
-
|
|
413
|
+
// `dead`: an anchored match with no thread left cannot recover.
|
|
414
|
+
states.push({ pcs: Int32Array.from(pcs), isMatch, dead: anchored && pcs.length === 0, next: new Int32Array(128).fill(-2), nextEnd: new Int32Array(128).fill(-2) })
|
|
414
415
|
stateIds.set(key, id)
|
|
415
416
|
return id
|
|
416
417
|
}
|
|
@@ -452,23 +453,36 @@ function __ataSafeRegex () {
|
|
|
452
453
|
}
|
|
453
454
|
if (id < 0) return testNFA(s)
|
|
454
455
|
let state = states[id]
|
|
455
|
-
|
|
456
|
+
if (len === 0) return state.isMatch
|
|
457
|
+
// The last character is stepped with the end-of-input flag set and is
|
|
458
|
+
// handled after the loop, so the loop body selects no table and tests
|
|
459
|
+
// no position.
|
|
460
|
+
const last = len - 1
|
|
461
|
+
for (let pos = 0; pos < last; pos++) {
|
|
456
462
|
if (state.isMatch) return true
|
|
457
463
|
const c = s.charCodeAt(pos)
|
|
458
|
-
const atEnd = pos + 1 === len
|
|
459
464
|
let nid
|
|
460
465
|
if (c < 128) {
|
|
461
|
-
|
|
462
|
-
nid =
|
|
463
|
-
if (nid === -2) { nid = step(state, c, atEnd); table[c] = nid }
|
|
466
|
+
nid = state.next[c]
|
|
467
|
+
if (nid === -2) { nid = step(state, c, false); state.next[c] = nid }
|
|
464
468
|
} else {
|
|
465
|
-
nid = step(state, c,
|
|
469
|
+
nid = step(state, c, false)
|
|
466
470
|
}
|
|
467
471
|
if (nid < 0) return testNFA(s)
|
|
468
472
|
state = states[nid]
|
|
469
|
-
if (
|
|
473
|
+
if (state.dead) return false
|
|
470
474
|
}
|
|
471
|
-
|
|
475
|
+
if (state.isMatch) return true
|
|
476
|
+
const c = s.charCodeAt(last)
|
|
477
|
+
let nid
|
|
478
|
+
if (c < 128) {
|
|
479
|
+
nid = state.nextEnd[c]
|
|
480
|
+
if (nid === -2) { nid = step(state, c, true); state.nextEnd[c] = nid }
|
|
481
|
+
} else {
|
|
482
|
+
nid = step(state, c, true)
|
|
483
|
+
}
|
|
484
|
+
if (nid < 0) return testNFA(s)
|
|
485
|
+
return states[nid].isMatch
|
|
472
486
|
}
|
|
473
487
|
|
|
474
488
|
return function test (s) {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// The runtime a schema module (`ata build --mode schema`) imports: the verdict
|
|
4
|
+
// and the errors both come from the interpreted engine walking the embedded
|
|
5
|
+
// schema, so a module is its schema and a few lines. Smaller and quicker to
|
|
6
|
+
// load than generated code, slower per check once warm; see docs/API.md.
|
|
7
|
+
// One interpreter per schema answers both, so the schema is prepared once.
|
|
8
|
+
// It loads lib/interpreter.js, which brings the plan compiler for schemas
|
|
9
|
+
// used often (TIER in lib/interpreter-core.js).
|
|
10
|
+
const { createInterpreter } = require('./interpreter');
|
|
11
|
+
const { errorsOf, interpreterOptions } = require('./error-runtime-entry');
|
|
12
|
+
|
|
13
|
+
function createValidator(schema, codeOf, options) {
|
|
14
|
+
const interp = createInterpreter(schema, interpreterOptions(options));
|
|
15
|
+
return {
|
|
16
|
+
isValid: (data) => interp.isValid(data),
|
|
17
|
+
errors: (data) => errorsOf(interp, schema, codeOf, data),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = { createValidator };
|