@amritk/generate-parsers 0.17.2 → 0.19.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.
Files changed (32) hide show
  1. package/AI.md +10 -0
  2. package/README.md +147 -19
  3. package/dist/generators/assert-supported-keywords.d.ts +13 -9
  4. package/dist/generators/assert-supported-keywords.js +58 -18
  5. package/dist/generators/build-schema.d.ts +9 -1
  6. package/dist/generators/build-schema.js +5 -2
  7. package/dist/generators/generate-deep-equal-check.d.ts +24 -0
  8. package/dist/generators/generate-deep-equal-check.js +35 -0
  9. package/dist/generators/generate-discriminated-union-validation.js +1 -2
  10. package/dist/generators/generate-enum-check.d.ts +8 -3
  11. package/dist/generators/generate-enum-check.js +8 -6
  12. package/dist/generators/generate-files.d.ts +7 -0
  13. package/dist/generators/generate-files.js +23 -4
  14. package/dist/generators/generate-parser-function.d.ts +1 -1
  15. package/dist/generators/generate-parser-function.js +212 -65
  16. package/dist/generators/generate-schema-checks.js +10 -8
  17. package/dist/generators/generate-strict-assertion.d.ts +89 -5
  18. package/dist/generators/generate-strict-assertion.js +429 -58
  19. package/dist/generators/generate-type-checks.d.ts +46 -10
  20. package/dist/generators/generate-type-checks.js +112 -16
  21. package/dist/generators/generate-unique-items-check.d.ts +16 -0
  22. package/dist/generators/generate-unique-items-check.js +32 -0
  23. package/dist/generators/generate-validation-expression.d.ts +29 -4
  24. package/dist/generators/generate-validation-expression.js +15 -6
  25. package/dist/generators/subschema-match.d.ts +23 -4
  26. package/dist/generators/subschema-match.js +312 -48
  27. package/dist/generators/unevaluated-match.d.ts +19 -0
  28. package/dist/generators/unevaluated-match.js +206 -0
  29. package/dist/helpers/collect-imports.d.ts +8 -0
  30. package/dist/helpers/collect-imports.js +1 -1
  31. package/dist/helpers/get-default-value.js +45 -2
  32. package/package.json +9 -3
@@ -1,8 +1,10 @@
1
- import { escapeRegexPattern } from "@amritk/helpers/escape-regex-pattern";
1
+ import { regexLiteral } from "@amritk/helpers/escape-regex-pattern";
2
2
  import { getMjstInstanceOf, getMjstPrimitive } from "@amritk/helpers/mjst-extension";
3
3
  import { multipleOfPassExpr } from "@amritk/helpers/multiple-of-check";
4
+ import { hasOwnCheck } from "@amritk/helpers/safe-accessor";
4
5
  import { hasAdditionalProperties, hasConst, hasEnum, hasExclusiveMaximum, hasExclusiveMinimum, hasMaxItems, hasMaximum, hasMaxLength, hasMaxProperties, hasMinItems, hasMinimum, hasMinLength, hasMinProperties, hasMultipleOf, hasPattern, hasProperties, hasRequired, hasType, hasUniqueItems, isSchemaObject } from "@amritk/helpers/schema-guards";
5
6
  import { generateEnumCheck } from "./generate-enum-check.js";
7
+ import { generateUniqueItemsCheck } from "./generate-unique-items-check.js";
6
8
  const OBJECT_KEYWORDS = [
7
9
  "properties",
8
10
  "required",
@@ -61,7 +63,7 @@ const generateInferredChecks = (accessor, schema, type) => {
61
63
  checks.push(`typeof ${accessor} === "object" && ${accessor} !== null && !Array.isArray(${accessor})`);
62
64
  if (hasRequired(schema) && schema.required.length > 0) {
63
65
  for (const requiredKey of schema.required) {
64
- checks.push(`${JSON.stringify(requiredKey)} in ${accessor}`);
66
+ checks.push(hasOwnCheck(accessor, requiredKey));
65
67
  }
66
68
  }
67
69
  if (hasMinProperties(schema)) {
@@ -84,13 +86,13 @@ const generateInferredChecks = (accessor, schema, type) => {
84
86
  checks.push(`${accessor}.length <= ${schema.maxItems}`);
85
87
  }
86
88
  if (hasUniqueItems(schema) && schema.uniqueItems === true) {
87
- checks.push(`new Set(${accessor}).size === ${accessor}.length`);
89
+ checks.push(generateUniqueItemsCheck(accessor, schema));
88
90
  }
89
91
  break;
90
92
  case "string":
91
93
  checks.push(`typeof ${accessor} === "string"`);
92
94
  if (hasPattern(schema)) {
93
- checks.push(`/${escapeRegexPattern(schema.pattern)}/.test(${accessor})`);
95
+ checks.push(`${regexLiteral(schema.pattern)}.test(${accessor})`);
94
96
  }
95
97
  if (hasMinLength(schema)) {
96
98
  checks.push(`${accessor}.length >= ${schema.minLength}`);
@@ -145,7 +147,7 @@ const generateSchemaChecks = (accessor, schema) => {
145
147
  }
146
148
  checks.push(...generateInferredChecks(accessor, schema, inferred));
147
149
  if (hasEnum(schema) && schema.enum.length > 0) {
148
- checks.push(`${JSON.stringify(schema.enum)}.includes(${accessor} as never)`);
150
+ checks.push(generateEnumCheck(accessor, schema.enum));
149
151
  }
150
152
  return checks;
151
153
  }
@@ -153,7 +155,7 @@ const generateSchemaChecks = (accessor, schema) => {
153
155
  case "string": {
154
156
  checks.push(`typeof ${accessor} === "string"`);
155
157
  if (hasPattern(schema)) {
156
- checks.push(`/${escapeRegexPattern(schema.pattern)}/.test(${accessor})`);
158
+ checks.push(`${regexLiteral(schema.pattern)}.test(${accessor})`);
157
159
  }
158
160
  if (hasMinLength(schema)) {
159
161
  checks.push(`${accessor}.length >= ${schema.minLength}`);
@@ -197,14 +199,14 @@ const generateSchemaChecks = (accessor, schema) => {
197
199
  checks.push(`${accessor}.length <= ${schema.maxItems}`);
198
200
  }
199
201
  if (hasUniqueItems(schema) && schema.uniqueItems === true) {
200
- checks.push(`new Set(${accessor}).size === ${accessor}.length`);
202
+ checks.push(generateUniqueItemsCheck(accessor, schema));
201
203
  }
202
204
  break;
203
205
  case "object": {
204
206
  checks.push(`typeof ${accessor} === "object" && ${accessor} !== null && !Array.isArray(${accessor})`);
205
207
  if (hasRequired(schema) && schema.required.length > 0) {
206
208
  for (const requiredKey of schema.required) {
207
- checks.push(`${JSON.stringify(requiredKey)} in ${accessor}`);
209
+ checks.push(hasOwnCheck(accessor, requiredKey));
208
210
  }
209
211
  }
210
212
  if (hasMinProperties(schema)) {
@@ -14,6 +14,74 @@ export type StrictAssertionContext = {
14
14
  readonly rootSchema?: Record<string, unknown>;
15
15
  readonly stripUnknown?: boolean;
16
16
  };
17
+ /**
18
+ * Why a node needs the whole-schema backstop, or `null` when the specialized
19
+ * assertions above already cover it. Each reason is a keyword whose enforcement
20
+ * no per-property / per-type check can express:
21
+ *
22
+ * - `unevaluated*` depends on which *other* keywords evaluated which keys, so it
23
+ * can only be answered against the whole node at once.
24
+ * - a `$ref` is normally enforced by the parser generated for its target, but
25
+ * that delegation covers neither an inlined build (no ref imports) nor the
26
+ * 2020-12 rule that a `$ref`'s *siblings* still apply.
27
+ * - `items: false` (with no tuple positions) admits only the empty array, and
28
+ * the item-check builder reads `items` through a guard that a boolean fails.
29
+ * - a constraint keyword with no `type` never reaches the typed constraint pass.
30
+ */
31
+ export declare const backstopReason: (schema: JSONSchema, context: StrictAssertionContext) => string | null;
32
+ /**
33
+ * The slice of a node the backstop actually has to prove.
34
+ *
35
+ * `unevaluated*` and `$ref` need the node whole — the first because its answer
36
+ * depends on what every sibling keyword evaluated, the second because a ref's
37
+ * siblings apply alongside its target. A bare constraint needs only itself, and
38
+ * proving it in isolation is what keeps an ordinary
39
+ * `{ allOf: [{…}, { $ref: … }], required: ['role'] }` — the OpenAPI idiom —
40
+ * generating: the `required` is provable even when the surrounding `allOf`
41
+ * member is too deep (or too unresolvable) to inline, and that member has its
42
+ * own enforcement anyway.
43
+ *
44
+ * `properties` rides along, flattened to `true` schemas, only because
45
+ * `additionalProperties` needs the declared key names to know which keys it
46
+ * owns. Their values are irrelevant to that question and are asserted
47
+ * per-property elsewhere.
48
+ */
49
+ export declare const backstopSchema: (schema: JSONSchema, reason: string) => JSONSchema;
50
+ /**
51
+ * The strict-mode backstop: a single whole-node match, emitted only for the
52
+ * keywords {@link backstopReason} identifies as unreachable by the specialized
53
+ * assertions. It is the same exact matcher `contains` and `propertyNames` use, so
54
+ * a `true` verdict means valid and a `false` one means invalid — which is what
55
+ * lets it stand alone as the enforcement for those keywords.
56
+ *
57
+ * Emitting nothing here is only safe because `assertNoUnsupportedKeywords`
58
+ * refuses generation for any node that needs a backstop the matcher cannot
59
+ * build.
60
+ */
61
+ export declare const generateBackstopAssertion: (acc: string, schema: JSONSchema, label: string, context: StrictAssertionContext, optional?: boolean) => string[];
62
+ /**
63
+ * Strict-mode `minProperties` / `maxProperties`. Both were read by the coercing
64
+ * expression builder and the subschema matcher but by nothing on the strict
65
+ * path, so a strict parser accepted `{}` against `{ minProperties: 2 }` — at the
66
+ * root, on a property, and inside the empty-object/record parsers alike. `guard`
67
+ * wraps the comparison in a runtime `isObject` for accessors whose type is
68
+ * reported separately; callers that have already proven an object pass `false`.
69
+ */
70
+ export declare const generateObjectSizeChecks: (acc: string, schema: JSONSchema, label: string, guard: boolean) => string[];
71
+ /**
72
+ * Strict-mode value checks for keys the schema constrains by *pattern* rather
73
+ * than by name: `patternProperties` (every key matching the regex) and a
74
+ * schema-valued `additionalProperties` (every key matched by neither
75
+ * `properties` nor a pattern). Both were read only by the *building* code paths
76
+ * — which coerce or copy — so a strict parser accepted `{ s_a: 1 }` against
77
+ * `patternProperties: { '^s_': { type: 'string' } }`.
78
+ *
79
+ * One `Object.keys` walk covers both, and a term is emitted only when
80
+ * {@link subschemaMatchExpr} proves its subschema exactly. `additionalProperties:
81
+ * false` is not handled here — that is the unknown-key rejection the parsers
82
+ * already emit.
83
+ */
84
+ export declare const generateKeyedValueChecks: (obj: string, schema: JSONSchema, label: string, context?: StrictAssertionContext) => string[];
17
85
  /**
18
86
  * Strict-mode `contains` / `minContains` / `maxContains`: at least `minContains`
19
87
  * (default 1) and at most `maxContains` (default ∞) items of the array must
@@ -24,7 +92,15 @@ export type StrictAssertionContext = {
24
92
  * `contains` or its subschema is not matchable inline (the generation-time guard
25
93
  * rejects the latter, so a strict parser never silently drops the constraint).
26
94
  */
27
- export declare const generateContainsCheck: (acc: string, schema: JSONSchema, label: string) => string[];
95
+ export declare const generateContainsCheck: (acc: string, schema: JSONSchema, label: string, context?: StrictAssertionContext) => string[];
96
+ /**
97
+ * The composition checks (`not`, `allOf`, `if`/`then`/`else`) for a value the
98
+ * caller has already proven present. Exported for the specialized object parsers
99
+ * (empty-object, pattern-properties, additionalProperties), which build their
100
+ * result outside {@link generateObjectStrictAssertion} and so would otherwise
101
+ * emit no composition check at all.
102
+ */
103
+ export declare const generateCompositionChecks: (acc: string, schema: JSONSchema, label: string, context?: StrictAssertionContext) => string[];
28
104
  /**
29
105
  * Emits the object-level keyword checks (`dependentRequired`, `dependentSchemas`,
30
106
  * `propertyNames`) for the object reached by `obj`. When `guard` is true the
@@ -34,7 +110,7 @@ export declare const generateContainsCheck: (acc: string, schema: JSONSchema, la
34
110
  * `guard = false` because the enclosing parser has already proven the value is
35
111
  * an object.
36
112
  */
37
- export declare const generateObjectKeywordChecks: (obj: string, schema: JSONSchema, label: string, guard: boolean) => string[];
113
+ export declare const generateObjectKeywordChecks: (obj: string, schema: JSONSchema, label: string, guard: boolean, context?: StrictAssertionContext) => string[];
38
114
  /**
39
115
  * Generates strict-mode assertions for the body of an object parser.
40
116
  * Throws on:
@@ -46,9 +122,17 @@ export declare const generateObjectKeywordChecks: (obj: string, schema: JSONSche
46
122
  * Properties with a `$ref` are validated by the nested parser's own strict
47
123
  * check when that parser is invoked downstream.
48
124
  */
125
+ /**
126
+ * The `allOf` members this assertion must enforce itself: plain object schemas
127
+ * written inline. A `$ref` member is enforced by the parser generated for its
128
+ * target, and a member that composes further (nested `allOf`/`oneOf`/`if`) is
129
+ * left alone rather than half-checked.
130
+ */
131
+ export declare const inlineAllOfMembers: (schema: JSONSchema) => JSONSchema[];
49
132
  export declare const generateObjectStrictAssertion: (schema: JSONSchema, typeName: string, context?: StrictAssertionContext) => string[];
50
133
  /**
51
- * Generates a single strict-mode assertion line for a non-object scalar parser.
52
- * Returns null when the schema has no type information to assert on.
134
+ * Generates the strict-mode assertion lines for a non-object (scalar, array,
135
+ * type-less) parser. Returns null when the schema constrains nothing this
136
+ * generator can assert on, which is the caller's cue to emit a bare cast.
53
137
  */
54
- export declare const generateScalarStrictAssertion: (schema: JSONSchema, typeName: string, rootSchema?: Record<string, unknown>) => string | null;
138
+ export declare const generateScalarStrictAssertion: (schema: JSONSchema, typeName: string, context?: StrictAssertionContext) => string | null;