@json-schema-engine/core 0.0.1
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/LICENSE +21 -0
- package/README.md +50 -0
- package/dist/coverage.d.ts +30 -0
- package/dist/coverage.d.ts.map +1 -0
- package/dist/coverage.js +81 -0
- package/dist/coverage.js.map +1 -0
- package/dist/cursor.d.ts +16 -0
- package/dist/cursor.d.ts.map +1 -0
- package/dist/cursor.js +28 -0
- package/dist/cursor.js.map +1 -0
- package/dist/dialect.d.ts +316 -0
- package/dist/dialect.d.ts.map +1 -0
- package/dist/dialect.js +135 -0
- package/dist/dialect.js.map +1 -0
- package/dist/engine.d.ts +203 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +493 -0
- package/dist/engine.js.map +1 -0
- package/dist/index.d.ts +182 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +370 -0
- package/dist/index.js.map +1 -0
- package/dist/json.d.ts +49 -0
- package/dist/json.d.ts.map +1 -0
- package/dist/json.js +128 -0
- package/dist/json.js.map +1 -0
- package/dist/keywords/applicator.d.ts +57 -0
- package/dist/keywords/applicator.d.ts.map +1 -0
- package/dist/keywords/applicator.js +789 -0
- package/dist/keywords/applicator.js.map +1 -0
- package/dist/keywords/core.d.ts +42 -0
- package/dist/keywords/core.d.ts.map +1 -0
- package/dist/keywords/core.js +139 -0
- package/dist/keywords/core.js.map +1 -0
- package/dist/keywords/format.d.ts +43 -0
- package/dist/keywords/format.d.ts.map +1 -0
- package/dist/keywords/format.js +101 -0
- package/dist/keywords/format.js.map +1 -0
- package/dist/keywords/metaschemas2019.d.ts +3 -0
- package/dist/keywords/metaschemas2019.d.ts.map +1 -0
- package/dist/keywords/metaschemas2019.js +406 -0
- package/dist/keywords/metaschemas2019.js.map +1 -0
- package/dist/keywords/metaschemas2020.d.ts +3 -0
- package/dist/keywords/metaschemas2020.d.ts.map +1 -0
- package/dist/keywords/metaschemas2020.js +441 -0
- package/dist/keywords/metaschemas2020.js.map +1 -0
- package/dist/keywords/metaschemas6.d.ts +3 -0
- package/dist/keywords/metaschemas6.d.ts.map +1 -0
- package/dist/keywords/metaschemas6.js +221 -0
- package/dist/keywords/metaschemas6.js.map +1 -0
- package/dist/keywords/metaschemas7.d.ts +3 -0
- package/dist/keywords/metaschemas7.d.ts.map +1 -0
- package/dist/keywords/metaschemas7.js +248 -0
- package/dist/keywords/metaschemas7.js.map +1 -0
- package/dist/keywords/unevaluated.d.ts +14 -0
- package/dist/keywords/unevaluated.d.ts.map +1 -0
- package/dist/keywords/unevaluated.js +296 -0
- package/dist/keywords/unevaluated.js.map +1 -0
- package/dist/keywords/validation.d.ts +21 -0
- package/dist/keywords/validation.d.ts.map +1 -0
- package/dist/keywords/validation.js +332 -0
- package/dist/keywords/validation.js.map +1 -0
- package/dist/keywords/vocab2019.d.ts +36 -0
- package/dist/keywords/vocab2019.d.ts.map +1 -0
- package/dist/keywords/vocab2019.js +585 -0
- package/dist/keywords/vocab2019.js.map +1 -0
- package/dist/keywords/vocab2020.d.ts +20 -0
- package/dist/keywords/vocab2020.d.ts.map +1 -0
- package/dist/keywords/vocab2020.js +55 -0
- package/dist/keywords/vocab2020.js.map +1 -0
- package/dist/keywords/vocab7.d.ts +46 -0
- package/dist/keywords/vocab7.d.ts.map +1 -0
- package/dist/keywords/vocab7.js +305 -0
- package/dist/keywords/vocab7.js.map +1 -0
- package/dist/loader.d.ts +41 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +13 -0
- package/dist/loader.js.map +1 -0
- package/dist/lowering.d.ts +371 -0
- package/dist/lowering.d.ts.map +1 -0
- package/dist/lowering.js +59 -0
- package/dist/lowering.js.map +1 -0
- package/dist/output.d.ts +251 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +357 -0
- package/dist/output.js.map +1 -0
- package/dist/records.d.ts +71 -0
- package/dist/records.d.ts.map +1 -0
- package/dist/records.js +150 -0
- package/dist/records.js.map +1 -0
- package/dist/ref.d.ts +10 -0
- package/dist/ref.d.ts.map +1 -0
- package/dist/ref.js +5 -0
- package/dist/ref.js.map +1 -0
- package/dist/regex.d.ts +45 -0
- package/dist/regex.d.ts.map +1 -0
- package/dist/regex.js +163 -0
- package/dist/regex.js.map +1 -0
- package/dist/registry.d.ts +141 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +407 -0
- package/dist/registry.js.map +1 -0
- package/dist/result.d.ts +109 -0
- package/dist/result.d.ts.map +1 -0
- package/dist/result.js +112 -0
- package/dist/result.js.map +1 -0
- package/dist/uri.d.ts +18 -0
- package/dist/uri.d.ts.map +1 -0
- package/dist/uri.js +27 -0
- package/dist/uri.js.map +1 -0
- package/dist/walk.d.ts +25 -0
- package/dist/walk.d.ts.map +1 -0
- package/dist/walk.js +65 -0
- package/dist/walk.js.map +1 -0
- package/package.json +32 -0
- package/src/coverage.ts +88 -0
- package/src/cursor.ts +42 -0
- package/src/dialect.ts +428 -0
- package/src/engine.ts +741 -0
- package/src/index.ts +699 -0
- package/src/json.ts +137 -0
- package/src/keywords/applicator.ts +895 -0
- package/src/keywords/core.ts +168 -0
- package/src/keywords/format.ts +143 -0
- package/src/keywords/metaschemas2019.ts +410 -0
- package/src/keywords/metaschemas2020.ts +443 -0
- package/src/keywords/metaschemas6.ts +226 -0
- package/src/keywords/metaschemas7.ts +253 -0
- package/src/keywords/unevaluated.ts +329 -0
- package/src/keywords/validation.ts +627 -0
- package/src/keywords/vocab2019.ts +676 -0
- package/src/keywords/vocab2020.ts +82 -0
- package/src/keywords/vocab7.ts +393 -0
- package/src/loader.ts +60 -0
- package/src/lowering.ts +414 -0
- package/src/output.ts +651 -0
- package/src/records.ts +226 -0
- package/src/ref.ts +14 -0
- package/src/regex.ts +171 -0
- package/src/registry.ts +481 -0
- package/src/result.ts +252 -0
- package/src/uri.ts +35 -0
- package/src/walk.ts +88 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// Core vocabulary behaviors, plus shared behavior factories.
|
|
2
|
+
|
|
3
|
+
import { JsonValue } from "../json.js";
|
|
4
|
+
import { KeywordBehavior, StaticFacts } from "../dialect.js";
|
|
5
|
+
|
|
6
|
+
/** 2020-12 core vocabulary URI. */
|
|
7
|
+
export const VOCAB_CORE = "https://json-schema.org/draft/2020-12/vocab/core";
|
|
8
|
+
|
|
9
|
+
/** Static facts for a keyword whose value is itself a subschema (e.g. `if`, `not`). */
|
|
10
|
+
const SELF: StaticFacts = { subschemas: [[]] };
|
|
11
|
+
/** Static facts for a keyword whose value is a name-keyed map of subschemas. */
|
|
12
|
+
const mapPositions = (value: JsonValue): StaticFacts =>
|
|
13
|
+
typeof value === "object" && value !== null && !Array.isArray(value)
|
|
14
|
+
? { subschemas: Object.keys(value).map((k) => [k]) }
|
|
15
|
+
: {};
|
|
16
|
+
|
|
17
|
+
/** Identifier/reserved keywords: no evaluation behavior, no annotation. */
|
|
18
|
+
export const structural = (id: string): KeywordBehavior => ({
|
|
19
|
+
id,
|
|
20
|
+
structural: true,
|
|
21
|
+
analyze: () => ({ produces: [] }),
|
|
22
|
+
evaluate: () => true,
|
|
23
|
+
lower: () => {
|
|
24
|
+
/* inert in compiled code too */
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* A keyword whose subschemas exist (for identification) but whose
|
|
30
|
+
* evaluation is driven by a sibling (`then`/`else` via `if`).
|
|
31
|
+
*/
|
|
32
|
+
export const inertSubschema = (id: string): KeywordBehavior => ({
|
|
33
|
+
id,
|
|
34
|
+
analyze: () => SELF,
|
|
35
|
+
evaluate: () => true,
|
|
36
|
+
lower: () => {
|
|
37
|
+
/* the driving sibling owns the application */
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
/** EXEMPLAR (annotation-only class): the keyword's value is its annotation. */
|
|
42
|
+
export const annotationOnly = (id: string): KeywordBehavior => ({
|
|
43
|
+
id,
|
|
44
|
+
evaluate: (_value, _cursor, ctx) => {
|
|
45
|
+
ctx.annotate();
|
|
46
|
+
return true;
|
|
47
|
+
},
|
|
48
|
+
lower: (_value, lctx) => {
|
|
49
|
+
lctx.emit({ kind: "annotate" });
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Placeholder for keywords owed by a later milestone: loud failure beats
|
|
55
|
+
* silently treating a known assertion/applicator as an annotation.
|
|
56
|
+
*/
|
|
57
|
+
export const notImplemented = (
|
|
58
|
+
id: string,
|
|
59
|
+
milestone: string,
|
|
60
|
+
): KeywordBehavior => ({
|
|
61
|
+
id,
|
|
62
|
+
evaluate: () => {
|
|
63
|
+
throw new Error(`keyword '${id}' is not implemented until ${milestone}`);
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const referenceFacts = (value: JsonValue): StaticFacts =>
|
|
68
|
+
typeof value === "string"
|
|
69
|
+
? {
|
|
70
|
+
references: [value],
|
|
71
|
+
// The resolved target applies in place, unconditionally.
|
|
72
|
+
applications: [
|
|
73
|
+
{
|
|
74
|
+
path: [],
|
|
75
|
+
ref: value,
|
|
76
|
+
mode: "inPlace",
|
|
77
|
+
conditional: false,
|
|
78
|
+
asserts: true,
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
}
|
|
82
|
+
: {};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* EXEMPLAR (reference class): resolve against the lexical base, apply the
|
|
86
|
+
* target at the same cursor. The engine owns the evaluation-path extension
|
|
87
|
+
* and the frame, so a reference behavior is one line.
|
|
88
|
+
*/
|
|
89
|
+
export const $ref: KeywordBehavior = {
|
|
90
|
+
id: `${VOCAB_CORE}#$ref`,
|
|
91
|
+
analyze: referenceFacts,
|
|
92
|
+
evaluate: (value, _cursor, ctx) =>
|
|
93
|
+
ctx.applyResolved(ctx.resolveRef(value as string)),
|
|
94
|
+
lower: (value, lctx) => {
|
|
95
|
+
lctx.emit({
|
|
96
|
+
kind: "apply",
|
|
97
|
+
apply: {
|
|
98
|
+
path: [],
|
|
99
|
+
ref: value as string,
|
|
100
|
+
cursor: { kind: "here" },
|
|
101
|
+
fold: "allMustPass",
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/** `$dynamicRef` (D8): resolves with dynamic-scope rebinding. */
|
|
108
|
+
export const $dynamicRef: KeywordBehavior = {
|
|
109
|
+
id: `${VOCAB_CORE}#$dynamicRef`,
|
|
110
|
+
analyze: (value) => ({
|
|
111
|
+
...referenceFacts(value),
|
|
112
|
+
dynamicScopeSensitive: true,
|
|
113
|
+
}),
|
|
114
|
+
evaluate: (value, _cursor, ctx) =>
|
|
115
|
+
ctx.applyResolved(ctx.resolveDynamic(value as string)),
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// 2019-09 core vocabulary: $recursiveRef/$recursiveAnchor are D8's degenerate
|
|
119
|
+
// case — resolution lives in the engine, anchor indexing in the registry's
|
|
120
|
+
// identifier extractor, so both behaviors are one-liners.
|
|
121
|
+
|
|
122
|
+
/** 2019-09 core vocabulary URI. */
|
|
123
|
+
export const VOCAB_CORE_2019 =
|
|
124
|
+
"https://json-schema.org/draft/2019-09/vocab/core";
|
|
125
|
+
|
|
126
|
+
/** `$recursiveRef`, 2019-09's degenerate case of `$dynamicRef` (D8). */
|
|
127
|
+
export const $recursiveRef: KeywordBehavior = {
|
|
128
|
+
id: `${VOCAB_CORE_2019}#$recursiveRef`,
|
|
129
|
+
analyze: (value) => ({
|
|
130
|
+
...referenceFacts(value),
|
|
131
|
+
dynamicScopeSensitive: true,
|
|
132
|
+
}),
|
|
133
|
+
evaluate: (value, _cursor, ctx) =>
|
|
134
|
+
ctx.applyResolved(ctx.resolveRecursive(value as string)),
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
/** `$recursiveAnchor`: structural only, indexed by the registration walk. */
|
|
138
|
+
export const $recursiveAnchor: KeywordBehavior = structural(
|
|
139
|
+
`${VOCAB_CORE_2019}#$recursiveAnchor`,
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
/** `$defs`: a map of named subschemas, reachable only by reference. */
|
|
143
|
+
export const $defs: KeywordBehavior = {
|
|
144
|
+
id: `${VOCAB_CORE}#$defs`,
|
|
145
|
+
structural: true,
|
|
146
|
+
analyze: mapPositions,
|
|
147
|
+
evaluate: () => true,
|
|
148
|
+
lower: () => {
|
|
149
|
+
/* contents are reachable only by reference */
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
/** The 2020-12 core vocabulary's keyword behaviors, by name. */
|
|
154
|
+
export const coreVocabulary: Record<string, KeywordBehavior> = {
|
|
155
|
+
$ref,
|
|
156
|
+
$dynamicRef,
|
|
157
|
+
$defs,
|
|
158
|
+
$id: structural(`${VOCAB_CORE}#$id`),
|
|
159
|
+
$schema: structural(`${VOCAB_CORE}#$schema`),
|
|
160
|
+
$anchor: structural(`${VOCAB_CORE}#$anchor`),
|
|
161
|
+
// Dynamic-anchor indexing happens in the registration walk (D8).
|
|
162
|
+
$dynamicAnchor: structural(`${VOCAB_CORE}#$dynamicAnchor`),
|
|
163
|
+
$vocabulary: structural(`${VOCAB_CORE}#$vocabulary`),
|
|
164
|
+
// $comment's value MUST NOT be collected as an annotation.
|
|
165
|
+
$comment: structural(`${VOCAB_CORE}#$comment`),
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
export { mapPositions, SELF };
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// The `format` keyword's asserting behavior (M7). Annotation-only `format`
|
|
2
|
+
// is the annotationOnly factory in each dialect module; this file supplies
|
|
3
|
+
// the assertion side: the format-table contract, the format-assertion
|
|
4
|
+
// vocabulary, and the best-effort configuration switch.
|
|
5
|
+
//
|
|
6
|
+
// Two assertion mechanisms with deliberately different unknown-format
|
|
7
|
+
// behavior:
|
|
8
|
+
// - format-assertion VOCABULARY: promises assertion, so a format the
|
|
9
|
+
// table cannot assert is refused at registration (UnknownFormatError).
|
|
10
|
+
// - format-annotation + the `assertFormats` configuration: best effort —
|
|
11
|
+
// recognized formats assert, unrecognized formats fall back to
|
|
12
|
+
// annotation-only.
|
|
13
|
+
|
|
14
|
+
import { JsonValue, JsonType, jsonTypeOf } from "../json.js";
|
|
15
|
+
import { KeywordBehavior } from "../dialect.js";
|
|
16
|
+
import { lowerIR } from "../lowering.js";
|
|
17
|
+
|
|
18
|
+
/** 2020-12 format-assertion vocabulary URI. */
|
|
19
|
+
export const VOCAB_FORMAT_ASSERTION =
|
|
20
|
+
"https://json-schema.org/draft/2020-12/vocab/format-assertion";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* One format's definition. `types` scopes the assertion: instances of
|
|
24
|
+
* other types are vacuously valid (every standard JSON Schema format is
|
|
25
|
+
* string-scoped, but the shape admits non-string formats — the
|
|
26
|
+
* OpenAPI-registered int32/int64/double family applies to numbers).
|
|
27
|
+
*/
|
|
28
|
+
export interface FormatDefinition {
|
|
29
|
+
/** instance types the format constrains; default ["string"] */
|
|
30
|
+
readonly types?: readonly (JsonType | "integer")[];
|
|
31
|
+
/** true when the (type-scoped) instance conforms */
|
|
32
|
+
test(value: JsonValue): boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Format name → definition. */
|
|
36
|
+
export type FormatTable = Readonly<Record<string, FormatDefinition>>;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* A schema uses a format the engine cannot assert while the
|
|
40
|
+
* format-assertion vocabulary is in effect. Thrown at registration:
|
|
41
|
+
* that vocabulary promises assertion, so unsupported formats are refused,
|
|
42
|
+
* never silently annotated.
|
|
43
|
+
*/
|
|
44
|
+
export class UnknownFormatError extends Error {}
|
|
45
|
+
|
|
46
|
+
const appliesTo = (
|
|
47
|
+
types: readonly (JsonType | "integer")[],
|
|
48
|
+
value: JsonValue,
|
|
49
|
+
): boolean =>
|
|
50
|
+
types.some((t) =>
|
|
51
|
+
t === "integer"
|
|
52
|
+
? typeof value === "number" && Number.isInteger(value)
|
|
53
|
+
: jsonTypeOf(value) === t,
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Builds an asserting `format` behavior over a table. `refuseUnknown`
|
|
58
|
+
* selects the vocabulary posture (refuse at registration) versus the
|
|
59
|
+
* best-effort configuration posture (annotate unknowns).
|
|
60
|
+
*
|
|
61
|
+
* Single-table contract: `lower()` resolves the format definition against
|
|
62
|
+
* the closed-over `table` at compile time, so it is correct only when that
|
|
63
|
+
* table is the compiling engine's `formats` — true by construction for the
|
|
64
|
+
* Engine-constructed instances, the only production call sites (both the
|
|
65
|
+
* `assertFormats` configuration and the format-assertion vocabulary close over
|
|
66
|
+
* `options.formats`, which the compiler reads back through `engine.formats`).
|
|
67
|
+
* A custom dialect that wires `assertingFormat` with a foreign table must drop
|
|
68
|
+
* `lower` (accepting the interpreter fallback) rather than compile against a
|
|
69
|
+
* table the artifact's runtime would not carry.
|
|
70
|
+
*/
|
|
71
|
+
export function assertingFormat(
|
|
72
|
+
id: string,
|
|
73
|
+
table: FormatTable,
|
|
74
|
+
refuseUnknown: boolean,
|
|
75
|
+
): KeywordBehavior {
|
|
76
|
+
return {
|
|
77
|
+
id,
|
|
78
|
+
analyze: (value) => {
|
|
79
|
+
if (
|
|
80
|
+
refuseUnknown &&
|
|
81
|
+
typeof value === "string" &&
|
|
82
|
+
!Object.hasOwn(table, value)
|
|
83
|
+
) {
|
|
84
|
+
throw new UnknownFormatError(
|
|
85
|
+
`format '${value}' is not supported; the format-assertion ` +
|
|
86
|
+
"vocabulary requires refusing formats it cannot assert",
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
// Report only formats the table can assert — the exact set lower()
|
|
90
|
+
// emits a formatTest for (an unknown format under the best-effort
|
|
91
|
+
// posture falls back to annotation-only and needs no table entry). This
|
|
92
|
+
// keeps plan.formats == the artifact's used-format set, so the runtime's
|
|
93
|
+
// missing-definition guard stays unreachable via public paths.
|
|
94
|
+
return typeof value === "string" && Object.hasOwn(table, value)
|
|
95
|
+
? { formats: [value] }
|
|
96
|
+
: {};
|
|
97
|
+
},
|
|
98
|
+
lower: (value, lctx) => {
|
|
99
|
+
// Annotate first, unconditionally — evaluate()'s annotate-before-
|
|
100
|
+
// assertion order (format's annotation value is the name).
|
|
101
|
+
lctx.emit({ kind: "annotate" });
|
|
102
|
+
if (typeof value !== "string") return; // metaschema's concern
|
|
103
|
+
// Resolve against the closed-over table (the compiling engine's, per the
|
|
104
|
+
// single-table contract above); the name is a schema constant. A
|
|
105
|
+
// refuseUnknown instance never reaches here undefined — analyze() threw
|
|
106
|
+
// at registration — so an undefined definition is the best-effort
|
|
107
|
+
// posture's unrecognized format: fall back to annotation-only, emitting
|
|
108
|
+
// nothing further (evaluate() parity).
|
|
109
|
+
const definition = Object.hasOwn(table, value) ? table[value] : undefined;
|
|
110
|
+
if (definition === undefined) return;
|
|
111
|
+
const { and, not, typeIs, when, failWith, constant, formatTest } =
|
|
112
|
+
lowerIR;
|
|
113
|
+
lctx.emit(
|
|
114
|
+
when(
|
|
115
|
+
and(
|
|
116
|
+
typeIs(lctx.instance, ...(definition.types ?? ["string"])),
|
|
117
|
+
not(formatTest(value, lctx.instance)),
|
|
118
|
+
),
|
|
119
|
+
[
|
|
120
|
+
failWith(
|
|
121
|
+
{ format: constant(value) },
|
|
122
|
+
"must match format '" + value + "'",
|
|
123
|
+
),
|
|
124
|
+
],
|
|
125
|
+
),
|
|
126
|
+
);
|
|
127
|
+
},
|
|
128
|
+
evaluate: (value, cursor, ctx) => {
|
|
129
|
+
// The annotation is recorded regardless of assertion outcome
|
|
130
|
+
// (format's annotation value is the format name).
|
|
131
|
+
ctx.annotate();
|
|
132
|
+
if (typeof value !== "string") return true; // metaschema's concern
|
|
133
|
+
const definition = Object.hasOwn(table, value) ? table[value] : undefined;
|
|
134
|
+
if (definition === undefined) return true; // best-effort fallback
|
|
135
|
+
if (!appliesTo(definition.types ?? ["string"], cursor.value)) {
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
if (definition.test(cursor.value)) return true;
|
|
139
|
+
ctx.error(`must match format '${value}'`, { format: value });
|
|
140
|
+
return false;
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
}
|