@formspec/build 0.1.0-alpha.11 → 0.1.0-alpha.12

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 (133) hide show
  1. package/README.md +51 -15
  2. package/dist/__tests__/chain-dsl-canonicalizer.test.d.ts +2 -0
  3. package/dist/__tests__/chain-dsl-canonicalizer.test.d.ts.map +1 -0
  4. package/dist/__tests__/constraint-validator.test.d.ts +2 -0
  5. package/dist/__tests__/constraint-validator.test.d.ts.map +1 -0
  6. package/dist/__tests__/extension-api.test.d.ts +2 -0
  7. package/dist/__tests__/extension-api.test.d.ts.map +1 -0
  8. package/dist/__tests__/fixtures/example-a-builtins.d.ts +18 -0
  9. package/dist/__tests__/fixtures/example-a-builtins.d.ts.map +1 -1
  10. package/dist/__tests__/ir-analyzer.test.d.ts +11 -0
  11. package/dist/__tests__/ir-analyzer.test.d.ts.map +1 -0
  12. package/dist/__tests__/ir-jsdoc-constraints.test.d.ts +12 -0
  13. package/dist/__tests__/ir-jsdoc-constraints.test.d.ts.map +1 -0
  14. package/dist/__tests__/ir-json-schema-generator.test.d.ts +11 -0
  15. package/dist/__tests__/ir-json-schema-generator.test.d.ts.map +1 -0
  16. package/dist/__tests__/ir-ui-schema-generator.test.d.ts +2 -0
  17. package/dist/__tests__/ir-ui-schema-generator.test.d.ts.map +1 -0
  18. package/dist/__tests__/jsdoc-constraints.test.d.ts +4 -4
  19. package/dist/__tests__/parity/fixtures/address/chain-dsl.d.ts +9 -0
  20. package/dist/__tests__/parity/fixtures/address/chain-dsl.d.ts.map +1 -0
  21. package/dist/__tests__/parity/fixtures/address/expected-ir.d.ts +9 -0
  22. package/dist/__tests__/parity/fixtures/address/expected-ir.d.ts.map +1 -0
  23. package/dist/__tests__/parity/fixtures/address/tsdoc.d.ts +19 -0
  24. package/dist/__tests__/parity/fixtures/address/tsdoc.d.ts.map +1 -0
  25. package/dist/__tests__/parity/fixtures/product-config/chain-dsl.d.ts +13 -0
  26. package/dist/__tests__/parity/fixtures/product-config/chain-dsl.d.ts.map +1 -0
  27. package/dist/__tests__/parity/fixtures/product-config/expected-ir.d.ts +9 -0
  28. package/dist/__tests__/parity/fixtures/product-config/expected-ir.d.ts.map +1 -0
  29. package/dist/__tests__/parity/fixtures/product-config/tsdoc.d.ts +28 -0
  30. package/dist/__tests__/parity/fixtures/product-config/tsdoc.d.ts.map +1 -0
  31. package/dist/__tests__/parity/fixtures/user-registration/chain-dsl.d.ts +12 -0
  32. package/dist/__tests__/parity/fixtures/user-registration/chain-dsl.d.ts.map +1 -0
  33. package/dist/__tests__/parity/fixtures/user-registration/expected-ir.d.ts +9 -0
  34. package/dist/__tests__/parity/fixtures/user-registration/expected-ir.d.ts.map +1 -0
  35. package/dist/__tests__/parity/fixtures/user-registration/tsdoc.d.ts +19 -0
  36. package/dist/__tests__/parity/fixtures/user-registration/tsdoc.d.ts.map +1 -0
  37. package/dist/__tests__/parity/parity.test.d.ts +14 -0
  38. package/dist/__tests__/parity/parity.test.d.ts.map +1 -0
  39. package/dist/__tests__/parity/utils.d.ts +139 -0
  40. package/dist/__tests__/parity/utils.d.ts.map +1 -0
  41. package/dist/analyzer/class-analyzer.d.ts +54 -99
  42. package/dist/analyzer/class-analyzer.d.ts.map +1 -1
  43. package/dist/analyzer/jsdoc-constraints.d.ts +78 -30
  44. package/dist/analyzer/jsdoc-constraints.d.ts.map +1 -1
  45. package/dist/analyzer/tsdoc-parser.d.ts +61 -0
  46. package/dist/analyzer/tsdoc-parser.d.ts.map +1 -0
  47. package/dist/browser.cjs +960 -309
  48. package/dist/browser.cjs.map +1 -1
  49. package/dist/browser.d.ts +10 -6
  50. package/dist/browser.d.ts.map +1 -1
  51. package/dist/browser.js +958 -308
  52. package/dist/browser.js.map +1 -1
  53. package/dist/build.d.ts +65 -150
  54. package/dist/canonicalize/chain-dsl-canonicalizer.d.ts +18 -0
  55. package/dist/canonicalize/chain-dsl-canonicalizer.d.ts.map +1 -0
  56. package/dist/canonicalize/index.d.ts +8 -0
  57. package/dist/canonicalize/index.d.ts.map +1 -0
  58. package/dist/canonicalize/tsdoc-canonicalizer.d.ts +34 -0
  59. package/dist/canonicalize/tsdoc-canonicalizer.d.ts.map +1 -0
  60. package/dist/cli.cjs +1417 -1656
  61. package/dist/cli.cjs.map +1 -1
  62. package/dist/cli.js +1421 -1647
  63. package/dist/cli.js.map +1 -1
  64. package/dist/extensions/index.d.ts +8 -0
  65. package/dist/extensions/index.d.ts.map +1 -0
  66. package/dist/extensions/registry.d.ts +55 -0
  67. package/dist/extensions/registry.d.ts.map +1 -0
  68. package/dist/generators/class-schema.d.ts +23 -38
  69. package/dist/generators/class-schema.d.ts.map +1 -1
  70. package/dist/generators/method-schema.d.ts +6 -8
  71. package/dist/generators/method-schema.d.ts.map +1 -1
  72. package/dist/index.cjs +1353 -1614
  73. package/dist/index.cjs.map +1 -1
  74. package/dist/index.d.ts +6 -8
  75. package/dist/index.d.ts.map +1 -1
  76. package/dist/index.js +1365 -1610
  77. package/dist/index.js.map +1 -1
  78. package/dist/internals.cjs +1604 -824
  79. package/dist/internals.cjs.map +1 -1
  80. package/dist/internals.d.ts +12 -3
  81. package/dist/internals.d.ts.map +1 -1
  82. package/dist/internals.js +1607 -820
  83. package/dist/internals.js.map +1 -1
  84. package/dist/json-schema/generator.d.ts +10 -5
  85. package/dist/json-schema/generator.d.ts.map +1 -1
  86. package/dist/json-schema/ir-generator.d.ts +84 -0
  87. package/dist/json-schema/ir-generator.d.ts.map +1 -0
  88. package/dist/json-schema/schema.d.ts +3 -3
  89. package/dist/json-schema/types.d.ts +5 -6
  90. package/dist/json-schema/types.d.ts.map +1 -1
  91. package/dist/ui-schema/generator.d.ts +5 -15
  92. package/dist/ui-schema/generator.d.ts.map +1 -1
  93. package/dist/ui-schema/ir-generator.d.ts +53 -0
  94. package/dist/ui-schema/ir-generator.d.ts.map +1 -0
  95. package/dist/validate/constraint-validator.d.ts +66 -0
  96. package/dist/validate/constraint-validator.d.ts.map +1 -0
  97. package/dist/validate/index.d.ts +9 -0
  98. package/dist/validate/index.d.ts.map +1 -0
  99. package/package.json +5 -4
  100. package/dist/__tests__/analyzer-edge-cases.test.d.ts +0 -13
  101. package/dist/__tests__/analyzer-edge-cases.test.d.ts.map +0 -1
  102. package/dist/__tests__/analyzer.test.d.ts +0 -5
  103. package/dist/__tests__/analyzer.test.d.ts.map +0 -1
  104. package/dist/__tests__/codegen.test.d.ts +0 -5
  105. package/dist/__tests__/codegen.test.d.ts.map +0 -1
  106. package/dist/__tests__/decorator-pipeline.test.d.ts +0 -11
  107. package/dist/__tests__/decorator-pipeline.test.d.ts.map +0 -1
  108. package/dist/__tests__/fixtures/example-b-decorators.d.ts +0 -5
  109. package/dist/__tests__/fixtures/example-b-decorators.d.ts.map +0 -1
  110. package/dist/__tests__/fixtures/example-b-extended.d.ts +0 -5
  111. package/dist/__tests__/fixtures/example-b-extended.d.ts.map +0 -1
  112. package/dist/__tests__/fixtures/example-c-custom.d.ts +0 -5
  113. package/dist/__tests__/fixtures/example-c-custom.d.ts.map +0 -1
  114. package/dist/__tests__/fixtures/example-c-decorators.d.ts +0 -5
  115. package/dist/__tests__/fixtures/example-c-decorators.d.ts.map +0 -1
  116. package/dist/__tests__/fixtures/example-d-mixed-decorators.d.ts +0 -6
  117. package/dist/__tests__/fixtures/example-d-mixed-decorators.d.ts.map +0 -1
  118. package/dist/__tests__/fixtures/example-e-decorators.d.ts +0 -11
  119. package/dist/__tests__/fixtures/example-e-decorators.d.ts.map +0 -1
  120. package/dist/__tests__/fixtures/example-e-no-namespace.d.ts +0 -5
  121. package/dist/__tests__/fixtures/example-e-no-namespace.d.ts.map +0 -1
  122. package/dist/__tests__/fixtures/example-jsdoc-constraints.d.ts +0 -16
  123. package/dist/__tests__/fixtures/example-jsdoc-constraints.d.ts.map +0 -1
  124. package/dist/__tests__/fixtures/example-nested-class.d.ts +0 -45
  125. package/dist/__tests__/fixtures/example-nested-class.d.ts.map +0 -1
  126. package/dist/__tests__/interface-types.test.d.ts +0 -11
  127. package/dist/__tests__/interface-types.test.d.ts.map +0 -1
  128. package/dist/analyzer/decorator-extractor.d.ts +0 -78
  129. package/dist/analyzer/decorator-extractor.d.ts.map +0 -1
  130. package/dist/analyzer/type-converter.d.ts +0 -75
  131. package/dist/analyzer/type-converter.d.ts.map +0 -1
  132. package/dist/codegen/index.d.ts +0 -75
  133. package/dist/codegen/index.d.ts.map +0 -1
package/dist/cli.js CHANGED
@@ -9,223 +9,642 @@ var __export = (target, all) => {
9
9
  __defProp(target, name, { get: all[name], enumerable: true });
10
10
  };
11
11
 
12
- // src/json-schema/schema.ts
13
- import { z } from "zod";
14
- var jsonSchemaTypeSchema, jsonSchema7Schema;
15
- var init_schema = __esm({
16
- "src/json-schema/schema.ts"() {
17
- "use strict";
18
- jsonSchemaTypeSchema = z.enum([
19
- "string",
20
- "number",
21
- "integer",
22
- "boolean",
23
- "object",
24
- "array",
25
- "null"
26
- ]);
27
- jsonSchema7Schema = z.lazy(
28
- () => z.object({
29
- $schema: z.string().optional(),
30
- $id: z.string().optional(),
31
- $ref: z.string().optional(),
32
- // Metadata
33
- title: z.string().optional(),
34
- description: z.string().optional(),
35
- deprecated: z.boolean().optional(),
36
- // Type
37
- type: z.union([jsonSchemaTypeSchema, z.array(jsonSchemaTypeSchema)]).optional(),
38
- // String validation
39
- minLength: z.number().optional(),
40
- maxLength: z.number().optional(),
41
- pattern: z.string().optional(),
42
- // Number validation
43
- minimum: z.number().optional(),
44
- maximum: z.number().optional(),
45
- exclusiveMinimum: z.number().optional(),
46
- exclusiveMaximum: z.number().optional(),
47
- // Enum
48
- enum: z.array(z.union([z.string(), z.number(), z.boolean(), z.null()])).readonly().optional(),
49
- const: z.union([z.string(), z.number(), z.boolean(), z.null()]).optional(),
50
- // Object
51
- properties: z.record(z.string(), jsonSchema7Schema).optional(),
52
- required: z.array(z.string()).optional(),
53
- additionalProperties: z.union([z.boolean(), jsonSchema7Schema]).optional(),
54
- // Array
55
- items: z.union([jsonSchema7Schema, z.array(jsonSchema7Schema)]).optional(),
56
- minItems: z.number().optional(),
57
- maxItems: z.number().optional(),
58
- // Composition
59
- allOf: z.array(jsonSchema7Schema).optional(),
60
- anyOf: z.array(jsonSchema7Schema).optional(),
61
- oneOf: z.array(jsonSchema7Schema).optional(),
62
- not: jsonSchema7Schema.optional(),
63
- // Conditional
64
- if: jsonSchema7Schema.optional(),
65
- then: jsonSchema7Schema.optional(),
66
- else: jsonSchema7Schema.optional(),
67
- // Format
68
- format: z.string().optional(),
69
- // Default
70
- default: z.unknown().optional(),
71
- // FormSpec extensions
72
- "x-formspec-source": z.string().optional(),
73
- "x-formspec-params": z.array(z.string()).readonly().optional(),
74
- "x-formspec-schemaSource": z.string().optional()
75
- }).passthrough()
76
- );
77
- }
78
- });
79
-
80
- // src/json-schema/generator.ts
81
- import { z as z2 } from "zod";
82
- function parseOrThrow(schema, value, label) {
83
- try {
84
- return schema.parse(value);
85
- } catch (error) {
86
- if (error instanceof z2.ZodError) {
87
- throw new Error(
88
- `Generated ${label} failed validation:
89
- ${error.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`).join("\n")}`
90
- );
91
- }
92
- throw error;
93
- }
12
+ // src/canonicalize/chain-dsl-canonicalizer.ts
13
+ import { IR_VERSION } from "@formspec/core";
14
+ function isGroup(el) {
15
+ return el._type === "group";
94
16
  }
95
- function generateNestedSchema(elements) {
96
- const properties = {};
97
- const required = [];
98
- collectFields(elements, properties, required);
99
- const uniqueRequired = [...new Set(required)];
17
+ function isConditional(el) {
18
+ return el._type === "conditional";
19
+ }
20
+ function isField(el) {
21
+ return el._type === "field";
22
+ }
23
+ function canonicalizeChainDSL(form) {
100
24
  return {
101
- type: "object",
102
- properties,
103
- ...uniqueRequired.length > 0 && { required: uniqueRequired }
25
+ kind: "form-ir",
26
+ irVersion: IR_VERSION,
27
+ elements: canonicalizeElements(form.elements),
28
+ typeRegistry: {},
29
+ provenance: CHAIN_DSL_PROVENANCE
104
30
  };
105
31
  }
106
- function fieldToJsonSchema(field) {
107
- const base = {};
108
- if (field.label !== void 0) {
109
- base.title = field.label;
32
+ function canonicalizeElements(elements) {
33
+ return elements.map(canonicalizeElement);
34
+ }
35
+ function canonicalizeElement(element) {
36
+ if (isField(element)) {
37
+ return canonicalizeField(element);
38
+ }
39
+ if (isGroup(element)) {
40
+ return canonicalizeGroup(element);
110
41
  }
42
+ if (isConditional(element)) {
43
+ return canonicalizeConditional(element);
44
+ }
45
+ const _exhaustive = element;
46
+ throw new Error(`Unknown element type: ${JSON.stringify(_exhaustive)}`);
47
+ }
48
+ function canonicalizeField(field) {
111
49
  switch (field._field) {
112
50
  case "text":
113
- return { ...base, type: "string" };
51
+ return canonicalizeTextField(field);
114
52
  case "number":
115
- return {
116
- ...base,
117
- type: "number",
118
- ...field.min !== void 0 && { minimum: field.min },
119
- ...field.max !== void 0 && { maximum: field.max }
120
- };
53
+ return canonicalizeNumberField(field);
121
54
  case "boolean":
122
- return { ...base, type: "boolean" };
123
- case "enum": {
124
- const opts = field.options;
125
- const isObjectOptions = opts.length > 0 && opts.every(
126
- (opt) => typeof opt === "object" && "id" in opt && "label" in opt
127
- );
128
- if (isObjectOptions) {
129
- return {
130
- ...base,
131
- type: "string",
132
- oneOf: opts.map((o) => ({
133
- const: o.id,
134
- title: o.label
135
- }))
136
- };
137
- }
138
- return { ...base, type: "string", enum: opts };
139
- }
55
+ return canonicalizeBooleanField(field);
56
+ case "enum":
57
+ return canonicalizeStaticEnumField(field);
140
58
  case "dynamic_enum":
141
- return {
142
- ...base,
143
- type: "string",
144
- "x-formspec-source": field.source,
145
- ...field.params !== void 0 && field.params.length > 0 && { "x-formspec-params": field.params }
146
- };
59
+ return canonicalizeDynamicEnumField(field);
147
60
  case "dynamic_schema":
148
- return {
149
- ...base,
150
- type: "object",
151
- additionalProperties: true,
152
- "x-formspec-schemaSource": field.schemaSource
153
- };
154
- case "array": {
155
- const arrayField = field;
156
- return {
157
- ...base,
158
- type: "array",
159
- items: generateNestedSchema(arrayField.items),
160
- ...arrayField.minItems !== void 0 && { minItems: arrayField.minItems },
161
- ...arrayField.maxItems !== void 0 && { maxItems: arrayField.maxItems }
162
- };
163
- }
164
- case "object": {
165
- const objectField = field;
166
- const nestedSchema = generateNestedSchema(objectField.properties);
167
- return {
168
- ...base,
169
- ...nestedSchema
170
- };
171
- }
61
+ return canonicalizeDynamicSchemaField(field);
62
+ case "array":
63
+ return canonicalizeArrayField(field);
64
+ case "object":
65
+ return canonicalizeObjectField(field);
172
66
  default: {
173
67
  const _exhaustive = field;
174
- return _exhaustive;
68
+ throw new Error(`Unknown field type: ${JSON.stringify(_exhaustive)}`);
69
+ }
70
+ }
71
+ }
72
+ function canonicalizeTextField(field) {
73
+ const type = { kind: "primitive", primitiveKind: "string" };
74
+ return buildFieldNode(
75
+ field.name,
76
+ type,
77
+ field.required,
78
+ buildAnnotations(field.label, field.placeholder)
79
+ );
80
+ }
81
+ function canonicalizeNumberField(field) {
82
+ const type = { kind: "primitive", primitiveKind: "number" };
83
+ const constraints = [];
84
+ if (field.min !== void 0) {
85
+ const c = {
86
+ kind: "constraint",
87
+ constraintKind: "minimum",
88
+ value: field.min,
89
+ provenance: CHAIN_DSL_PROVENANCE
90
+ };
91
+ constraints.push(c);
92
+ }
93
+ if (field.max !== void 0) {
94
+ const c = {
95
+ kind: "constraint",
96
+ constraintKind: "maximum",
97
+ value: field.max,
98
+ provenance: CHAIN_DSL_PROVENANCE
99
+ };
100
+ constraints.push(c);
101
+ }
102
+ return buildFieldNode(
103
+ field.name,
104
+ type,
105
+ field.required,
106
+ buildAnnotations(field.label),
107
+ constraints
108
+ );
109
+ }
110
+ function canonicalizeBooleanField(field) {
111
+ const type = { kind: "primitive", primitiveKind: "boolean" };
112
+ return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
113
+ }
114
+ function canonicalizeStaticEnumField(field) {
115
+ const members = field.options.map((opt) => {
116
+ if (typeof opt === "string") {
117
+ return { value: opt };
118
+ }
119
+ return { value: opt.id, displayName: opt.label };
120
+ });
121
+ const type = { kind: "enum", members };
122
+ return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
123
+ }
124
+ function canonicalizeDynamicEnumField(field) {
125
+ const type = {
126
+ kind: "dynamic",
127
+ dynamicKind: "enum",
128
+ sourceKey: field.source,
129
+ parameterFields: field.params ? [...field.params] : []
130
+ };
131
+ return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
132
+ }
133
+ function canonicalizeDynamicSchemaField(field) {
134
+ const type = {
135
+ kind: "dynamic",
136
+ dynamicKind: "schema",
137
+ sourceKey: field.schemaSource,
138
+ parameterFields: []
139
+ };
140
+ return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
141
+ }
142
+ function canonicalizeArrayField(field) {
143
+ const itemProperties = buildObjectProperties(field.items);
144
+ const itemsType = {
145
+ kind: "object",
146
+ properties: itemProperties,
147
+ additionalProperties: false
148
+ };
149
+ const type = { kind: "array", items: itemsType };
150
+ const constraints = [];
151
+ if (field.minItems !== void 0) {
152
+ const c = {
153
+ kind: "constraint",
154
+ constraintKind: "minItems",
155
+ value: field.minItems,
156
+ provenance: CHAIN_DSL_PROVENANCE
157
+ };
158
+ constraints.push(c);
159
+ }
160
+ if (field.maxItems !== void 0) {
161
+ const c = {
162
+ kind: "constraint",
163
+ constraintKind: "maxItems",
164
+ value: field.maxItems,
165
+ provenance: CHAIN_DSL_PROVENANCE
166
+ };
167
+ constraints.push(c);
168
+ }
169
+ return buildFieldNode(
170
+ field.name,
171
+ type,
172
+ field.required,
173
+ buildAnnotations(field.label),
174
+ constraints
175
+ );
176
+ }
177
+ function canonicalizeObjectField(field) {
178
+ const properties = buildObjectProperties(field.properties);
179
+ const type = {
180
+ kind: "object",
181
+ properties,
182
+ additionalProperties: false
183
+ };
184
+ return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
185
+ }
186
+ function canonicalizeGroup(g) {
187
+ return {
188
+ kind: "group",
189
+ label: g.label,
190
+ elements: canonicalizeElements(g.elements),
191
+ provenance: CHAIN_DSL_PROVENANCE
192
+ };
193
+ }
194
+ function canonicalizeConditional(c) {
195
+ return {
196
+ kind: "conditional",
197
+ fieldName: c.field,
198
+ // Conditional values from the chain DSL are JSON-serializable primitives
199
+ // (strings, numbers, booleans) produced by the `is()` predicate helper.
200
+ value: assertJsonValue(c.value),
201
+ elements: canonicalizeElements(c.elements),
202
+ provenance: CHAIN_DSL_PROVENANCE
203
+ };
204
+ }
205
+ function assertJsonValue(v) {
206
+ if (v === null || typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
207
+ return v;
208
+ }
209
+ if (Array.isArray(v)) {
210
+ return v.map(assertJsonValue);
211
+ }
212
+ if (typeof v === "object") {
213
+ const result = {};
214
+ for (const [key, val] of Object.entries(v)) {
215
+ result[key] = assertJsonValue(val);
216
+ }
217
+ return result;
218
+ }
219
+ throw new TypeError(`Conditional value is not a valid JsonValue: ${typeof v}`);
220
+ }
221
+ function buildFieldNode(name, type, required, annotations, constraints = []) {
222
+ return {
223
+ kind: "field",
224
+ name,
225
+ type,
226
+ required: required === true,
227
+ constraints,
228
+ annotations,
229
+ provenance: CHAIN_DSL_PROVENANCE
230
+ };
231
+ }
232
+ function buildAnnotations(label, placeholder) {
233
+ const annotations = [];
234
+ if (label !== void 0) {
235
+ const a = {
236
+ kind: "annotation",
237
+ annotationKind: "displayName",
238
+ value: label,
239
+ provenance: CHAIN_DSL_PROVENANCE
240
+ };
241
+ annotations.push(a);
242
+ }
243
+ if (placeholder !== void 0) {
244
+ const a = {
245
+ kind: "annotation",
246
+ annotationKind: "placeholder",
247
+ value: placeholder,
248
+ provenance: CHAIN_DSL_PROVENANCE
249
+ };
250
+ annotations.push(a);
251
+ }
252
+ return annotations;
253
+ }
254
+ function buildObjectProperties(elements, insideConditional = false) {
255
+ const properties = [];
256
+ for (const el of elements) {
257
+ if (isField(el)) {
258
+ const fieldNode = canonicalizeField(el);
259
+ properties.push({
260
+ name: fieldNode.name,
261
+ type: fieldNode.type,
262
+ // Fields inside a conditional branch are always optional in the
263
+ // data schema, regardless of their `required` flag — the condition
264
+ // may not be met, so the field may be absent.
265
+ optional: insideConditional || !fieldNode.required,
266
+ constraints: fieldNode.constraints,
267
+ annotations: fieldNode.annotations,
268
+ provenance: CHAIN_DSL_PROVENANCE
269
+ });
270
+ } else if (isGroup(el)) {
271
+ properties.push(...buildObjectProperties(el.elements, insideConditional));
272
+ } else if (isConditional(el)) {
273
+ properties.push(...buildObjectProperties(el.elements, true));
274
+ }
275
+ }
276
+ return properties;
277
+ }
278
+ var CHAIN_DSL_PROVENANCE;
279
+ var init_chain_dsl_canonicalizer = __esm({
280
+ "src/canonicalize/chain-dsl-canonicalizer.ts"() {
281
+ "use strict";
282
+ CHAIN_DSL_PROVENANCE = {
283
+ surface: "chain-dsl",
284
+ file: "",
285
+ line: 0,
286
+ column: 0
287
+ };
288
+ }
289
+ });
290
+
291
+ // src/canonicalize/tsdoc-canonicalizer.ts
292
+ import { IR_VERSION as IR_VERSION2 } from "@formspec/core";
293
+ function canonicalizeTSDoc(analysis, source) {
294
+ const file = source?.file ?? "";
295
+ const provenance = {
296
+ surface: "tsdoc",
297
+ file,
298
+ line: 1,
299
+ column: 0
300
+ };
301
+ const elements = assembleElements(analysis.fields, analysis.fieldLayouts, provenance);
302
+ return {
303
+ kind: "form-ir",
304
+ irVersion: IR_VERSION2,
305
+ elements,
306
+ typeRegistry: analysis.typeRegistry,
307
+ provenance
308
+ };
309
+ }
310
+ function assembleElements(fields, layouts, provenance) {
311
+ const elements = [];
312
+ const groupMap = /* @__PURE__ */ new Map();
313
+ const topLevelOrder = [];
314
+ for (let i = 0; i < fields.length; i++) {
315
+ const field = fields[i];
316
+ const layout = layouts[i];
317
+ if (!field || !layout) continue;
318
+ const element = wrapInConditional(field, layout, provenance);
319
+ if (layout.groupLabel !== void 0) {
320
+ const label = layout.groupLabel;
321
+ let groupElements = groupMap.get(label);
322
+ if (!groupElements) {
323
+ groupElements = [];
324
+ groupMap.set(label, groupElements);
325
+ topLevelOrder.push({ type: "group", label });
326
+ }
327
+ groupElements.push(element);
328
+ } else {
329
+ topLevelOrder.push({ type: "element", element });
330
+ }
331
+ }
332
+ for (const entry of topLevelOrder) {
333
+ if (entry.type === "group") {
334
+ const groupElements = groupMap.get(entry.label);
335
+ if (groupElements) {
336
+ const groupNode = {
337
+ kind: "group",
338
+ label: entry.label,
339
+ elements: groupElements,
340
+ provenance
341
+ };
342
+ elements.push(groupNode);
343
+ groupMap.delete(entry.label);
344
+ }
345
+ } else {
346
+ elements.push(entry.element);
175
347
  }
176
348
  }
349
+ return elements;
177
350
  }
178
- function collectFields(elements, properties, required) {
351
+ function wrapInConditional(field, layout, provenance) {
352
+ if (layout.showWhen === void 0) {
353
+ return field;
354
+ }
355
+ const conditional = {
356
+ kind: "conditional",
357
+ fieldName: layout.showWhen.field,
358
+ value: layout.showWhen.value,
359
+ elements: [field],
360
+ provenance
361
+ };
362
+ return conditional;
363
+ }
364
+ var init_tsdoc_canonicalizer = __esm({
365
+ "src/canonicalize/tsdoc-canonicalizer.ts"() {
366
+ "use strict";
367
+ }
368
+ });
369
+
370
+ // src/canonicalize/index.ts
371
+ var init_canonicalize = __esm({
372
+ "src/canonicalize/index.ts"() {
373
+ "use strict";
374
+ init_chain_dsl_canonicalizer();
375
+ init_tsdoc_canonicalizer();
376
+ }
377
+ });
378
+
379
+ // src/json-schema/ir-generator.ts
380
+ function makeContext() {
381
+ return { defs: {} };
382
+ }
383
+ function generateJsonSchemaFromIR(ir) {
384
+ const ctx = makeContext();
385
+ for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
386
+ ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
387
+ }
388
+ const properties = {};
389
+ const required = [];
390
+ collectFields(ir.elements, properties, required, ctx);
391
+ const uniqueRequired = [...new Set(required)];
392
+ const result = {
393
+ $schema: "https://json-schema.org/draft/2020-12/schema",
394
+ type: "object",
395
+ properties,
396
+ ...uniqueRequired.length > 0 && { required: uniqueRequired }
397
+ };
398
+ if (Object.keys(ctx.defs).length > 0) {
399
+ result.$defs = ctx.defs;
400
+ }
401
+ return result;
402
+ }
403
+ function collectFields(elements, properties, required, ctx) {
179
404
  for (const element of elements) {
180
- switch (element._type) {
405
+ switch (element.kind) {
181
406
  case "field":
182
- properties[element.name] = fieldToJsonSchema(element);
183
- if (element.required === true) {
407
+ properties[element.name] = generateFieldSchema(element, ctx);
408
+ if (element.required) {
184
409
  required.push(element.name);
185
410
  }
186
411
  break;
187
412
  case "group":
188
- collectFields(element.elements, properties, required);
413
+ collectFields(element.elements, properties, required, ctx);
189
414
  break;
190
415
  case "conditional":
191
- collectFields(
192
- element.elements,
193
- properties,
194
- required
195
- );
416
+ collectFields(element.elements, properties, required, ctx);
196
417
  break;
418
+ default: {
419
+ const _exhaustive = element;
420
+ void _exhaustive;
421
+ }
197
422
  }
198
423
  }
199
424
  }
200
- function generateJsonSchema(form) {
425
+ function generateFieldSchema(field, ctx) {
426
+ const schema = generateTypeNode(field.type, ctx);
427
+ applyConstraints(schema, field.constraints);
428
+ applyAnnotations(schema, field.annotations);
429
+ return schema;
430
+ }
431
+ function generateTypeNode(type, ctx) {
432
+ switch (type.kind) {
433
+ case "primitive":
434
+ return generatePrimitiveType(type);
435
+ case "enum":
436
+ return generateEnumType(type);
437
+ case "array":
438
+ return generateArrayType(type, ctx);
439
+ case "object":
440
+ return generateObjectType(type, ctx);
441
+ case "union":
442
+ return generateUnionType(type, ctx);
443
+ case "reference":
444
+ return generateReferenceType(type);
445
+ case "dynamic":
446
+ return generateDynamicType(type);
447
+ case "custom":
448
+ return generateCustomType(type);
449
+ default: {
450
+ const _exhaustive = type;
451
+ return _exhaustive;
452
+ }
453
+ }
454
+ }
455
+ function generatePrimitiveType(type) {
456
+ return { type: type.primitiveKind };
457
+ }
458
+ function generateEnumType(type) {
459
+ const hasDisplayNames = type.members.some((m) => m.displayName !== void 0);
460
+ if (hasDisplayNames) {
461
+ return {
462
+ oneOf: type.members.map((m) => {
463
+ const entry = { const: m.value };
464
+ if (m.displayName !== void 0) {
465
+ entry.title = m.displayName;
466
+ }
467
+ return entry;
468
+ })
469
+ };
470
+ }
471
+ return { enum: type.members.map((m) => m.value) };
472
+ }
473
+ function generateArrayType(type, ctx) {
474
+ return {
475
+ type: "array",
476
+ items: generateTypeNode(type.items, ctx)
477
+ };
478
+ }
479
+ function generateObjectType(type, ctx) {
201
480
  const properties = {};
202
481
  const required = [];
203
- collectFields(form.elements, properties, required);
204
- const uniqueRequired = [...new Set(required)];
205
- const result = {
206
- $schema: "https://json-schema.org/draft-07/schema#",
482
+ for (const prop of type.properties) {
483
+ properties[prop.name] = generatePropertySchema(prop, ctx);
484
+ if (!prop.optional) {
485
+ required.push(prop.name);
486
+ }
487
+ }
488
+ const schema = { type: "object", properties };
489
+ if (required.length > 0) {
490
+ schema.required = required;
491
+ }
492
+ if (!type.additionalProperties) {
493
+ schema.additionalProperties = false;
494
+ }
495
+ return schema;
496
+ }
497
+ function generatePropertySchema(prop, ctx) {
498
+ const schema = generateTypeNode(prop.type, ctx);
499
+ applyConstraints(schema, prop.constraints);
500
+ applyAnnotations(schema, prop.annotations);
501
+ return schema;
502
+ }
503
+ function generateUnionType(type, ctx) {
504
+ if (isBooleanUnion(type)) {
505
+ return { type: "boolean" };
506
+ }
507
+ return {
508
+ anyOf: type.members.map((m) => generateTypeNode(m, ctx))
509
+ };
510
+ }
511
+ function isBooleanUnion(type) {
512
+ if (type.members.length !== 2) return false;
513
+ const kinds = type.members.map((m) => m.kind);
514
+ return kinds.every((k) => k === "primitive") && type.members.every((m) => m.kind === "primitive" && m.primitiveKind === "boolean");
515
+ }
516
+ function generateReferenceType(type) {
517
+ return { $ref: `#/$defs/${type.name}` };
518
+ }
519
+ function generateDynamicType(type) {
520
+ if (type.dynamicKind === "enum") {
521
+ const schema = {
522
+ type: "string",
523
+ "x-formspec-source": type.sourceKey
524
+ };
525
+ if (type.parameterFields.length > 0) {
526
+ schema["x-formspec-params"] = [...type.parameterFields];
527
+ }
528
+ return schema;
529
+ }
530
+ return {
207
531
  type: "object",
208
- properties,
209
- ...uniqueRequired.length > 0 && { required: uniqueRequired }
532
+ additionalProperties: true,
533
+ "x-formspec-schemaSource": type.sourceKey
210
534
  };
211
- return parseOrThrow(jsonSchema7Schema, result, "JSON Schema");
535
+ }
536
+ function generateCustomType(_type) {
537
+ return { type: "object" };
538
+ }
539
+ function applyConstraints(schema, constraints) {
540
+ for (const constraint of constraints) {
541
+ switch (constraint.constraintKind) {
542
+ case "minimum":
543
+ schema.minimum = constraint.value;
544
+ break;
545
+ case "maximum":
546
+ schema.maximum = constraint.value;
547
+ break;
548
+ case "exclusiveMinimum":
549
+ schema.exclusiveMinimum = constraint.value;
550
+ break;
551
+ case "exclusiveMaximum":
552
+ schema.exclusiveMaximum = constraint.value;
553
+ break;
554
+ case "multipleOf": {
555
+ const { value } = constraint;
556
+ if (value === 1 && schema.type === "number") {
557
+ schema.type = "integer";
558
+ } else {
559
+ schema.multipleOf = value;
560
+ }
561
+ break;
562
+ }
563
+ case "minLength":
564
+ schema.minLength = constraint.value;
565
+ break;
566
+ case "maxLength":
567
+ schema.maxLength = constraint.value;
568
+ break;
569
+ case "minItems":
570
+ schema.minItems = constraint.value;
571
+ break;
572
+ case "maxItems":
573
+ schema.maxItems = constraint.value;
574
+ break;
575
+ case "pattern":
576
+ schema.pattern = constraint.pattern;
577
+ break;
578
+ case "uniqueItems":
579
+ schema.uniqueItems = constraint.value;
580
+ break;
581
+ case "allowedMembers":
582
+ break;
583
+ case "custom":
584
+ break;
585
+ default: {
586
+ const _exhaustive = constraint;
587
+ void _exhaustive;
588
+ }
589
+ }
590
+ }
591
+ }
592
+ function applyAnnotations(schema, annotations) {
593
+ for (const annotation of annotations) {
594
+ switch (annotation.annotationKind) {
595
+ case "displayName":
596
+ schema.title = annotation.value;
597
+ break;
598
+ case "description":
599
+ schema.description = annotation.value;
600
+ break;
601
+ case "defaultValue":
602
+ schema.default = annotation.value;
603
+ break;
604
+ case "deprecated":
605
+ schema.deprecated = true;
606
+ break;
607
+ case "placeholder":
608
+ break;
609
+ case "formatHint":
610
+ break;
611
+ case "custom":
612
+ break;
613
+ default: {
614
+ const _exhaustive = annotation;
615
+ void _exhaustive;
616
+ }
617
+ }
618
+ }
619
+ }
620
+ var init_ir_generator = __esm({
621
+ "src/json-schema/ir-generator.ts"() {
622
+ "use strict";
623
+ }
624
+ });
625
+
626
+ // src/json-schema/generator.ts
627
+ function generateJsonSchema(form) {
628
+ const ir = canonicalizeChainDSL(form);
629
+ return generateJsonSchemaFromIR(ir);
212
630
  }
213
631
  var init_generator = __esm({
214
632
  "src/json-schema/generator.ts"() {
215
633
  "use strict";
216
- init_schema();
634
+ init_canonicalize();
635
+ init_ir_generator();
217
636
  }
218
637
  });
219
638
 
220
639
  // src/ui-schema/schema.ts
221
- import { z as z3 } from "zod";
640
+ import { z } from "zod";
222
641
  var jsonPointerSchema, ruleEffectSchema, uiSchemaElementTypeSchema, ruleConditionSchema, schemaBasedConditionSchema, ruleSchema, uiSchemaElementSchema, controlSchema, verticalLayoutSchema, horizontalLayoutSchema, groupLayoutSchema, categorySchema, categorizationSchema, labelElementSchema, uiSchema;
223
- var init_schema2 = __esm({
642
+ var init_schema = __esm({
224
643
  "src/ui-schema/schema.ts"() {
225
644
  "use strict";
226
- jsonPointerSchema = z3.string();
227
- ruleEffectSchema = z3.enum(["SHOW", "HIDE", "ENABLE", "DISABLE"]);
228
- uiSchemaElementTypeSchema = z3.enum([
645
+ jsonPointerSchema = z.string();
646
+ ruleEffectSchema = z.enum(["SHOW", "HIDE", "ENABLE", "DISABLE"]);
647
+ uiSchemaElementTypeSchema = z.enum([
229
648
  "Control",
230
649
  "VerticalLayout",
231
650
  "HorizontalLayout",
@@ -234,32 +653,32 @@ var init_schema2 = __esm({
234
653
  "Category",
235
654
  "Label"
236
655
  ]);
237
- ruleConditionSchema = z3.lazy(
238
- () => z3.object({
239
- const: z3.unknown().optional(),
240
- enum: z3.array(z3.unknown()).readonly().optional(),
241
- type: z3.string().optional(),
656
+ ruleConditionSchema = z.lazy(
657
+ () => z.object({
658
+ const: z.unknown().optional(),
659
+ enum: z.array(z.unknown()).readonly().optional(),
660
+ type: z.string().optional(),
242
661
  not: ruleConditionSchema.optional(),
243
- minimum: z3.number().optional(),
244
- maximum: z3.number().optional(),
245
- exclusiveMinimum: z3.number().optional(),
246
- exclusiveMaximum: z3.number().optional(),
247
- minLength: z3.number().optional(),
248
- properties: z3.record(z3.string(), ruleConditionSchema).optional(),
249
- required: z3.array(z3.string()).optional(),
250
- allOf: z3.array(ruleConditionSchema).optional()
662
+ minimum: z.number().optional(),
663
+ maximum: z.number().optional(),
664
+ exclusiveMinimum: z.number().optional(),
665
+ exclusiveMaximum: z.number().optional(),
666
+ minLength: z.number().optional(),
667
+ properties: z.record(z.string(), ruleConditionSchema).optional(),
668
+ required: z.array(z.string()).optional(),
669
+ allOf: z.array(ruleConditionSchema).optional()
251
670
  }).strict()
252
671
  );
253
- schemaBasedConditionSchema = z3.object({
672
+ schemaBasedConditionSchema = z.object({
254
673
  scope: jsonPointerSchema,
255
674
  schema: ruleConditionSchema
256
675
  }).strict();
257
- ruleSchema = z3.object({
676
+ ruleSchema = z.object({
258
677
  effect: ruleEffectSchema,
259
678
  condition: schemaBasedConditionSchema
260
679
  }).strict();
261
- uiSchemaElementSchema = z3.lazy(
262
- () => z3.union([
680
+ uiSchemaElementSchema = z.lazy(
681
+ () => z.union([
263
682
  controlSchema,
264
683
  verticalLayoutSchema,
265
684
  horizontalLayoutSchema,
@@ -269,75 +688,75 @@ var init_schema2 = __esm({
269
688
  labelElementSchema
270
689
  ])
271
690
  );
272
- controlSchema = z3.object({
273
- type: z3.literal("Control"),
691
+ controlSchema = z.object({
692
+ type: z.literal("Control"),
274
693
  scope: jsonPointerSchema,
275
- label: z3.union([z3.string(), z3.literal(false)]).optional(),
694
+ label: z.union([z.string(), z.literal(false)]).optional(),
276
695
  rule: ruleSchema.optional(),
277
- options: z3.record(z3.string(), z3.unknown()).optional()
696
+ options: z.record(z.string(), z.unknown()).optional()
278
697
  }).passthrough();
279
- verticalLayoutSchema = z3.lazy(
280
- () => z3.object({
281
- type: z3.literal("VerticalLayout"),
282
- elements: z3.array(uiSchemaElementSchema),
698
+ verticalLayoutSchema = z.lazy(
699
+ () => z.object({
700
+ type: z.literal("VerticalLayout"),
701
+ elements: z.array(uiSchemaElementSchema),
283
702
  rule: ruleSchema.optional(),
284
- options: z3.record(z3.string(), z3.unknown()).optional()
703
+ options: z.record(z.string(), z.unknown()).optional()
285
704
  }).passthrough()
286
705
  );
287
- horizontalLayoutSchema = z3.lazy(
288
- () => z3.object({
289
- type: z3.literal("HorizontalLayout"),
290
- elements: z3.array(uiSchemaElementSchema),
706
+ horizontalLayoutSchema = z.lazy(
707
+ () => z.object({
708
+ type: z.literal("HorizontalLayout"),
709
+ elements: z.array(uiSchemaElementSchema),
291
710
  rule: ruleSchema.optional(),
292
- options: z3.record(z3.string(), z3.unknown()).optional()
711
+ options: z.record(z.string(), z.unknown()).optional()
293
712
  }).passthrough()
294
713
  );
295
- groupLayoutSchema = z3.lazy(
296
- () => z3.object({
297
- type: z3.literal("Group"),
298
- label: z3.string(),
299
- elements: z3.array(uiSchemaElementSchema),
714
+ groupLayoutSchema = z.lazy(
715
+ () => z.object({
716
+ type: z.literal("Group"),
717
+ label: z.string(),
718
+ elements: z.array(uiSchemaElementSchema),
300
719
  rule: ruleSchema.optional(),
301
- options: z3.record(z3.string(), z3.unknown()).optional()
720
+ options: z.record(z.string(), z.unknown()).optional()
302
721
  }).passthrough()
303
722
  );
304
- categorySchema = z3.lazy(
305
- () => z3.object({
306
- type: z3.literal("Category"),
307
- label: z3.string(),
308
- elements: z3.array(uiSchemaElementSchema),
723
+ categorySchema = z.lazy(
724
+ () => z.object({
725
+ type: z.literal("Category"),
726
+ label: z.string(),
727
+ elements: z.array(uiSchemaElementSchema),
309
728
  rule: ruleSchema.optional(),
310
- options: z3.record(z3.string(), z3.unknown()).optional()
729
+ options: z.record(z.string(), z.unknown()).optional()
311
730
  }).passthrough()
312
731
  );
313
- categorizationSchema = z3.lazy(
314
- () => z3.object({
315
- type: z3.literal("Categorization"),
316
- elements: z3.array(categorySchema),
317
- label: z3.string().optional(),
732
+ categorizationSchema = z.lazy(
733
+ () => z.object({
734
+ type: z.literal("Categorization"),
735
+ elements: z.array(categorySchema),
736
+ label: z.string().optional(),
318
737
  rule: ruleSchema.optional(),
319
- options: z3.record(z3.string(), z3.unknown()).optional()
738
+ options: z.record(z.string(), z.unknown()).optional()
320
739
  }).passthrough()
321
740
  );
322
- labelElementSchema = z3.object({
323
- type: z3.literal("Label"),
324
- text: z3.string(),
741
+ labelElementSchema = z.object({
742
+ type: z.literal("Label"),
743
+ text: z.string(),
325
744
  rule: ruleSchema.optional(),
326
- options: z3.record(z3.string(), z3.unknown()).optional()
745
+ options: z.record(z.string(), z.unknown()).optional()
327
746
  }).passthrough();
328
- uiSchema = z3.lazy(
329
- () => z3.union([verticalLayoutSchema, horizontalLayoutSchema, groupLayoutSchema, categorizationSchema])
747
+ uiSchema = z.lazy(
748
+ () => z.union([verticalLayoutSchema, horizontalLayoutSchema, groupLayoutSchema, categorizationSchema])
330
749
  );
331
750
  }
332
751
  });
333
752
 
334
- // src/ui-schema/generator.ts
335
- import { z as z4 } from "zod";
336
- function parseOrThrow2(schema, value, label) {
753
+ // src/ui-schema/ir-generator.ts
754
+ import { z as z2 } from "zod";
755
+ function parseOrThrow(schema, value, label) {
337
756
  try {
338
757
  return schema.parse(value);
339
758
  } catch (error) {
340
- if (error instanceof z4.ZodError) {
759
+ if (error instanceof z2.ZodError) {
341
760
  throw new Error(
342
761
  `Generated ${label} failed validation:
343
762
  ${error.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`).join("\n")}`
@@ -382,115 +801,76 @@ function combineRules(parentRule, childRule) {
382
801
  }
383
802
  };
384
803
  }
385
- function elementsToUiSchema(elements, parentRule) {
804
+ function fieldNodeToControl(field, parentRule) {
805
+ const displayNameAnnotation = field.annotations.find((a) => a.annotationKind === "displayName");
806
+ const control = {
807
+ type: "Control",
808
+ scope: fieldToScope(field.name),
809
+ ...displayNameAnnotation !== void 0 && { label: displayNameAnnotation.value },
810
+ ...parentRule !== void 0 && { rule: parentRule }
811
+ };
812
+ return control;
813
+ }
814
+ function groupNodeToLayout(group, parentRule) {
815
+ return {
816
+ type: "Group",
817
+ label: group.label,
818
+ elements: irElementsToUiSchema(group.elements, parentRule),
819
+ ...parentRule !== void 0 && { rule: parentRule }
820
+ };
821
+ }
822
+ function irElementsToUiSchema(elements, parentRule) {
386
823
  const result = [];
387
824
  for (const element of elements) {
388
- switch (element._type) {
825
+ switch (element.kind) {
389
826
  case "field": {
390
- const control = {
391
- type: "Control",
392
- scope: fieldToScope(element.name),
393
- ...element.label !== void 0 && { label: element.label },
394
- ...parentRule !== void 0 && { rule: parentRule }
395
- };
396
- result.push(control);
827
+ result.push(fieldNodeToControl(element, parentRule));
397
828
  break;
398
829
  }
399
830
  case "group": {
400
- const groupElement = element;
401
- const group = {
402
- type: "Group",
403
- label: groupElement.label,
404
- elements: elementsToUiSchema(groupElement.elements, parentRule),
405
- ...parentRule !== void 0 && { rule: parentRule }
406
- };
407
- result.push(group);
831
+ result.push(groupNodeToLayout(element, parentRule));
408
832
  break;
409
833
  }
410
834
  case "conditional": {
411
- const conditionalElement = element;
412
- const newRule = createShowRule(conditionalElement.field, conditionalElement.value);
835
+ const newRule = createShowRule(element.fieldName, element.value);
413
836
  const combinedRule = parentRule !== void 0 ? combineRules(parentRule, newRule) : newRule;
414
- const childElements = elementsToUiSchema(conditionalElement.elements, combinedRule);
837
+ const childElements = irElementsToUiSchema(element.elements, combinedRule);
415
838
  result.push(...childElements);
416
839
  break;
417
840
  }
841
+ default: {
842
+ const _exhaustive = element;
843
+ void _exhaustive;
844
+ throw new Error("Unhandled IR element kind");
845
+ }
418
846
  }
419
847
  }
420
848
  return result;
421
849
  }
422
- function formSpecFieldToElement(field, scopePrefix = "#/properties") {
423
- const control = {
424
- type: "Control",
425
- scope: `${scopePrefix}/${field.id}`
426
- };
427
- if (field.label !== void 0) {
428
- control.label = field.label;
429
- }
430
- if (field.showWhen !== void 0 && typeof field.showWhen === "object" && "field" in field.showWhen && "value" in field.showWhen) {
431
- const sw = field.showWhen;
432
- control.rule = {
433
- effect: "SHOW",
434
- condition: {
435
- scope: `#/properties/${sw.field}`,
436
- schema: { const: sw.value }
437
- }
438
- };
439
- }
440
- return control;
441
- }
442
- function generateUiSchemaFromFields(fields) {
443
- const groupMap = /* @__PURE__ */ new Map();
444
- const orderedKeys = [];
445
- const ungrouped = [];
446
- for (const field of fields) {
447
- const element = formSpecFieldToElement(field);
448
- if (field.group !== void 0) {
449
- if (!groupMap.has(field.group)) {
450
- groupMap.set(field.group, []);
451
- orderedKeys.push(field.group);
452
- }
453
- groupMap.get(field.group).push(element);
454
- } else {
455
- orderedKeys.push(null);
456
- ungrouped.push(element);
457
- }
458
- }
459
- const elements = [];
460
- let ungroupedIndex = 0;
461
- for (const key of orderedKeys) {
462
- if (key === null) {
463
- const el = ungrouped[ungroupedIndex++];
464
- if (el !== void 0) {
465
- elements.push(el);
466
- }
467
- } else {
468
- const groupElements = groupMap.get(key) ?? [];
469
- const groupLayout = {
470
- type: "Group",
471
- label: key,
472
- elements: groupElements
473
- };
474
- elements.push(groupLayout);
475
- }
476
- }
850
+ function generateUiSchemaFromIR(ir) {
477
851
  const result = {
478
852
  type: "VerticalLayout",
479
- elements
853
+ elements: irElementsToUiSchema(ir.elements)
480
854
  };
481
- return parseOrThrow2(uiSchema, result, "UI Schema");
855
+ return parseOrThrow(uiSchema, result, "UI Schema");
482
856
  }
857
+ var init_ir_generator2 = __esm({
858
+ "src/ui-schema/ir-generator.ts"() {
859
+ "use strict";
860
+ init_schema();
861
+ }
862
+ });
863
+
864
+ // src/ui-schema/generator.ts
483
865
  function generateUiSchema(form) {
484
- const result = {
485
- type: "VerticalLayout",
486
- elements: elementsToUiSchema(form.elements)
487
- };
488
- return parseOrThrow2(uiSchema, result, "UI Schema");
866
+ const ir = canonicalizeChainDSL(form);
867
+ return generateUiSchemaFromIR(ir);
489
868
  }
490
869
  var init_generator2 = __esm({
491
870
  "src/ui-schema/generator.ts"() {
492
871
  "use strict";
493
- init_schema2();
872
+ init_canonicalize();
873
+ init_ir_generator2();
494
874
  }
495
875
  });
496
876
 
@@ -507,296 +887,243 @@ var init_types = __esm({
507
887
  }
508
888
  });
509
889
 
510
- // src/analyzer/decorator-extractor.ts
890
+ // src/json-schema/schema.ts
891
+ import { z as z3 } from "zod";
892
+ var jsonSchemaTypeSchema, jsonSchema7Schema;
893
+ var init_schema2 = __esm({
894
+ "src/json-schema/schema.ts"() {
895
+ "use strict";
896
+ jsonSchemaTypeSchema = z3.enum([
897
+ "string",
898
+ "number",
899
+ "integer",
900
+ "boolean",
901
+ "object",
902
+ "array",
903
+ "null"
904
+ ]);
905
+ jsonSchema7Schema = z3.lazy(
906
+ () => z3.object({
907
+ $schema: z3.string().optional(),
908
+ $id: z3.string().optional(),
909
+ $ref: z3.string().optional(),
910
+ // Metadata
911
+ title: z3.string().optional(),
912
+ description: z3.string().optional(),
913
+ deprecated: z3.boolean().optional(),
914
+ // Type
915
+ type: z3.union([jsonSchemaTypeSchema, z3.array(jsonSchemaTypeSchema)]).optional(),
916
+ // String validation
917
+ minLength: z3.number().optional(),
918
+ maxLength: z3.number().optional(),
919
+ pattern: z3.string().optional(),
920
+ // Number validation
921
+ minimum: z3.number().optional(),
922
+ maximum: z3.number().optional(),
923
+ exclusiveMinimum: z3.number().optional(),
924
+ exclusiveMaximum: z3.number().optional(),
925
+ // Enum
926
+ enum: z3.array(z3.union([z3.string(), z3.number(), z3.boolean(), z3.null()])).readonly().optional(),
927
+ const: z3.union([z3.string(), z3.number(), z3.boolean(), z3.null()]).optional(),
928
+ // Object
929
+ properties: z3.record(z3.string(), jsonSchema7Schema).optional(),
930
+ required: z3.array(z3.string()).optional(),
931
+ additionalProperties: z3.union([z3.boolean(), jsonSchema7Schema]).optional(),
932
+ // Array
933
+ items: z3.union([jsonSchema7Schema, z3.array(jsonSchema7Schema)]).optional(),
934
+ minItems: z3.number().optional(),
935
+ maxItems: z3.number().optional(),
936
+ // Composition
937
+ allOf: z3.array(jsonSchema7Schema).optional(),
938
+ anyOf: z3.array(jsonSchema7Schema).optional(),
939
+ oneOf: z3.array(jsonSchema7Schema).optional(),
940
+ not: jsonSchema7Schema.optional(),
941
+ // Conditional
942
+ if: jsonSchema7Schema.optional(),
943
+ then: jsonSchema7Schema.optional(),
944
+ else: jsonSchema7Schema.optional(),
945
+ // Format
946
+ format: z3.string().optional(),
947
+ // Default
948
+ default: z3.unknown().optional(),
949
+ // FormSpec extensions
950
+ "x-formspec-source": z3.string().optional(),
951
+ "x-formspec-params": z3.array(z3.string()).readonly().optional(),
952
+ "x-formspec-schemaSource": z3.string().optional()
953
+ }).passthrough()
954
+ );
955
+ }
956
+ });
957
+
958
+ // src/analyzer/program.ts
511
959
  import * as ts from "typescript";
512
- import "@formspec/core";
513
- function extractDecorators(member) {
514
- const decorators = [];
515
- const modifiers = ts.canHaveDecorators(member) ? ts.getDecorators(member) : void 0;
516
- if (!modifiers) return decorators;
517
- for (const decorator of modifiers) {
518
- const info = parseDecorator(decorator);
519
- if (info) {
520
- decorators.push(info);
960
+ import * as path from "path";
961
+ function createProgramContext(filePath) {
962
+ const absolutePath = path.resolve(filePath);
963
+ const fileDir = path.dirname(absolutePath);
964
+ const configPath = ts.findConfigFile(fileDir, ts.sys.fileExists.bind(ts.sys), "tsconfig.json");
965
+ let compilerOptions;
966
+ let fileNames;
967
+ if (configPath) {
968
+ const configFile = ts.readConfigFile(configPath, ts.sys.readFile.bind(ts.sys));
969
+ if (configFile.error) {
970
+ throw new Error(
971
+ `Error reading tsconfig.json: ${ts.flattenDiagnosticMessageText(configFile.error.messageText, "\n")}`
972
+ );
521
973
  }
522
- }
523
- return decorators;
524
- }
525
- function parseDecorator(decorator) {
526
- const expr = decorator.expression;
527
- if (ts.isIdentifier(expr)) {
528
- return {
529
- name: expr.text,
530
- args: [],
531
- node: decorator
532
- };
533
- }
534
- if (ts.isCallExpression(expr)) {
535
- const callee = expr.expression;
536
- let name = null;
537
- if (ts.isIdentifier(callee)) {
538
- name = callee.text;
539
- } else if (ts.isPropertyAccessExpression(callee)) {
540
- name = callee.name.text;
974
+ const parsed = ts.parseJsonConfigFileContent(
975
+ configFile.config,
976
+ ts.sys,
977
+ path.dirname(configPath)
978
+ );
979
+ if (parsed.errors.length > 0) {
980
+ const errorMessages = parsed.errors.map((e) => ts.flattenDiagnosticMessageText(e.messageText, "\n")).join("\n");
981
+ throw new Error(`Error parsing tsconfig.json: ${errorMessages}`);
541
982
  }
542
- if (!name) return null;
543
- const args = expr.arguments.map(extractArgValue);
544
- return {
545
- name,
546
- args,
547
- node: decorator
983
+ compilerOptions = parsed.options;
984
+ fileNames = parsed.fileNames.includes(absolutePath) ? parsed.fileNames : [...parsed.fileNames, absolutePath];
985
+ } else {
986
+ compilerOptions = {
987
+ target: ts.ScriptTarget.ES2022,
988
+ module: ts.ModuleKind.NodeNext,
989
+ moduleResolution: ts.ModuleResolutionKind.NodeNext,
990
+ strict: true,
991
+ skipLibCheck: true,
992
+ declaration: true
548
993
  };
994
+ fileNames = [absolutePath];
549
995
  }
550
- return null;
551
- }
552
- function extractArgValue(node) {
553
- if (ts.isStringLiteral(node)) {
554
- return node.text;
555
- }
556
- if (ts.isNumericLiteral(node)) {
557
- return Number(node.text);
558
- }
559
- if (node.kind === ts.SyntaxKind.TrueKeyword) {
560
- return true;
561
- }
562
- if (node.kind === ts.SyntaxKind.FalseKeyword) {
563
- return false;
564
- }
565
- if (node.kind === ts.SyntaxKind.NullKeyword) {
566
- return null;
567
- }
568
- if (ts.isPrefixUnaryExpression(node)) {
569
- if (node.operator === ts.SyntaxKind.MinusToken && ts.isNumericLiteral(node.operand)) {
570
- return -Number(node.operand.text);
571
- }
572
- if (node.operator === ts.SyntaxKind.PlusToken && ts.isNumericLiteral(node.operand)) {
573
- return Number(node.operand.text);
574
- }
575
- }
576
- if (ts.isArrayLiteralExpression(node)) {
577
- return node.elements.map((el) => {
578
- if (ts.isSpreadElement(el)) {
579
- return null;
580
- }
581
- return extractArgValue(el);
582
- });
583
- }
584
- if (ts.isObjectLiteralExpression(node)) {
585
- const obj = {};
586
- for (const prop of node.properties) {
587
- if (ts.isPropertyAssignment(prop)) {
588
- const key = getPropertyName(prop.name);
589
- if (key) {
590
- obj[key] = extractArgValue(prop.initializer);
591
- }
592
- } else if (ts.isShorthandPropertyAssignment(prop)) {
593
- const key = prop.name.text;
594
- obj[key] = null;
595
- }
596
- }
597
- return obj;
598
- }
599
- if (ts.isNoSubstitutionTemplateLiteral(node)) {
600
- return node.text;
601
- }
602
- if (ts.isRegularExpressionLiteral(node)) {
603
- const regexText = node.text;
604
- const lastSlash = regexText.lastIndexOf("/");
605
- if (lastSlash > 0) {
606
- return regexText.substring(1, lastSlash);
607
- }
608
- return regexText;
609
- }
610
- if (ts.isNewExpression(node)) {
611
- if (ts.isIdentifier(node.expression) && node.expression.text === "RegExp" && node.arguments && node.arguments.length > 0) {
612
- const firstArg = node.arguments[0];
613
- if (firstArg && ts.isStringLiteral(firstArg)) {
614
- return firstArg.text;
615
- }
616
- }
617
- }
618
- if (ts.isIdentifier(node)) {
619
- return null;
620
- }
621
- return null;
622
- }
623
- function getPropertyName(name) {
624
- if (ts.isIdentifier(name)) {
625
- return name.text;
626
- }
627
- if (ts.isStringLiteral(name)) {
628
- return name.text;
629
- }
630
- if (ts.isNumericLiteral(name)) {
631
- return name.text;
996
+ const program = ts.createProgram(fileNames, compilerOptions);
997
+ const sourceFile = program.getSourceFile(absolutePath);
998
+ if (!sourceFile) {
999
+ throw new Error(`Could not find source file: ${absolutePath}`);
632
1000
  }
633
- return null;
1001
+ return {
1002
+ program,
1003
+ checker: program.getTypeChecker(),
1004
+ sourceFile
1005
+ };
634
1006
  }
635
- function isFormSpecDecoratorsPath(fileName) {
636
- const normalized = fileName.replace(/\\/g, "/");
637
- return normalized.includes("node_modules/@formspec/decorators") || normalized.includes("/packages/decorators/");
638
- }
639
- function resolveDecorator(decorator, checker) {
640
- const expr = decorator.expression;
641
- let targetNode;
642
- let name;
643
- if (ts.isIdentifier(expr)) {
644
- targetNode = expr;
645
- name = expr.text;
646
- } else if (ts.isCallExpression(expr)) {
647
- if (ts.isIdentifier(expr.expression)) {
648
- targetNode = expr.expression;
649
- name = expr.expression.text;
650
- } else {
651
- return null;
652
- }
653
- } else {
654
- return null;
655
- }
656
- if (name in FORMSPEC_DECORATORS) {
657
- const symbol = checker.getSymbolAtLocation(targetNode);
658
- if (symbol) {
659
- const declarations = symbol.declarations;
660
- if (declarations && declarations.length > 0) {
661
- const decl = declarations[0];
662
- if (decl) {
663
- const sourceFile = decl.getSourceFile();
664
- const fileName = sourceFile.fileName;
665
- if (isFormSpecDecoratorsPath(fileName)) {
666
- return {
667
- name,
668
- isFormSpec: true,
669
- isMarker: !ts.isCallExpression(expr)
670
- };
671
- }
672
- }
673
- }
674
- }
675
- }
676
- const resolvedSymbol = checker.getSymbolAtLocation(targetNode);
677
- if (!resolvedSymbol) return null;
678
- const type = checker.getTypeOfSymbol(resolvedSymbol);
679
- const props = type.getProperties();
680
- let extendsBuiltin;
681
- let extensionName;
682
- let isMarker = false;
683
- for (const prop of props) {
684
- const escapedName = prop.getEscapedName();
685
- if (escapedName.startsWith("__@") && (escapedName.includes("formspec.extends") || escapedName.includes("FORMSPEC_EXTENDS"))) {
686
- const propType = checker.getTypeOfSymbol(prop);
687
- if (propType.isStringLiteral()) {
688
- extendsBuiltin = propType.value;
689
- }
690
- }
691
- if (escapedName.startsWith("__@") && (escapedName.includes("formspec.extension") || escapedName.includes("FORMSPEC_EXTENSION"))) {
692
- const propType = checker.getTypeOfSymbol(prop);
693
- if (propType.isStringLiteral()) {
694
- extensionName = propType.value;
695
- }
696
- }
697
- if (escapedName.startsWith("__@") && (escapedName.includes("formspec.marker") || escapedName.includes("FORMSPEC_MARKER"))) {
698
- isMarker = true;
1007
+ function findNodeByName(sourceFile, name, predicate, getName) {
1008
+ let result = null;
1009
+ function visit(node) {
1010
+ if (result) return;
1011
+ if (predicate(node) && getName(node) === name) {
1012
+ result = node;
1013
+ return;
699
1014
  }
1015
+ ts.forEachChild(node, visit);
700
1016
  }
701
- if (extendsBuiltin) {
702
- return {
703
- name,
704
- extendsBuiltin,
705
- isFormSpec: true,
706
- isMarker: false
707
- };
708
- }
709
- if (extensionName) {
710
- return {
711
- name,
712
- extensionName,
713
- isFormSpec: true,
714
- isMarker
715
- };
716
- }
717
- if (isMarker) {
718
- return {
719
- name,
720
- isFormSpec: true,
721
- isMarker: true
722
- };
723
- }
724
- return null;
1017
+ visit(sourceFile);
1018
+ return result;
1019
+ }
1020
+ function findClassByName(sourceFile, className) {
1021
+ return findNodeByName(sourceFile, className, ts.isClassDeclaration, (n) => n.name?.text);
1022
+ }
1023
+ function findInterfaceByName(sourceFile, interfaceName) {
1024
+ return findNodeByName(sourceFile, interfaceName, ts.isInterfaceDeclaration, (n) => n.name.text);
1025
+ }
1026
+ function findTypeAliasByName(sourceFile, aliasName) {
1027
+ return findNodeByName(sourceFile, aliasName, ts.isTypeAliasDeclaration, (n) => n.name.text);
725
1028
  }
726
- var FORMSPEC_DECORATORS;
727
- var init_decorator_extractor = __esm({
728
- "src/analyzer/decorator-extractor.ts"() {
1029
+ var init_program = __esm({
1030
+ "src/analyzer/program.ts"() {
729
1031
  "use strict";
730
- FORMSPEC_DECORATORS = {
731
- // Display metadata
732
- Field: { argTypes: ["object"] },
733
- // Grouping
734
- Group: { argTypes: ["string"] },
735
- // Conditional display
736
- ShowWhen: { argTypes: ["object"] },
737
- // Enum options
738
- EnumOptions: { argTypes: ["array"] },
739
- // Numeric constraints
740
- Minimum: { argTypes: ["number"] },
741
- Maximum: { argTypes: ["number"] },
742
- ExclusiveMinimum: { argTypes: ["number"] },
743
- ExclusiveMaximum: { argTypes: ["number"] },
744
- // String constraints
745
- MinLength: { argTypes: ["number"] },
746
- MaxLength: { argTypes: ["number"] },
747
- Pattern: { argTypes: ["string"] }
748
- };
749
1032
  }
750
1033
  });
751
1034
 
752
- // src/analyzer/jsdoc-constraints.ts
1035
+ // src/analyzer/tsdoc-parser.ts
753
1036
  import * as ts2 from "typescript";
754
- import { CONSTRAINT_TAG_DEFINITIONS } from "@formspec/core";
755
- function extractJSDocConstraints(node) {
756
- const results = [];
757
- const jsDocTags = ts2.getJSDocTags(node);
758
- for (const tag of jsDocTags) {
759
- const tagName = tag.tagName.text;
760
- if (!(tagName in CONSTRAINT_TAG_DEFINITIONS)) {
761
- continue;
762
- }
763
- const constraintName = tagName;
764
- const expectedType = CONSTRAINT_TAG_DEFINITIONS[constraintName];
765
- const commentText = getTagCommentText(tag);
766
- if (commentText === void 0 || commentText === "") {
767
- continue;
768
- }
769
- const trimmed = commentText.trim();
770
- if (trimmed === "") {
771
- continue;
772
- }
773
- if (expectedType === "number") {
774
- const value = Number(trimmed);
775
- if (Number.isNaN(value)) {
1037
+ import {
1038
+ TSDocParser,
1039
+ TSDocConfiguration,
1040
+ TSDocTagDefinition,
1041
+ TSDocTagSyntaxKind,
1042
+ DocPlainText,
1043
+ DocSoftBreak,
1044
+ TextRange
1045
+ } from "@microsoft/tsdoc";
1046
+ import {
1047
+ BUILTIN_CONSTRAINT_DEFINITIONS
1048
+ } from "@formspec/core";
1049
+ function isBuiltinConstraintName(tagName) {
1050
+ return tagName in BUILTIN_CONSTRAINT_DEFINITIONS;
1051
+ }
1052
+ function createFormSpecTSDocConfig() {
1053
+ const config = new TSDocConfiguration();
1054
+ for (const tagName of Object.keys(BUILTIN_CONSTRAINT_DEFINITIONS)) {
1055
+ config.addTagDefinition(
1056
+ new TSDocTagDefinition({
1057
+ tagName: "@" + tagName,
1058
+ syntaxKind: TSDocTagSyntaxKind.BlockTag,
1059
+ allowMultiple: true
1060
+ })
1061
+ );
1062
+ }
1063
+ return config;
1064
+ }
1065
+ function getParser() {
1066
+ sharedParser ??= new TSDocParser(createFormSpecTSDocConfig());
1067
+ return sharedParser;
1068
+ }
1069
+ function parseTSDocTags(node, file = "") {
1070
+ const constraints = [];
1071
+ const annotations = [];
1072
+ const sourceFile = node.getSourceFile();
1073
+ const sourceText = sourceFile.getFullText();
1074
+ const commentRanges = ts2.getLeadingCommentRanges(sourceText, node.getFullStart());
1075
+ if (commentRanges) {
1076
+ for (const range of commentRanges) {
1077
+ if (range.kind !== ts2.SyntaxKind.MultiLineCommentTrivia) {
776
1078
  continue;
777
1079
  }
778
- results.push(createSyntheticDecorator(constraintName, value));
779
- } else if (expectedType === "json") {
780
- try {
781
- const parsed = JSON.parse(trimmed);
782
- if (!Array.isArray(parsed)) {
783
- continue;
784
- }
785
- results.push(createSyntheticDecorator(constraintName, parsed));
786
- } catch {
1080
+ const commentText = sourceText.substring(range.pos, range.end);
1081
+ if (!commentText.startsWith("/**")) {
787
1082
  continue;
788
1083
  }
789
- } else {
790
- results.push(createSyntheticDecorator(constraintName, trimmed));
1084
+ const parser = getParser();
1085
+ const parserContext = parser.parseRange(
1086
+ TextRange.fromStringRange(sourceText, range.pos, range.end)
1087
+ );
1088
+ const docComment = parserContext.docComment;
1089
+ for (const block of docComment.customBlocks) {
1090
+ const tagName = block.blockTag.tagName.substring(1);
1091
+ if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
1092
+ const text = extractBlockText(block).trim();
1093
+ if (text === "") continue;
1094
+ const provenance = provenanceForComment(range, sourceFile, file, tagName);
1095
+ const constraintNode = parseConstraintValue(tagName, text, provenance);
1096
+ if (constraintNode) {
1097
+ constraints.push(constraintNode);
1098
+ }
1099
+ }
1100
+ if (docComment.deprecatedBlock !== void 0) {
1101
+ annotations.push({
1102
+ kind: "annotation",
1103
+ annotationKind: "deprecated",
1104
+ provenance: provenanceForComment(range, sourceFile, file, "deprecated")
1105
+ });
1106
+ }
1107
+ }
1108
+ }
1109
+ const jsDocTagsAll = ts2.getJSDocTags(node);
1110
+ for (const tag of jsDocTagsAll) {
1111
+ const tagName = tag.tagName.text;
1112
+ if (!TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
1113
+ const commentText = getTagCommentText(tag);
1114
+ if (commentText === void 0 || commentText.trim() === "") continue;
1115
+ const text = commentText.trim();
1116
+ const provenance = provenanceForJSDocTag(tag, file);
1117
+ const constraintNode = parseConstraintValue(tagName, text, provenance);
1118
+ if (constraintNode) {
1119
+ constraints.push(constraintNode);
791
1120
  }
792
1121
  }
793
- return results;
794
- }
795
- function extractJSDocFieldMetadata(node) {
796
- const jsDocTags = ts2.getJSDocTags(node);
797
1122
  let displayName;
798
1123
  let description;
799
- for (const tag of jsDocTags) {
1124
+ let displayNameTag;
1125
+ let descriptionTag;
1126
+ for (const tag of jsDocTagsAll) {
800
1127
  const tagName = tag.tagName.text;
801
1128
  const commentText = getTagCommentText(tag);
802
1129
  if (commentText === void 0 || commentText.trim() === "") {
@@ -805,18 +1132,132 @@ function extractJSDocFieldMetadata(node) {
805
1132
  const trimmed = commentText.trim();
806
1133
  if (tagName === "Field_displayName") {
807
1134
  displayName = trimmed;
1135
+ displayNameTag = tag;
808
1136
  } else if (tagName === "Field_description") {
809
1137
  description = trimmed;
1138
+ descriptionTag = tag;
1139
+ }
1140
+ }
1141
+ if (displayName !== void 0 && displayNameTag) {
1142
+ annotations.push({
1143
+ kind: "annotation",
1144
+ annotationKind: "displayName",
1145
+ value: displayName,
1146
+ provenance: provenanceForJSDocTag(displayNameTag, file)
1147
+ });
1148
+ }
1149
+ if (description !== void 0 && descriptionTag) {
1150
+ annotations.push({
1151
+ kind: "annotation",
1152
+ annotationKind: "description",
1153
+ value: description,
1154
+ provenance: provenanceForJSDocTag(descriptionTag, file)
1155
+ });
1156
+ }
1157
+ return { constraints, annotations };
1158
+ }
1159
+ function extractBlockText(block) {
1160
+ return extractPlainText(block.content);
1161
+ }
1162
+ function extractPlainText(node) {
1163
+ let result = "";
1164
+ if (node instanceof DocPlainText) {
1165
+ return node.text;
1166
+ }
1167
+ if (node instanceof DocSoftBreak) {
1168
+ return " ";
1169
+ }
1170
+ if (typeof node.getChildNodes === "function") {
1171
+ for (const child of node.getChildNodes()) {
1172
+ result += extractPlainText(child);
810
1173
  }
811
1174
  }
812
- if (displayName === void 0 && description === void 0) {
1175
+ return result;
1176
+ }
1177
+ function parseConstraintValue(tagName, text, provenance) {
1178
+ if (!isBuiltinConstraintName(tagName)) {
1179
+ return null;
1180
+ }
1181
+ const expectedType = BUILTIN_CONSTRAINT_DEFINITIONS[tagName];
1182
+ if (expectedType === "number") {
1183
+ const value = Number(text);
1184
+ if (Number.isNaN(value)) {
1185
+ return null;
1186
+ }
1187
+ const numericKind = NUMERIC_CONSTRAINT_MAP[tagName];
1188
+ if (numericKind) {
1189
+ return {
1190
+ kind: "constraint",
1191
+ constraintKind: numericKind,
1192
+ value,
1193
+ provenance
1194
+ };
1195
+ }
1196
+ const lengthKind = LENGTH_CONSTRAINT_MAP[tagName];
1197
+ if (lengthKind) {
1198
+ return {
1199
+ kind: "constraint",
1200
+ constraintKind: lengthKind,
1201
+ value,
1202
+ provenance
1203
+ };
1204
+ }
813
1205
  return null;
814
1206
  }
815
- const fieldOpts = {
816
- ...displayName !== void 0 ? { displayName } : {},
817
- ...description !== void 0 ? { description } : {}
1207
+ if (expectedType === "json") {
1208
+ try {
1209
+ const parsed = JSON.parse(text);
1210
+ if (!Array.isArray(parsed)) {
1211
+ return null;
1212
+ }
1213
+ const members = [];
1214
+ for (const item of parsed) {
1215
+ if (typeof item === "string" || typeof item === "number") {
1216
+ members.push(item);
1217
+ } else if (typeof item === "object" && item !== null && "id" in item) {
1218
+ const id = item["id"];
1219
+ if (typeof id === "string" || typeof id === "number") {
1220
+ members.push(id);
1221
+ }
1222
+ }
1223
+ }
1224
+ return {
1225
+ kind: "constraint",
1226
+ constraintKind: "allowedMembers",
1227
+ members,
1228
+ provenance
1229
+ };
1230
+ } catch {
1231
+ return null;
1232
+ }
1233
+ }
1234
+ return {
1235
+ kind: "constraint",
1236
+ constraintKind: "pattern",
1237
+ pattern: text,
1238
+ provenance
1239
+ };
1240
+ }
1241
+ function provenanceForComment(range, sourceFile, file, tagName) {
1242
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(range.pos);
1243
+ return {
1244
+ surface: "tsdoc",
1245
+ file,
1246
+ line: line + 1,
1247
+ column: character,
1248
+ tagName: "@" + tagName
1249
+ };
1250
+ }
1251
+ function provenanceForJSDocTag(tag, file) {
1252
+ const sourceFile = tag.getSourceFile();
1253
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(tag.getStart());
1254
+ return {
1255
+ surface: "tsdoc",
1256
+ file,
1257
+ line: line + 1,
1258
+ column: character,
1259
+ tagName: "@" + tag.tagName.text
818
1260
  };
819
- return createSyntheticDecorator("Field", fieldOpts);
820
1261
  }
821
1262
  function getTagCommentText(tag) {
822
1263
  if (tag.comment === void 0) {
@@ -827,36 +1268,104 @@ function getTagCommentText(tag) {
827
1268
  }
828
1269
  return ts2.getTextOfJSDocComment(tag.comment);
829
1270
  }
830
- function createSyntheticDecorator(name, value) {
1271
+ var NUMERIC_CONSTRAINT_MAP, LENGTH_CONSTRAINT_MAP, TAGS_REQUIRING_RAW_TEXT, sharedParser;
1272
+ var init_tsdoc_parser = __esm({
1273
+ "src/analyzer/tsdoc-parser.ts"() {
1274
+ "use strict";
1275
+ NUMERIC_CONSTRAINT_MAP = {
1276
+ Minimum: "minimum",
1277
+ Maximum: "maximum",
1278
+ ExclusiveMinimum: "exclusiveMinimum",
1279
+ ExclusiveMaximum: "exclusiveMaximum"
1280
+ };
1281
+ LENGTH_CONSTRAINT_MAP = {
1282
+ MinLength: "minLength",
1283
+ MaxLength: "maxLength"
1284
+ };
1285
+ TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["Pattern", "EnumOptions"]);
1286
+ }
1287
+ });
1288
+
1289
+ // src/analyzer/jsdoc-constraints.ts
1290
+ import * as ts3 from "typescript";
1291
+ import {
1292
+ BUILTIN_CONSTRAINT_DEFINITIONS as BUILTIN_CONSTRAINT_DEFINITIONS2
1293
+ } from "@formspec/core";
1294
+ function extractJSDocConstraintNodes(node, file = "") {
1295
+ const result = parseTSDocTags(node, file);
1296
+ return [...result.constraints];
1297
+ }
1298
+ function extractJSDocAnnotationNodes(node, file = "") {
1299
+ const result = parseTSDocTags(node, file);
1300
+ return [...result.annotations];
1301
+ }
1302
+ function extractDefaultValueAnnotation(initializer, file = "") {
1303
+ if (!initializer) return null;
1304
+ let value;
1305
+ if (ts3.isStringLiteral(initializer)) {
1306
+ value = initializer.text;
1307
+ } else if (ts3.isNumericLiteral(initializer)) {
1308
+ value = Number(initializer.text);
1309
+ } else if (initializer.kind === ts3.SyntaxKind.TrueKeyword) {
1310
+ value = true;
1311
+ } else if (initializer.kind === ts3.SyntaxKind.FalseKeyword) {
1312
+ value = false;
1313
+ } else if (initializer.kind === ts3.SyntaxKind.NullKeyword) {
1314
+ value = null;
1315
+ } else if (ts3.isPrefixUnaryExpression(initializer)) {
1316
+ if (initializer.operator === ts3.SyntaxKind.MinusToken && ts3.isNumericLiteral(initializer.operand)) {
1317
+ value = -Number(initializer.operand.text);
1318
+ }
1319
+ }
1320
+ if (value === void 0) return null;
1321
+ const sourceFile = initializer.getSourceFile();
1322
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(initializer.getStart());
831
1323
  return {
832
- name,
833
- args: [value],
834
- node: void 0
1324
+ kind: "annotation",
1325
+ annotationKind: "defaultValue",
1326
+ value,
1327
+ provenance: {
1328
+ surface: "tsdoc",
1329
+ file,
1330
+ line: line + 1,
1331
+ column: character
1332
+ }
835
1333
  };
836
1334
  }
837
1335
  var init_jsdoc_constraints = __esm({
838
1336
  "src/analyzer/jsdoc-constraints.ts"() {
839
1337
  "use strict";
1338
+ init_tsdoc_parser();
840
1339
  }
841
1340
  });
842
1341
 
843
1342
  // src/analyzer/class-analyzer.ts
844
- import * as ts3 from "typescript";
845
- function analyzeClass(classDecl, checker) {
1343
+ import * as ts4 from "typescript";
1344
+ function isObjectType(type) {
1345
+ return !!(type.flags & ts4.TypeFlags.Object);
1346
+ }
1347
+ function isTypeReference(type) {
1348
+ return !!(type.flags & ts4.TypeFlags.Object) && !!(type.objectFlags & ts4.ObjectFlags.Reference);
1349
+ }
1350
+ function analyzeClassToIR(classDecl, checker, file = "") {
846
1351
  const name = classDecl.name?.text ?? "AnonymousClass";
847
1352
  const fields = [];
1353
+ const fieldLayouts = [];
1354
+ const typeRegistry = {};
1355
+ const visiting = /* @__PURE__ */ new Set();
848
1356
  const instanceMethods = [];
849
1357
  const staticMethods = [];
850
1358
  for (const member of classDecl.members) {
851
- if (ts3.isPropertyDeclaration(member)) {
852
- const fieldInfo = analyzeField(member, checker);
853
- if (fieldInfo) {
854
- fields.push(fieldInfo);
1359
+ if (ts4.isPropertyDeclaration(member)) {
1360
+ const fieldNode = analyzeFieldToIR(member, checker, file, typeRegistry, visiting);
1361
+ if (fieldNode) {
1362
+ fields.push(fieldNode);
1363
+ fieldLayouts.push({});
855
1364
  }
856
- } else if (ts3.isMethodDeclaration(member)) {
1365
+ } else if (ts4.isMethodDeclaration(member)) {
857
1366
  const methodInfo = analyzeMethod(member, checker);
858
1367
  if (methodInfo) {
859
- const isStatic = member.modifiers?.some((m) => m.kind === ts3.SyntaxKind.StaticKeyword);
1368
+ const isStatic = member.modifiers?.some((m) => m.kind === ts4.SyntaxKind.StaticKeyword);
860
1369
  if (isStatic) {
861
1370
  staticMethods.push(methodInfo);
862
1371
  } else {
@@ -865,159 +1374,29 @@ function analyzeClass(classDecl, checker) {
865
1374
  }
866
1375
  }
867
1376
  }
868
- return {
869
- name,
870
- fields,
871
- instanceMethods,
872
- staticMethods
873
- };
874
- }
875
- function analyzeField(prop, checker) {
876
- if (!ts3.isIdentifier(prop.name)) {
877
- return null;
878
- }
879
- const name = prop.name.text;
880
- const typeNode = prop.type;
881
- const type = checker.getTypeAtLocation(prop);
882
- const optional = prop.questionToken !== void 0;
883
- const decorators = extractDecorators(prop);
884
- for (const dec of decorators) {
885
- if (dec.node) {
886
- const resolved = resolveDecorator(dec.node, checker);
887
- if (resolved) {
888
- dec.resolved = resolved;
889
- }
890
- }
891
- }
892
- if (prop.type) {
893
- const aliasConstraints = extractTypeAliasConstraints(prop.type, checker);
894
- decorators.push(...aliasConstraints);
895
- }
896
- const jsdocConstraints = extractJSDocConstraints(prop);
897
- decorators.push(...jsdocConstraints);
898
- const deprecated = hasDeprecatedTag(prop);
899
- const defaultValue = extractDefaultValue(prop.initializer);
900
- return {
901
- name,
902
- typeNode,
903
- type,
904
- optional,
905
- decorators,
906
- deprecated,
907
- defaultValue
908
- };
909
- }
910
- function extractTypeAliasConstraints(typeNode, checker) {
911
- if (!ts3.isTypeReferenceNode(typeNode)) return [];
912
- const symbol = checker.getSymbolAtLocation(typeNode.typeName);
913
- if (!symbol?.declarations) return [];
914
- const aliasDecl = symbol.declarations.find(ts3.isTypeAliasDeclaration);
915
- if (!aliasDecl) return [];
916
- if (ts3.isTypeLiteralNode(aliasDecl.type)) return [];
917
- return extractJSDocConstraints(aliasDecl);
918
- }
919
- function hasDeprecatedTag(node) {
920
- const jsDocTags = ts3.getJSDocTags(node);
921
- return jsDocTags.some((tag) => tag.tagName.text === "deprecated");
922
- }
923
- function extractDefaultValue(initializer) {
924
- if (!initializer) return void 0;
925
- if (ts3.isStringLiteral(initializer)) {
926
- return initializer.text;
927
- }
928
- if (ts3.isNumericLiteral(initializer)) {
929
- return Number(initializer.text);
930
- }
931
- if (initializer.kind === ts3.SyntaxKind.TrueKeyword) {
932
- return true;
933
- }
934
- if (initializer.kind === ts3.SyntaxKind.FalseKeyword) {
935
- return false;
936
- }
937
- if (initializer.kind === ts3.SyntaxKind.NullKeyword) {
938
- return null;
939
- }
940
- if (ts3.isPrefixUnaryExpression(initializer)) {
941
- if (initializer.operator === ts3.SyntaxKind.MinusToken && ts3.isNumericLiteral(initializer.operand)) {
942
- return -Number(initializer.operand.text);
943
- }
944
- }
945
- return void 0;
946
- }
947
- function analyzeMethod(method, checker) {
948
- if (!ts3.isIdentifier(method.name)) {
949
- return null;
950
- }
951
- const name = method.name.text;
952
- const parameters = [];
953
- for (const param of method.parameters) {
954
- if (ts3.isIdentifier(param.name)) {
955
- const paramInfo = analyzeParameter(param, checker);
956
- parameters.push(paramInfo);
957
- }
958
- }
959
- const returnTypeNode = method.type;
960
- const signature = checker.getSignatureFromDeclaration(method);
961
- const returnType = signature ? checker.getReturnTypeOfSignature(signature) : checker.getTypeAtLocation(method);
962
- return {
963
- name,
964
- parameters,
965
- returnTypeNode,
966
- returnType
967
- };
968
- }
969
- function analyzeParameter(param, checker) {
970
- const name = ts3.isIdentifier(param.name) ? param.name.text : "param";
971
- const typeNode = param.type;
972
- const type = checker.getTypeAtLocation(param);
973
- const formSpecExportName = detectFormSpecReference(typeNode);
974
- const optional = param.questionToken !== void 0 || param.initializer !== void 0;
975
- return {
976
- name,
977
- typeNode,
978
- type,
979
- formSpecExportName,
980
- optional
981
- };
982
- }
983
- function detectFormSpecReference(typeNode) {
984
- if (!typeNode) return null;
985
- if (!ts3.isTypeReferenceNode(typeNode)) return null;
986
- const typeName = ts3.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : ts3.isQualifiedName(typeNode.typeName) ? typeNode.typeName.right.text : null;
987
- if (typeName !== "InferSchema" && typeName !== "InferFormSchema") return null;
988
- const typeArg = typeNode.typeArguments?.[0];
989
- if (!typeArg || !ts3.isTypeQueryNode(typeArg)) return null;
990
- if (ts3.isIdentifier(typeArg.exprName)) {
991
- return typeArg.exprName.text;
992
- }
993
- if (ts3.isQualifiedName(typeArg.exprName)) {
994
- return typeArg.exprName.right.text;
995
- }
996
- return null;
1377
+ return { name, fields, fieldLayouts, typeRegistry, instanceMethods, staticMethods };
997
1378
  }
998
- function analyzeInterface(interfaceDecl, checker) {
1379
+ function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
999
1380
  const name = interfaceDecl.name.text;
1000
1381
  const fields = [];
1382
+ const typeRegistry = {};
1383
+ const visiting = /* @__PURE__ */ new Set();
1001
1384
  for (const member of interfaceDecl.members) {
1002
- if (ts3.isPropertySignature(member)) {
1003
- const fieldInfo = analyzeInterfaceProperty(member, checker);
1004
- if (fieldInfo) {
1005
- fields.push(fieldInfo);
1385
+ if (ts4.isPropertySignature(member)) {
1386
+ const fieldNode = analyzeInterfacePropertyToIR(member, checker, file, typeRegistry, visiting);
1387
+ if (fieldNode) {
1388
+ fields.push(fieldNode);
1006
1389
  }
1007
1390
  }
1008
1391
  }
1009
- return {
1010
- name,
1011
- fields,
1012
- instanceMethods: [],
1013
- staticMethods: []
1014
- };
1392
+ const fieldLayouts = fields.map(() => ({}));
1393
+ return { name, fields, fieldLayouts, typeRegistry, instanceMethods: [], staticMethods: [] };
1015
1394
  }
1016
- function analyzeTypeAlias(typeAlias, checker) {
1017
- if (!ts3.isTypeLiteralNode(typeAlias.type)) {
1395
+ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
1396
+ if (!ts4.isTypeLiteralNode(typeAlias.type)) {
1018
1397
  const sourceFile = typeAlias.getSourceFile();
1019
1398
  const { line } = sourceFile.getLineAndCharacterOfPosition(typeAlias.getStart());
1020
- const kindDesc = ts3.SyntaxKind[typeAlias.type.kind] ?? "unknown";
1399
+ const kindDesc = ts4.SyntaxKind[typeAlias.type.kind] ?? "unknown";
1021
1400
  return {
1022
1401
  ok: false,
1023
1402
  error: `Type alias "${typeAlias.name.text}" at line ${String(line + 1)} is not an object type literal (found ${kindDesc})`
@@ -1025,11 +1404,13 @@ function analyzeTypeAlias(typeAlias, checker) {
1025
1404
  }
1026
1405
  const name = typeAlias.name.text;
1027
1406
  const fields = [];
1407
+ const typeRegistry = {};
1408
+ const visiting = /* @__PURE__ */ new Set();
1028
1409
  for (const member of typeAlias.type.members) {
1029
- if (ts3.isPropertySignature(member)) {
1030
- const fieldInfo = analyzeInterfaceProperty(member, checker);
1031
- if (fieldInfo) {
1032
- fields.push(fieldInfo);
1410
+ if (ts4.isPropertySignature(member)) {
1411
+ const fieldNode = analyzeInterfacePropertyToIR(member, checker, file, typeRegistry, visiting);
1412
+ if (fieldNode) {
1413
+ fields.push(fieldNode);
1033
1414
  }
1034
1415
  }
1035
1416
  }
@@ -1038,525 +1419,374 @@ function analyzeTypeAlias(typeAlias, checker) {
1038
1419
  analysis: {
1039
1420
  name,
1040
1421
  fields,
1422
+ fieldLayouts: fields.map(() => ({})),
1423
+ typeRegistry,
1041
1424
  instanceMethods: [],
1042
1425
  staticMethods: []
1043
1426
  }
1044
1427
  };
1045
1428
  }
1046
- function analyzeInterfaceProperty(prop, checker) {
1047
- if (!ts3.isIdentifier(prop.name)) {
1429
+ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
1430
+ if (!ts4.isIdentifier(prop.name)) {
1048
1431
  return null;
1049
1432
  }
1050
1433
  const name = prop.name.text;
1051
- const typeNode = prop.type;
1052
- const type = checker.getTypeAtLocation(prop);
1434
+ const tsType = checker.getTypeAtLocation(prop);
1053
1435
  const optional = prop.questionToken !== void 0;
1054
- const decorators = [];
1055
- if (typeNode) {
1056
- const aliasConstraints = extractTypeAliasConstraints(typeNode, checker);
1057
- decorators.push(...aliasConstraints);
1058
- }
1059
- const fieldMetadata = extractJSDocFieldMetadata(prop);
1060
- if (fieldMetadata) {
1061
- decorators.push(fieldMetadata);
1062
- }
1063
- const jsdocConstraints = extractJSDocConstraints(prop);
1064
- decorators.push(...jsdocConstraints);
1065
- const deprecated = hasDeprecatedTag(prop);
1436
+ const provenance = provenanceForNode(prop, file);
1437
+ const type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
1438
+ const constraints = [];
1439
+ if (prop.type) {
1440
+ constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
1441
+ }
1442
+ constraints.push(...extractJSDocConstraintNodes(prop, file));
1443
+ const annotations = [];
1444
+ annotations.push(...extractJSDocAnnotationNodes(prop, file));
1445
+ const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
1446
+ if (defaultAnnotation) {
1447
+ annotations.push(defaultAnnotation);
1448
+ }
1066
1449
  return {
1450
+ kind: "field",
1067
1451
  name,
1068
- typeNode,
1069
1452
  type,
1070
- optional,
1071
- decorators,
1072
- deprecated,
1073
- defaultValue: void 0
1453
+ required: !optional,
1454
+ constraints,
1455
+ annotations,
1456
+ provenance
1074
1457
  };
1075
1458
  }
1076
- var init_class_analyzer = __esm({
1077
- "src/analyzer/class-analyzer.ts"() {
1078
- "use strict";
1079
- init_decorator_extractor();
1080
- init_jsdoc_constraints();
1081
- }
1082
- });
1083
-
1084
- // src/analyzer/type-converter.ts
1085
- import * as ts4 from "typescript";
1086
- function getNamedTypeFieldInfoMap(type, checker) {
1087
- const symbols = [type.getSymbol(), type.aliasSymbol].filter(
1088
- (s) => s?.declarations != null && s.declarations.length > 0
1089
- );
1090
- for (const symbol of symbols) {
1091
- const declarations = symbol.declarations;
1092
- if (!declarations) continue;
1093
- const classDecl = declarations.find(ts4.isClassDeclaration);
1094
- if (classDecl) {
1095
- const map = /* @__PURE__ */ new Map();
1096
- for (const member of classDecl.members) {
1097
- if (ts4.isPropertyDeclaration(member) && ts4.isIdentifier(member.name)) {
1098
- const fieldInfo = analyzeField(member, checker);
1099
- if (fieldInfo) map.set(fieldInfo.name, fieldInfo);
1100
- }
1101
- }
1102
- return map;
1103
- }
1104
- const interfaceDecl = declarations.find(ts4.isInterfaceDeclaration);
1105
- if (interfaceDecl) {
1106
- return buildFieldInfoMapFromSignatures(interfaceDecl.members, checker);
1107
- }
1108
- const typeAliasDecl = declarations.find(ts4.isTypeAliasDeclaration);
1109
- if (typeAliasDecl && ts4.isTypeLiteralNode(typeAliasDecl.type)) {
1110
- return buildFieldInfoMapFromSignatures(typeAliasDecl.type.members, checker);
1111
- }
1112
- }
1113
- return null;
1114
- }
1115
- function buildFieldInfoMapFromSignatures(members, checker) {
1116
- const map = /* @__PURE__ */ new Map();
1117
- for (const member of members) {
1118
- if (ts4.isPropertySignature(member)) {
1119
- const fieldInfo = analyzeInterfaceProperty(member, checker);
1120
- if (fieldInfo) {
1121
- map.set(fieldInfo.name, fieldInfo);
1122
- }
1123
- }
1459
+ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting) {
1460
+ if (!ts4.isIdentifier(prop.name)) {
1461
+ return null;
1124
1462
  }
1125
- return map;
1126
- }
1127
- function getObjectPropertyInfos(type, checker) {
1128
- const fieldInfoMap = getNamedTypeFieldInfoMap(type, checker);
1129
- const result = [];
1130
- for (const prop of type.getProperties()) {
1131
- const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
1132
- if (!declaration) continue;
1133
- const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
1134
- const optional = !!(prop.flags & ts4.SymbolFlags.Optional);
1135
- const fieldInfo = fieldInfoMap?.get(prop.name) ?? void 0;
1136
- result.push({ name: prop.name, type: propType, optional, fieldInfo });
1463
+ const name = prop.name.text;
1464
+ const tsType = checker.getTypeAtLocation(prop);
1465
+ const optional = prop.questionToken !== void 0;
1466
+ const provenance = provenanceForNode(prop, file);
1467
+ const type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
1468
+ const constraints = [];
1469
+ if (prop.type) {
1470
+ constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
1137
1471
  }
1138
- return result;
1139
- }
1140
- function convertType(type, checker) {
1141
- return convertTypeInternal(type, checker, /* @__PURE__ */ new Set());
1472
+ constraints.push(...extractJSDocConstraintNodes(prop, file));
1473
+ const annotations = [];
1474
+ annotations.push(...extractJSDocAnnotationNodes(prop, file));
1475
+ return {
1476
+ kind: "field",
1477
+ name,
1478
+ type,
1479
+ required: !optional,
1480
+ constraints,
1481
+ annotations,
1482
+ provenance
1483
+ };
1142
1484
  }
1143
- function convertTypeInternal(type, checker, visiting) {
1485
+ function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
1144
1486
  if (type.flags & ts4.TypeFlags.String) {
1145
- return { jsonSchema: { type: "string" }, formSpecFieldType: "text" };
1487
+ return { kind: "primitive", primitiveKind: "string" };
1146
1488
  }
1147
1489
  if (type.flags & ts4.TypeFlags.Number) {
1148
- return { jsonSchema: { type: "number" }, formSpecFieldType: "number" };
1490
+ return { kind: "primitive", primitiveKind: "number" };
1149
1491
  }
1150
1492
  if (type.flags & ts4.TypeFlags.Boolean) {
1151
- return { jsonSchema: { type: "boolean" }, formSpecFieldType: "boolean" };
1493
+ return { kind: "primitive", primitiveKind: "boolean" };
1152
1494
  }
1153
1495
  if (type.flags & ts4.TypeFlags.Null) {
1154
- return { jsonSchema: { type: "null" }, formSpecFieldType: "null" };
1496
+ return { kind: "primitive", primitiveKind: "null" };
1155
1497
  }
1156
1498
  if (type.flags & ts4.TypeFlags.Undefined) {
1157
- return { jsonSchema: {}, formSpecFieldType: "undefined" };
1499
+ return { kind: "primitive", primitiveKind: "null" };
1158
1500
  }
1159
1501
  if (type.isStringLiteral()) {
1160
1502
  return {
1161
- jsonSchema: { const: type.value },
1162
- formSpecFieldType: "enum"
1503
+ kind: "enum",
1504
+ members: [{ value: type.value }]
1163
1505
  };
1164
1506
  }
1165
1507
  if (type.isNumberLiteral()) {
1166
1508
  return {
1167
- jsonSchema: { const: type.value },
1168
- formSpecFieldType: "number"
1509
+ kind: "enum",
1510
+ members: [{ value: type.value }]
1169
1511
  };
1170
1512
  }
1171
1513
  if (type.isUnion()) {
1172
- return convertUnionType(type, checker, visiting);
1514
+ return resolveUnionType(type, checker, file, typeRegistry, visiting);
1173
1515
  }
1174
1516
  if (checker.isArrayType(type)) {
1175
- return convertArrayType(type, checker, visiting);
1517
+ return resolveArrayType(type, checker, file, typeRegistry, visiting);
1176
1518
  }
1177
- if (type.flags & ts4.TypeFlags.Object) {
1178
- return convertObjectType(type, checker, visiting);
1519
+ if (isObjectType(type)) {
1520
+ return resolveObjectType(type, checker, file, typeRegistry, visiting);
1179
1521
  }
1180
- return { jsonSchema: {}, formSpecFieldType: "unknown" };
1522
+ return { kind: "primitive", primitiveKind: "string" };
1181
1523
  }
1182
- function convertUnionType(type, checker, visiting) {
1183
- const types = type.types;
1184
- const nonNullTypes = types.filter(
1524
+ function resolveUnionType(type, checker, file, typeRegistry, visiting) {
1525
+ const allTypes = type.types;
1526
+ const nonNullTypes = allTypes.filter(
1185
1527
  (t) => !(t.flags & (ts4.TypeFlags.Null | ts4.TypeFlags.Undefined))
1186
1528
  );
1187
- const hasNull = types.some((t) => t.flags & ts4.TypeFlags.Null);
1188
- const isBooleanUnion = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts4.TypeFlags.BooleanLiteral);
1189
- if (isBooleanUnion) {
1190
- const result = {
1191
- jsonSchema: { type: "boolean" },
1192
- formSpecFieldType: "boolean"
1193
- };
1529
+ const hasNull = allTypes.some((t) => t.flags & ts4.TypeFlags.Null);
1530
+ const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts4.TypeFlags.BooleanLiteral);
1531
+ if (isBooleanUnion2) {
1532
+ const boolNode = { kind: "primitive", primitiveKind: "boolean" };
1194
1533
  if (hasNull) {
1195
- result.jsonSchema = { oneOf: [{ type: "boolean" }, { type: "null" }] };
1534
+ return {
1535
+ kind: "union",
1536
+ members: [boolNode, { kind: "primitive", primitiveKind: "null" }]
1537
+ };
1196
1538
  }
1197
- return result;
1539
+ return boolNode;
1198
1540
  }
1199
1541
  const allStringLiterals = nonNullTypes.every((t) => t.isStringLiteral());
1200
1542
  if (allStringLiterals && nonNullTypes.length > 0) {
1201
- const enumValues = nonNullTypes.map((t) => t.value);
1202
- const result = {
1203
- jsonSchema: { enum: enumValues },
1204
- formSpecFieldType: "enum"
1543
+ const stringTypes = nonNullTypes.filter((t) => t.isStringLiteral());
1544
+ const enumNode = {
1545
+ kind: "enum",
1546
+ members: stringTypes.map((t) => ({ value: t.value }))
1205
1547
  };
1206
1548
  if (hasNull) {
1207
- result.jsonSchema = { oneOf: [{ enum: enumValues }, { type: "null" }] };
1549
+ return {
1550
+ kind: "union",
1551
+ members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
1552
+ };
1208
1553
  }
1209
- return result;
1554
+ return enumNode;
1210
1555
  }
1211
1556
  const allNumberLiterals = nonNullTypes.every((t) => t.isNumberLiteral());
1212
1557
  if (allNumberLiterals && nonNullTypes.length > 0) {
1213
- const enumValues = nonNullTypes.map((t) => t.value);
1214
- const result = {
1215
- jsonSchema: { enum: enumValues },
1216
- formSpecFieldType: "enum"
1558
+ const numberTypes = nonNullTypes.filter((t) => t.isNumberLiteral());
1559
+ const enumNode = {
1560
+ kind: "enum",
1561
+ members: numberTypes.map((t) => ({ value: t.value }))
1217
1562
  };
1218
1563
  if (hasNull) {
1219
- result.jsonSchema = { oneOf: [{ enum: enumValues }, { type: "null" }] };
1564
+ return {
1565
+ kind: "union",
1566
+ members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
1567
+ };
1220
1568
  }
1221
- return result;
1569
+ return enumNode;
1222
1570
  }
1223
1571
  if (nonNullTypes.length === 1 && nonNullTypes[0]) {
1224
- const result = convertTypeInternal(nonNullTypes[0], checker, visiting);
1572
+ const inner = resolveTypeNode(nonNullTypes[0], checker, file, typeRegistry, visiting);
1225
1573
  if (hasNull) {
1226
- result.jsonSchema = { oneOf: [result.jsonSchema, { type: "null" }] };
1574
+ return {
1575
+ kind: "union",
1576
+ members: [inner, { kind: "primitive", primitiveKind: "null" }]
1577
+ };
1227
1578
  }
1228
- return result;
1579
+ return inner;
1229
1580
  }
1230
- const schemas = nonNullTypes.map((t) => convertTypeInternal(t, checker, visiting).jsonSchema);
1581
+ const members = nonNullTypes.map(
1582
+ (t) => resolveTypeNode(t, checker, file, typeRegistry, visiting)
1583
+ );
1231
1584
  if (hasNull) {
1232
- schemas.push({ type: "null" });
1585
+ members.push({ kind: "primitive", primitiveKind: "null" });
1233
1586
  }
1234
- return {
1235
- jsonSchema: { oneOf: schemas },
1236
- formSpecFieldType: "union"
1237
- };
1587
+ return { kind: "union", members };
1238
1588
  }
1239
- function convertArrayType(type, checker, visiting) {
1240
- const typeArgs = type.typeArguments;
1589
+ function resolveArrayType(type, checker, file, typeRegistry, visiting) {
1590
+ const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
1241
1591
  const elementType = typeArgs?.[0];
1242
- const itemSchema = elementType ? convertTypeInternal(elementType, checker, visiting).jsonSchema : {};
1243
- return {
1244
- jsonSchema: {
1245
- type: "array",
1246
- items: itemSchema
1247
- },
1248
- formSpecFieldType: "array"
1249
- };
1592
+ const items = elementType ? resolveTypeNode(elementType, checker, file, typeRegistry, visiting) : { kind: "primitive", primitiveKind: "string" };
1593
+ return { kind: "array", items };
1250
1594
  }
1251
- function convertObjectType(type, checker, visiting) {
1595
+ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
1252
1596
  if (visiting.has(type)) {
1253
- return { jsonSchema: { type: "object" }, formSpecFieldType: "object" };
1597
+ return { kind: "object", properties: [], additionalProperties: false };
1254
1598
  }
1255
1599
  visiting.add(type);
1256
- const properties = {};
1257
- const required = [];
1258
- for (const propInfo of getObjectPropertyInfos(type, checker)) {
1259
- const propSchema = convertTypeInternal(propInfo.type, checker, visiting).jsonSchema;
1260
- properties[propInfo.name] = propInfo.fieldInfo ? applyDecoratorsToSchema(propSchema, propInfo.fieldInfo.decorators, propInfo.fieldInfo) : propSchema;
1261
- if (!propInfo.optional) {
1262
- required.push(propInfo.name);
1263
- }
1600
+ const typeName = getNamedTypeName(type);
1601
+ if (typeName && typeName in typeRegistry) {
1602
+ visiting.delete(type);
1603
+ return { kind: "reference", name: typeName, typeArguments: [] };
1604
+ }
1605
+ const properties = [];
1606
+ const fieldInfoMap = getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting);
1607
+ for (const prop of type.getProperties()) {
1608
+ const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
1609
+ if (!declaration) continue;
1610
+ const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
1611
+ const optional = !!(prop.flags & ts4.SymbolFlags.Optional);
1612
+ const propTypeNode = resolveTypeNode(propType, checker, file, typeRegistry, visiting);
1613
+ const fieldNodeInfo = fieldInfoMap?.get(prop.name);
1614
+ properties.push({
1615
+ name: prop.name,
1616
+ type: propTypeNode,
1617
+ optional,
1618
+ constraints: fieldNodeInfo?.constraints ?? [],
1619
+ annotations: fieldNodeInfo?.annotations ?? [],
1620
+ provenance: fieldNodeInfo?.provenance ?? provenanceForFile(file)
1621
+ });
1264
1622
  }
1265
1623
  visiting.delete(type);
1266
- return {
1267
- jsonSchema: {
1268
- type: "object",
1269
- properties,
1270
- ...required.length > 0 ? { required } : {}
1271
- },
1272
- formSpecFieldType: "object"
1273
- };
1274
- }
1275
- function createFormSpecField(fieldName, type, decorators, optional, checker, visitedTypes = /* @__PURE__ */ new Set()) {
1276
- const { formSpecFieldType } = convertType(type, checker);
1277
- const field = {
1278
- _field: formSpecFieldType,
1279
- id: fieldName
1624
+ const objectNode = {
1625
+ kind: "object",
1626
+ properties,
1627
+ additionalProperties: false
1280
1628
  };
1281
- if (!optional) {
1282
- field.required = true;
1283
- }
1284
- if (formSpecFieldType === "object" && type.flags & ts4.TypeFlags.Object) {
1285
- if (!visitedTypes.has(type)) {
1286
- visitedTypes.add(type);
1287
- const nestedFields = [];
1288
- for (const propInfo of getObjectPropertyInfos(type, checker)) {
1289
- nestedFields.push(
1290
- createFormSpecField(
1291
- propInfo.name,
1292
- propInfo.type,
1293
- propInfo.fieldInfo?.decorators ?? [],
1294
- propInfo.optional,
1295
- checker,
1296
- visitedTypes
1297
- )
1298
- );
1299
- }
1300
- visitedTypes.delete(type);
1301
- if (nestedFields.length > 0) {
1302
- field.fields = nestedFields;
1303
- }
1304
- }
1629
+ if (typeName) {
1630
+ typeRegistry[typeName] = {
1631
+ name: typeName,
1632
+ type: objectNode,
1633
+ provenance: provenanceForFile(file)
1634
+ };
1635
+ return { kind: "reference", name: typeName, typeArguments: [] };
1305
1636
  }
1306
- for (const dec of decorators) {
1307
- applyDecoratorToField(field, dec);
1308
- }
1309
- return field;
1310
- }
1311
- function applyDecoratorToField(field, decorator) {
1312
- const { args } = decorator;
1313
- const resolved = decorator.resolved;
1314
- const effectiveName = resolved?.extendsBuiltin ?? decorator.name;
1315
- switch (effectiveName) {
1316
- case "Field": {
1317
- const opts = args[0];
1318
- if (typeof opts === "object" && opts !== null && !Array.isArray(opts)) {
1319
- if (typeof opts["displayName"] === "string") {
1320
- field.label = opts["displayName"];
1321
- }
1322
- if (typeof opts["description"] === "string") {
1323
- field.description = opts["description"];
1324
- }
1325
- if (typeof opts["placeholder"] === "string") {
1326
- field.placeholder = opts["placeholder"];
1327
- }
1328
- }
1329
- break;
1330
- }
1331
- case "Minimum":
1332
- if (typeof args[0] === "number") {
1333
- field.min = args[0];
1334
- }
1335
- break;
1336
- case "Maximum":
1337
- if (typeof args[0] === "number") {
1338
- field.max = args[0];
1339
- }
1340
- break;
1341
- case "MinLength":
1342
- if (typeof args[0] === "number") {
1343
- field.minLength = args[0];
1344
- }
1345
- break;
1346
- case "MaxLength":
1347
- if (typeof args[0] === "number") {
1348
- field.maxLength = args[0];
1349
- }
1350
- break;
1351
- case "Pattern":
1352
- if (typeof args[0] === "string") {
1353
- field.pattern = args[0];
1354
- }
1355
- break;
1356
- case "EnumOptions":
1357
- if (Array.isArray(args[0])) {
1358
- field.options = args[0];
1359
- }
1360
- break;
1361
- case "ShowWhen":
1362
- if (typeof args[0] === "object" && args[0] !== null) {
1363
- field.showWhen = args[0];
1364
- }
1365
- break;
1366
- case "Group":
1367
- if (typeof args[0] === "string") {
1368
- field.group = args[0];
1369
- }
1370
- break;
1371
- }
1372
- }
1373
- function applyDecoratorsToSchema(schema, decorators, fieldInfo) {
1374
- const result = { ...schema };
1375
- for (const dec of decorators) {
1376
- const { args } = dec;
1377
- const resolved = dec.resolved;
1378
- const effectiveName = resolved?.extendsBuiltin ?? dec.name;
1379
- switch (effectiveName) {
1380
- case "Field": {
1381
- const opts = args[0];
1382
- if (typeof opts === "object" && opts !== null && !Array.isArray(opts)) {
1383
- if (typeof opts["displayName"] === "string") {
1384
- result.title = opts["displayName"];
1385
- }
1386
- if (typeof opts["description"] === "string") {
1387
- result.description = opts["description"];
1637
+ return objectNode;
1638
+ }
1639
+ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting) {
1640
+ const symbols = [type.getSymbol(), type.aliasSymbol].filter(
1641
+ (s) => s?.declarations != null && s.declarations.length > 0
1642
+ );
1643
+ for (const symbol of symbols) {
1644
+ const declarations = symbol.declarations;
1645
+ if (!declarations) continue;
1646
+ const classDecl = declarations.find(ts4.isClassDeclaration);
1647
+ if (classDecl) {
1648
+ const map = /* @__PURE__ */ new Map();
1649
+ for (const member of classDecl.members) {
1650
+ if (ts4.isPropertyDeclaration(member) && ts4.isIdentifier(member.name)) {
1651
+ const fieldNode = analyzeFieldToIR(member, checker, file, typeRegistry, visiting);
1652
+ if (fieldNode) {
1653
+ map.set(fieldNode.name, {
1654
+ constraints: [...fieldNode.constraints],
1655
+ annotations: [...fieldNode.annotations],
1656
+ provenance: fieldNode.provenance
1657
+ });
1388
1658
  }
1389
1659
  }
1390
- break;
1391
- }
1392
- case "Minimum":
1393
- if (typeof args[0] === "number") {
1394
- result.minimum = args[0];
1395
- }
1396
- break;
1397
- case "Maximum":
1398
- if (typeof args[0] === "number") {
1399
- result.maximum = args[0];
1400
- }
1401
- break;
1402
- case "ExclusiveMinimum":
1403
- if (typeof args[0] === "number") {
1404
- result.exclusiveMinimum = args[0];
1405
- }
1406
- break;
1407
- case "ExclusiveMaximum":
1408
- if (typeof args[0] === "number") {
1409
- result.exclusiveMaximum = args[0];
1410
- }
1411
- break;
1412
- case "MinLength":
1413
- if (typeof args[0] === "number") {
1414
- result.minLength = args[0];
1415
- }
1416
- break;
1417
- case "MaxLength":
1418
- if (typeof args[0] === "number") {
1419
- result.maxLength = args[0];
1420
- }
1421
- break;
1422
- case "Pattern":
1423
- if (typeof args[0] === "string") {
1424
- result.pattern = args[0];
1425
- }
1426
- break;
1427
- }
1428
- if (resolved?.extensionName && /^[a-z][a-z0-9-]*$/.test(resolved.extensionName)) {
1429
- const key = `x-formspec-${resolved.extensionName}`;
1430
- if (resolved.isMarker) {
1431
- setSchemaExtension(result, key, true);
1432
- } else {
1433
- setSchemaExtension(result, key, args[0] ?? true);
1434
1660
  }
1661
+ return map;
1435
1662
  }
1436
- }
1437
- if (fieldInfo) {
1438
- if (fieldInfo.deprecated) {
1439
- result.deprecated = true;
1663
+ const interfaceDecl = declarations.find(ts4.isInterfaceDeclaration);
1664
+ if (interfaceDecl) {
1665
+ return buildFieldNodeInfoMap(interfaceDecl.members, checker, file, typeRegistry, visiting);
1440
1666
  }
1441
- if (fieldInfo.defaultValue !== void 0) {
1442
- result.default = fieldInfo.defaultValue;
1667
+ const typeAliasDecl = declarations.find(ts4.isTypeAliasDeclaration);
1668
+ if (typeAliasDecl && ts4.isTypeLiteralNode(typeAliasDecl.type)) {
1669
+ return buildFieldNodeInfoMap(
1670
+ typeAliasDecl.type.members,
1671
+ checker,
1672
+ file,
1673
+ typeRegistry,
1674
+ visiting
1675
+ );
1443
1676
  }
1444
1677
  }
1445
- return result;
1678
+ return null;
1446
1679
  }
1447
- var init_type_converter = __esm({
1448
- "src/analyzer/type-converter.ts"() {
1449
- "use strict";
1450
- init_class_analyzer();
1451
- init_types();
1452
- }
1453
- });
1454
-
1455
- // src/analyzer/program.ts
1456
- import * as ts5 from "typescript";
1457
- import * as path from "path";
1458
- function createProgramContext(filePath) {
1459
- const absolutePath = path.resolve(filePath);
1460
- const fileDir = path.dirname(absolutePath);
1461
- const configPath = ts5.findConfigFile(fileDir, ts5.sys.fileExists.bind(ts5.sys), "tsconfig.json");
1462
- let compilerOptions;
1463
- let fileNames;
1464
- if (configPath) {
1465
- const configFile = ts5.readConfigFile(configPath, ts5.sys.readFile.bind(ts5.sys));
1466
- if (configFile.error) {
1467
- throw new Error(
1468
- `Error reading tsconfig.json: ${ts5.flattenDiagnosticMessageText(configFile.error.messageText, "\n")}`
1469
- );
1470
- }
1471
- const parsed = ts5.parseJsonConfigFileContent(
1472
- configFile.config,
1473
- ts5.sys,
1474
- path.dirname(configPath)
1475
- );
1476
- if (parsed.errors.length > 0) {
1477
- const errorMessages = parsed.errors.map((e) => ts5.flattenDiagnosticMessageText(e.messageText, "\n")).join("\n");
1478
- throw new Error(`Error parsing tsconfig.json: ${errorMessages}`);
1680
+ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting) {
1681
+ const map = /* @__PURE__ */ new Map();
1682
+ for (const member of members) {
1683
+ if (ts4.isPropertySignature(member)) {
1684
+ const fieldNode = analyzeInterfacePropertyToIR(member, checker, file, typeRegistry, visiting);
1685
+ if (fieldNode) {
1686
+ map.set(fieldNode.name, {
1687
+ constraints: [...fieldNode.constraints],
1688
+ annotations: [...fieldNode.annotations],
1689
+ provenance: fieldNode.provenance
1690
+ });
1691
+ }
1479
1692
  }
1480
- compilerOptions = parsed.options;
1481
- fileNames = parsed.fileNames.includes(absolutePath) ? parsed.fileNames : [...parsed.fileNames, absolutePath];
1482
- } else {
1483
- compilerOptions = {
1484
- target: ts5.ScriptTarget.ES2022,
1485
- module: ts5.ModuleKind.NodeNext,
1486
- moduleResolution: ts5.ModuleResolutionKind.NodeNext,
1487
- strict: true,
1488
- skipLibCheck: true,
1489
- declaration: true
1490
- };
1491
- fileNames = [absolutePath];
1492
- }
1493
- const program = ts5.createProgram(fileNames, compilerOptions);
1494
- const sourceFile = program.getSourceFile(absolutePath);
1495
- if (!sourceFile) {
1496
- throw new Error(`Could not find source file: ${absolutePath}`);
1497
1693
  }
1694
+ return map;
1695
+ }
1696
+ function extractTypeAliasConstraintNodes(typeNode, checker, file) {
1697
+ if (!ts4.isTypeReferenceNode(typeNode)) return [];
1698
+ const symbol = checker.getSymbolAtLocation(typeNode.typeName);
1699
+ if (!symbol?.declarations) return [];
1700
+ const aliasDecl = symbol.declarations.find(ts4.isTypeAliasDeclaration);
1701
+ if (!aliasDecl) return [];
1702
+ if (ts4.isTypeLiteralNode(aliasDecl.type)) return [];
1703
+ return extractJSDocConstraintNodes(aliasDecl, file);
1704
+ }
1705
+ function provenanceForNode(node, file) {
1706
+ const sourceFile = node.getSourceFile();
1707
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
1498
1708
  return {
1499
- program,
1500
- checker: program.getTypeChecker(),
1501
- sourceFile
1709
+ surface: "tsdoc",
1710
+ file,
1711
+ line: line + 1,
1712
+ column: character
1502
1713
  };
1503
1714
  }
1504
- function findNodeByName(sourceFile, name, predicate, getName) {
1505
- let result = null;
1506
- function visit(node) {
1507
- if (result) return;
1508
- if (predicate(node) && getName(node) === name) {
1509
- result = node;
1510
- return;
1715
+ function provenanceForFile(file) {
1716
+ return { surface: "tsdoc", file, line: 0, column: 0 };
1717
+ }
1718
+ function getNamedTypeName(type) {
1719
+ const symbol = type.getSymbol();
1720
+ if (symbol?.declarations) {
1721
+ const decl = symbol.declarations[0];
1722
+ if (decl && (ts4.isClassDeclaration(decl) || ts4.isInterfaceDeclaration(decl) || ts4.isTypeAliasDeclaration(decl))) {
1723
+ const name = ts4.isClassDeclaration(decl) ? decl.name?.text : decl.name.text;
1724
+ if (name) return name;
1511
1725
  }
1512
- ts5.forEachChild(node, visit);
1513
1726
  }
1514
- visit(sourceFile);
1515
- return result;
1727
+ const aliasSymbol = type.aliasSymbol;
1728
+ if (aliasSymbol?.declarations) {
1729
+ const aliasDecl = aliasSymbol.declarations.find(ts4.isTypeAliasDeclaration);
1730
+ if (aliasDecl) {
1731
+ return aliasDecl.name.text;
1732
+ }
1733
+ }
1734
+ return null;
1516
1735
  }
1517
- function findClassByName(sourceFile, className) {
1518
- return findNodeByName(sourceFile, className, ts5.isClassDeclaration, (n) => n.name?.text);
1736
+ function analyzeMethod(method, checker) {
1737
+ if (!ts4.isIdentifier(method.name)) {
1738
+ return null;
1739
+ }
1740
+ const name = method.name.text;
1741
+ const parameters = [];
1742
+ for (const param of method.parameters) {
1743
+ if (ts4.isIdentifier(param.name)) {
1744
+ const paramInfo = analyzeParameter(param, checker);
1745
+ parameters.push(paramInfo);
1746
+ }
1747
+ }
1748
+ const returnTypeNode = method.type;
1749
+ const signature = checker.getSignatureFromDeclaration(method);
1750
+ const returnType = signature ? checker.getReturnTypeOfSignature(signature) : checker.getTypeAtLocation(method);
1751
+ return { name, parameters, returnTypeNode, returnType };
1519
1752
  }
1520
- function findInterfaceByName(sourceFile, interfaceName) {
1521
- return findNodeByName(sourceFile, interfaceName, ts5.isInterfaceDeclaration, (n) => n.name.text);
1753
+ function analyzeParameter(param, checker) {
1754
+ const name = ts4.isIdentifier(param.name) ? param.name.text : "param";
1755
+ const typeNode = param.type;
1756
+ const type = checker.getTypeAtLocation(param);
1757
+ const formSpecExportName = detectFormSpecReference(typeNode);
1758
+ const optional = param.questionToken !== void 0 || param.initializer !== void 0;
1759
+ return { name, typeNode, type, formSpecExportName, optional };
1522
1760
  }
1523
- function findTypeAliasByName(sourceFile, aliasName) {
1524
- return findNodeByName(sourceFile, aliasName, ts5.isTypeAliasDeclaration, (n) => n.name.text);
1761
+ function detectFormSpecReference(typeNode) {
1762
+ if (!typeNode) return null;
1763
+ if (!ts4.isTypeReferenceNode(typeNode)) return null;
1764
+ const typeName = ts4.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : ts4.isQualifiedName(typeNode.typeName) ? typeNode.typeName.right.text : null;
1765
+ if (typeName !== "InferSchema" && typeName !== "InferFormSchema") return null;
1766
+ const typeArg = typeNode.typeArguments?.[0];
1767
+ if (!typeArg || !ts4.isTypeQueryNode(typeArg)) return null;
1768
+ if (ts4.isIdentifier(typeArg.exprName)) {
1769
+ return typeArg.exprName.text;
1770
+ }
1771
+ if (ts4.isQualifiedName(typeArg.exprName)) {
1772
+ return typeArg.exprName.right.text;
1773
+ }
1774
+ return null;
1525
1775
  }
1526
- var init_program = __esm({
1527
- "src/analyzer/program.ts"() {
1776
+ var init_class_analyzer = __esm({
1777
+ "src/analyzer/class-analyzer.ts"() {
1528
1778
  "use strict";
1779
+ init_jsdoc_constraints();
1529
1780
  }
1530
1781
  });
1531
1782
 
1532
1783
  // src/generators/class-schema.ts
1533
- function generateClassSchemas(analysis, checker) {
1534
- const properties = {};
1535
- const required = [];
1536
- const uiElements = [];
1537
- for (const field of analysis.fields) {
1538
- const { jsonSchema: baseSchema } = convertType(field.type, checker);
1539
- const fieldSchema = applyDecoratorsToSchema(baseSchema, field.decorators, field);
1540
- properties[field.name] = fieldSchema;
1541
- if (!field.optional) {
1542
- required.push(field.name);
1543
- }
1544
- const formSpecField = createFormSpecField(
1545
- field.name,
1546
- field.type,
1547
- field.decorators,
1548
- field.optional,
1549
- checker
1550
- );
1551
- uiElements.push(formSpecField);
1552
- }
1553
- const jsonSchema = {
1554
- type: "object",
1555
- properties,
1556
- ...required.length > 0 ? { required } : {}
1784
+ function generateClassSchemas(analysis, source) {
1785
+ const ir = canonicalizeTSDoc(analysis, source);
1786
+ return {
1787
+ jsonSchema: generateJsonSchemaFromIR(ir),
1788
+ uiSchema: generateUiSchemaFromIR(ir)
1557
1789
  };
1558
- const uiSchema2 = generateUiSchemaFromFields(uiElements);
1559
- return { jsonSchema, uiSchema: uiSchema2 };
1560
1790
  }
1561
1791
  function generateSchemasFromClass(options) {
1562
1792
  const ctx = createProgramContext(options.filePath);
@@ -1564,26 +1794,27 @@ function generateSchemasFromClass(options) {
1564
1794
  if (!classDecl) {
1565
1795
  throw new Error(`Class "${options.className}" not found in ${options.filePath}`);
1566
1796
  }
1567
- const analysis = analyzeClass(classDecl, ctx.checker);
1568
- return generateClassSchemas(analysis, ctx.checker);
1797
+ const analysis = analyzeClassToIR(classDecl, ctx.checker, options.filePath);
1798
+ return generateClassSchemas(analysis, { file: options.filePath });
1569
1799
  }
1570
1800
  function generateSchemas(options) {
1571
1801
  const ctx = createProgramContext(options.filePath);
1802
+ const source = { file: options.filePath };
1572
1803
  const classDecl = findClassByName(ctx.sourceFile, options.typeName);
1573
1804
  if (classDecl) {
1574
- const analysis = analyzeClass(classDecl, ctx.checker);
1575
- return generateClassSchemas(analysis, ctx.checker);
1805
+ const analysis = analyzeClassToIR(classDecl, ctx.checker, options.filePath);
1806
+ return generateClassSchemas(analysis, source);
1576
1807
  }
1577
1808
  const interfaceDecl = findInterfaceByName(ctx.sourceFile, options.typeName);
1578
1809
  if (interfaceDecl) {
1579
- const analysis = analyzeInterface(interfaceDecl, ctx.checker);
1580
- return generateClassSchemas(analysis, ctx.checker);
1810
+ const analysis = analyzeInterfaceToIR(interfaceDecl, ctx.checker, options.filePath);
1811
+ return generateClassSchemas(analysis, source);
1581
1812
  }
1582
1813
  const typeAlias = findTypeAliasByName(ctx.sourceFile, options.typeName);
1583
1814
  if (typeAlias) {
1584
- const result = analyzeTypeAlias(typeAlias, ctx.checker);
1815
+ const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, options.filePath);
1585
1816
  if (result.ok) {
1586
- return generateClassSchemas(result.analysis, ctx.checker);
1817
+ return generateClassSchemas(result.analysis, source);
1587
1818
  }
1588
1819
  throw new Error(result.error);
1589
1820
  }
@@ -1594,463 +1825,11 @@ function generateSchemas(options) {
1594
1825
  var init_class_schema = __esm({
1595
1826
  "src/generators/class-schema.ts"() {
1596
1827
  "use strict";
1597
- init_type_converter();
1598
- init_generator2();
1599
1828
  init_program();
1600
1829
  init_class_analyzer();
1601
- }
1602
- });
1603
-
1604
- // src/codegen/index.ts
1605
- import * as ts6 from "typescript";
1606
- import * as path2 from "path";
1607
- import * as fs from "fs";
1608
- function findDecoratedClasses(files, baseDir) {
1609
- const program = ts6.createProgram(files, {
1610
- target: ts6.ScriptTarget.ESNext,
1611
- module: ts6.ModuleKind.ESNext,
1612
- moduleResolution: ts6.ModuleResolutionKind.NodeNext,
1613
- // TC39 Stage 3 decorators — do not use experimentalDecorators
1614
- strict: true
1615
- });
1616
- const checker = program.getTypeChecker();
1617
- const results = [];
1618
- for (const sourceFile of program.getSourceFiles()) {
1619
- if (sourceFile.isDeclarationFile) continue;
1620
- if (!files.some((f) => path2.resolve(f) === sourceFile.fileName)) continue;
1621
- ts6.forEachChild(sourceFile, (node) => {
1622
- if (ts6.isClassDeclaration(node) && hasDecoratedProperties(node)) {
1623
- const className = node.name?.text;
1624
- if (!className) return;
1625
- const typeMetadata = extractTypeMetadata(node, checker);
1626
- const relativePath = path2.relative(baseDir, sourceFile.fileName);
1627
- const exported = isClassExported(node, sourceFile);
1628
- results.push({
1629
- name: className,
1630
- sourcePath: relativePath.replace(/\.tsx?$/, ""),
1631
- typeMetadata,
1632
- isExported: exported
1633
- });
1634
- }
1635
- });
1636
- }
1637
- return results;
1638
- }
1639
- function hasDecoratedProperties(node) {
1640
- return node.members.some((member) => {
1641
- if (!ts6.isPropertyDeclaration(member)) return false;
1642
- const decorators = ts6.getDecorators(member);
1643
- return decorators !== void 0 && decorators.length > 0;
1644
- });
1645
- }
1646
- function isClassExported(classNode, sourceFile) {
1647
- const className = classNode.name?.text;
1648
- if (!className) return false;
1649
- const modifiers = ts6.getModifiers(classNode);
1650
- if (modifiers) {
1651
- const hasExport = modifiers.some((mod) => mod.kind === ts6.SyntaxKind.ExportKeyword);
1652
- if (hasExport) return true;
1653
- }
1654
- for (const statement of sourceFile.statements) {
1655
- if (ts6.isExportDeclaration(statement) && statement.exportClause) {
1656
- if (ts6.isNamedExports(statement.exportClause)) {
1657
- for (const element of statement.exportClause.elements) {
1658
- const localName = element.propertyName?.text ?? element.name.text;
1659
- if (localName === className) {
1660
- return true;
1661
- }
1662
- }
1663
- }
1664
- }
1665
- }
1666
- return false;
1667
- }
1668
- function extractTypeMetadata(classNode, checker) {
1669
- const metadata = {};
1670
- for (const member of classNode.members) {
1671
- if (!ts6.isPropertyDeclaration(member)) continue;
1672
- if (!member.name || !ts6.isIdentifier(member.name)) continue;
1673
- const symbol = checker.getSymbolAtLocation(member.name);
1674
- if (!symbol) continue;
1675
- const type = checker.getTypeOfSymbolAtLocation(symbol, member);
1676
- const fieldName = member.name.text;
1677
- const isOptional = !!(symbol.flags & ts6.SymbolFlags.Optional);
1678
- const typeInfo = convertTypeToMetadata(type, checker);
1679
- if (isOptional) {
1680
- typeInfo.optional = true;
1681
- }
1682
- metadata[fieldName] = typeInfo;
1683
- }
1684
- return metadata;
1685
- }
1686
- function convertTypeToMetadata(type, checker, visited = /* @__PURE__ */ new Set()) {
1687
- const isObjectType = (type.flags & ts6.TypeFlags.Object) !== 0;
1688
- if (isObjectType) {
1689
- if (visited.has(type)) {
1690
- return { type: "unknown" };
1691
- }
1692
- visited.add(type);
1693
- }
1694
- if (type.isUnion()) {
1695
- const types = type.types;
1696
- const nonNullTypes = types.filter(
1697
- (t) => !(t.flags & (ts6.TypeFlags.Null | ts6.TypeFlags.Undefined))
1698
- );
1699
- const hasNull = types.some((t) => t.flags & ts6.TypeFlags.Null);
1700
- const isBooleanUnion = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts6.TypeFlags.BooleanLiteral);
1701
- if (isBooleanUnion) {
1702
- const result = { type: "boolean" };
1703
- if (hasNull) result.nullable = true;
1704
- return result;
1705
- }
1706
- const allStringLiterals = nonNullTypes.every((t) => t.isStringLiteral());
1707
- if (allStringLiterals && nonNullTypes.length > 0) {
1708
- const result = {
1709
- type: "enum",
1710
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion -- TypeScript doesn't narrow array types from `every` predicate
1711
- values: nonNullTypes.map((t) => t.value)
1712
- };
1713
- if (hasNull) result.nullable = true;
1714
- return result;
1715
- }
1716
- const allNumberLiterals = nonNullTypes.every((t) => t.isNumberLiteral());
1717
- if (allNumberLiterals && nonNullTypes.length > 0) {
1718
- const result = {
1719
- type: "enum",
1720
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion -- TypeScript doesn't narrow array types from `every` predicate
1721
- values: nonNullTypes.map((t) => t.value)
1722
- };
1723
- if (hasNull) result.nullable = true;
1724
- return result;
1725
- }
1726
- if (nonNullTypes.length === 1) {
1727
- const singleType = nonNullTypes[0];
1728
- if (!singleType) return { type: "unknown" };
1729
- const result = convertTypeToMetadata(singleType, checker, visited);
1730
- if (hasNull) result.nullable = true;
1731
- return result;
1732
- }
1733
- return { type: "unknown" };
1734
- }
1735
- if (type.flags & ts6.TypeFlags.String) return { type: "string" };
1736
- if (type.flags & ts6.TypeFlags.Number) return { type: "number" };
1737
- if (type.flags & ts6.TypeFlags.Boolean) return { type: "boolean" };
1738
- if (type.isStringLiteral()) {
1739
- return { type: "enum", values: [type.value] };
1740
- }
1741
- if (type.isNumberLiteral()) {
1742
- return { type: "enum", values: [type.value] };
1743
- }
1744
- if (checker.isArrayType(type)) {
1745
- const typeArgs = type.typeArguments;
1746
- return {
1747
- type: "array",
1748
- itemType: typeArgs?.[0] ? convertTypeToMetadata(typeArgs[0], checker, visited) : { type: "unknown" }
1749
- };
1750
- }
1751
- if (type.flags & ts6.TypeFlags.Object) {
1752
- const properties = {};
1753
- for (const prop of type.getProperties()) {
1754
- const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
1755
- if (!declaration) continue;
1756
- const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
1757
- const propOptional = !!(prop.flags & ts6.SymbolFlags.Optional);
1758
- const propMeta = convertTypeToMetadata(propType, checker, visited);
1759
- if (propOptional) propMeta.optional = true;
1760
- properties[prop.name] = propMeta;
1761
- }
1762
- if (Object.keys(properties).length > 0) {
1763
- return { type: "object", properties };
1764
- }
1765
- return { type: "object" };
1766
- }
1767
- return { type: "unknown" };
1768
- }
1769
- function generateCodegenOutput(classes, outputPath, baseDir) {
1770
- const outputDir = path2.dirname(path2.resolve(outputPath));
1771
- const lines = [
1772
- "/**",
1773
- " * Auto-generated by FormSpec CLI.",
1774
- " * DO NOT EDIT - changes will be overwritten.",
1775
- " *",
1776
- " * This file provides:",
1777
- " * - Type metadata patches for runtime schema generation",
1778
- " * - Inferred schema types (e.g., UserFormSchema)",
1779
- " * - Type-safe FormSpec accessors (e.g., getUserFormFormSpec())",
1780
- " *",
1781
- " * Usage:",
1782
- " * import { UserFormSchema, getUserFormFormSpec } from './__formspec_types__';",
1783
- " *",
1784
- " * const data: UserFormSchema = { name: 'Alice', country: 'us' };",
1785
- " * const spec = getUserFormFormSpec();",
1786
- " */",
1787
- "",
1788
- "/* eslint-disable @typescript-eslint/no-explicit-any */",
1789
- ""
1790
- ];
1791
- for (const cls of classes) {
1792
- const absoluteSourcePath = path2.resolve(baseDir, cls.sourcePath);
1793
- const importPath = getRelativeImportPath(outputDir, absoluteSourcePath);
1794
- lines.push(`import { ${cls.name} } from "${importPath}";`);
1795
- }
1796
- lines.push("");
1797
- lines.push("// =============================================================================");
1798
- lines.push("// Type Metadata Patches");
1799
- lines.push("// =============================================================================");
1800
- lines.push("");
1801
- for (const cls of classes) {
1802
- const metadataJson = JSON.stringify(cls.typeMetadata, null, 2).split("\n").map((line, i) => i === 0 ? line : " " + line).join("\n");
1803
- lines.push(`(${cls.name} as any).__formspec_types__ = ${metadataJson};`);
1804
- lines.push("");
1805
- }
1806
- lines.push("// =============================================================================");
1807
- lines.push("// Inferred Schema Types");
1808
- lines.push("// =============================================================================");
1809
- lines.push("");
1810
- for (const cls of classes) {
1811
- lines.push(generateSchemaType(cls));
1812
- lines.push("");
1813
- }
1814
- lines.push("// =============================================================================");
1815
- lines.push("// Type-Safe FormSpec Accessors");
1816
- lines.push("// =============================================================================");
1817
- lines.push("");
1818
- for (const cls of classes) {
1819
- lines.push(generateTypedAccessor(cls));
1820
- lines.push("");
1821
- }
1822
- return lines.join("\n");
1823
- }
1824
- function metadataToTypeString(metadata) {
1825
- const baseType = metadataToBaseTypeString(metadata);
1826
- if (metadata.nullable && metadata.optional) {
1827
- return `${baseType} | null | undefined`;
1828
- }
1829
- if (metadata.nullable) {
1830
- return `${baseType} | null`;
1831
- }
1832
- if (metadata.optional) {
1833
- return `${baseType} | undefined`;
1834
- }
1835
- return baseType;
1836
- }
1837
- function needsPropertyQuoting(key) {
1838
- if (!VALID_IDENTIFIER.test(key)) {
1839
- return true;
1840
- }
1841
- return RESERVED_WORDS.has(key);
1842
- }
1843
- function escapePropertyKey(key) {
1844
- if (needsPropertyQuoting(key)) {
1845
- return JSON.stringify(key);
1846
- }
1847
- return key;
1848
- }
1849
- function metadataToBaseTypeString(metadata) {
1850
- switch (metadata.type) {
1851
- case "string":
1852
- return "string";
1853
- case "number":
1854
- return "number";
1855
- case "boolean":
1856
- return "boolean";
1857
- case "enum":
1858
- if (metadata.values && metadata.values.length > 0) {
1859
- return metadata.values.map((v) => typeof v === "string" ? JSON.stringify(v) : String(v)).join(" | ");
1860
- }
1861
- return "string";
1862
- case "array":
1863
- if (metadata.itemType) {
1864
- const itemType = metadataToTypeString(metadata.itemType);
1865
- if (itemType.includes("|")) {
1866
- return `(${itemType})[]`;
1867
- }
1868
- return `${itemType}[]`;
1869
- }
1870
- return "unknown[]";
1871
- case "object":
1872
- if (metadata.properties && Object.keys(metadata.properties).length > 0) {
1873
- const props = Object.entries(metadata.properties).map(([key, propMeta]) => {
1874
- const optional = propMeta.optional ? "?" : "";
1875
- return `${escapePropertyKey(key)}${optional}: ${metadataToTypeString(propMeta)}`;
1876
- }).join("; ");
1877
- return `{ ${props} }`;
1878
- }
1879
- return "Record<string, unknown>";
1880
- default:
1881
- return "unknown";
1882
- }
1883
- }
1884
- function generateSchemaType(cls) {
1885
- const props = Object.entries(cls.typeMetadata).map(([fieldName, metadata]) => {
1886
- const optional = metadata.optional ? "?" : "";
1887
- const typeStr = metadataToTypeString(metadata);
1888
- return ` ${escapePropertyKey(fieldName)}${optional}: ${typeStr};`;
1889
- }).join("\n");
1890
- return `export type ${cls.name}Schema = {
1891
- ${props}
1892
- };`;
1893
- }
1894
- function metadataTypeToFieldType(type) {
1895
- switch (type) {
1896
- case "string":
1897
- return "text";
1898
- case "number":
1899
- return "number";
1900
- case "boolean":
1901
- return "boolean";
1902
- case "enum":
1903
- return "enum";
1904
- case "array":
1905
- return "array";
1906
- case "object":
1907
- return "object";
1908
- default:
1909
- return "text";
1910
- }
1911
- }
1912
- function generateTypedAccessor(cls) {
1913
- const lines = [];
1914
- const elementTypes = Object.entries(cls.typeMetadata).map(([fieldName, metadata]) => {
1915
- const fieldType = metadataTypeToFieldType(metadata.type);
1916
- const required = !metadata.optional;
1917
- let elementType = `{ readonly _field: "${fieldType}"; readonly id: "${fieldName}"; readonly required: ${String(required)}`;
1918
- if (metadata.type === "enum" && metadata.values) {
1919
- const optionValues = metadata.values.map((v) => typeof v === "string" ? JSON.stringify(v) : String(v)).join(", ");
1920
- elementType += `; readonly options: readonly [${optionValues}]`;
1921
- }
1922
- elementType += " }";
1923
- return elementType;
1924
- });
1925
- lines.push(`export type ${cls.name}Elements = readonly [
1926
- ${elementTypes.join(",\n ")}
1927
- ];`);
1928
- lines.push("");
1929
- lines.push(`export type ${cls.name}FormSpec = { readonly elements: ${cls.name}Elements };`);
1930
- lines.push("");
1931
- lines.push(`/**`);
1932
- lines.push(` * Type-safe FormSpec accessor for ${cls.name}.`);
1933
- lines.push(` * Reads the patched type metadata from the class.`);
1934
- lines.push(` */`);
1935
- lines.push(`export function get${cls.name}FormSpec(): ${cls.name}FormSpec {`);
1936
- lines.push(
1937
- ` const types = (${cls.name} as any).__formspec_types__ as Record<string, unknown>[];`
1938
- );
1939
- lines.push(` return { elements: types } as unknown as ${cls.name}FormSpec;`);
1940
- lines.push(`}`);
1941
- return lines.join("\n");
1942
- }
1943
- function getRelativeImportPath(outputDir, sourcePath) {
1944
- let relativePath = path2.relative(outputDir, sourcePath);
1945
- if (!relativePath.startsWith(".")) {
1946
- relativePath = "./" + relativePath;
1947
- }
1948
- relativePath = relativePath.replace(/\\/g, "/");
1949
- return relativePath + ".js";
1950
- }
1951
- function runCodegen(options) {
1952
- const baseDir = options.baseDir ?? path2.dirname(options.output);
1953
- const absoluteFiles = options.files.map((f) => path2.resolve(f));
1954
- console.log(`Scanning ${String(absoluteFiles.length)} file(s) for decorated classes...`);
1955
- const classes = findDecoratedClasses(absoluteFiles, baseDir);
1956
- if (classes.length === 0) {
1957
- console.log("No decorated classes found.");
1958
- return;
1959
- }
1960
- console.log(`Found ${String(classes.length)} decorated class(es):`);
1961
- for (const cls of classes) {
1962
- const fieldCount = Object.keys(cls.typeMetadata).length;
1963
- console.log(` - ${cls.name} (${String(fieldCount)} field(s))`);
1964
- }
1965
- const unexported = classes.filter((cls) => !cls.isExported);
1966
- if (unexported.length > 0) {
1967
- console.warn(
1968
- `
1969
- \u26A0\uFE0F Warning: The following decorated classes are not exported from their source files:`
1970
- );
1971
- for (const cls of unexported) {
1972
- console.warn(` - ${cls.name} (${cls.sourcePath})`);
1973
- }
1974
- console.warn(
1975
- `
1976
- The generated code will fail to compile because it cannot import these classes.`
1977
- );
1978
- console.warn(` To fix this, add 'export' to the class declaration, for example:`);
1979
- console.warn(` export class YourClassName { ... }
1980
- `);
1981
- }
1982
- const output = generateCodegenOutput(classes, options.output, baseDir);
1983
- const outputDir = path2.dirname(options.output);
1984
- if (!fs.existsSync(outputDir)) {
1985
- fs.mkdirSync(outputDir, { recursive: true });
1986
- }
1987
- fs.writeFileSync(options.output, output);
1988
- console.log(`
1989
- Generated: ${options.output}`);
1990
- }
1991
- var VALID_IDENTIFIER, RESERVED_WORDS;
1992
- var init_codegen = __esm({
1993
- "src/codegen/index.ts"() {
1994
- "use strict";
1995
- VALID_IDENTIFIER = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
1996
- RESERVED_WORDS = /* @__PURE__ */ new Set([
1997
- // Keywords
1998
- "break",
1999
- "case",
2000
- "catch",
2001
- "continue",
2002
- "debugger",
2003
- "default",
2004
- "delete",
2005
- "do",
2006
- "else",
2007
- "finally",
2008
- "for",
2009
- "function",
2010
- "if",
2011
- "in",
2012
- "instanceof",
2013
- "new",
2014
- "return",
2015
- "switch",
2016
- "this",
2017
- "throw",
2018
- "try",
2019
- "typeof",
2020
- "var",
2021
- "void",
2022
- "while",
2023
- "with",
2024
- "class",
2025
- "const",
2026
- "enum",
2027
- "export",
2028
- "extends",
2029
- "import",
2030
- "super",
2031
- "implements",
2032
- "interface",
2033
- "let",
2034
- "package",
2035
- "private",
2036
- "protected",
2037
- "public",
2038
- "static",
2039
- "yield",
2040
- // ES6+ keywords
2041
- "async",
2042
- "await",
2043
- // Literal values (not technically keywords but best to quote)
2044
- "null",
2045
- "true",
2046
- "false",
2047
- // Accessor keywords
2048
- "get",
2049
- "set",
2050
- // Strict mode reserved
2051
- "arguments",
2052
- "eval"
2053
- ]);
1830
+ init_canonicalize();
1831
+ init_ir_generator();
1832
+ init_ir_generator2();
2054
1833
  }
2055
1834
  });
2056
1835
 
@@ -2061,13 +1840,10 @@ __export(index_exports, {
2061
1840
  categorizationSchema: () => categorizationSchema,
2062
1841
  categorySchema: () => categorySchema,
2063
1842
  controlSchema: () => controlSchema,
2064
- findDecoratedClasses: () => findDecoratedClasses,
2065
- generateCodegenOutput: () => generateCodegenOutput,
2066
1843
  generateJsonSchema: () => generateJsonSchema,
2067
1844
  generateSchemas: () => generateSchemas,
2068
1845
  generateSchemasFromClass: () => generateSchemasFromClass,
2069
1846
  generateUiSchema: () => generateUiSchema,
2070
- generateUiSchemaFromFields: () => generateUiSchemaFromFields,
2071
1847
  getSchemaExtension: () => getSchemaExtension,
2072
1848
  groupLayoutSchema: () => groupLayoutSchema,
2073
1849
  horizontalLayoutSchema: () => horizontalLayoutSchema,
@@ -2077,7 +1853,6 @@ __export(index_exports, {
2077
1853
  ruleConditionSchema: () => ruleConditionSchema,
2078
1854
  ruleEffectSchema: () => ruleEffectSchema,
2079
1855
  ruleSchema: () => ruleSchema,
2080
- runCodegen: () => runCodegen,
2081
1856
  schemaBasedConditionSchema: () => schemaBasedConditionSchema,
2082
1857
  setSchemaExtension: () => setSchemaExtension,
2083
1858
  uiSchemaElementSchema: () => uiSchemaElementSchema,
@@ -2086,8 +1861,8 @@ __export(index_exports, {
2086
1861
  verticalLayoutSchema: () => verticalLayoutSchema,
2087
1862
  writeSchemas: () => writeSchemas
2088
1863
  });
2089
- import * as fs2 from "fs";
2090
- import * as path3 from "path";
1864
+ import * as fs from "fs";
1865
+ import * as path2 from "path";
2091
1866
  function buildFormSchemas(form) {
2092
1867
  return {
2093
1868
  jsonSchema: generateJsonSchema(form),
@@ -2097,13 +1872,13 @@ function buildFormSchemas(form) {
2097
1872
  function writeSchemas(form, options) {
2098
1873
  const { outDir, name = "schema", indent = 2 } = options;
2099
1874
  const { jsonSchema, uiSchema: uiSchema2 } = buildFormSchemas(form);
2100
- if (!fs2.existsSync(outDir)) {
2101
- fs2.mkdirSync(outDir, { recursive: true });
1875
+ if (!fs.existsSync(outDir)) {
1876
+ fs.mkdirSync(outDir, { recursive: true });
2102
1877
  }
2103
- const jsonSchemaPath = path3.join(outDir, `${name}-schema.json`);
2104
- const uiSchemaPath = path3.join(outDir, `${name}-uischema.json`);
2105
- fs2.writeFileSync(jsonSchemaPath, JSON.stringify(jsonSchema, null, indent));
2106
- fs2.writeFileSync(uiSchemaPath, JSON.stringify(uiSchema2, null, indent));
1878
+ const jsonSchemaPath = path2.join(outDir, `${name}-schema.json`);
1879
+ const uiSchemaPath = path2.join(outDir, `${name}-uischema.json`);
1880
+ fs.writeFileSync(jsonSchemaPath, JSON.stringify(jsonSchema, null, indent));
1881
+ fs.writeFileSync(uiSchemaPath, JSON.stringify(uiSchema2, null, indent));
2107
1882
  return { jsonSchemaPath, uiSchemaPath };
2108
1883
  }
2109
1884
  var init_index = __esm({
@@ -2112,17 +1887,16 @@ var init_index = __esm({
2112
1887
  init_generator();
2113
1888
  init_generator2();
2114
1889
  init_types();
2115
- init_schema2();
2116
1890
  init_schema();
1891
+ init_schema2();
2117
1892
  init_generator();
2118
1893
  init_generator2();
2119
1894
  init_class_schema();
2120
- init_codegen();
2121
1895
  }
2122
1896
  });
2123
1897
 
2124
1898
  // src/cli.ts
2125
- import * as path4 from "path";
1899
+ import * as path3 from "path";
2126
1900
  import { pathToFileURL } from "url";
2127
1901
  function printHelp() {
2128
1902
  console.log(`
@@ -2194,7 +1968,7 @@ function parseArgs(args) {
2194
1968
  return null;
2195
1969
  }
2196
1970
  if (!name) {
2197
- name = path4.basename(inputFile, path4.extname(inputFile));
1971
+ name = path3.basename(inputFile, path3.extname(inputFile));
2198
1972
  }
2199
1973
  return { inputFile, outDir, name };
2200
1974
  }
@@ -2205,7 +1979,7 @@ async function main() {
2205
1979
  process.exit(1);
2206
1980
  }
2207
1981
  const { inputFile, outDir, name } = options;
2208
- const absoluteInput = path4.resolve(process.cwd(), inputFile);
1982
+ const absoluteInput = path3.resolve(process.cwd(), inputFile);
2209
1983
  try {
2210
1984
  const fileUrl = pathToFileURL(absoluteInput).href;
2211
1985
  const module = await import(fileUrl);