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

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