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

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