typesea 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +85 -6
- package/README.md +143 -28
- package/dist/adapters/index.d.ts +50 -8
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +169 -48
- package/dist/aot/index.d.ts +19 -3
- package/dist/aot/index.d.ts.map +1 -1
- package/dist/aot/index.js +115 -17
- package/dist/async/index.d.ts +28 -56
- package/dist/async/index.d.ts.map +1 -1
- package/dist/async/index.js +94 -37
- package/dist/builders/composite.d.ts +43 -9
- package/dist/builders/composite.d.ts.map +1 -1
- package/dist/builders/composite.js +100 -17
- package/dist/builders/index.d.ts +8 -5
- package/dist/builders/index.d.ts.map +1 -1
- package/dist/builders/index.js +7 -4
- package/dist/builders/modifier.d.ts +36 -5
- package/dist/builders/modifier.d.ts.map +1 -1
- package/dist/builders/modifier.js +52 -5
- package/dist/builders/object/guard.d.ts +72 -24
- package/dist/builders/object/guard.d.ts.map +1 -1
- package/dist/builders/object/guard.js +139 -29
- package/dist/builders/object/index.d.ts +4 -2
- package/dist/builders/object/index.d.ts.map +1 -1
- package/dist/builders/object/index.js +3 -1
- package/dist/builders/object/schema.d.ts +88 -11
- package/dist/builders/object/schema.d.ts.map +1 -1
- package/dist/builders/object/schema.js +290 -23
- package/dist/builders/object/types.d.ts +20 -31
- package/dist/builders/object/types.d.ts.map +1 -1
- package/dist/builders/object/types.js +2 -0
- package/dist/builders/runtime.d.ts +40 -0
- package/dist/builders/runtime.d.ts.map +1 -0
- package/dist/builders/runtime.js +150 -0
- package/dist/builders/scalar.d.ts +49 -9
- package/dist/builders/scalar.d.ts.map +1 -1
- package/dist/builders/scalar.js +87 -9
- package/dist/builders/table.d.ts +35 -5
- package/dist/builders/table.d.ts.map +1 -1
- package/dist/builders/table.js +35 -5
- package/dist/builders/types.d.ts +20 -4
- package/dist/builders/types.d.ts.map +1 -1
- package/dist/builders/types.js +2 -0
- package/dist/compile/check-composite.d.ts +25 -2
- package/dist/compile/check-composite.d.ts.map +1 -1
- package/dist/compile/check-composite.js +699 -27
- package/dist/compile/check-scalar.d.ts +88 -0
- package/dist/compile/check-scalar.d.ts.map +1 -1
- package/dist/compile/check-scalar.js +570 -3
- package/dist/compile/check.d.ts +12 -0
- package/dist/compile/check.d.ts.map +1 -1
- package/dist/compile/check.js +62 -3
- package/dist/compile/context.d.ts +47 -9
- package/dist/compile/context.d.ts.map +1 -1
- package/dist/compile/context.js +53 -8
- package/dist/compile/first.d.ts +26 -0
- package/dist/compile/first.d.ts.map +1 -0
- package/dist/compile/first.js +850 -0
- package/dist/compile/graph-predicate.d.ts +4 -2
- package/dist/compile/graph-predicate.d.ts.map +1 -1
- package/dist/compile/graph-predicate.js +2272 -165
- package/dist/compile/guard.d.ts +16 -24
- package/dist/compile/guard.d.ts.map +1 -1
- package/dist/compile/guard.js +202 -72
- package/dist/compile/index.d.ts +3 -1
- package/dist/compile/index.d.ts.map +1 -1
- package/dist/compile/index.js +2 -0
- package/dist/compile/issue.d.ts +110 -0
- package/dist/compile/issue.d.ts.map +1 -1
- package/dist/compile/issue.js +184 -1
- package/dist/compile/names.d.ts +12 -2
- package/dist/compile/names.d.ts.map +1 -1
- package/dist/compile/names.js +19 -3
- package/dist/compile/predicate.d.ts +24 -0
- package/dist/compile/predicate.d.ts.map +1 -1
- package/dist/compile/predicate.js +287 -10
- package/dist/compile/runtime.d.ts +100 -13
- package/dist/compile/runtime.d.ts.map +1 -1
- package/dist/compile/runtime.js +56 -6
- package/dist/compile/source.d.ts +10 -2
- package/dist/compile/source.d.ts.map +1 -1
- package/dist/compile/source.js +385 -26
- package/dist/compile/types.d.ts +22 -0
- package/dist/compile/types.d.ts.map +1 -1
- package/dist/compile/types.js +2 -0
- package/dist/decoder/index.d.ts +92 -46
- package/dist/decoder/index.d.ts.map +1 -1
- package/dist/decoder/index.js +266 -39
- package/dist/evaluate/check-composite.d.ts +111 -2
- package/dist/evaluate/check-composite.d.ts.map +1 -1
- package/dist/evaluate/check-composite.js +343 -8
- package/dist/evaluate/check-scalar.d.ts +25 -0
- package/dist/evaluate/check-scalar.d.ts.map +1 -1
- package/dist/evaluate/check-scalar.js +124 -3
- package/dist/evaluate/check.d.ts +7 -0
- package/dist/evaluate/check.d.ts.map +1 -1
- package/dist/evaluate/check.js +62 -4
- package/dist/evaluate/index.d.ts +2 -0
- package/dist/evaluate/index.d.ts.map +1 -1
- package/dist/evaluate/index.js +2 -0
- package/dist/evaluate/issue.d.ts +11 -1
- package/dist/evaluate/issue.d.ts.map +1 -1
- package/dist/evaluate/issue.js +15 -1
- package/dist/evaluate/predicate.d.ts +16 -5
- package/dist/evaluate/predicate.d.ts.map +1 -1
- package/dist/evaluate/predicate.js +20 -5
- package/dist/evaluate/shared.d.ts +78 -13
- package/dist/evaluate/shared.d.ts.map +1 -1
- package/dist/evaluate/shared.js +101 -8
- package/dist/evaluate/state.d.ts +35 -13
- package/dist/evaluate/state.d.ts.map +1 -1
- package/dist/evaluate/state.js +35 -2
- package/dist/guard/array.d.ts +48 -0
- package/dist/guard/array.d.ts.map +1 -0
- package/dist/guard/array.js +84 -0
- package/dist/guard/base.d.ts +111 -31
- package/dist/guard/base.d.ts.map +1 -1
- package/dist/guard/base.js +165 -32
- package/dist/guard/date.d.ts +34 -0
- package/dist/guard/date.d.ts.map +1 -0
- package/dist/guard/date.js +60 -0
- package/dist/guard/error.d.ts +10 -5
- package/dist/guard/error.d.ts.map +1 -1
- package/dist/guard/error.js +10 -5
- package/dist/guard/index.d.ts +4 -0
- package/dist/guard/index.d.ts.map +1 -1
- package/dist/guard/index.js +4 -0
- package/dist/guard/number.d.ts +86 -11
- package/dist/guard/number.d.ts.map +1 -1
- package/dist/guard/number.js +159 -11
- package/dist/guard/props.d.ts +27 -3
- package/dist/guard/props.d.ts.map +1 -1
- package/dist/guard/props.js +27 -3
- package/dist/guard/read.d.ts +115 -10
- package/dist/guard/read.d.ts.map +1 -1
- package/dist/guard/read.js +185 -10
- package/dist/guard/registry.d.ts +12 -2
- package/dist/guard/registry.d.ts.map +1 -1
- package/dist/guard/registry.js +15 -3
- package/dist/guard/string.d.ts +115 -13
- package/dist/guard/string.d.ts.map +1 -1
- package/dist/guard/string.js +250 -13
- package/dist/guard/types.d.ts +110 -40
- package/dist/guard/types.d.ts.map +1 -1
- package/dist/guard/types.js +2 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/internal/index.d.ts +42 -6
- package/dist/internal/index.d.ts.map +1 -1
- package/dist/internal/index.js +51 -8
- package/dist/ir/builder.d.ts +17 -127
- package/dist/ir/builder.d.ts.map +1 -1
- package/dist/ir/builder.js +80 -137
- package/dist/ir/freeze.d.ts +4 -0
- package/dist/ir/freeze.d.ts.map +1 -1
- package/dist/ir/freeze.js +66 -0
- package/dist/ir/index.d.ts +3 -1
- package/dist/ir/index.d.ts.map +1 -1
- package/dist/ir/index.js +2 -0
- package/dist/ir/regexp.d.ts +2 -0
- package/dist/ir/regexp.d.ts.map +1 -1
- package/dist/ir/regexp.js +2 -0
- package/dist/ir/types.d.ts +94 -56
- package/dist/ir/types.d.ts.map +1 -1
- package/dist/ir/types.js +2 -0
- package/dist/ir/validate.d.ts +8 -1
- package/dist/ir/validate.d.ts.map +1 -1
- package/dist/ir/validate.js +511 -61
- package/dist/issue/index.d.ts +42 -10
- package/dist/issue/index.d.ts.map +1 -1
- package/dist/issue/index.js +65 -11
- package/dist/json-schema/emit-combinator.d.ts +44 -4
- package/dist/json-schema/emit-combinator.d.ts.map +1 -1
- package/dist/json-schema/emit-combinator.js +44 -4
- package/dist/json-schema/emit-composite.d.ts +16 -2
- package/dist/json-schema/emit-composite.d.ts.map +1 -1
- package/dist/json-schema/emit-composite.js +81 -13
- package/dist/json-schema/emit-scalar.d.ts +26 -3
- package/dist/json-schema/emit-scalar.d.ts.map +1 -1
- package/dist/json-schema/emit-scalar.js +124 -10
- package/dist/json-schema/emit-types.d.ts +11 -1
- package/dist/json-schema/emit-types.d.ts.map +1 -1
- package/dist/json-schema/emit-types.js +2 -0
- package/dist/json-schema/emit.d.ts +12 -1
- package/dist/json-schema/emit.d.ts.map +1 -1
- package/dist/json-schema/emit.js +23 -3
- package/dist/json-schema/freeze.d.ts +13 -2
- package/dist/json-schema/freeze.d.ts.map +1 -1
- package/dist/json-schema/freeze.js +41 -8
- package/dist/json-schema/index.d.ts +16 -2
- package/dist/json-schema/index.d.ts.map +1 -1
- package/dist/json-schema/index.js +23 -3
- package/dist/json-schema/issue.d.ts +4 -1
- package/dist/json-schema/issue.d.ts.map +1 -1
- package/dist/json-schema/issue.js +4 -1
- package/dist/json-schema/read.d.ts +24 -3
- package/dist/json-schema/read.d.ts.map +1 -1
- package/dist/json-schema/read.js +59 -12
- package/dist/json-schema/types.d.ts +45 -16
- package/dist/json-schema/types.d.ts.map +1 -1
- package/dist/json-schema/types.js +2 -0
- package/dist/kind/index.d.ts +40 -28
- package/dist/kind/index.d.ts.map +1 -1
- package/dist/kind/index.js +41 -13
- package/dist/lower/index.d.ts +6 -1
- package/dist/lower/index.d.ts.map +1 -1
- package/dist/lower/index.js +462 -46
- package/dist/message/index.d.ts +64 -10
- package/dist/message/index.d.ts.map +1 -1
- package/dist/message/index.js +155 -17
- package/dist/optimize/algebraic.d.ts +54 -0
- package/dist/optimize/algebraic.d.ts.map +1 -0
- package/dist/optimize/algebraic.js +314 -0
- package/dist/optimize/compact.d.ts +8 -1
- package/dist/optimize/compact.d.ts.map +1 -1
- package/dist/optimize/compact.js +13 -2
- package/dist/optimize/domain.d.ts +16 -0
- package/dist/optimize/domain.d.ts.map +1 -0
- package/dist/optimize/domain.js +619 -0
- package/dist/optimize/fold-boolean.d.ts +17 -2
- package/dist/optimize/fold-boolean.d.ts.map +1 -1
- package/dist/optimize/fold-boolean.js +59 -14
- package/dist/optimize/fold-common.d.ts +43 -8
- package/dist/optimize/fold-common.d.ts.map +1 -1
- package/dist/optimize/fold-common.js +37 -6
- package/dist/optimize/fold-constraints.d.ts +33 -0
- package/dist/optimize/fold-constraints.d.ts.map +1 -0
- package/dist/optimize/fold-constraints.js +484 -0
- package/dist/optimize/fold-scalar.d.ts +98 -13
- package/dist/optimize/fold-scalar.d.ts.map +1 -1
- package/dist/optimize/fold-scalar.js +98 -13
- package/dist/optimize/fold.d.ts +8 -1
- package/dist/optimize/fold.d.ts.map +1 -1
- package/dist/optimize/fold.js +22 -2
- package/dist/optimize/index.d.ts +9 -1
- package/dist/optimize/index.d.ts.map +1 -1
- package/dist/optimize/index.js +18 -3
- package/dist/optimize/map-node.d.ts +3 -1
- package/dist/optimize/map-node.d.ts.map +1 -1
- package/dist/optimize/map-node.js +48 -3
- package/dist/optimize/peephole.d.ts +16 -0
- package/dist/optimize/peephole.d.ts.map +1 -0
- package/dist/optimize/peephole.js +254 -0
- package/dist/optimize/remap.d.ts +2 -0
- package/dist/optimize/remap.d.ts.map +1 -1
- package/dist/optimize/remap.js +2 -0
- package/dist/optimize/rewrite.d.ts +13 -8
- package/dist/optimize/rewrite.d.ts.map +1 -1
- package/dist/optimize/rewrite.js +13 -8
- package/dist/plan/cache.d.ts +9 -3
- package/dist/plan/cache.d.ts.map +1 -1
- package/dist/plan/cache.js +34 -6
- package/dist/plan/index.d.ts +2 -0
- package/dist/plan/index.d.ts.map +1 -1
- package/dist/plan/index.js +2 -0
- package/dist/plan/predicate.d.ts +2 -0
- package/dist/plan/predicate.d.ts.map +1 -1
- package/dist/plan/predicate.js +298 -29
- package/dist/plan/schema-predicate.d.ts +6 -0
- package/dist/plan/schema-predicate.d.ts.map +1 -1
- package/dist/plan/schema-predicate.js +382 -19
- package/dist/plan/types.d.ts +2 -0
- package/dist/plan/types.d.ts.map +1 -1
- package/dist/plan/types.js +2 -0
- package/dist/result/index.d.ts +19 -5
- package/dist/result/index.d.ts.map +1 -1
- package/dist/result/index.js +10 -2
- package/dist/schema/common.d.ts +69 -6
- package/dist/schema/common.d.ts.map +1 -1
- package/dist/schema/common.js +104 -10
- package/dist/schema/freeze.d.ts +4 -0
- package/dist/schema/freeze.d.ts.map +1 -1
- package/dist/schema/freeze.js +40 -0
- package/dist/schema/index.d.ts +5 -2
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +4 -1
- package/dist/schema/lazy.d.ts +4 -0
- package/dist/schema/lazy.d.ts.map +1 -1
- package/dist/schema/lazy.js +4 -0
- package/dist/schema/literal.d.ts +7 -1
- package/dist/schema/literal.d.ts.map +1 -1
- package/dist/schema/literal.js +7 -1
- package/dist/schema/types.d.ts +109 -100
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/schema/types.js +13 -2
- package/dist/schema/undefined.d.ts +17 -0
- package/dist/schema/undefined.d.ts.map +1 -0
- package/dist/schema/undefined.js +77 -0
- package/dist/schema/validate.d.ts +8 -1
- package/dist/schema/validate.d.ts.map +1 -1
- package/dist/schema/validate.js +255 -57
- package/docs/api.md +128 -8
- package/docs/assets/benchmark-headline.svg +163 -0
- package/docs/engine-notes.md +62 -15
- package/docs/index.html +1340 -702
- package/docs/ko/api.md +375 -0
- package/docs/ko/engine-notes.md +156 -0
- package/docs/ko/readme.md +378 -0
- package/package.json +66 -65
|
@@ -0,0 +1,850 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file first.ts
|
|
3
|
+
* @brief First-fault diagnostic validator function table emitter.
|
|
4
|
+
* @details Generated first-fault helpers return one frozen issue immediately,
|
|
5
|
+
* keeping hot rejection diagnostics out of the full issue collector.
|
|
6
|
+
*/
|
|
7
|
+
import { ArrayCheckTag, DateCheckTag, NumberCheckTag, ObjectModeTag, PresenceTag, SchemaTag, StringCheckTag } from "../kind/index.js";
|
|
8
|
+
import { EMAIL_PATTERN, IPV4_PATTERN, IPV6_PATTERN, ISO_DATETIME_PATTERN, ISO_DATE_PATTERN, ULID_PATTERN, URL_PATTERN, UUID_PATTERN, schemaCanAcceptUndefined } from "../schema/index.js";
|
|
9
|
+
import { pushLiteral, pushRegex, pushSchema, stringRef } from "./context.js";
|
|
10
|
+
import { stringLiteral } from "./names.js";
|
|
11
|
+
import { emitUnion } from "./predicate.js";
|
|
12
|
+
/**
|
|
13
|
+
* @brief Emit or reuse a first-fault diagnostic function.
|
|
14
|
+
* @details Each schema identity maps to one generated first-fault function so
|
|
15
|
+
* recursive and shared schemas keep stable function identities in source.
|
|
16
|
+
* @param schema Schema whose first diagnostic should be emitted.
|
|
17
|
+
* @param context Shared code-generation context.
|
|
18
|
+
* @returns Generated first-fault function name.
|
|
19
|
+
*/
|
|
20
|
+
export function emitFirstFunction(schema, context) {
|
|
21
|
+
const cached = context.firstFunctionNames.get(schema);
|
|
22
|
+
if (cached !== undefined) {
|
|
23
|
+
return cached;
|
|
24
|
+
}
|
|
25
|
+
const name = `f${String(context.firstFunctions.length)}`;
|
|
26
|
+
const source = {
|
|
27
|
+
name,
|
|
28
|
+
body: ""
|
|
29
|
+
};
|
|
30
|
+
context.firstFunctionNames.set(schema, name);
|
|
31
|
+
context.firstFunctions.push(source);
|
|
32
|
+
source.body = emitFirstBody(schema, "v", "p", context);
|
|
33
|
+
return name;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* @brief Emit all first-fault diagnostic functions.
|
|
37
|
+
* @details Generated functions return undefined on success or one frozen issue
|
|
38
|
+
* on failure. They never allocate an issue array.
|
|
39
|
+
* @param context Shared code-generation context with accumulated sources.
|
|
40
|
+
* @returns Concatenated JavaScript function declarations.
|
|
41
|
+
*/
|
|
42
|
+
export function emitFirstFunctions(context) {
|
|
43
|
+
const chunks = new Array(context.firstFunctions.length);
|
|
44
|
+
for (let index = 0; index < context.firstFunctions.length; index += 1) {
|
|
45
|
+
const source = context.firstFunctions[index];
|
|
46
|
+
if (source !== undefined) {
|
|
47
|
+
chunks[index] = `function ${source.name}(v,p){${source.body}}`;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return chunks.join("");
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* @brief Emit the first-fault body for one schema.
|
|
54
|
+
* @details The body mirrors check() diagnostic order but returns as soon as the
|
|
55
|
+
* first machine-readable issue is known.
|
|
56
|
+
* @param schema Schema represented by this function.
|
|
57
|
+
* @param value Generated expression for the candidate value.
|
|
58
|
+
* @param path Generated expression for the mutable path stack.
|
|
59
|
+
* @param context Shared code-generation context.
|
|
60
|
+
* @returns JavaScript source for first-fault diagnostic collection.
|
|
61
|
+
*/
|
|
62
|
+
function emitFirstBody(schema, value, path, context) {
|
|
63
|
+
switch (schema.tag) {
|
|
64
|
+
case SchemaTag.Unknown:
|
|
65
|
+
return "return;";
|
|
66
|
+
case SchemaTag.Never:
|
|
67
|
+
return emitFirstIssue(path, "expected_never", "never", `a(${value})`);
|
|
68
|
+
case SchemaTag.String:
|
|
69
|
+
return emitStringFirst(schema, value, path, context);
|
|
70
|
+
case SchemaTag.Number:
|
|
71
|
+
return emitNumberFirst(schema, value, path);
|
|
72
|
+
case SchemaTag.Date:
|
|
73
|
+
return emitDateFirst(schema, value, path);
|
|
74
|
+
case SchemaTag.BigInt:
|
|
75
|
+
return `if(typeof ${value}!=="bigint")${emitFirstIssue(path, "expected_bigint", "bigint", `a(${value})`)}`;
|
|
76
|
+
case SchemaTag.Symbol:
|
|
77
|
+
return `if(typeof ${value}!=="symbol")${emitFirstIssue(path, "expected_symbol", "symbol", `a(${value})`)}`;
|
|
78
|
+
case SchemaTag.Boolean:
|
|
79
|
+
return `if(typeof ${value}!=="boolean")${emitFirstIssue(path, "expected_boolean", "boolean", `a(${value})`)}`;
|
|
80
|
+
case SchemaTag.Literal:
|
|
81
|
+
return emitLiteralFirst(schema.value, value, path, context);
|
|
82
|
+
case SchemaTag.Array:
|
|
83
|
+
return emitArrayFirst(schema, value, path, context);
|
|
84
|
+
case SchemaTag.Tuple:
|
|
85
|
+
if (schema.rest !== undefined) {
|
|
86
|
+
return emitDynamicFirst(schema, value, path, context);
|
|
87
|
+
}
|
|
88
|
+
return emitTupleFirst(schema.items, value, path, context);
|
|
89
|
+
case SchemaTag.Record:
|
|
90
|
+
return emitRecordFirst(schema.value, value, path, context);
|
|
91
|
+
case SchemaTag.Map:
|
|
92
|
+
case SchemaTag.Set:
|
|
93
|
+
case SchemaTag.InstanceOf:
|
|
94
|
+
case SchemaTag.Property:
|
|
95
|
+
return emitDynamicFirst(schema, value, path, context);
|
|
96
|
+
case SchemaTag.Object:
|
|
97
|
+
return emitObjectFirst(schema, value, path, context);
|
|
98
|
+
case SchemaTag.Union:
|
|
99
|
+
return `if(!${emitUnion(schema.options, value, context)})${emitFirstIssue(path, "expected_union", "union", `a(${value})`)}`;
|
|
100
|
+
case SchemaTag.Intersection:
|
|
101
|
+
return [
|
|
102
|
+
emitChildFirst(schema.left, value, path, context),
|
|
103
|
+
emitChildFirst(schema.right, value, path, context)
|
|
104
|
+
].join("");
|
|
105
|
+
case SchemaTag.Optional:
|
|
106
|
+
case SchemaTag.Undefinedable:
|
|
107
|
+
return `if(${value}!==undefined){${emitChildFirst(schema.inner, value, path, context)}}`;
|
|
108
|
+
case SchemaTag.Nullable:
|
|
109
|
+
return `if(${value}!==null){${emitChildFirst(schema.inner, value, path, context)}}`;
|
|
110
|
+
case SchemaTag.DiscriminatedUnion:
|
|
111
|
+
return emitDiscriminatedUnionFirst(schema.key, schema.cases, value, path, context);
|
|
112
|
+
case SchemaTag.Brand:
|
|
113
|
+
return emitChildFirst(schema.inner, value, path, context);
|
|
114
|
+
case SchemaTag.Lazy:
|
|
115
|
+
case SchemaTag.Refine:
|
|
116
|
+
return emitDynamicFirst(schema, value, path, context);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* @brief Emit first-fault Date diagnostics.
|
|
121
|
+
* @param schema Date schema with normalized checks.
|
|
122
|
+
* @param value Generated expression for the candidate value.
|
|
123
|
+
* @param path Generated path expression.
|
|
124
|
+
* @returns JavaScript source returning one Date issue on failure.
|
|
125
|
+
*/
|
|
126
|
+
function emitDateFirst(schema, value, path) {
|
|
127
|
+
const parts = [
|
|
128
|
+
`if(!dg(${value}))${emitFirstIssue(path, "expected_date", "valid Date", `a(${value})`)}`
|
|
129
|
+
];
|
|
130
|
+
const checks = schema.checks;
|
|
131
|
+
for (let index = 0; index < checks.length; index += 1) {
|
|
132
|
+
const check = checks[index];
|
|
133
|
+
if (check === undefined) {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const actual = `new Date(dt(${value})).toISOString()`;
|
|
137
|
+
switch (check.tag) {
|
|
138
|
+
case DateCheckTag.Min:
|
|
139
|
+
parts.push(`if(dt(${value})<${String(check.value)})${emitFirstIssue(path, "expected_gte", `>= ${new Date(check.value).toISOString()}`, actual)}`);
|
|
140
|
+
break;
|
|
141
|
+
case DateCheckTag.Max:
|
|
142
|
+
parts.push(`if(dt(${value})>${String(check.value)})${emitFirstIssue(path, "expected_lte", `<= ${new Date(check.value).toISOString()}`, actual)}`);
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return parts.join("");
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* @brief Emit a first-fault fallback call for runtime-only schema nodes.
|
|
150
|
+
* @param schema Schema captured in the dynamic side table.
|
|
151
|
+
* @param value Generated candidate value expression.
|
|
152
|
+
* @param path Generated path expression.
|
|
153
|
+
* @param context Shared emission context.
|
|
154
|
+
* @returns JavaScript source delegating first-fault diagnostics to the interpreter.
|
|
155
|
+
*/
|
|
156
|
+
function emitDynamicFirst(schema, value, path, context) {
|
|
157
|
+
return `return mf(${String(pushSchema(context, schema))},${value},${path});`;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* @brief Emit first-fault string diagnostics.
|
|
161
|
+
* @details String constraints run only after the type guard succeeds, matching
|
|
162
|
+
* the full collector while avoiding later checks after the first issue.
|
|
163
|
+
* @param schema String schema with scalar checks.
|
|
164
|
+
* @param value Generated expression for the candidate value.
|
|
165
|
+
* @param path Generated expression for the current path.
|
|
166
|
+
* @param context Shared code-generation context.
|
|
167
|
+
* @returns JavaScript source for string first-fault diagnostics.
|
|
168
|
+
*/
|
|
169
|
+
function emitStringFirst(schema, value, path, context) {
|
|
170
|
+
const parts = [
|
|
171
|
+
`if(typeof ${value}!=="string")${emitFirstIssue(path, "expected_string", "string", `a(${value})`)}`
|
|
172
|
+
];
|
|
173
|
+
const checks = schema.checks;
|
|
174
|
+
for (let index = 0; index < checks.length; index += 1) {
|
|
175
|
+
const check = checks[index];
|
|
176
|
+
if (check === undefined) {
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
switch (check.tag) {
|
|
180
|
+
case StringCheckTag.Min:
|
|
181
|
+
parts.push(`if(${value}.length<${String(check.value)})${emitFirstIssue(path, "expected_min_length", `length >= ${String(check.value)}`, `"length "+String(${value}.length)`)}`);
|
|
182
|
+
break;
|
|
183
|
+
case StringCheckTag.Max:
|
|
184
|
+
parts.push(`if(${value}.length>${String(check.value)})${emitFirstIssue(path, "expected_max_length", `length <= ${String(check.value)}`, `"length "+String(${value}.length)`)}`);
|
|
185
|
+
break;
|
|
186
|
+
case StringCheckTag.Regex:
|
|
187
|
+
parts.push(emitPatternFirst(value, path, check.regex, check.name, context));
|
|
188
|
+
break;
|
|
189
|
+
case StringCheckTag.Uuid:
|
|
190
|
+
parts.push(emitPatternFirst(value, path, UUID_PATTERN, "uuid", context));
|
|
191
|
+
break;
|
|
192
|
+
case StringCheckTag.Email:
|
|
193
|
+
parts.push(emitPatternFirst(value, path, EMAIL_PATTERN, "email", context));
|
|
194
|
+
break;
|
|
195
|
+
case StringCheckTag.Url:
|
|
196
|
+
parts.push(emitPatternFirst(value, path, URL_PATTERN, "url", context));
|
|
197
|
+
break;
|
|
198
|
+
case StringCheckTag.IsoDate:
|
|
199
|
+
parts.push(emitPatternFirst(value, path, ISO_DATE_PATTERN, "iso_date", context));
|
|
200
|
+
break;
|
|
201
|
+
case StringCheckTag.IsoDateTime:
|
|
202
|
+
parts.push(emitPatternFirst(value, path, ISO_DATETIME_PATTERN, "iso_datetime", context));
|
|
203
|
+
break;
|
|
204
|
+
case StringCheckTag.Ulid:
|
|
205
|
+
parts.push(emitPatternFirst(value, path, ULID_PATTERN, "ulid", context));
|
|
206
|
+
break;
|
|
207
|
+
case StringCheckTag.Ipv4:
|
|
208
|
+
parts.push(emitPatternFirst(value, path, IPV4_PATTERN, "ipv4", context));
|
|
209
|
+
break;
|
|
210
|
+
case StringCheckTag.Ipv6:
|
|
211
|
+
parts.push(emitPatternFirst(value, path, IPV6_PATTERN, "ipv6", context));
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return parts.join("");
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* @brief Emit first-fault number diagnostics.
|
|
219
|
+
* @details Finite-number proof is checked once before integer or bound checks.
|
|
220
|
+
* @param schema Number schema with scalar checks.
|
|
221
|
+
* @param value Generated expression for the candidate value.
|
|
222
|
+
* @param path Generated expression for the current path.
|
|
223
|
+
* @returns JavaScript source for number first-fault diagnostics.
|
|
224
|
+
*/
|
|
225
|
+
function emitNumberFirst(schema, value, path) {
|
|
226
|
+
const parts = [
|
|
227
|
+
`if(typeof ${value}!=="number"||!Number.isFinite(${value}))${emitFirstIssue(path, "expected_number", "number", `a(${value})`)}`
|
|
228
|
+
];
|
|
229
|
+
const checks = schema.checks;
|
|
230
|
+
for (let index = 0; index < checks.length; index += 1) {
|
|
231
|
+
const check = checks[index];
|
|
232
|
+
if (check === undefined) {
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
switch (check.tag) {
|
|
236
|
+
case NumberCheckTag.Integer:
|
|
237
|
+
parts.push(`if(!Number.isInteger(${value}))${emitFirstIssueExpr(path, "expected_integer", stringLiteral("integer"), stringLiteral("number"))}`);
|
|
238
|
+
break;
|
|
239
|
+
case NumberCheckTag.Gte:
|
|
240
|
+
parts.push(`if(${value}<${String(check.value)})${emitFirstIssue(path, "expected_gte", `>= ${String(check.value)}`, `String(${value})`)}`);
|
|
241
|
+
break;
|
|
242
|
+
case NumberCheckTag.Lte:
|
|
243
|
+
parts.push(`if(${value}>${String(check.value)})${emitFirstIssue(path, "expected_lte", `<= ${String(check.value)}`, `String(${value})`)}`);
|
|
244
|
+
break;
|
|
245
|
+
case NumberCheckTag.Gt:
|
|
246
|
+
parts.push(`if(${value}<=${String(check.value)})${emitFirstIssue(path, "expected_gt", `> ${String(check.value)}`, `String(${value})`)}`);
|
|
247
|
+
break;
|
|
248
|
+
case NumberCheckTag.Lt:
|
|
249
|
+
parts.push(`if(${value}>=${String(check.value)})${emitFirstIssue(path, "expected_lt", `< ${String(check.value)}`, `String(${value})`)}`);
|
|
250
|
+
break;
|
|
251
|
+
case NumberCheckTag.MultipleOf:
|
|
252
|
+
parts.push(`if(${value}%${String(check.value)}!==0)${emitFirstIssue(path, "expected_multiple_of", `multiple of ${String(check.value)}`, `String(${value})`)}`);
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return parts.join("");
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* @brief Emit first-fault literal diagnostics.
|
|
260
|
+
* @details Object.is preserves NaN and signed-zero semantics while literals stay
|
|
261
|
+
* in the generated side table.
|
|
262
|
+
* @param literal Literal value expected by the schema.
|
|
263
|
+
* @param value Generated expression for the candidate value.
|
|
264
|
+
* @param path Generated expression for the current path.
|
|
265
|
+
* @param context Shared code-generation context.
|
|
266
|
+
* @returns JavaScript source for literal first-fault diagnostics.
|
|
267
|
+
*/
|
|
268
|
+
function emitLiteralFirst(literal, value, path, context) {
|
|
269
|
+
const index = pushLiteral(context, literal);
|
|
270
|
+
return `if(!Object.is(${value},l[${String(index)}]))${emitFirstIssueExpr(path, "expected_literal", `le(l[${String(index)}])`, `a(${value})`)}`;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* @brief Emit first-fault array diagnostics.
|
|
274
|
+
* @details Safe mode keeps descriptor reads so accessors are reported without
|
|
275
|
+
* executing user getters. Unsafe modes mirror the fast diagnostic contract.
|
|
276
|
+
* @param item Schema applied to each array slot.
|
|
277
|
+
* @param value Generated expression for the candidate value.
|
|
278
|
+
* @param path Generated expression for the current path.
|
|
279
|
+
* @param context Shared code-generation context.
|
|
280
|
+
* @returns JavaScript source for array first-fault diagnostics.
|
|
281
|
+
*/
|
|
282
|
+
function emitArrayFirst(schema, value, path, context) {
|
|
283
|
+
if (isUnsafeMode(context)) {
|
|
284
|
+
return emitUnsafeArrayFirst(schema, value, path, context);
|
|
285
|
+
}
|
|
286
|
+
const item = schema.item;
|
|
287
|
+
const parts = [
|
|
288
|
+
`if(!Array.isArray(${value}))${emitFirstIssue(path, "expected_array", "array", `a(${value})`)}`,
|
|
289
|
+
emitArrayLengthFirst(schema, value, path)
|
|
290
|
+
];
|
|
291
|
+
if (schemaCanAcceptUndefined(item)) {
|
|
292
|
+
parts.push(`const xs=Object.getOwnPropertyNames(${value});`, "for(let xi=0;xi<xs.length;xi+=1){", "const key=xs[xi];", `if(!ai(key,${value}.length))continue;`, "const i=Number(key);", `const d=gp(${value},key);`, `if(d!==undefined&&!h.call(d,"value"))${emitFirstIssueAtSegment(path, "i", "expected_array", "data property", stringLiteral("accessor"))}`, `if(d!==undefined){${emitChildFirstAtSegment(item, "d.value", path, "i", context)}}`, "}");
|
|
293
|
+
return parts.join("");
|
|
294
|
+
}
|
|
295
|
+
parts.push(`for(let i=0;i<${value}.length;i+=1){`, `const d=gp(${value},i);`, `if(d===undefined){${emitChildFirstAtSegment(item, "undefined", path, "i", context)}}`, `else if(!h.call(d,"value"))${emitFirstIssueAtSegment(path, "i", "expected_array", "data property", stringLiteral("accessor"))}`, `else{${emitChildFirstAtSegment(item, "d.value", path, "i", context)}}`, "}");
|
|
296
|
+
return parts.join("");
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* @brief Emit fast-mode first-fault array diagnostics.
|
|
300
|
+
* @details Trusted arrays use direct indexed loads and skip descriptor checks.
|
|
301
|
+
* @param item Schema applied to each array slot.
|
|
302
|
+
* @param value Generated expression for the candidate value.
|
|
303
|
+
* @param path Generated expression for the current path.
|
|
304
|
+
* @param context Shared code-generation context.
|
|
305
|
+
* @returns JavaScript source for unsafe array first-fault diagnostics.
|
|
306
|
+
*/
|
|
307
|
+
function emitUnsafeArrayFirst(schema, value, path, context) {
|
|
308
|
+
const item = schema.item;
|
|
309
|
+
return [
|
|
310
|
+
`if(!Array.isArray(${value}))${emitFirstIssue(path, "expected_array", "array", `a(${value})`)}`,
|
|
311
|
+
emitArrayLengthFirst(schema, value, path),
|
|
312
|
+
`for(let i=0;i<${value}.length;i+=1){`,
|
|
313
|
+
`const av=${value}[i];`,
|
|
314
|
+
emitChildFirstAtSegment(item, "av", path, "i", context),
|
|
315
|
+
"}"
|
|
316
|
+
].join("");
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* @brief Emit first-fault array length diagnostics.
|
|
320
|
+
* @param schema Array schema with normalized checks.
|
|
321
|
+
* @param value Generated expression for the candidate array.
|
|
322
|
+
* @param path Generated expression for the current path.
|
|
323
|
+
* @returns JavaScript source returning the first length issue, or empty source.
|
|
324
|
+
*/
|
|
325
|
+
function emitArrayLengthFirst(schema, value, path) {
|
|
326
|
+
const parts = [];
|
|
327
|
+
const checks = schema.checks;
|
|
328
|
+
for (let index = 0; index < checks.length; index += 1) {
|
|
329
|
+
const check = checks[index];
|
|
330
|
+
if (check === undefined) {
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
switch (check.tag) {
|
|
334
|
+
case ArrayCheckTag.Min:
|
|
335
|
+
parts.push(`if(${value}.length<${String(check.value)})${emitFirstIssue(path, "expected_min_length", `length >= ${String(check.value)}`, `"length "+String(${value}.length)`)}`);
|
|
336
|
+
break;
|
|
337
|
+
case ArrayCheckTag.Max:
|
|
338
|
+
parts.push(`if(${value}.length>${String(check.value)})${emitFirstIssue(path, "expected_max_length", `length <= ${String(check.value)}`, `"length "+String(${value}.length)`)}`);
|
|
339
|
+
break;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return parts.join("");
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* @brief Emit first-fault tuple diagnostics.
|
|
346
|
+
* @details Tuple length is reported before item diagnostics, matching the full
|
|
347
|
+
* diagnostic collector's first issue ordering.
|
|
348
|
+
* @param items Tuple item schemas.
|
|
349
|
+
* @param value Generated expression for the candidate value.
|
|
350
|
+
* @param path Generated expression for the current path.
|
|
351
|
+
* @param context Shared code-generation context.
|
|
352
|
+
* @returns JavaScript source for tuple first-fault diagnostics.
|
|
353
|
+
*/
|
|
354
|
+
function emitTupleFirst(items, value, path, context) {
|
|
355
|
+
if (isUnsafeMode(context)) {
|
|
356
|
+
return emitUnsafeTupleFirst(items, value, path, context);
|
|
357
|
+
}
|
|
358
|
+
const parts = [
|
|
359
|
+
`if(!Array.isArray(${value}))${emitFirstIssue(path, "expected_tuple", "tuple", `a(${value})`)}`,
|
|
360
|
+
`if(${value}.length!==${String(items.length)})${emitFirstIssue(path, "expected_tuple_length", `length ${String(items.length)}`, `"length "+String(${value}.length)`)}`,
|
|
361
|
+
`const n=${value}.length<${String(items.length)}?${value}.length:${String(items.length)};`
|
|
362
|
+
];
|
|
363
|
+
for (let index = 0; index < items.length; index += 1) {
|
|
364
|
+
const item = items[index];
|
|
365
|
+
if (item === undefined) {
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
const segment = String(index);
|
|
369
|
+
parts.push(`if(${segment}<n){`, `const d=gp(${value},${segment});`, `if(d===undefined){${emitChildFirstAtSegment(item, "undefined", path, segment, context)}}`, `else if(!h.call(d,"value"))${emitFirstIssueAtSegment(path, segment, "expected_tuple", "data property", stringLiteral("accessor"))}`, `else{${emitChildFirstAtSegment(item, "d.value", path, segment, context)}}`, "}");
|
|
370
|
+
}
|
|
371
|
+
return parts.join("");
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* @brief Emit fast-mode first-fault tuple diagnostics.
|
|
375
|
+
* @details Trusted tuples use direct indexed reads after the length check.
|
|
376
|
+
* @param items Tuple item schemas.
|
|
377
|
+
* @param value Generated expression for the candidate value.
|
|
378
|
+
* @param path Generated expression for the current path.
|
|
379
|
+
* @param context Shared code-generation context.
|
|
380
|
+
* @returns JavaScript source for unsafe tuple first-fault diagnostics.
|
|
381
|
+
*/
|
|
382
|
+
function emitUnsafeTupleFirst(items, value, path, context) {
|
|
383
|
+
const parts = [
|
|
384
|
+
`if(!Array.isArray(${value}))${emitFirstIssue(path, "expected_tuple", "tuple", `a(${value})`)}`,
|
|
385
|
+
`if(${value}.length!==${String(items.length)})${emitFirstIssue(path, "expected_tuple_length", `length ${String(items.length)}`, `"length "+String(${value}.length)`)}`,
|
|
386
|
+
`const n=${value}.length<${String(items.length)}?${value}.length:${String(items.length)};`
|
|
387
|
+
];
|
|
388
|
+
for (let index = 0; index < items.length; index += 1) {
|
|
389
|
+
const item = items[index];
|
|
390
|
+
if (item !== undefined) {
|
|
391
|
+
const segment = String(index);
|
|
392
|
+
parts.push(`if(${segment}<n){`, `const tv=${value}[${segment}];`, emitChildFirstAtSegment(item, "tv", path, segment, context), "}");
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return parts.join("");
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* @brief Emit first-fault record diagnostics.
|
|
399
|
+
* @details Safe mode validates own enumerable data slots through descriptors.
|
|
400
|
+
* @param item Schema applied to each record value.
|
|
401
|
+
* @param value Generated expression for the candidate value.
|
|
402
|
+
* @param path Generated expression for the current path.
|
|
403
|
+
* @param context Shared code-generation context.
|
|
404
|
+
* @returns JavaScript source for record first-fault diagnostics.
|
|
405
|
+
*/
|
|
406
|
+
function emitRecordFirst(item, value, path, context) {
|
|
407
|
+
if (isUnsafeMode(context)) {
|
|
408
|
+
return emitUnsafeRecordFirst(item, value, path, context);
|
|
409
|
+
}
|
|
410
|
+
return [
|
|
411
|
+
`if(${objectRejectExpression(value)})${emitFirstIssue(path, "expected_record", "record", `a(${value})`)}`,
|
|
412
|
+
`for(const key in ${value}){`,
|
|
413
|
+
`if(!h.call(${value},key))continue;`,
|
|
414
|
+
`const d=gp(${value},key);`,
|
|
415
|
+
`if(d===undefined||!h.call(d,"value"))${emitFirstIssueAtSegment(path, "key", "expected_record", "data property", stringLiteral("accessor or missing"))}`,
|
|
416
|
+
emitChildFirstAtSegment(item, "d.value", path, "key", context),
|
|
417
|
+
"}"
|
|
418
|
+
].join("");
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* @brief Emit fast-mode first-fault record diagnostics.
|
|
422
|
+
* @details Unsafe record checks use direct reads and optional own-key filtering.
|
|
423
|
+
* @param item Schema applied to each record value.
|
|
424
|
+
* @param value Generated expression for the candidate value.
|
|
425
|
+
* @param path Generated expression for the current path.
|
|
426
|
+
* @param context Shared code-generation context.
|
|
427
|
+
* @returns JavaScript source for unsafe record first-fault diagnostics.
|
|
428
|
+
*/
|
|
429
|
+
function emitUnsafeRecordFirst(item, value, path, context) {
|
|
430
|
+
return [
|
|
431
|
+
`if(${objectRejectExpression(value)})${emitFirstIssue(path, "expected_record", "record", `a(${value})`)}`,
|
|
432
|
+
`for(const key in ${value}){`,
|
|
433
|
+
isUncheckedMode(context) ? "" : `if(!h.call(${value},key))continue;`,
|
|
434
|
+
`const rv=${value}[key];`,
|
|
435
|
+
emitChildFirstAtSegment(item, "rv", path, "key", context),
|
|
436
|
+
"}"
|
|
437
|
+
].join("");
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* @brief Emit first-fault object diagnostics.
|
|
441
|
+
* @details Object fields keep schema diagnostic order. Strict-key diagnostics
|
|
442
|
+
* run only after declared fields match, just like the full collector.
|
|
443
|
+
* @param schema Object schema to emit.
|
|
444
|
+
* @param value Generated expression for the candidate value.
|
|
445
|
+
* @param path Generated expression for the current path.
|
|
446
|
+
* @param context Shared code-generation context.
|
|
447
|
+
* @returns JavaScript source for object first-fault diagnostics.
|
|
448
|
+
*/
|
|
449
|
+
function emitObjectFirst(schema, value, path, context) {
|
|
450
|
+
if (isUnsafeMode(context)) {
|
|
451
|
+
return emitUnsafeObjectFirst(schema, value, path, context);
|
|
452
|
+
}
|
|
453
|
+
const parts = [
|
|
454
|
+
`if(${objectRejectExpression(value)})${emitFirstIssue(path, "expected_object", "object", `a(${value})`)}`
|
|
455
|
+
];
|
|
456
|
+
const entries = schema.entries;
|
|
457
|
+
const keyExpressions = [];
|
|
458
|
+
for (let index = 0; index < entries.length; index += 1) {
|
|
459
|
+
const entry = entries[index];
|
|
460
|
+
if (entry === undefined) {
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
463
|
+
const key = stringRef(context, entry.key);
|
|
464
|
+
keyExpressions.push(key);
|
|
465
|
+
parts.push("{");
|
|
466
|
+
if (entry.presence === PresenceTag.Required) {
|
|
467
|
+
parts.push(`const d=gp(${value},${key});`, `if(d===undefined||!h.call(d,"value"))${emitFirstIssueAtSegment(path, key, "expected_required_key", "present key", stringLiteral("missing"))}`, emitChildFirstAtSegment(entry.schema, "d.value", path, key, context));
|
|
468
|
+
}
|
|
469
|
+
else {
|
|
470
|
+
parts.push(`if(h.call(${value},${key})){`, `const d=gp(${value},${key});`, `if(d===undefined||!h.call(d,"value"))${emitFirstIssueAtSegment(path, key, "expected_object", "data property", stringLiteral("accessor"))}`, emitChildFirstAtSegment(entry.schema, "d.value", path, key, context), "}");
|
|
471
|
+
}
|
|
472
|
+
parts.push("}");
|
|
473
|
+
}
|
|
474
|
+
if (schema.catchall !== undefined) {
|
|
475
|
+
parts.push(emitObjectCatchallFirst(schema, value, path, context));
|
|
476
|
+
}
|
|
477
|
+
else if (schema.mode === ObjectModeTag.Strict) {
|
|
478
|
+
parts.push(`const xs=Object.getOwnPropertyNames(${value});`, "const xn=xs.length;", "for(let i=0;i<xn;i+=1){", "const key=xs[i];", `if(!(${safeKeyMembershipExpression("key", keyExpressions)}))${emitFirstIssueAtSegment(path, "key", "unrecognized_key", "known key", stringLiteral("extra key"))}`, "}", `const ys=Object.getOwnPropertySymbols(${value});`, "const yn=ys.length;", "for(let i=0;i<yn;i+=1){", "const key=ys[i];", `if(key!==undefined)${emitFirstIssueAtSegment(path, "String(key)", "unrecognized_key", "known key", stringLiteral("extra key"))}`, "}");
|
|
479
|
+
}
|
|
480
|
+
return parts.join("");
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* @brief Emit first-fault diagnostics for object catchall keys.
|
|
484
|
+
*/
|
|
485
|
+
function emitObjectCatchallFirst(schema, value, path, context) {
|
|
486
|
+
const catchall = schema.catchall;
|
|
487
|
+
if (catchall === undefined) {
|
|
488
|
+
return "";
|
|
489
|
+
}
|
|
490
|
+
const keyExpressions = new Array(schema.keys.length);
|
|
491
|
+
for (let index = 0; index < schema.keys.length; index += 1) {
|
|
492
|
+
const key = schema.keys[index];
|
|
493
|
+
if (key !== undefined) {
|
|
494
|
+
keyExpressions[index] = stringRef(context, key);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
return [
|
|
498
|
+
`const cx=Reflect.ownKeys(${value});`,
|
|
499
|
+
"const cn=cx.length;",
|
|
500
|
+
"for(let ci=0;ci<cn;ci+=1){",
|
|
501
|
+
"const key=cx[ci];",
|
|
502
|
+
`if(typeof key==="string"&&(${safeKeyMembershipExpression("key", keyExpressions)}))continue;`,
|
|
503
|
+
"const pk=typeof key===\"string\"?key:String(key);",
|
|
504
|
+
`const cd=gp(${value},key);`,
|
|
505
|
+
`if(cd===undefined||!h.call(cd,"value"))${emitFirstIssueAtSegment(path, "pk", "expected_object", "data property", stringLiteral("accessor"))}`,
|
|
506
|
+
emitChildFirstAtSegment(catchall, "cd.value", path, "pk", context),
|
|
507
|
+
"}"
|
|
508
|
+
].join("");
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* @brief Emit fast-mode first-fault object diagnostics.
|
|
512
|
+
* @details Trusted object diagnostics share the direct-read contract used by
|
|
513
|
+
* unsafe and unchecked predicates.
|
|
514
|
+
* @param schema Object schema to emit.
|
|
515
|
+
* @param value Generated expression for the candidate value.
|
|
516
|
+
* @param path Generated expression for the current path.
|
|
517
|
+
* @param context Shared code-generation context.
|
|
518
|
+
* @returns JavaScript source for unsafe object first-fault diagnostics.
|
|
519
|
+
*/
|
|
520
|
+
function emitUnsafeObjectFirst(schema, value, path, context) {
|
|
521
|
+
const parts = [
|
|
522
|
+
`if(${objectRejectExpression(value)})${emitFirstIssue(path, "expected_object", "object", `a(${value})`)}`
|
|
523
|
+
];
|
|
524
|
+
const keys = new Array(schema.entries.length);
|
|
525
|
+
for (let index = 0; index < schema.entries.length; index += 1) {
|
|
526
|
+
const entry = schema.entries[index];
|
|
527
|
+
if (entry === undefined) {
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
keys[index] = entry.key;
|
|
531
|
+
const key = unsafeStringLiteralExpression(entry.key);
|
|
532
|
+
const itemValue = `v${String(index)}`;
|
|
533
|
+
parts.push("{", `const ${itemValue}=${unsafePropertyReadExpression(value, entry.key)};`);
|
|
534
|
+
if (entry.presence === PresenceTag.Optional) {
|
|
535
|
+
parts.push(`if(${itemValue}!==undefined){${emitChildFirstAtSegment(entry.schema, itemValue, path, key, context)}}`, `else if(h.call(${value},${key})){${emitChildFirstAtSegment(entry.schema, itemValue, path, key, context)}}`);
|
|
536
|
+
}
|
|
537
|
+
else if (schemaCanAcceptUndefined(entry.schema)) {
|
|
538
|
+
parts.push(`if(${itemValue}===undefined&&!h.call(${value},${key}))${emitFirstIssueAtSegment(path, key, "expected_required_key", "present key", stringLiteral("missing"))}`, emitChildFirstAtSegment(entry.schema, itemValue, path, key, context));
|
|
539
|
+
}
|
|
540
|
+
else {
|
|
541
|
+
parts.push(emitChildFirstAtSegment(entry.schema, itemValue, path, key, context));
|
|
542
|
+
}
|
|
543
|
+
parts.push("}");
|
|
544
|
+
}
|
|
545
|
+
if (schema.catchall !== undefined) {
|
|
546
|
+
parts.push(emitUnsafeObjectCatchallFirst(schema, value, path, context));
|
|
547
|
+
}
|
|
548
|
+
else if (schema.mode === ObjectModeTag.Strict && !isUncheckedMode(context)) {
|
|
549
|
+
parts.push(`for(const key in ${value}){`, `if(h.call(${value},key)&&!(${unsafeKeyMembershipExpression("key", keys)}))${emitFirstIssueAtSegment(path, "key", "unrecognized_key", "known key", stringLiteral("extra key"))}`, "}");
|
|
550
|
+
}
|
|
551
|
+
return parts.join("");
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* @brief Emit unsafe first-fault diagnostics for object catchall keys.
|
|
555
|
+
*/
|
|
556
|
+
function emitUnsafeObjectCatchallFirst(schema, value, path, context) {
|
|
557
|
+
const catchall = schema.catchall;
|
|
558
|
+
if (catchall === undefined) {
|
|
559
|
+
return "";
|
|
560
|
+
}
|
|
561
|
+
return [
|
|
562
|
+
`const cx=Reflect.ownKeys(${value});`,
|
|
563
|
+
"const cn=cx.length;",
|
|
564
|
+
"for(let ci=0;ci<cn;ci+=1){",
|
|
565
|
+
"const key=cx[ci];",
|
|
566
|
+
`if(typeof key==="string"&&(${unsafeKeyMembershipExpression("key", schema.keys)}))continue;`,
|
|
567
|
+
"const pk=typeof key===\"string\"?key:String(key);",
|
|
568
|
+
emitChildFirstAtSegment(catchall, `${value}[key]`, path, "pk", context),
|
|
569
|
+
"}"
|
|
570
|
+
].join("");
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* @brief Emit first-fault discriminated-union diagnostics.
|
|
574
|
+
* @details The discriminant read stays descriptor-backed in safe mode and direct
|
|
575
|
+
* in fast modes.
|
|
576
|
+
* @param key Discriminant property name.
|
|
577
|
+
* @param cases Union case table.
|
|
578
|
+
* @param value Generated expression for the candidate value.
|
|
579
|
+
* @param path Generated expression for the current path.
|
|
580
|
+
* @param context Shared code-generation context.
|
|
581
|
+
* @returns JavaScript source for discriminated-union first-fault diagnostics.
|
|
582
|
+
*/
|
|
583
|
+
function emitDiscriminatedUnionFirst(key, cases, value, path, context) {
|
|
584
|
+
if (isUnsafeMode(context)) {
|
|
585
|
+
return emitUnsafeDiscriminatedUnionFirst(key, cases, value, path, context);
|
|
586
|
+
}
|
|
587
|
+
const keyRef = stringRef(context, key);
|
|
588
|
+
const parts = [
|
|
589
|
+
`if(${objectRejectExpression(value)})${emitFirstIssue(path, "expected_object", "object", `a(${value})`)}`,
|
|
590
|
+
`const dd=gp(${value},${keyRef});`,
|
|
591
|
+
`if(dd===undefined||!h.call(dd,"value"))${emitFirstIssueAtSegment(path, keyRef, "expected_discriminant", "data property", stringLiteral("missing or accessor"))}`,
|
|
592
|
+
"const dv=dd.value;",
|
|
593
|
+
`if(typeof dv!=="string")${emitFirstIssueAtSegment(path, keyRef, "expected_discriminant", "string discriminant", "a(dv)")}`
|
|
594
|
+
];
|
|
595
|
+
for (let index = 0; index < cases.length; index += 1) {
|
|
596
|
+
const unionCase = cases[index];
|
|
597
|
+
if (unionCase === undefined) {
|
|
598
|
+
continue;
|
|
599
|
+
}
|
|
600
|
+
const literalIndex = pushLiteral(context, unionCase.literal);
|
|
601
|
+
parts.push(`if(Object.is(dv,l[${String(literalIndex)}])){${emitChildFirst(unionCase.schema, value, path, context)}return;}`);
|
|
602
|
+
}
|
|
603
|
+
parts.push(emitFirstIssueExprAtSegment(path, keyRef, "expected_discriminant", stringLiteral("known discriminant"), "le(dv)"));
|
|
604
|
+
return parts.join("");
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* @brief Emit fast-mode first-fault discriminated-union diagnostics.
|
|
608
|
+
* @details Trusted discriminants use direct property reads and strict equality
|
|
609
|
+
* against static case strings.
|
|
610
|
+
* @param key Discriminant property name.
|
|
611
|
+
* @param cases Union case table.
|
|
612
|
+
* @param value Generated expression for the candidate value.
|
|
613
|
+
* @param path Generated expression for the current path.
|
|
614
|
+
* @param context Shared code-generation context.
|
|
615
|
+
* @returns JavaScript source for unsafe discriminated-union first-fault diagnostics.
|
|
616
|
+
*/
|
|
617
|
+
function emitUnsafeDiscriminatedUnionFirst(key, cases, value, path, context) {
|
|
618
|
+
const keyRef = unsafeStringLiteralExpression(key);
|
|
619
|
+
const parts = [
|
|
620
|
+
`if(${objectRejectExpression(value)})${emitFirstIssue(path, "expected_object", "object", `a(${value})`)}`,
|
|
621
|
+
`const dv=${unsafePropertyReadExpression(value, key)};`,
|
|
622
|
+
`if(typeof dv!=="string")${emitFirstIssueAtSegment(path, keyRef, "expected_discriminant", "string discriminant", "a(dv)")}`
|
|
623
|
+
];
|
|
624
|
+
for (let index = 0; index < cases.length; index += 1) {
|
|
625
|
+
const unionCase = cases[index];
|
|
626
|
+
if (unionCase !== undefined) {
|
|
627
|
+
parts.push(`if(dv===${unsafeStringLiteralExpression(unionCase.literal)}){${emitChildFirst(unionCase.schema, value, path, context)}return;}`);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
parts.push(emitFirstIssueExprAtSegment(path, keyRef, "expected_discriminant", stringLiteral("known discriminant"), "le(dv)"));
|
|
631
|
+
return parts.join("");
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* @brief Emit one child first-fault call at the current path.
|
|
635
|
+
* @details The temporary issue local is scoped so repeated generated calls keep
|
|
636
|
+
* monomorphic source without name collisions.
|
|
637
|
+
* @param schema Child schema to invoke.
|
|
638
|
+
* @param value Generated child value expression.
|
|
639
|
+
* @param path Generated path expression.
|
|
640
|
+
* @param context Shared code-generation context.
|
|
641
|
+
* @returns JavaScript source that returns the child issue when present.
|
|
642
|
+
*/
|
|
643
|
+
function emitChildFirst(schema, value, path, context) {
|
|
644
|
+
const child = emitFirstFunction(schema, context);
|
|
645
|
+
return `{const e=${child}(${value},${path});if(e!==undefined)return e;}`;
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* @brief Emit one child first-fault call under an appended segment.
|
|
649
|
+
* @details The mutable path is restored before returning so sibling checks keep
|
|
650
|
+
* the same path prefix even when a child succeeds.
|
|
651
|
+
* @param schema Child schema to invoke.
|
|
652
|
+
* @param value Generated child value expression.
|
|
653
|
+
* @param path Generated path expression.
|
|
654
|
+
* @param segment Generated path segment expression.
|
|
655
|
+
* @param context Shared code-generation context.
|
|
656
|
+
* @returns JavaScript source that returns the child issue when present.
|
|
657
|
+
*/
|
|
658
|
+
function emitChildFirstAtSegment(schema, value, path, segment, context) {
|
|
659
|
+
const child = emitFirstFunction(schema, context);
|
|
660
|
+
return `{${path}.push(${segment});const e=${child}(${value},${path});${path}.pop();if(e!==undefined)return e;}`;
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* @brief Emit first-fault pattern diagnostics.
|
|
664
|
+
* @details RegExp instances stay in the side table and stateful expressions get
|
|
665
|
+
* the same lastIndex reset as the full collector.
|
|
666
|
+
* @param value Generated string value expression.
|
|
667
|
+
* @param path Generated path expression.
|
|
668
|
+
* @param regex Pattern to test.
|
|
669
|
+
* @param name Human-readable pattern name.
|
|
670
|
+
* @param context Shared code-generation context.
|
|
671
|
+
* @returns JavaScript source that returns a pattern issue on failure.
|
|
672
|
+
*/
|
|
673
|
+
function emitPatternFirst(value, path, regex, name, context) {
|
|
674
|
+
const source = regex === UUID_PATTERN ? UUID_PATTERN : regex;
|
|
675
|
+
const index = pushRegex(context, source);
|
|
676
|
+
const access = `r[${String(index)}]`;
|
|
677
|
+
const test = regexNeedsLastIndexReset(source)
|
|
678
|
+
? `((${access}.lastIndex=0),!${access}.test(${value}))`
|
|
679
|
+
: `!${access}.test(${value})`;
|
|
680
|
+
return `if(${test})${emitFirstIssueExpr(path, "expected_pattern", stringRef(context, name), stringLiteral("string"))}`;
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* @brief Emit a root-path first issue return.
|
|
684
|
+
* @param path Generated path expression.
|
|
685
|
+
* @param code Issue code string.
|
|
686
|
+
* @param expected Expected label.
|
|
687
|
+
* @param actualExpression Generated actual label expression.
|
|
688
|
+
* @returns JavaScript return statement.
|
|
689
|
+
*/
|
|
690
|
+
function emitFirstIssue(path, code, expected, actualExpression) {
|
|
691
|
+
return emitFirstIssueExpr(path, code, stringLiteral(expected), actualExpression);
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* @brief Emit a root-path first issue return with expression operands.
|
|
695
|
+
* @param path Generated path expression.
|
|
696
|
+
* @param code Issue code string.
|
|
697
|
+
* @param expectedExpression Generated expected label expression.
|
|
698
|
+
* @param actualExpression Generated actual label expression.
|
|
699
|
+
* @returns JavaScript return statement.
|
|
700
|
+
*/
|
|
701
|
+
function emitFirstIssueExpr(path, code, expectedExpression, actualExpression) {
|
|
702
|
+
return `return fq(${path},${stringLiteral(code)},${expectedExpression},${actualExpression});`;
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* @brief Emit a one-segment first issue return.
|
|
706
|
+
* @param path Generated path expression.
|
|
707
|
+
* @param segment Generated appended segment expression.
|
|
708
|
+
* @param code Issue code string.
|
|
709
|
+
* @param expected Expected label.
|
|
710
|
+
* @param actualExpression Generated actual label expression.
|
|
711
|
+
* @returns JavaScript return statement.
|
|
712
|
+
*/
|
|
713
|
+
function emitFirstIssueAtSegment(path, segment, code, expected, actualExpression) {
|
|
714
|
+
return emitFirstIssueExprAtSegment(path, segment, code, stringLiteral(expected), actualExpression);
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* @brief Emit a one-segment first issue return with expression operands.
|
|
718
|
+
* @param path Generated path expression.
|
|
719
|
+
* @param segment Generated appended segment expression.
|
|
720
|
+
* @param code Issue code string.
|
|
721
|
+
* @param expectedExpression Generated expected label expression.
|
|
722
|
+
* @param actualExpression Generated actual label expression.
|
|
723
|
+
* @returns JavaScript return statement.
|
|
724
|
+
*/
|
|
725
|
+
function emitFirstIssueExprAtSegment(path, segment, code, expectedExpression, actualExpression) {
|
|
726
|
+
const stringIndex = readStringRefIndex(segment);
|
|
727
|
+
if (stringIndex !== undefined) {
|
|
728
|
+
return `return fq1s(${path},${stringIndex},${stringLiteral(code)},${expectedExpression},${actualExpression});`;
|
|
729
|
+
}
|
|
730
|
+
return `return fq1(${path},${segment},${stringLiteral(code)},${expectedExpression},${actualExpression});`;
|
|
731
|
+
}
|
|
732
|
+
/**
|
|
733
|
+
* @brief Test whether a RegExp must reset lastIndex before testing.
|
|
734
|
+
* @param regex RegExp stored in the generated side table.
|
|
735
|
+
* @returns True for global or sticky patterns.
|
|
736
|
+
*/
|
|
737
|
+
function regexNeedsLastIndexReset(regex) {
|
|
738
|
+
return regex.global || regex.sticky;
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* @brief Emit a plain-object rejection expression.
|
|
742
|
+
* @param value Generated value expression.
|
|
743
|
+
* @returns JavaScript expression that rejects arrays, null, and primitives.
|
|
744
|
+
*/
|
|
745
|
+
function objectRejectExpression(value) {
|
|
746
|
+
return `typeof ${value}!=="object"||${value}===null||Array.isArray(${value})`;
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* @brief Emit safe strict-key membership expression.
|
|
750
|
+
* @param key Generated key expression.
|
|
751
|
+
* @param keyExpressions Allowed key expressions.
|
|
752
|
+
* @returns JavaScript boolean expression.
|
|
753
|
+
*/
|
|
754
|
+
function safeKeyMembershipExpression(key, keyExpressions) {
|
|
755
|
+
if (keyExpressions.length === 0) {
|
|
756
|
+
return "false";
|
|
757
|
+
}
|
|
758
|
+
const parts = new Array(keyExpressions.length);
|
|
759
|
+
for (let index = 0; index < keyExpressions.length; index += 1) {
|
|
760
|
+
const value = keyExpressions[index];
|
|
761
|
+
parts[index] = value === undefined ? "false" : `${key}===${value}`;
|
|
762
|
+
}
|
|
763
|
+
return `(${parts.join("||")})`;
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* @brief Emit unsafe strict-key membership expression.
|
|
767
|
+
* @param key Generated key expression.
|
|
768
|
+
* @param keys Allowed static keys.
|
|
769
|
+
* @returns JavaScript boolean expression.
|
|
770
|
+
*/
|
|
771
|
+
function unsafeKeyMembershipExpression(key, keys) {
|
|
772
|
+
if (keys.length === 0) {
|
|
773
|
+
return "false";
|
|
774
|
+
}
|
|
775
|
+
const parts = new Array(keys.length);
|
|
776
|
+
for (let index = 0; index < keys.length; index += 1) {
|
|
777
|
+
const value = keys[index];
|
|
778
|
+
parts[index] = value === undefined
|
|
779
|
+
? "false"
|
|
780
|
+
: `${key}===${unsafeStringLiteralExpression(value)}`;
|
|
781
|
+
}
|
|
782
|
+
return `(${parts.join("||")})`;
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* @brief Emit a fast-mode property read expression.
|
|
786
|
+
* @param objectExpression Generated object expression.
|
|
787
|
+
* @param key Static property key.
|
|
788
|
+
* @returns Dot access for identifier keys, bracket access otherwise.
|
|
789
|
+
*/
|
|
790
|
+
function unsafePropertyReadExpression(objectExpression, key) {
|
|
791
|
+
if (isAsciiIdentifierName(key)) {
|
|
792
|
+
return `${objectExpression}.${key}`;
|
|
793
|
+
}
|
|
794
|
+
return `${objectExpression}[${unsafeStringLiteralExpression(key)}]`;
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* @brief Test whether a static key can be emitted after a dot.
|
|
798
|
+
* @param value Candidate key string.
|
|
799
|
+
* @returns True for ASCII identifier names.
|
|
800
|
+
*/
|
|
801
|
+
function isAsciiIdentifierName(value) {
|
|
802
|
+
return /^[A-Za-z_$][0-9A-Za-z_$]*$/u.test(value);
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* @brief Escape one static string literal for unsafe-mode source.
|
|
806
|
+
* @param value String value to serialize.
|
|
807
|
+
* @returns JSON string literal safe for generated source text.
|
|
808
|
+
*/
|
|
809
|
+
function unsafeStringLiteralExpression(value) {
|
|
810
|
+
return JSON.stringify(value)
|
|
811
|
+
.replace(/\u2028/gu, "\\u2028")
|
|
812
|
+
.replace(/\u2029/gu, "\\u2029");
|
|
813
|
+
}
|
|
814
|
+
/**
|
|
815
|
+
* @brief Check whether a compile mode uses direct reads.
|
|
816
|
+
* @param context Shared code-generation context.
|
|
817
|
+
* @returns True for unsafe or unchecked modes.
|
|
818
|
+
*/
|
|
819
|
+
function isUnsafeMode(context) {
|
|
820
|
+
return context.mode !== "safe";
|
|
821
|
+
}
|
|
822
|
+
/**
|
|
823
|
+
* @brief Check whether a compile mode skips strict extras.
|
|
824
|
+
* @param context Shared code-generation context.
|
|
825
|
+
* @returns True for unchecked mode.
|
|
826
|
+
*/
|
|
827
|
+
function isUncheckedMode(context) {
|
|
828
|
+
return context.mode === "unchecked";
|
|
829
|
+
}
|
|
830
|
+
/**
|
|
831
|
+
* @brief Extract the side-table index from a string reference expression.
|
|
832
|
+
* @param expression Candidate expression such as `u[2]`.
|
|
833
|
+
* @returns Numeric index text, or undefined for dynamic expressions.
|
|
834
|
+
*/
|
|
835
|
+
function readStringRefIndex(expression) {
|
|
836
|
+
if (!expression.startsWith("u[") || !expression.endsWith("]")) {
|
|
837
|
+
return undefined;
|
|
838
|
+
}
|
|
839
|
+
const value = expression.slice(2, -1);
|
|
840
|
+
if (value.length === 0) {
|
|
841
|
+
return undefined;
|
|
842
|
+
}
|
|
843
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
844
|
+
const code = value.charCodeAt(index);
|
|
845
|
+
if (code < 48 || code > 57) {
|
|
846
|
+
return undefined;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
return value;
|
|
850
|
+
}
|