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