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

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