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

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