@amritk/generate-validators 0.11.12 → 0.12.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.
@@ -1,11 +1,24 @@
1
- import { escapeRegexPattern } from "@amritk/helpers/escape-regex-pattern";
1
+ import { regexFlagsFor, regexLiteral } from "@amritk/helpers/escape-regex-pattern";
2
2
  import { getMjstInstanceOf, getMjstPrimitive } from "@amritk/helpers/mjst-extension";
3
3
  import { multipleOfFailExpr, multipleOfPassExpr } from "@amritk/helpers/multiple-of-check";
4
4
  import { refToName } from "@amritk/helpers/ref-to-name";
5
5
  import { safeAccessor } from "@amritk/helpers/safe-accessor";
6
6
  import { hasAdditionalProperties, hasAllOf, hasAnyOf, hasConst, hasDependentRequired, hasEnum, hasExclusiveMaximum, hasExclusiveMinimum, hasItems, hasMaxItems, hasMaximum, hasMaxLength, hasMaxProperties, hasMinItems, hasMinimum, hasMinLength, hasMinProperties, hasMultipleOf, hasOneOf, hasPattern, hasProperties, hasPropertyNames, hasRef, hasRequired, hasStrictExclusiveMaximum, hasStrictExclusiveMinimum, hasType, hasUniqueItems, isObjectSchema, isSchemaObject } from "@amritk/helpers/schema-guards";
7
+ import { maxLengthFailExpr, maxLengthPassExpr, minLengthFailExpr, minLengthPassExpr } from "@amritk/helpers/string-length-check";
7
8
  import { unknownKeyCheck } from "@amritk/helpers/unknown-key-check";
9
+ import { assertGeneratableRefs } from "./assert-generatable-refs.js";
10
+ import { assertUnevaluatedGeneratable, UNPROVABLE_COVERAGE_MESSAGE } from "./assert-unevaluated-generatable.js";
11
+ import { unevaluatedItemsExpr, unevaluatedPropertiesExpr } from "./unevaluated-match.js";
8
12
  const validatorName = (typeName) => `validate${typeName}`;
13
+ const propertyRead = (objVar, key) => safeAccessor(objVar, key);
14
+ const PROTOTYPE_MEMBERS = new Set(Object.getOwnPropertyNames(Object.prototype));
15
+ const protoLocalName = (key, depth) => `_own${depth}_${key}`;
16
+ const hasOwnCheck = (objVar, key) => {
17
+ if (PROTOTYPE_MEMBERS.has(key))
18
+ return `Object.hasOwn(${objVar}, ${JSON.stringify(key)})`;
19
+ return `${JSON.stringify(key)} in ${objVar}`;
20
+ };
21
+ const missingCheck = (objVar, key) => `!(${hasOwnCheck(objVar, key)})`;
9
22
  const typeofString = (type) => {
10
23
  if (type === "integer")
11
24
  return "number";
@@ -17,6 +30,19 @@ const constMismatchCondition = (accessor, value) => {
17
30
  }
18
31
  return `!valuesEqual(${accessor}, ${JSON.stringify(value)})`;
19
32
  };
33
+ const enumMembershipExpr = (values, acc) => {
34
+ if (values.length === 0)
35
+ return "false";
36
+ const parts = values.map((value) => {
37
+ if (typeof value === "number" && Number.isNaN(value))
38
+ return `Number.isNaN(${acc})`;
39
+ if (value !== null && typeof value === "object")
40
+ return `valuesEqual(${acc}, ${JSON.stringify(value)})`;
41
+ return `${acc} === ${JSON.stringify(value)}`;
42
+ });
43
+ return `(${parts.join(" || ")})`;
44
+ };
45
+ const FALSE_SCHEMA_MESSAGE = "boolean schema is false";
20
46
  const SCALAR_ITEM_TYPES = /* @__PURE__ */ new Set(["string", "number", "integer", "boolean", "null"]);
21
47
  const schemaIsScalarOnly = (schema) => {
22
48
  if (!isSchemaObject(schema))
@@ -59,12 +85,19 @@ const wrongTypeCondition = (accessor, type) => {
59
85
  return "";
60
86
  }
61
87
  };
88
+ const declaresObjectType = (schema) => hasType(schema) && schema.type === "object";
62
89
  const getTypeArray = (schema) => {
63
90
  if (!isSchemaObject(schema) || !("type" in schema) || !Array.isArray(schema.type))
64
91
  return null;
65
92
  return schema.type;
66
93
  };
67
- const createRootContext = () => ({ objVar: "obj", pathPrefix: "${_path}", depth: 0, hoisted: [] });
94
+ const createRootContext = (rootSchema) => ({
95
+ objVar: "obj",
96
+ pathPrefix: "${_path}",
97
+ depth: 0,
98
+ hoisted: [],
99
+ rootSchema
100
+ });
68
101
  const pointerSegment = (key) => key.replace(/~/g, "~0").replace(/\//g, "~1").replace(/[\\`$]/g, "\\$&");
69
102
  const patternPropertySources = (schema) => {
70
103
  if (!isSchemaObject(schema) || !("patternProperties" in schema))
@@ -87,7 +120,8 @@ const generateStrictKeyChecks = (schema, ctx) => {
87
120
  let patternGuard = "";
88
121
  if (patterns.length > 0) {
89
122
  const patternsName = `_patterns${ctx.hoisted.length}`;
90
- ctx.hoisted.push(`const ${patternsName} = [${patterns.map((p) => `new RegExp(${JSON.stringify(p)})`).join(", ")}]`);
123
+ const compiled = patterns.map((p) => `new RegExp(${JSON.stringify(p)}, ${JSON.stringify(regexFlagsFor(p))})`);
124
+ ctx.hoisted.push(`const ${patternsName} = [${compiled.join(", ")}]`);
91
125
  patternGuard = ` && !${patternsName}.some((re) => re.test(_key${d}))`;
92
126
  }
93
127
  return [
@@ -107,25 +141,57 @@ const generateMissingRequiredChecks = (schema, ctx) => {
107
141
  for (const key of schema.required) {
108
142
  if (Object.hasOwn(props, key))
109
143
  continue;
110
- lines.push(` if (!(${JSON.stringify(key)} in ${ctx.objVar})) {`);
144
+ lines.push(` if (${missingCheck(ctx.objVar, key)}) {`);
111
145
  lines.push(` errors.push({ message: ${JSON.stringify(`must have required property '${key}'`)}, path: ${parentPath} })`);
112
146
  lines.push(` }`);
113
147
  }
114
148
  return lines;
115
149
  };
116
150
  const generatePropertyChecks = (key, propSchema, isRequired, suffix, ctx) => {
151
+ if (propSchema === false) {
152
+ const path = `\`${ctx.pathPrefix}/${pointerSegment(key)}\``;
153
+ const parentPath = ctx.depth === 0 ? "_path" : `\`${ctx.pathPrefix}\``;
154
+ if (isRequired) {
155
+ return [
156
+ ` if (${missingCheck(ctx.objVar, key)}) {`,
157
+ ` errors.push({ message: ${JSON.stringify(`must have required property '${key}'`)}, path: ${parentPath} })`,
158
+ ` } else {`,
159
+ ` errors.push({ message: ${JSON.stringify(FALSE_SCHEMA_MESSAGE)}, path: ${path} })`,
160
+ ` }`
161
+ ];
162
+ }
163
+ return [
164
+ ` if (${hasOwnCheck(ctx.objVar, key)}) {`,
165
+ ` errors.push({ message: ${JSON.stringify(FALSE_SCHEMA_MESSAGE)}, path: ${path} })`,
166
+ ` }`
167
+ ];
168
+ }
169
+ const raw = PROTOTYPE_MEMBERS.has(key) ? protoLocalName(key, ctx.depth) : propertyRead(ctx.objVar, key);
170
+ const lines = [
171
+ ...generatePropertyCheckLines(key, propSchema, isRequired, suffix, ctx),
172
+ // `unevaluated*` is a sibling of everything else the property declares, and
173
+ // several of the branches above return early, so it is appended here rather
174
+ // than emitted inside them. The checks carry their own runtime type guard, so
175
+ // an absent optional property is inert and needs no presence gate.
176
+ ...generateUnevaluatedChecks(raw, `\`${ctx.pathPrefix}/${pointerSegment(key)}\``, propSchema, suffix, ctx)
177
+ ];
178
+ if (lines.length === 0 || !PROTOTYPE_MEMBERS.has(key))
179
+ return lines;
180
+ return [` const ${protoLocalName(key, ctx.depth)} = ${propertyRead(ctx.objVar, key)}`, ...lines];
181
+ };
182
+ const generatePropertyCheckLines = (key, propSchema, isRequired, suffix, ctx) => {
117
183
  if (!isSchemaObject(propSchema)) {
118
184
  if (isRequired && propSchema === true) {
119
185
  const parentPath2 = ctx.depth === 0 ? "_path" : `\`${ctx.pathPrefix}\``;
120
186
  return [
121
- ` if (!(${JSON.stringify(key)} in ${ctx.objVar})) {`,
187
+ ` if (${missingCheck(ctx.objVar, key)}) {`,
122
188
  ` errors.push({ message: ${JSON.stringify(`must have required property '${key}'`)}, path: ${parentPath2} })`,
123
189
  ` }`
124
190
  ];
125
191
  }
126
192
  return [];
127
193
  }
128
- const raw = `${ctx.objVar}[${JSON.stringify(key)}]`;
194
+ const raw = PROTOTYPE_MEMBERS.has(key) ? protoLocalName(key, ctx.depth) : propertyRead(ctx.objVar, key);
129
195
  const path = `\`${ctx.pathPrefix}/${pointerSegment(key)}\``;
130
196
  const parentPath = ctx.depth === 0 ? "_path" : `\`${ctx.pathPrefix}\``;
131
197
  const lines = [];
@@ -142,7 +208,7 @@ const generatePropertyChecks = (key, propSchema, isRequired, suffix, ctx) => {
142
208
  ...siblings
143
209
  ];
144
210
  if (isRequired) {
145
- lines.push(` if (!(${JSON.stringify(key)} in ${ctx.objVar})) {`);
211
+ lines.push(` if (${missingCheck(ctx.objVar, key)}) {`);
146
212
  lines.push(` errors.push({ message: ${JSON.stringify(`must have required property '${key}'`)}, path: ${parentPath} })`);
147
213
  lines.push(` } else {`);
148
214
  lines.push(...delegate);
@@ -157,7 +223,7 @@ const generatePropertyChecks = (key, propSchema, isRequired, suffix, ctx) => {
157
223
  const instanceOf = getMjstInstanceOf(propSchema);
158
224
  if (instanceOf) {
159
225
  if (isRequired) {
160
- lines.push(` if (!(${JSON.stringify(key)} in ${ctx.objVar})) {`);
226
+ lines.push(` if (${missingCheck(ctx.objVar, key)}) {`);
161
227
  lines.push(` errors.push({ message: ${JSON.stringify(`must have required property '${key}'`)}, path: ${parentPath} })`);
162
228
  lines.push(` } else if (!(${raw} instanceof ${instanceOf})) {`);
163
229
  lines.push(` errors.push({ message: 'must be ${instanceOf}', path: ${path} })`);
@@ -172,7 +238,7 @@ const generatePropertyChecks = (key, propSchema, isRequired, suffix, ctx) => {
172
238
  const primitive = getMjstPrimitive(propSchema);
173
239
  if (primitive) {
174
240
  if (isRequired) {
175
- lines.push(` if (!(${JSON.stringify(key)} in ${ctx.objVar})) {`);
241
+ lines.push(` if (${missingCheck(ctx.objVar, key)}) {`);
176
242
  lines.push(` errors.push({ message: ${JSON.stringify(`must have required property '${key}'`)}, path: ${parentPath} })`);
177
243
  lines.push(` } else if (typeof ${raw} !== "${primitive}") {`);
178
244
  lines.push(` errors.push({ message: 'must be ${primitive}', path: ${path} })`);
@@ -188,7 +254,7 @@ const generatePropertyChecks = (key, propSchema, isRequired, suffix, ctx) => {
188
254
  const mismatch = constMismatchCondition(raw, propSchema.const);
189
255
  const msg = JSON.stringify(`must be ${JSON.stringify(propSchema.const)}`);
190
256
  if (isRequired) {
191
- lines.push(` if (!(${JSON.stringify(key)} in ${ctx.objVar})) {`);
257
+ lines.push(` if (${missingCheck(ctx.objVar, key)}) {`);
192
258
  lines.push(` errors.push({ message: ${JSON.stringify(`must have required property '${key}'`)}, path: ${parentPath} })`);
193
259
  lines.push(` } else if (${mismatch}) {`);
194
260
  lines.push(` errors.push({ message: ${msg}, path: ${path} })`);
@@ -201,16 +267,16 @@ const generatePropertyChecks = (key, propSchema, isRequired, suffix, ctx) => {
201
267
  return lines;
202
268
  }
203
269
  if (hasEnum(propSchema)) {
204
- const allowed = JSON.stringify(propSchema.enum);
270
+ const member = enumMembershipExpr(propSchema.enum, raw);
205
271
  const label = propSchema.enum.map((v) => JSON.stringify(v)).join(", ");
206
272
  if (isRequired) {
207
- lines.push(` if (!(${JSON.stringify(key)} in ${ctx.objVar})) {`);
273
+ lines.push(` if (${missingCheck(ctx.objVar, key)}) {`);
208
274
  lines.push(` errors.push({ message: ${JSON.stringify(`must have required property '${key}'`)}, path: ${parentPath} })`);
209
- lines.push(` } else if (!(${allowed} as unknown[]).includes(${raw})) {`);
275
+ lines.push(` } else if (!${member}) {`);
210
276
  lines.push(` errors.push({ message: ${JSON.stringify(`must be one of: ${label}`)}, path: ${path} })`);
211
277
  lines.push(` }`);
212
278
  } else {
213
- lines.push(` if (${raw} !== undefined && !(${allowed} as unknown[]).includes(${raw})) {`);
279
+ lines.push(` if (${raw} !== undefined && !${member}) {`);
214
280
  lines.push(` errors.push({ message: ${JSON.stringify(`must be one of: ${label}`)}, path: ${path} })`);
215
281
  lines.push(` }`);
216
282
  }
@@ -221,7 +287,7 @@ const generatePropertyChecks = (key, propSchema, isRequired, suffix, ctx) => {
221
287
  const allWrong = typeArray.map((t) => wrongTypeCondition(raw, t)).filter((c) => c !== "").map((c) => `(${c})`).join(" && ");
222
288
  const label = typeArray.map((t) => typeofString(t)).join(" or ");
223
289
  if (isRequired) {
224
- lines.push(` if (!(${JSON.stringify(key)} in ${ctx.objVar})) {`);
290
+ lines.push(` if (${missingCheck(ctx.objVar, key)}) {`);
225
291
  lines.push(` errors.push({ message: ${JSON.stringify(`must have required property '${key}'`)}, path: ${parentPath} })`);
226
292
  if (allWrong) {
227
293
  lines.push(` } else if (${allWrong}) {`);
@@ -241,7 +307,7 @@ const generatePropertyChecks = (key, propSchema, isRequired, suffix, ctx) => {
241
307
  const wrongType = wrongTypeCondition(raw, t);
242
308
  const typLabel = typeofString(t);
243
309
  if (isRequired) {
244
- lines.push(` if (!(${JSON.stringify(key)} in ${ctx.objVar})) {`);
310
+ lines.push(` if (${missingCheck(ctx.objVar, key)}) {`);
245
311
  lines.push(` errors.push({ message: ${JSON.stringify(`must have required property '${key}'`)}, path: ${parentPath} })`);
246
312
  if (wrongType) {
247
313
  lines.push(` } else if (${wrongType}) {`);
@@ -266,7 +332,7 @@ const generatePropertyChecks = (key, propSchema, isRequired, suffix, ctx) => {
266
332
  lines.push(` }`);
267
333
  }
268
334
  } else if (isRequired) {
269
- lines.push(` if (!(${JSON.stringify(key)} in ${ctx.objVar})) {`);
335
+ lines.push(` if (${missingCheck(ctx.objVar, key)}) {`);
270
336
  lines.push(` errors.push({ message: ${JSON.stringify(`must have required property '${key}'`)}, path: ${parentPath} })`);
271
337
  if (extraLines.length > 0) {
272
338
  lines.push(` } else {`);
@@ -287,19 +353,19 @@ const generateConstraintChecks = (key, raw, path, propSchema, suffix, ctx) => {
287
353
  const lines = [];
288
354
  if (hasPattern(propSchema) || hasMinLength(propSchema) || hasMaxLength(propSchema)) {
289
355
  if (hasPattern(propSchema)) {
290
- const re = escapeRegexPattern(propSchema.pattern);
356
+ const re = regexLiteral(propSchema.pattern);
291
357
  const msg = JSON.stringify(`must match pattern ${propSchema.pattern}`);
292
- lines.push(` if (typeof ${raw} === 'string' && !/${re}/.test(${raw})) {`);
358
+ lines.push(` if (typeof ${raw} === 'string' && !${re}.test(${raw})) {`);
293
359
  lines.push(` errors.push({ message: ${msg}, path: ${path} })`);
294
360
  lines.push(` }`);
295
361
  }
296
362
  if (hasMinLength(propSchema)) {
297
- lines.push(` if (typeof ${raw} === 'string' && ${raw}.length < ${propSchema.minLength}) {`);
363
+ lines.push(` if (typeof ${raw} === 'string' && ${minLengthFailExpr(raw, propSchema.minLength)}) {`);
298
364
  lines.push(` errors.push({ message: 'must have at least ${propSchema.minLength} characters', path: ${path} })`);
299
365
  lines.push(` }`);
300
366
  }
301
367
  if (hasMaxLength(propSchema)) {
302
- lines.push(` if (typeof ${raw} === 'string' && ${raw}.length > ${propSchema.maxLength}) {`);
368
+ lines.push(` if (typeof ${raw} === 'string' && ${maxLengthFailExpr(raw, propSchema.maxLength)}) {`);
303
369
  lines.push(` errors.push({ message: 'must have at most ${propSchema.maxLength} characters', path: ${path} })`);
304
370
  lines.push(` }`);
305
371
  }
@@ -307,27 +373,25 @@ const generateConstraintChecks = (key, raw, path, propSchema, suffix, ctx) => {
307
373
  if (hasMinimum(propSchema) || hasMaximum(propSchema) || hasExclusiveMinimum(propSchema) || hasExclusiveMaximum(propSchema) || hasMultipleOf(propSchema)) {
308
374
  if (hasMinimum(propSchema)) {
309
375
  const strict = hasStrictExclusiveMinimum(propSchema);
310
- const op = strict ? "<=" : "<";
311
376
  const rel = strict ? ">" : ">=";
312
- lines.push(` if (typeof ${raw} === 'number' && ${raw} ${op} ${propSchema.minimum}) {`);
377
+ lines.push(` if (typeof ${raw} === 'number' && !(${raw} ${rel} ${propSchema.minimum})) {`);
313
378
  lines.push(` errors.push({ message: 'must be ${rel} ${propSchema.minimum}', path: ${path} })`);
314
379
  lines.push(` }`);
315
380
  }
316
381
  if (hasMaximum(propSchema)) {
317
382
  const strict = hasStrictExclusiveMaximum(propSchema);
318
- const op = strict ? ">=" : ">";
319
383
  const rel = strict ? "<" : "<=";
320
- lines.push(` if (typeof ${raw} === 'number' && ${raw} ${op} ${propSchema.maximum}) {`);
384
+ lines.push(` if (typeof ${raw} === 'number' && !(${raw} ${rel} ${propSchema.maximum})) {`);
321
385
  lines.push(` errors.push({ message: 'must be ${rel} ${propSchema.maximum}', path: ${path} })`);
322
386
  lines.push(` }`);
323
387
  }
324
388
  if (hasExclusiveMinimum(propSchema)) {
325
- lines.push(` if (typeof ${raw} === 'number' && ${raw} <= ${propSchema.exclusiveMinimum}) {`);
389
+ lines.push(` if (typeof ${raw} === 'number' && !(${raw} > ${propSchema.exclusiveMinimum})) {`);
326
390
  lines.push(` errors.push({ message: 'must be > ${propSchema.exclusiveMinimum}', path: ${path} })`);
327
391
  lines.push(` }`);
328
392
  }
329
393
  if (hasExclusiveMaximum(propSchema)) {
330
- lines.push(` if (typeof ${raw} === 'number' && ${raw} >= ${propSchema.exclusiveMaximum}) {`);
394
+ lines.push(` if (typeof ${raw} === 'number' && !(${raw} < ${propSchema.exclusiveMaximum})) {`);
331
395
  lines.push(` errors.push({ message: 'must be < ${propSchema.exclusiveMaximum}', path: ${path} })`);
332
396
  lines.push(` }`);
333
397
  }
@@ -339,12 +403,14 @@ const generateConstraintChecks = (key, raw, path, propSchema, suffix, ctx) => {
339
403
  }
340
404
  if (hasItems(propSchema)) {
341
405
  const itemSchema = propSchema.items;
406
+ const prefix = sp["prefixItems"];
407
+ const firstTailIndex = Array.isArray(prefix) ? prefix.length : 0;
342
408
  const iv = `_i${ctx.depth}`;
343
409
  const itemPath = `\`${path.slice(1, -1)}/\${${iv}}\``;
344
410
  if (hasRef(itemSchema)) {
345
411
  const vName = validatorName(refToName(itemSchema.$ref, suffix));
346
412
  lines.push(` if (Array.isArray(${raw})) {`);
347
- lines.push(` for (let ${iv} = 0; ${iv} < ${raw}.length; ${iv}++) {`);
413
+ lines.push(` for (let ${iv} = ${firstTailIndex}; ${iv} < ${raw}.length; ${iv}++) {`);
348
414
  lines.push(` const _ir = ${vName}(${raw}[${iv}], ${itemPath})`);
349
415
  lines.push(` if (_ir !== true) errors.push(..._ir.errors)`);
350
416
  lines.push(` }`);
@@ -354,7 +420,7 @@ const generateConstraintChecks = (key, raw, path, propSchema, suffix, ctx) => {
354
420
  const detail = generateValueChecks("", itemVar, itemPath, itemSchema, suffix, ctx, true);
355
421
  if (detail.length > 0) {
356
422
  lines.push(` if (Array.isArray(${raw})) {`);
357
- lines.push(` for (let ${iv} = 0; ${iv} < ${raw}.length; ${iv}++) {`);
423
+ lines.push(` for (let ${iv} = ${firstTailIndex}; ${iv} < ${raw}.length; ${iv}++) {`);
358
424
  lines.push(` const ${itemVar} = ${raw}[${iv}]`);
359
425
  lines.push(...detail.map((l) => ` ${l}`));
360
426
  lines.push(` }`);
@@ -362,7 +428,7 @@ const generateConstraintChecks = (key, raw, path, propSchema, suffix, ctx) => {
362
428
  }
363
429
  }
364
430
  }
365
- if (hasMinItems(propSchema) || hasMaxItems(propSchema) || hasUniqueItems(propSchema) && propSchema.uniqueItems === true || isSchemaObject(sp["contains"]) || Array.isArray(sp["prefixItems"]) || sp["items"] === false && !Array.isArray(sp["prefixItems"])) {
431
+ if (hasMinItems(propSchema) || hasMaxItems(propSchema) || hasUniqueItems(propSchema) && propSchema.uniqueItems === true || "contains" in sp || Array.isArray(sp["prefixItems"]) || sp["items"] === false && !Array.isArray(sp["prefixItems"])) {
366
432
  if (sp["items"] === false && !Array.isArray(sp["prefixItems"])) {
367
433
  lines.push(` if (Array.isArray(${raw}) && ${raw}.length > 0) {`);
368
434
  lines.push(` errors.push({ message: 'must NOT have more than 0 items', path: ${path} })`);
@@ -384,7 +450,7 @@ const generateConstraintChecks = (key, raw, path, propSchema, suffix, ctx) => {
384
450
  lines.push(` errors.push({ message: 'must NOT have duplicate items', path: ${path} })`);
385
451
  lines.push(` }`);
386
452
  }
387
- if (isSchemaObject(sp["contains"])) {
453
+ if ("contains" in sp) {
388
454
  const min = typeof sp["minContains"] === "number" ? sp["minContains"] : 1;
389
455
  const max = typeof sp["maxContains"] === "number" ? sp["maxContains"] : void 0;
390
456
  const matchExpr = generateMatchesExpr("_c", sp["contains"], suffix, ctx);
@@ -418,10 +484,24 @@ const generateConstraintChecks = (key, raw, path, propSchema, suffix, ctx) => {
418
484
  lines.push(...generateInlineObjectChecks(key, propSchema, raw, suffix, ctx));
419
485
  return lines;
420
486
  };
421
- const generateValueChecks = (_key, raw, path, propSchema, suffix, ctx, required = false) => {
487
+ const generateValueChecks = (key, raw, path, propSchema, suffix, ctx, required = false) => [
488
+ ...generateValueCheckLines(key, raw, path, propSchema, suffix, ctx, required),
489
+ // Appended rather than folded into the checks above because several of those
490
+ // branches (`$ref`, `const`, `enum`, x-mjst) return early: `unevaluated*` is a
491
+ // sibling of whatever else the node declares, and dropping it would leave the
492
+ // validator accepting documents the interpreter rejects.
493
+ ...generateUnevaluatedChecks(raw, path, propSchema, suffix, ctx)
494
+ ];
495
+ const generateValueCheckLines = (_key, raw, path, propSchema, suffix, ctx, required = false) => {
496
+ const lines = [];
497
+ if (propSchema === false) {
498
+ const report = `errors.push({ message: ${JSON.stringify(FALSE_SCHEMA_MESSAGE)}, path: ${path} })`;
499
+ if (required)
500
+ return [` ${report}`];
501
+ return [` if (${raw} !== undefined) {`, ` ${report}`, ` }`];
502
+ }
422
503
  if (!isSchemaObject(propSchema))
423
504
  return [];
424
- const lines = [];
425
505
  const presence = required ? "" : `${raw} !== undefined && `;
426
506
  if (hasRef(propSchema)) {
427
507
  const vName = validatorName(refToName(propSchema.$ref, suffix));
@@ -459,9 +539,8 @@ const generateValueChecks = (_key, raw, path, propSchema, suffix, ctx, required
459
539
  return lines;
460
540
  }
461
541
  if (hasEnum(propSchema)) {
462
- const allowed = JSON.stringify(propSchema.enum);
463
542
  const label = propSchema.enum.map((v) => JSON.stringify(v)).join(", ");
464
- lines.push(` if (${presence}!(${allowed} as unknown[]).includes(${raw})) {`);
543
+ lines.push(` if (${presence}!${enumMembershipExpr(propSchema.enum, raw)}) {`);
465
544
  lines.push(` errors.push({ message: ${JSON.stringify(`must be one of: ${label}`)}, path: ${path} })`);
466
545
  lines.push(` }`);
467
546
  return lines;
@@ -476,11 +555,22 @@ const generateValueChecks = (_key, raw, path, propSchema, suffix, ctx, required
476
555
  lines.push(` }`);
477
556
  }
478
557
  }
558
+ const typeArray = getTypeArray(propSchema);
559
+ if (typeArray) {
560
+ const allWrong = typeArray.map((t) => wrongTypeCondition(raw, t)).filter((c) => c !== "").map((c) => `(${c})`).join(" && ");
561
+ if (allWrong) {
562
+ const label = typeArray.map((t) => typeofString(t)).join(" or ");
563
+ lines.push(` if (${presence}(${allWrong})) {`);
564
+ lines.push(` errors.push({ message: ${JSON.stringify(`must be ${label}`)}, path: ${path} })`);
565
+ lines.push(` }`);
566
+ }
567
+ }
479
568
  const valueCtx = {
480
569
  objVar: ctx.objVar,
481
570
  pathPrefix: path.slice(1, -1),
482
571
  depth: ctx.depth + 1,
483
- hoisted: ctx.hoisted
572
+ hoisted: ctx.hoisted,
573
+ rootSchema: ctx.rootSchema
484
574
  };
485
575
  lines.push(...generateConstraintChecks("", raw, path, propSchema, suffix, valueCtx));
486
576
  lines.push(...generateCombinatorChecks("", raw, path, propSchema, suffix, valueCtx));
@@ -501,6 +591,41 @@ const generateMatchesExpr = (raw, sub, suffix, ctx) => {
501
591
  ${body}
502
592
  return _m.length === 0 })()`;
503
593
  };
594
+ const unevaluatedMatcher = (suffix, ctx) => (accessor, schema, depth) => generateMatchesExpr(accessor, schema, suffix, { ...ctx, depth: ctx.depth + depth + 1 });
595
+ const generateUnevaluatedChecks = (raw, path, schema, suffix, ctx) => {
596
+ if (!isSchemaObject(schema))
597
+ return [];
598
+ const s = schema;
599
+ if (!("unevaluatedProperties" in s) && !("unevaluatedItems" in s))
600
+ return [];
601
+ const match = unevaluatedMatcher(suffix, ctx);
602
+ const lines = [];
603
+ const properties = unevaluatedPropertiesExpr(raw, schema, ctx.rootSchema, ctx.depth, match);
604
+ if (properties === null)
605
+ throw new Error(UNPROVABLE_COVERAGE_MESSAGE("unevaluatedProperties"));
606
+ if (properties !== void 0) {
607
+ lines.push(` if (typeof ${raw} === 'object' && ${raw} !== null && !Array.isArray(${raw})) {`);
608
+ for (const statement of properties.setup)
609
+ lines.push(` ${statement}`);
610
+ lines.push(` if (!(${properties.expr})) {`);
611
+ lines.push(` errors.push({ message: 'must NOT have unevaluated properties', path: ${path} })`);
612
+ lines.push(` }`);
613
+ lines.push(` }`);
614
+ }
615
+ const items = unevaluatedItemsExpr(raw, schema, ctx.rootSchema, ctx.depth, match);
616
+ if (items === null)
617
+ throw new Error(UNPROVABLE_COVERAGE_MESSAGE("unevaluatedItems"));
618
+ if (items !== void 0) {
619
+ lines.push(` if (Array.isArray(${raw})) {`);
620
+ for (const statement of items.setup)
621
+ lines.push(` ${statement}`);
622
+ lines.push(` if (!(${items.expr})) {`);
623
+ lines.push(` errors.push({ message: 'must NOT have unevaluated items', path: ${path} })`);
624
+ lines.push(` }`);
625
+ lines.push(` }`);
626
+ }
627
+ return lines;
628
+ };
504
629
  const generateCombinatorChecks = (key, raw, path, schema, suffix, ctx) => {
505
630
  if (!isSchemaObject(schema))
506
631
  return [];
@@ -553,13 +678,13 @@ const generatePatternAndAdditionalChecks = (schema, suffix, ctx) => {
553
678
  const patternsRecord = "patternProperties" in schema && typeof schema.patternProperties === "object" && schema.patternProperties !== null ? schema.patternProperties : {};
554
679
  const patternEntries = Object.entries(patternsRecord);
555
680
  for (const [pattern, sub] of patternEntries) {
556
- const re = escapeRegexPattern(pattern);
681
+ const re = regexLiteral(pattern);
557
682
  const kv = `_pk${d}`;
558
683
  const valueChecks = generateValueChecks(`\${${kv}}`, `${obj}[${kv}]`, `\`${ctx.pathPrefix}/\${${kv}}\``, sub, suffix, ctx);
559
684
  if (valueChecks.length === 0)
560
685
  continue;
561
686
  lines.push(` for (const ${kv} in ${obj}) {`);
562
- lines.push(` if (/${re}/.test(${kv})) {`);
687
+ lines.push(` if (${re}.test(${kv})) {`);
563
688
  lines.push(...valueChecks.map((line) => ` ${line}`));
564
689
  lines.push(` }`);
565
690
  lines.push(` }`);
@@ -574,7 +699,7 @@ const generatePatternAndAdditionalChecks = (schema, suffix, ctx) => {
574
699
  if (known.length > 0)
575
700
  lines.push(` if (${JSON.stringify(known)}.includes(${kv})) continue`);
576
701
  for (const pattern of Object.keys(patternsRecord)) {
577
- lines.push(` if (/${escapeRegexPattern(pattern)}/.test(${kv})) continue`);
702
+ lines.push(` if (${regexLiteral(pattern)}.test(${kv})) continue`);
578
703
  }
579
704
  lines.push(...valueChecks.map((line) => ` ${line}`));
580
705
  lines.push(` }`);
@@ -592,7 +717,8 @@ const generateInlineObjectChecks = (key, propSchema, raw, suffix, ctx) => {
592
717
  // appending `/${key}` would emit a spurious `//` or trailing `/` in error paths.
593
718
  pathPrefix: key === "" ? ctx.pathPrefix : `${ctx.pathPrefix}/${pointerSegment(key)}`,
594
719
  depth: ctx.depth + 1,
595
- hoisted: ctx.hoisted
720
+ hoisted: ctx.hoisted,
721
+ rootSchema: ctx.rootSchema
596
722
  };
597
723
  const required = new Set(hasRequired(propSchema) ? propSchema.required : []);
598
724
  const properties = hasProperties(propSchema) ? propSchema.properties : {};
@@ -607,7 +733,7 @@ const generateInlineObjectChecks = (key, propSchema, raw, suffix, ctx) => {
607
733
  innerLines.push(...generateDependentSchemasChecks(propSchema, suffix, child));
608
734
  innerLines.push(...generateDependenciesChecks(propSchema, suffix, child));
609
735
  innerLines.push(...generateMinMaxPropertiesChecks(propSchema, child));
610
- if (hasPropertyNames(propSchema) && isSchemaObject(propSchema.propertyNames)) {
736
+ if (hasPropertyNames(propSchema)) {
611
737
  innerLines.push(...generatePropertyNameChecks(propSchema.propertyNames, suffix, child));
612
738
  }
613
739
  if (innerLines.length === 0)
@@ -620,14 +746,13 @@ const generateInlineObjectChecks = (key, propSchema, raw, suffix, ctx) => {
620
746
  ];
621
747
  };
622
748
  const generatePropertyNameChecks = (nameSchema, suffix, ctx) => {
623
- if (!isSchemaObject(nameSchema))
624
- return [];
625
749
  const at = `\`${ctx.pathPrefix}/\${_name}\``;
626
750
  const nameCtx = {
627
751
  objVar: ctx.objVar,
628
752
  pathPrefix: `${ctx.pathPrefix}/\${_name}`,
629
753
  depth: ctx.depth + 1,
630
- hoisted: ctx.hoisted
754
+ hoisted: ctx.hoisted,
755
+ rootSchema: ctx.rootSchema
631
756
  };
632
757
  const checks = generateValueChecks("", "_name", at, nameSchema, suffix, nameCtx, true);
633
758
  if (checks.length === 0)
@@ -645,7 +770,7 @@ const generateDependentRequiredChecks = (schema, ctx) => {
645
770
  continue;
646
771
  for (const dep of deps) {
647
772
  const msg = JSON.stringify(`must have property '${dep}' when '${trigger}' is present`);
648
- lines.push(` if (${JSON.stringify(trigger)} in ${obj} && !(${JSON.stringify(dep)} in ${obj})) {`);
773
+ lines.push(` if (${hasOwnCheck(obj, trigger)} && ${missingCheck(obj, dep)}) {`);
649
774
  lines.push(` errors.push({ message: ${msg}, path: ${at} })`);
650
775
  lines.push(` }`);
651
776
  }
@@ -667,7 +792,7 @@ const generateDependentSchemasChecks = (schema, suffix, ctx) => {
667
792
  continue;
668
793
  if (sub === false) {
669
794
  const msg = JSON.stringify(`must NOT have property '${trigger}'`);
670
- lines.push(` if (${JSON.stringify(trigger)} in ${obj}) {`);
795
+ lines.push(` if (${hasOwnCheck(obj, trigger)}) {`);
671
796
  lines.push(` errors.push({ message: ${msg}, path: ${at} })`);
672
797
  lines.push(` }`);
673
798
  continue;
@@ -677,7 +802,7 @@ const generateDependentSchemasChecks = (schema, suffix, ctx) => {
677
802
  const checks = generateValueChecks("", obj, objPath, sub, suffix, ctx);
678
803
  if (checks.length === 0)
679
804
  continue;
680
- lines.push(` if (${JSON.stringify(trigger)} in ${obj}) {`);
805
+ lines.push(` if (${hasOwnCheck(obj, trigger)}) {`);
681
806
  lines.push(...checks.map((line) => ` ${line}`));
682
807
  lines.push(` }`);
683
808
  }
@@ -699,7 +824,7 @@ const generateDependenciesChecks = (schema, suffix, ctx) => {
699
824
  if (typeof key !== "string")
700
825
  continue;
701
826
  const msg = JSON.stringify(`must have property '${key}' when '${trigger}' is present`);
702
- lines.push(` if (${JSON.stringify(trigger)} in ${obj} && !(${JSON.stringify(key)} in ${obj})) {`);
827
+ lines.push(` if (${hasOwnCheck(obj, trigger)} && ${missingCheck(obj, key)}) {`);
703
828
  lines.push(` errors.push({ message: ${msg}, path: ${at} })`);
704
829
  lines.push(` }`);
705
830
  }
@@ -709,7 +834,7 @@ const generateDependenciesChecks = (schema, suffix, ctx) => {
709
834
  continue;
710
835
  if (value === false) {
711
836
  const msg = JSON.stringify(`must NOT have property '${trigger}'`);
712
- lines.push(` if (${JSON.stringify(trigger)} in ${obj}) {`);
837
+ lines.push(` if (${hasOwnCheck(obj, trigger)}) {`);
713
838
  lines.push(` errors.push({ message: ${msg}, path: ${at} })`);
714
839
  lines.push(` }`);
715
840
  continue;
@@ -719,7 +844,7 @@ const generateDependenciesChecks = (schema, suffix, ctx) => {
719
844
  const checks = generateValueChecks("", obj, objPath, value, suffix, ctx);
720
845
  if (checks.length === 0)
721
846
  continue;
722
- lines.push(` if (${JSON.stringify(trigger)} in ${obj}) {`);
847
+ lines.push(` if (${hasOwnCheck(obj, trigger)}) {`);
723
848
  lines.push(...checks.map((line) => ` ${line}`));
724
849
  lines.push(` }`);
725
850
  }
@@ -755,11 +880,17 @@ const hasObjectLevelCombinator = (schema) => {
755
880
  return false;
756
881
  return hasAllOf(schema) || hasAnyOf(schema) || hasOneOf(schema) || "not" in schema || "if" in schema;
757
882
  };
883
+ const carriesUnevaluated = (schema) => {
884
+ if (!isSchemaObject(schema))
885
+ return false;
886
+ const s = schema;
887
+ return "unevaluatedProperties" in s && s["unevaluatedProperties"] !== true || "unevaluatedItems" in s && s["unevaluatedItems"] !== true;
888
+ };
758
889
  const guardPropConditions = (key, propSchema, objAcc) => {
759
890
  if (!isSchemaObject(propSchema))
760
891
  return null;
761
892
  const raw = safeAccessor(objAcc, key);
762
- if (hasRef(propSchema) || hasEnum(propSchema) || hasConst(propSchema) || hasOneOf(propSchema) || hasAnyOf(propSchema) || hasAllOf(propSchema) || "not" in propSchema || "if" in propSchema || getMjstInstanceOf(propSchema) !== void 0 || getMjstPrimitive(propSchema) !== void 0 || hasPattern(propSchema) || hasMinLength(propSchema) || hasMaxLength(propSchema) || hasMinimum(propSchema) || hasMaximum(propSchema) || hasExclusiveMinimum(propSchema) || hasExclusiveMaximum(propSchema) || hasMultipleOf(propSchema) || hasItems(propSchema)) {
893
+ if (hasRef(propSchema) || hasEnum(propSchema) || hasConst(propSchema) || hasOneOf(propSchema) || hasAnyOf(propSchema) || hasAllOf(propSchema) || "not" in propSchema || "if" in propSchema || getMjstInstanceOf(propSchema) !== void 0 || getMjstPrimitive(propSchema) !== void 0 || hasPattern(propSchema) || hasMinLength(propSchema) || hasMaxLength(propSchema) || hasMinimum(propSchema) || hasMaximum(propSchema) || hasExclusiveMinimum(propSchema) || hasExclusiveMaximum(propSchema) || hasMultipleOf(propSchema) || hasItems(propSchema) || carriesUnevaluated(propSchema)) {
763
894
  return null;
764
895
  }
765
896
  if (!hasType(propSchema))
@@ -807,6 +938,8 @@ const guardObjectConditions = (schema, raw, objAcc) => {
807
938
  return null;
808
939
  if (hasObjectLevelCombinator(schema))
809
940
  return null;
941
+ if (carriesUnevaluated(schema))
942
+ return null;
810
943
  let strict = false;
811
944
  if (hasAdditionalProperties(schema)) {
812
945
  if (schema.additionalProperties === false)
@@ -838,11 +971,11 @@ const guardObjectConditions = (schema, raw, objAcc) => {
838
971
  }
839
972
  return conditions;
840
973
  };
841
- const generateObjectValidator = (schema, typeName, suffix) => {
974
+ const generateObjectValidator = (schema, typeName, suffix, rootSchema) => {
842
975
  const vName = validatorName(typeName);
843
976
  const required = new Set(hasRequired(schema) ? schema.required : []);
844
977
  const properties = hasProperties(schema) ? schema.properties : {};
845
- const ctx = createRootContext();
978
+ const ctx = createRootContext(rootSchema);
846
979
  const propertyLines = [];
847
980
  for (const [key, propSchema] of Object.entries(properties)) {
848
981
  const checks = generatePropertyChecks(key, propSchema, required.has(key), suffix, ctx);
@@ -857,7 +990,7 @@ const generateObjectValidator = (schema, typeName, suffix) => {
857
990
  propertyLines.push(...generateDependentSchemasChecks(schema, suffix, ctx));
858
991
  propertyLines.push(...generateDependenciesChecks(schema, suffix, ctx));
859
992
  propertyLines.push(...generateMinMaxPropertiesChecks(schema, ctx));
860
- if (hasPropertyNames(schema) && isSchemaObject(schema.propertyNames)) {
993
+ if (hasPropertyNames(schema)) {
861
994
  propertyLines.push(...generatePropertyNameChecks(schema.propertyNames, suffix, ctx));
862
995
  }
863
996
  propertyLines.push(...generateCombinatorChecks("", "obj", "`${_path}`", schema, suffix, ctx));
@@ -897,17 +1030,10 @@ const generateObjectValidator = (schema, typeName, suffix) => {
897
1030
  ].join("\n");
898
1031
  };
899
1032
  const guardName = (typeName) => `is${typeName}`;
900
- const enumMembershipExpr = (values, acc) => {
901
- const allPrimitive = values.length > 0 && values.every((v) => (v === null || typeof v !== "object") && typeof v !== "function") && !values.some((v) => typeof v === "number" && Number.isNaN(v));
902
- if (allPrimitive) {
903
- return `(${values.map((v) => `${acc} === ${JSON.stringify(v)}`).join(" || ")})`;
904
- }
905
- return `(${JSON.stringify(values)} as unknown[]).includes(${acc})`;
906
- };
907
1033
  const booleanLeafExpr = (schema, acc) => {
908
1034
  if (!isSchemaObject(schema))
909
1035
  return null;
910
- if (hasRef(schema) || hasConst(schema) || hasOneOf(schema) || "anyOf" in schema || "allOf" in schema || "not" in schema || "if" in schema || "contains" in schema || "prefixItems" in schema || getMjstInstanceOf(schema) !== void 0 || getMjstPrimitive(schema) !== void 0) {
1036
+ if (hasRef(schema) || hasConst(schema) || hasOneOf(schema) || "anyOf" in schema || "allOf" in schema || "not" in schema || "if" in schema || "contains" in schema || "prefixItems" in schema || carriesUnevaluated(schema) || getMjstInstanceOf(schema) !== void 0 || getMjstPrimitive(schema) !== void 0) {
911
1037
  return null;
912
1038
  }
913
1039
  if (hasEnum(schema)) {
@@ -917,18 +1043,20 @@ const booleanLeafExpr = (schema, acc) => {
917
1043
  return null;
918
1044
  const t = schema.type;
919
1045
  switch (t) {
920
- // Each constraint is the exact negation of the validator's error condition
921
- // (`!(len < min)`, not `len >= min`) so edge values most importantly `NaN`,
922
- // which the validator accepts for a constrained number since `NaN < min` is
923
- // false get the identical verdict.
1046
+ // Each constraint is the exact negation of the validator's error condition,
1047
+ // so edge values `NaN` above all, which compares `false` against every
1048
+ // operator get the identical verdict on both paths. The numeric bounds are
1049
+ // written as the pass condition (`x >= min`) because the validator's error
1050
+ // condition is its negation; the length checks come from the same
1051
+ // `string-length-check` emitter the validator uses, for the same reason.
924
1052
  case "string": {
925
1053
  const parts = [`typeof ${acc} === 'string'`];
926
1054
  if (hasPattern(schema))
927
- parts.push(`/${escapeRegexPattern(schema.pattern)}/.test(${acc})`);
1055
+ parts.push(`${regexLiteral(schema.pattern)}.test(${acc})`);
928
1056
  if (hasMinLength(schema))
929
- parts.push(`!(${acc}.length < ${schema.minLength})`);
1057
+ parts.push(minLengthPassExpr(acc, schema.minLength));
930
1058
  if (hasMaxLength(schema))
931
- parts.push(`!(${acc}.length > ${schema.maxLength})`);
1059
+ parts.push(maxLengthPassExpr(acc, schema.maxLength));
932
1060
  return parts.join(" && ");
933
1061
  }
934
1062
  case "number":
@@ -937,13 +1065,13 @@ const booleanLeafExpr = (schema, acc) => {
937
1065
  if (t === "integer")
938
1066
  parts.push(`Number.isInteger(${acc})`);
939
1067
  if (hasMinimum(schema))
940
- parts.push(`!(${acc} ${hasStrictExclusiveMinimum(schema) ? "<=" : "<"} ${schema.minimum})`);
1068
+ parts.push(`${acc} ${hasStrictExclusiveMinimum(schema) ? ">" : ">="} ${schema.minimum}`);
941
1069
  if (hasMaximum(schema))
942
- parts.push(`!(${acc} ${hasStrictExclusiveMaximum(schema) ? ">=" : ">"} ${schema.maximum})`);
1070
+ parts.push(`${acc} ${hasStrictExclusiveMaximum(schema) ? "<" : "<="} ${schema.maximum}`);
943
1071
  if (hasExclusiveMinimum(schema))
944
- parts.push(`!(${acc} <= ${schema.exclusiveMinimum})`);
1072
+ parts.push(`${acc} > ${schema.exclusiveMinimum}`);
945
1073
  if (hasExclusiveMaximum(schema))
946
- parts.push(`!(${acc} >= ${schema.exclusiveMaximum})`);
1074
+ parts.push(`${acc} < ${schema.exclusiveMaximum}`);
947
1075
  if (hasMultipleOf(schema))
948
1076
  parts.push(multipleOfPassExpr(acc, schema.multipleOf));
949
1077
  return parts.join(" && ");
@@ -995,6 +1123,8 @@ const booleanObjectParts = (schema, raw, objAcc) => {
995
1123
  return null;
996
1124
  if (hasObjectLevelCombinator(schema))
997
1125
  return null;
1126
+ if (carriesUnevaluated(schema))
1127
+ return null;
998
1128
  let strict = false;
999
1129
  if (hasAdditionalProperties(schema)) {
1000
1130
  if (schema.additionalProperties === false)
@@ -1011,6 +1141,8 @@ const booleanObjectParts = (schema, raw, objAcc) => {
1011
1141
  const propSchema = properties[key];
1012
1142
  if (propSchema === void 0 || !isSchemaObject(propSchema))
1013
1143
  return null;
1144
+ if (PROTOTYPE_MEMBERS.has(key))
1145
+ return null;
1014
1146
  const member = safeAccessor(objAcc, key);
1015
1147
  const expr = booleanLeafExpr(propSchema, member);
1016
1148
  if (expr === null)
@@ -1029,16 +1161,33 @@ const booleanObjectParts = (schema, raw, objAcc) => {
1029
1161
  }
1030
1162
  return parts;
1031
1163
  };
1164
+ const IMPLICIT_OBJECT_KEYWORDS = ["properties", "patternProperties", "additionalProperties"];
1165
+ const typeDescribesEveryAcceptedValue = (schema) => {
1166
+ if (!isSchemaObject(schema))
1167
+ return true;
1168
+ const s = schema;
1169
+ if ("type" in s || "enum" in s || "const" in s || "$ref" in s)
1170
+ return true;
1171
+ for (const keyword of ["allOf", "anyOf", "oneOf"]) {
1172
+ const branches = s[keyword];
1173
+ if (!Array.isArray(branches))
1174
+ continue;
1175
+ if (!branches.every((branch) => typeDescribesEveryAcceptedValue(branch)))
1176
+ return false;
1177
+ }
1178
+ return !IMPLICIT_OBJECT_KEYWORDS.some((keyword) => keyword in s);
1179
+ };
1032
1180
  const generateBooleanGuard = (schema, typeName, _suffix = "") => {
1033
1181
  const name = guardName(typeName);
1034
- const fallback = `export const ${name} = (input: unknown): input is ${typeName} => ${validatorName(typeName)}(input) === true`;
1182
+ const returns = typeDescribesEveryAcceptedValue(rewriteNullable(schema)) ? `input is ${typeName}` : "boolean";
1183
+ const fallback = `export const ${name} = (input: unknown): ${returns} => ${validatorName(typeName)}(input) === true`;
1035
1184
  const rewritten = rewriteNullable(schema);
1036
- if (isObjectSchema(rewritten)) {
1185
+ if (declaresObjectType(rewritten)) {
1037
1186
  const parts = booleanObjectParts(rewritten, "input", "obj");
1038
1187
  if (parts === null)
1039
1188
  return fallback;
1040
1189
  return [
1041
- `export const ${name} = (input: unknown): input is ${typeName} => {`,
1190
+ `export const ${name} = (input: unknown): ${returns} => {`,
1042
1191
  ` const obj = input as Record<string, unknown>`,
1043
1192
  ` return (`,
1044
1193
  parts.map((part) => ` ${part}`).join(" &&\n"),
@@ -1049,11 +1198,18 @@ const generateBooleanGuard = (schema, typeName, _suffix = "") => {
1049
1198
  const expr = booleanLeafExpr(rewritten, "input");
1050
1199
  if (expr === null)
1051
1200
  return fallback;
1052
- return `export const ${name} = (input: unknown): input is ${typeName} => ${expr}`;
1201
+ return `export const ${name} = (input: unknown): ${returns} => ${expr}`;
1053
1202
  };
1054
- const generateScalarValidator = (schema, typeName, suffix) => {
1203
+ const generateScalarValidator = (schema, typeName, suffix, rootSchema) => {
1055
1204
  const vName = validatorName(typeName);
1056
1205
  if (!isSchemaObject(schema)) {
1206
+ if (schema === false) {
1207
+ return [
1208
+ `export const ${vName} = (_input: unknown, _path = ''): ValidationResult => {`,
1209
+ ` return { valid: false, errors: [{ message: ${JSON.stringify(FALSE_SCHEMA_MESSAGE)}, path: _path }] }`,
1210
+ `}`
1211
+ ].join("\n");
1212
+ }
1057
1213
  return [`export const ${vName} = (_input: unknown, _path = ''): ValidationResult => {`, ` return true`, `}`].join("\n");
1058
1214
  }
1059
1215
  if (hasRef(schema)) {
@@ -1099,11 +1255,10 @@ const generateScalarValidator = (schema, typeName, suffix) => {
1099
1255
  ].join("\n");
1100
1256
  }
1101
1257
  if (hasEnum(schema)) {
1102
- const allowed = JSON.stringify(schema.enum);
1103
1258
  const label = schema.enum.map((v) => JSON.stringify(v)).join(", ");
1104
1259
  return [
1105
1260
  `export const ${vName} = (input: unknown, _path = ''): ValidationResult => {`,
1106
- ` if (!(${allowed} as unknown[]).includes(input)) {`,
1261
+ ` if (!${enumMembershipExpr(schema.enum, "input")}) {`,
1107
1262
  ` return { valid: false, errors: [{ message: ${JSON.stringify(`must be one of: ${label}`)}, path: _path }] }`,
1108
1263
  ` }`,
1109
1264
  ` return true`,
@@ -1111,7 +1266,7 @@ const generateScalarValidator = (schema, typeName, suffix) => {
1111
1266
  ].join("\n");
1112
1267
  }
1113
1268
  if (hasAllOf(schema) || hasAnyOf(schema) || hasOneOf(schema) || "not" in schema || "if" in schema) {
1114
- const ctx = createRootContext();
1269
+ const ctx = createRootContext(rootSchema);
1115
1270
  const checks = [];
1116
1271
  const rootPath = "`${_path}`";
1117
1272
  const rootTypeArray2 = getTypeArray(schema);
@@ -1123,7 +1278,6 @@ const generateScalarValidator = (schema, typeName, suffix) => {
1123
1278
  checks.push(` errors.push({ message: ${JSON.stringify(`must be ${label}`)}, path: ${rootPath} })`);
1124
1279
  checks.push(` }`);
1125
1280
  }
1126
- checks.push(...generateConstraintChecks("", "input", rootPath, schema, suffix, ctx));
1127
1281
  } else if (hasType(schema)) {
1128
1282
  const t = schema.type;
1129
1283
  const wrongType = wrongTypeCondition("input", t);
@@ -1132,8 +1286,8 @@ const generateScalarValidator = (schema, typeName, suffix) => {
1132
1286
  checks.push(` errors.push({ message: 'must be ${typeofString(t)}', path: ${rootPath} })`);
1133
1287
  checks.push(` }`);
1134
1288
  }
1135
- checks.push(...generateConstraintChecks("", "input", rootPath, schema, suffix, ctx));
1136
1289
  }
1290
+ checks.push(...generateConstraintChecks("", "input", rootPath, schema, suffix, ctx));
1137
1291
  checks.push(...generateCombinatorChecks("", "input", rootPath, schema, suffix, ctx));
1138
1292
  const body = checks.join("\n").replaceAll("errors.push(", "(errors ??= []).push(");
1139
1293
  const hoistedBlock = ctx.hoisted.length > 0 ? `${ctx.hoisted.join("\n")}
@@ -1149,21 +1303,33 @@ const generateScalarValidator = (schema, typeName, suffix) => {
1149
1303
  }
1150
1304
  const rootTypeArray = getTypeArray(schema);
1151
1305
  if (rootTypeArray) {
1306
+ const ctx = createRootContext(rootSchema);
1307
+ const rootPath = "`${_path}`";
1308
+ const checks = [];
1152
1309
  const allWrong = rootTypeArray.map((t) => wrongTypeCondition("input", t)).filter((c) => c !== "").map((c) => `(${c})`).join(" && ");
1153
- const label = rootTypeArray.map((t) => typeofString(t)).join(" or ");
1154
- if (!allWrong) {
1310
+ if (allWrong) {
1311
+ const label = rootTypeArray.map((t) => typeofString(t)).join(" or ");
1312
+ checks.push(` if (${allWrong}) {`);
1313
+ checks.push(` errors.push({ message: ${JSON.stringify(`must be ${label}`)}, path: ${rootPath} })`);
1314
+ checks.push(` }`);
1315
+ }
1316
+ checks.push(...generateConstraintChecks("", "input", rootPath, schema, suffix, ctx));
1317
+ if (checks.length === 0) {
1155
1318
  return [
1156
1319
  `export const ${vName} = (_input: unknown, _path = ''): ValidationResult => {`,
1157
1320
  ` return true`,
1158
1321
  `}`
1159
1322
  ].join("\n");
1160
1323
  }
1324
+ const body = checks.join("\n").replaceAll("errors.push(", "(errors ??= []).push(");
1325
+ const hoistedBlock = ctx.hoisted.length > 0 ? `${ctx.hoisted.join("\n")}
1326
+
1327
+ ` : "";
1161
1328
  return [
1162
- `export const ${vName} = (input: unknown, _path = ''): ValidationResult => {`,
1163
- ` if (${allWrong}) {`,
1164
- ` return { valid: false, errors: [{ message: ${JSON.stringify(`must be ${label}`)}, path: _path }] }`,
1165
- ` }`,
1166
- ` return true`,
1329
+ `${hoistedBlock}export const ${vName} = (input: unknown, _path = ''): ValidationResult => {`,
1330
+ ` let errors: ValidationError[] | undefined`,
1331
+ body,
1332
+ ` return errors !== undefined ? { valid: false, errors } : true`,
1167
1333
  `}`
1168
1334
  ].join("\n");
1169
1335
  }
@@ -1171,7 +1337,7 @@ const generateScalarValidator = (schema, typeName, suffix) => {
1171
1337
  const t = schema.type;
1172
1338
  const wrongType = wrongTypeCondition("input", t);
1173
1339
  const typLabel = typeofString(t);
1174
- const rootCtx = createRootContext();
1340
+ const rootCtx = createRootContext(rootSchema);
1175
1341
  const constraintLines = generateConstraintChecks("", "input", "`${_path}`", schema, suffix, rootCtx);
1176
1342
  if (!wrongType) {
1177
1343
  return [
@@ -1204,27 +1370,36 @@ const generateScalarValidator = (schema, typeName, suffix) => {
1204
1370
  `}`
1205
1371
  ].join("\n");
1206
1372
  }
1207
- return [`export const ${vName} = (_input: unknown, _path = ''): ValidationResult => {`, ` return true`, `}`].join("\n");
1373
+ const typelessCtx = createRootContext(rootSchema);
1374
+ const typelessChecks = generateConstraintChecks("", "input", "`${_path}`", schema, suffix, typelessCtx);
1375
+ if (typelessChecks.length === 0) {
1376
+ return [`export const ${vName} = (_input: unknown, _path = ''): ValidationResult => {`, ` return true`, `}`].join("\n");
1377
+ }
1378
+ const typelessHoisted = typelessCtx.hoisted.length > 0 ? `${typelessCtx.hoisted.join("\n")}
1379
+
1380
+ ` : "";
1381
+ return [
1382
+ `${typelessHoisted}export const ${vName} = (input: unknown, _path = ''): ValidationResult => {`,
1383
+ ` let errors: ValidationError[] | undefined`,
1384
+ typelessChecks.join("\n").replaceAll("errors.push(", "(errors ??= []).push("),
1385
+ ` return errors !== undefined ? { valid: false, errors } : true`,
1386
+ `}`
1387
+ ].join("\n");
1208
1388
  };
1209
- const assertNoUnsupportedKeywords = (schema, typeName) => {
1210
- const visit = (node) => {
1211
- if (typeof node !== "object" || node === null)
1212
- return;
1213
- if (Array.isArray(node)) {
1214
- for (const item of node)
1215
- visit(item);
1216
- return;
1217
- }
1218
- const record = node;
1219
- for (const keyword of ["unevaluatedProperties", "unevaluatedItems"]) {
1220
- if (keyword in record && record[keyword] !== true) {
1221
- throw new Error(`[${typeName}] unsupported keyword "${keyword}": the validator generator does not implement it and would silently accept documents the interpreter rejects. Validate this schema with the runtime interpreter, or remove the keyword.`);
1222
- }
1223
- }
1224
- for (const value of Object.values(record))
1225
- visit(value);
1226
- };
1227
- visit(schema);
1389
+ const generateUnevaluatedRootValidator = (schema, typeName, suffix, rootSchema) => {
1390
+ const ctx = createRootContext(rootSchema);
1391
+ const checks = generateValueChecks("", "input", "`${_path}`", schema, suffix, ctx, true);
1392
+ const body = checks.join("\n").replaceAll("errors.push(", "(errors ??= []).push(");
1393
+ const hoistedBlock = ctx.hoisted.length > 0 ? `${ctx.hoisted.join("\n")}
1394
+
1395
+ ` : "";
1396
+ return [
1397
+ `${hoistedBlock}export const ${validatorName(typeName)} = (input: unknown, _path = ''): ValidationResult => {`,
1398
+ ` let errors: ValidationError[] | undefined`,
1399
+ body,
1400
+ ` return errors !== undefined ? { valid: false, errors } : true`,
1401
+ `}`
1402
+ ].join("\n");
1228
1403
  };
1229
1404
  const SINGLE_SUBSCHEMA_KEYS = /* @__PURE__ */ new Set([
1230
1405
  "additionalProperties",
@@ -1240,6 +1415,13 @@ const SINGLE_SUBSCHEMA_KEYS = /* @__PURE__ */ new Set([
1240
1415
  ]);
1241
1416
  const SUBSCHEMA_LIST_KEYS = /* @__PURE__ */ new Set(["allOf", "anyOf", "oneOf", "prefixItems"]);
1242
1417
  const SUBSCHEMA_MAP_KEYS = /* @__PURE__ */ new Set(["properties", "patternProperties", "dependentSchemas", "$defs", "definitions"]);
1418
+ const defineEntry = (target, key, value) => {
1419
+ if (key === "__proto__") {
1420
+ Object.defineProperty(target, key, { value, writable: true, enumerable: true, configurable: true });
1421
+ return;
1422
+ }
1423
+ target[key] = value;
1424
+ };
1243
1425
  const rewriteNullable = (node) => {
1244
1426
  if (typeof node !== "object" || node === null || Array.isArray(node))
1245
1427
  return node;
@@ -1249,37 +1431,43 @@ const rewriteNullable = (node) => {
1249
1431
  if (key === "nullable")
1250
1432
  continue;
1251
1433
  if (SINGLE_SUBSCHEMA_KEYS.has(key)) {
1252
- out[key] = rewriteNullable(value);
1434
+ defineEntry(out, key, rewriteNullable(value));
1253
1435
  } else if (key === "items") {
1254
- out[key] = Array.isArray(value) ? value.map(rewriteNullable) : rewriteNullable(value);
1436
+ defineEntry(out, key, Array.isArray(value) ? value.map(rewriteNullable) : rewriteNullable(value));
1255
1437
  } else if (SUBSCHEMA_LIST_KEYS.has(key) && Array.isArray(value)) {
1256
- out[key] = value.map(rewriteNullable);
1438
+ defineEntry(out, key, value.map(rewriteNullable));
1257
1439
  } else if (SUBSCHEMA_MAP_KEYS.has(key) && typeof value === "object" && value !== null) {
1258
1440
  const mapped = {};
1259
- for (const [name, sub] of Object.entries(value))
1260
- mapped[name] = rewriteNullable(sub);
1261
- out[key] = mapped;
1441
+ for (const [name, sub] of Object.entries(value)) {
1442
+ defineEntry(mapped, name, rewriteNullable(sub));
1443
+ }
1444
+ defineEntry(out, key, mapped);
1262
1445
  } else if (key === "dependencies" && typeof value === "object" && value !== null) {
1263
1446
  const mapped = {};
1264
1447
  for (const [name, sub] of Object.entries(value)) {
1265
- mapped[name] = Array.isArray(sub) ? sub : rewriteNullable(sub);
1448
+ defineEntry(mapped, name, Array.isArray(sub) ? sub : rewriteNullable(sub));
1266
1449
  }
1267
- out[key] = mapped;
1450
+ defineEntry(out, key, mapped);
1268
1451
  } else {
1269
- out[key] = value;
1452
+ defineEntry(out, key, value);
1270
1453
  }
1271
1454
  }
1272
1455
  if (src["nullable"] === true)
1273
1456
  return { anyOf: [{ type: "null" }, out] };
1274
1457
  return out;
1275
1458
  };
1276
- const generateValidatorFunction = (schema, typeName, suffix = "") => {
1277
- assertNoUnsupportedKeywords(schema, typeName);
1459
+ const generateValidatorFunction = (schema, typeName, suffix = "", rootSchema) => {
1460
+ assertGeneratableRefs(schema, typeName);
1278
1461
  const rewritten = rewriteNullable(schema);
1279
- if (isObjectSchema(rewritten)) {
1280
- return generateObjectValidator(rewritten, typeName, suffix);
1462
+ const document = rootSchema ?? schema;
1463
+ assertUnevaluatedGeneratable(rewritten, typeName, document, unevaluatedMatcher(suffix, createRootContext(document)));
1464
+ if (carriesUnevaluated(rewritten)) {
1465
+ return generateUnevaluatedRootValidator(rewritten, typeName, suffix, document);
1466
+ }
1467
+ if (declaresObjectType(rewritten)) {
1468
+ return generateObjectValidator(rewritten, typeName, suffix, document);
1281
1469
  }
1282
- return generateScalarValidator(rewritten, typeName, suffix);
1470
+ return generateScalarValidator(rewritten, typeName, suffix, document);
1283
1471
  };
1284
1472
  export {
1285
1473
  generateBooleanGuard,