@formspec/build 0.1.0-alpha.27 → 0.1.0-alpha.29

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 (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +3 -2
  3. package/dist/analyzer/class-analyzer.d.ts +12 -6
  4. package/dist/analyzer/class-analyzer.d.ts.map +1 -1
  5. package/dist/analyzer/program.d.ts +3 -2
  6. package/dist/analyzer/program.d.ts.map +1 -1
  7. package/dist/browser.cjs +485 -76
  8. package/dist/browser.cjs.map +1 -1
  9. package/dist/browser.js +486 -77
  10. package/dist/browser.js.map +1 -1
  11. package/dist/build-alpha.d.ts +18 -2
  12. package/dist/build-beta.d.ts +18 -2
  13. package/dist/build-internal.d.ts +18 -2
  14. package/dist/build.d.ts +18 -2
  15. package/dist/canonicalize/chain-dsl-canonicalizer.d.ts +5 -2
  16. package/dist/canonicalize/chain-dsl-canonicalizer.d.ts.map +1 -1
  17. package/dist/canonicalize/tsdoc-canonicalizer.d.ts +5 -1
  18. package/dist/canonicalize/tsdoc-canonicalizer.d.ts.map +1 -1
  19. package/dist/cli.cjs +1460 -143
  20. package/dist/cli.cjs.map +1 -1
  21. package/dist/cli.js +1458 -139
  22. package/dist/cli.js.map +1 -1
  23. package/dist/generators/class-schema.d.ts +6 -1
  24. package/dist/generators/class-schema.d.ts.map +1 -1
  25. package/dist/generators/method-schema.d.ts.map +1 -1
  26. package/dist/generators/mixed-authoring.d.ts.map +1 -1
  27. package/dist/index.cjs +1425 -141
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.d.ts +4 -1
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +1425 -139
  32. package/dist/index.js.map +1 -1
  33. package/dist/internals.cjs +1416 -178
  34. package/dist/internals.cjs.map +1 -1
  35. package/dist/internals.js +1416 -176
  36. package/dist/internals.js.map +1 -1
  37. package/dist/json-schema/generator.d.ts +3 -1
  38. package/dist/json-schema/generator.d.ts.map +1 -1
  39. package/dist/json-schema/ir-generator.d.ts.map +1 -1
  40. package/dist/metadata/collision-guards.d.ts +3 -0
  41. package/dist/metadata/collision-guards.d.ts.map +1 -0
  42. package/dist/metadata/index.d.ts +7 -0
  43. package/dist/metadata/index.d.ts.map +1 -0
  44. package/dist/metadata/policy.d.ts +11 -0
  45. package/dist/metadata/policy.d.ts.map +1 -0
  46. package/dist/metadata/resolve.d.ts +20 -0
  47. package/dist/metadata/resolve.d.ts.map +1 -0
  48. package/dist/ui-schema/generator.d.ts +11 -2
  49. package/dist/ui-schema/generator.d.ts.map +1 -1
  50. package/dist/ui-schema/ir-generator.d.ts +2 -1
  51. package/dist/ui-schema/ir-generator.d.ts.map +1 -1
  52. package/package.json +7 -6
package/dist/index.js CHANGED
@@ -1,5 +1,314 @@
1
1
  // src/canonicalize/chain-dsl-canonicalizer.ts
2
- import { IR_VERSION } from "@formspec/core/internals";
2
+ import { IR_VERSION, _getFormSpecMetadataPolicy } from "@formspec/core/internals";
3
+
4
+ // src/metadata/policy.ts
5
+ var NOOP_INFLECT = () => "";
6
+ function normalizePluralization(input) {
7
+ if (input?.mode === "infer-if-missing") {
8
+ return {
9
+ mode: "infer-if-missing",
10
+ infer: () => "",
11
+ inflect: input.inflect
12
+ };
13
+ }
14
+ if (input?.mode === "require-explicit") {
15
+ return {
16
+ mode: "require-explicit",
17
+ infer: () => "",
18
+ inflect: NOOP_INFLECT
19
+ };
20
+ }
21
+ return {
22
+ mode: "disabled",
23
+ infer: () => "",
24
+ inflect: NOOP_INFLECT
25
+ };
26
+ }
27
+ function normalizeScalarPolicy(input) {
28
+ if (input?.mode === "infer-if-missing") {
29
+ return {
30
+ mode: "infer-if-missing",
31
+ infer: input.infer,
32
+ pluralization: normalizePluralization(input.pluralization)
33
+ };
34
+ }
35
+ if (input?.mode === "require-explicit") {
36
+ return {
37
+ mode: "require-explicit",
38
+ infer: () => "",
39
+ pluralization: normalizePluralization(input.pluralization)
40
+ };
41
+ }
42
+ return {
43
+ mode: "disabled",
44
+ infer: () => "",
45
+ pluralization: normalizePluralization(input?.pluralization)
46
+ };
47
+ }
48
+ function normalizeDeclarationPolicy(input) {
49
+ return {
50
+ apiName: normalizeScalarPolicy(input?.apiName),
51
+ displayName: normalizeScalarPolicy(input?.displayName)
52
+ };
53
+ }
54
+ function normalizeMetadataPolicy(input) {
55
+ return {
56
+ type: normalizeDeclarationPolicy(input?.type),
57
+ field: normalizeDeclarationPolicy(input?.field),
58
+ method: normalizeDeclarationPolicy(input?.method)
59
+ };
60
+ }
61
+ function getDeclarationMetadataPolicy(policy, declarationKind) {
62
+ return policy[declarationKind];
63
+ }
64
+ function makeMetadataContext(surface, declarationKind, logicalName, buildContext) {
65
+ return {
66
+ surface,
67
+ declarationKind,
68
+ logicalName,
69
+ ...buildContext !== void 0 && { buildContext }
70
+ };
71
+ }
72
+
73
+ // src/metadata/resolve.ts
74
+ function toExplicitScalar(value) {
75
+ return value !== void 0 && value.trim() !== "" ? { value, source: "explicit" } : void 0;
76
+ }
77
+ function toExplicitResolvedMetadata(explicit) {
78
+ if (explicit === void 0) {
79
+ return void 0;
80
+ }
81
+ const apiName = toExplicitScalar(explicit.apiName);
82
+ const displayName = toExplicitScalar(explicit.displayName);
83
+ const apiNamePlural = toExplicitScalar(explicit.apiNamePlural);
84
+ const displayNamePlural = toExplicitScalar(explicit.displayNamePlural);
85
+ const metadata = {
86
+ ...apiName !== void 0 && { apiName },
87
+ ...displayName !== void 0 && { displayName },
88
+ ...apiNamePlural !== void 0 && { apiNamePlural },
89
+ ...displayNamePlural !== void 0 && { displayNamePlural }
90
+ };
91
+ return Object.keys(metadata).length > 0 ? metadata : void 0;
92
+ }
93
+ function resolveScalar(current, policy, context, metadataLabel) {
94
+ if (current !== void 0) {
95
+ return current;
96
+ }
97
+ if (policy.mode === "require-explicit") {
98
+ throw new Error(
99
+ `Metadata policy requires explicit ${metadataLabel} for ${context.declarationKind} "${context.logicalName}" on the ${context.surface} surface.`
100
+ );
101
+ }
102
+ if (policy.mode !== "infer-if-missing") {
103
+ return void 0;
104
+ }
105
+ const inferredValue = policy.infer(context);
106
+ return inferredValue.trim() !== "" ? { value: inferredValue, source: "inferred" } : void 0;
107
+ }
108
+ function resolvePlural(current, singular, policy, context, metadataLabel) {
109
+ if (current !== void 0) {
110
+ return current;
111
+ }
112
+ if (policy.mode === "require-explicit") {
113
+ throw new Error(
114
+ `Metadata policy requires explicit ${metadataLabel} for ${context.declarationKind} "${context.logicalName}" on the ${context.surface} surface.`
115
+ );
116
+ }
117
+ if (singular === void 0 || policy.mode !== "infer-if-missing") {
118
+ return void 0;
119
+ }
120
+ const pluralValue = policy.inflect({ ...context, singular: singular.value });
121
+ return pluralValue.trim() !== "" ? { value: pluralValue, source: "inferred" } : void 0;
122
+ }
123
+ function resolveResolvedMetadata(current, policy, context) {
124
+ const apiName = resolveScalar(current?.apiName, policy.apiName, context, "apiName");
125
+ const displayName = resolveScalar(
126
+ current?.displayName,
127
+ policy.displayName,
128
+ context,
129
+ "displayName"
130
+ );
131
+ const apiNamePlural = resolvePlural(
132
+ current?.apiNamePlural,
133
+ apiName,
134
+ policy.apiName.pluralization,
135
+ context,
136
+ "apiNamePlural"
137
+ );
138
+ const displayNamePlural = resolvePlural(
139
+ current?.displayNamePlural,
140
+ displayName,
141
+ policy.displayName.pluralization,
142
+ context,
143
+ "displayNamePlural"
144
+ );
145
+ if (apiName === void 0 && displayName === void 0 && apiNamePlural === void 0 && displayNamePlural === void 0) {
146
+ return void 0;
147
+ }
148
+ return {
149
+ ...apiName !== void 0 && { apiName },
150
+ ...displayName !== void 0 && { displayName },
151
+ ...apiNamePlural !== void 0 && { apiNamePlural },
152
+ ...displayNamePlural !== void 0 && { displayNamePlural }
153
+ };
154
+ }
155
+ function pickResolvedMetadataValue(baseValue, overlayValue) {
156
+ if (overlayValue?.source === "explicit") {
157
+ return overlayValue;
158
+ }
159
+ if (baseValue?.source === "explicit") {
160
+ return baseValue;
161
+ }
162
+ return baseValue ?? overlayValue;
163
+ }
164
+ function resolveTypeNodeMetadata(type, options) {
165
+ switch (type.kind) {
166
+ case "array":
167
+ return {
168
+ ...type,
169
+ items: resolveTypeNodeMetadata(type.items, options)
170
+ };
171
+ case "object":
172
+ return {
173
+ ...type,
174
+ properties: type.properties.map((property) => resolveObjectPropertyMetadata(property, options))
175
+ };
176
+ case "record":
177
+ return {
178
+ ...type,
179
+ valueType: resolveTypeNodeMetadata(type.valueType, options)
180
+ };
181
+ case "union":
182
+ return {
183
+ ...type,
184
+ members: type.members.map((member) => resolveTypeNodeMetadata(member, options))
185
+ };
186
+ case "reference":
187
+ case "primitive":
188
+ case "enum":
189
+ case "dynamic":
190
+ case "custom":
191
+ return type;
192
+ default: {
193
+ const _exhaustive = type;
194
+ return _exhaustive;
195
+ }
196
+ }
197
+ }
198
+ function resolveObjectPropertyMetadata(property, options) {
199
+ const metadata = resolveResolvedMetadata(property.metadata, options.policy.field, {
200
+ surface: options.surface,
201
+ declarationKind: "field",
202
+ logicalName: property.name,
203
+ ...options.buildContext !== void 0 && { buildContext: options.buildContext }
204
+ });
205
+ return {
206
+ ...property,
207
+ ...metadata !== void 0 && { metadata },
208
+ type: resolveTypeNodeMetadata(property.type, options)
209
+ };
210
+ }
211
+ function resolveFieldMetadataNode(field, options) {
212
+ const metadata = resolveResolvedMetadata(field.metadata, options.policy.field, {
213
+ surface: options.surface,
214
+ declarationKind: "field",
215
+ logicalName: field.name,
216
+ ...options.buildContext !== void 0 && { buildContext: options.buildContext }
217
+ });
218
+ return {
219
+ ...field,
220
+ ...metadata !== void 0 && { metadata },
221
+ type: resolveTypeNodeMetadata(field.type, options)
222
+ };
223
+ }
224
+ function resolveFormElementMetadata(element, options) {
225
+ switch (element.kind) {
226
+ case "field":
227
+ return resolveFieldMetadataNode(element, options);
228
+ case "group":
229
+ return {
230
+ ...element,
231
+ elements: element.elements.map((child) => resolveFormElementMetadata(child, options))
232
+ };
233
+ case "conditional":
234
+ return {
235
+ ...element,
236
+ elements: element.elements.map((child) => resolveFormElementMetadata(child, options))
237
+ };
238
+ default: {
239
+ const _exhaustive = element;
240
+ return _exhaustive;
241
+ }
242
+ }
243
+ }
244
+ function resolveTypeDefinitionMetadata(typeDefinition, options) {
245
+ const metadata = resolveResolvedMetadata(typeDefinition.metadata, options.policy.type, {
246
+ surface: options.surface,
247
+ declarationKind: "type",
248
+ logicalName: typeDefinition.name,
249
+ ...options.buildContext !== void 0 && { buildContext: options.buildContext }
250
+ });
251
+ return {
252
+ ...typeDefinition,
253
+ ...metadata !== void 0 && { metadata },
254
+ type: resolveTypeNodeMetadata(typeDefinition.type, options)
255
+ };
256
+ }
257
+ function resolveMetadata(explicit, policy, context) {
258
+ return resolveResolvedMetadata(toExplicitResolvedMetadata(explicit), policy, context);
259
+ }
260
+ function mergeResolvedMetadata(baseMetadata, overlayMetadata) {
261
+ const apiName = pickResolvedMetadataValue(baseMetadata?.apiName, overlayMetadata?.apiName);
262
+ const displayName = pickResolvedMetadataValue(
263
+ baseMetadata?.displayName,
264
+ overlayMetadata?.displayName
265
+ );
266
+ const apiNamePlural = pickResolvedMetadataValue(
267
+ baseMetadata?.apiNamePlural,
268
+ overlayMetadata?.apiNamePlural
269
+ );
270
+ const displayNamePlural = pickResolvedMetadataValue(
271
+ baseMetadata?.displayNamePlural,
272
+ overlayMetadata?.displayNamePlural
273
+ );
274
+ if (apiName === void 0 && displayName === void 0 && apiNamePlural === void 0 && displayNamePlural === void 0) {
275
+ return void 0;
276
+ }
277
+ return {
278
+ ...apiName !== void 0 && { apiName },
279
+ ...displayName !== void 0 && { displayName },
280
+ ...apiNamePlural !== void 0 && { apiNamePlural },
281
+ ...displayNamePlural !== void 0 && { displayNamePlural }
282
+ };
283
+ }
284
+ function getSerializedName(logicalName, metadata) {
285
+ return metadata?.apiName?.value ?? logicalName;
286
+ }
287
+ function getDisplayName(metadata) {
288
+ return metadata?.displayName?.value;
289
+ }
290
+ function resolveFormIRMetadata(ir, options) {
291
+ const rootLogicalName = options.rootLogicalName ?? ir.name ?? "FormSpec";
292
+ const metadata = resolveResolvedMetadata(ir.metadata, options.policy.type, {
293
+ surface: options.surface,
294
+ declarationKind: "type",
295
+ logicalName: rootLogicalName,
296
+ ...options.buildContext !== void 0 && { buildContext: options.buildContext }
297
+ });
298
+ return {
299
+ ...ir,
300
+ ...metadata !== void 0 && { metadata },
301
+ elements: ir.elements.map((element) => resolveFormElementMetadata(element, options)),
302
+ typeRegistry: Object.fromEntries(
303
+ Object.entries(ir.typeRegistry).map(([name, definition]) => [
304
+ name,
305
+ resolveTypeDefinitionMetadata(definition, options)
306
+ ])
307
+ )
308
+ };
309
+ }
310
+
311
+ // src/canonicalize/chain-dsl-canonicalizer.ts
3
312
  var CHAIN_DSL_PROVENANCE = {
4
313
  surface: "chain-dsl",
5
314
  file: "",
@@ -15,57 +324,60 @@ function isConditional(el) {
15
324
  function isField(el) {
16
325
  return el._type === "field";
17
326
  }
18
- function canonicalizeChainDSL(form) {
327
+ function canonicalizeChainDSL(form, options) {
328
+ const metadataPolicy = normalizeMetadataPolicy(
329
+ options?.metadata ?? _getFormSpecMetadataPolicy(form)
330
+ );
19
331
  return {
20
332
  kind: "form-ir",
21
333
  irVersion: IR_VERSION,
22
- elements: canonicalizeElements(form.elements),
334
+ elements: canonicalizeElements(form.elements, metadataPolicy),
23
335
  rootAnnotations: [],
24
336
  typeRegistry: {},
25
337
  provenance: CHAIN_DSL_PROVENANCE
26
338
  };
27
339
  }
28
- function canonicalizeElements(elements) {
29
- return elements.map(canonicalizeElement);
340
+ function canonicalizeElements(elements, metadataPolicy) {
341
+ return elements.map((element) => canonicalizeElement(element, metadataPolicy));
30
342
  }
31
- function canonicalizeElement(element) {
343
+ function canonicalizeElement(element, metadataPolicy) {
32
344
  if (isField(element)) {
33
- return canonicalizeField(element);
345
+ return canonicalizeField(element, metadataPolicy);
34
346
  }
35
347
  if (isGroup(element)) {
36
- return canonicalizeGroup(element);
348
+ return canonicalizeGroup(element, metadataPolicy);
37
349
  }
38
350
  if (isConditional(element)) {
39
- return canonicalizeConditional(element);
351
+ return canonicalizeConditional(element, metadataPolicy);
40
352
  }
41
353
  const _exhaustive = element;
42
354
  throw new Error(`Unknown element type: ${JSON.stringify(_exhaustive)}`);
43
355
  }
44
- function canonicalizeField(field) {
356
+ function canonicalizeField(field, metadataPolicy) {
45
357
  switch (field._field) {
46
358
  case "text":
47
- return canonicalizeTextField(field);
359
+ return canonicalizeTextField(field, metadataPolicy);
48
360
  case "number":
49
- return canonicalizeNumberField(field);
361
+ return canonicalizeNumberField(field, metadataPolicy);
50
362
  case "boolean":
51
- return canonicalizeBooleanField(field);
363
+ return canonicalizeBooleanField(field, metadataPolicy);
52
364
  case "enum":
53
- return canonicalizeStaticEnumField(field);
365
+ return canonicalizeStaticEnumField(field, metadataPolicy);
54
366
  case "dynamic_enum":
55
- return canonicalizeDynamicEnumField(field);
367
+ return canonicalizeDynamicEnumField(field, metadataPolicy);
56
368
  case "dynamic_schema":
57
- return canonicalizeDynamicSchemaField(field);
369
+ return canonicalizeDynamicSchemaField(field, metadataPolicy);
58
370
  case "array":
59
- return canonicalizeArrayField(field);
371
+ return canonicalizeArrayField(field, metadataPolicy);
60
372
  case "object":
61
- return canonicalizeObjectField(field);
373
+ return canonicalizeObjectField(field, metadataPolicy);
62
374
  default: {
63
375
  const _exhaustive = field;
64
376
  throw new Error(`Unknown field type: ${JSON.stringify(_exhaustive)}`);
65
377
  }
66
378
  }
67
379
  }
68
- function canonicalizeTextField(field) {
380
+ function canonicalizeTextField(field, metadataPolicy) {
69
381
  const type = { kind: "primitive", primitiveKind: "string" };
70
382
  const constraints = [];
71
383
  if (field.minLength !== void 0) {
@@ -97,13 +409,14 @@ function canonicalizeTextField(field) {
97
409
  }
98
410
  return buildFieldNode(
99
411
  field.name,
412
+ resolveFieldMetadata(field.name, field, metadataPolicy),
100
413
  type,
101
414
  field.required,
102
- buildAnnotations(field.label, field.placeholder),
415
+ buildAnnotations(getExplicitDisplayName(field), field.placeholder),
103
416
  constraints
104
417
  );
105
418
  }
106
- function canonicalizeNumberField(field) {
419
+ function canonicalizeNumberField(field, metadataPolicy) {
107
420
  const type = { kind: "primitive", primitiveKind: "number" };
108
421
  const constraints = [];
109
422
  if (field.min !== void 0) {
@@ -135,17 +448,24 @@ function canonicalizeNumberField(field) {
135
448
  }
136
449
  return buildFieldNode(
137
450
  field.name,
451
+ resolveFieldMetadata(field.name, field, metadataPolicy),
138
452
  type,
139
453
  field.required,
140
- buildAnnotations(field.label),
454
+ buildAnnotations(getExplicitDisplayName(field)),
141
455
  constraints
142
456
  );
143
457
  }
144
- function canonicalizeBooleanField(field) {
458
+ function canonicalizeBooleanField(field, metadataPolicy) {
145
459
  const type = { kind: "primitive", primitiveKind: "boolean" };
146
- return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
460
+ return buildFieldNode(
461
+ field.name,
462
+ resolveFieldMetadata(field.name, field, metadataPolicy),
463
+ type,
464
+ field.required,
465
+ buildAnnotations(getExplicitDisplayName(field))
466
+ );
147
467
  }
148
- function canonicalizeStaticEnumField(field) {
468
+ function canonicalizeStaticEnumField(field, metadataPolicy) {
149
469
  const members = field.options.map((opt) => {
150
470
  if (typeof opt === "string") {
151
471
  return { value: opt };
@@ -153,28 +473,46 @@ function canonicalizeStaticEnumField(field) {
153
473
  return { value: opt.id, displayName: opt.label };
154
474
  });
155
475
  const type = { kind: "enum", members };
156
- return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
476
+ return buildFieldNode(
477
+ field.name,
478
+ resolveFieldMetadata(field.name, field, metadataPolicy),
479
+ type,
480
+ field.required,
481
+ buildAnnotations(getExplicitDisplayName(field))
482
+ );
157
483
  }
158
- function canonicalizeDynamicEnumField(field) {
484
+ function canonicalizeDynamicEnumField(field, metadataPolicy) {
159
485
  const type = {
160
486
  kind: "dynamic",
161
487
  dynamicKind: "enum",
162
488
  sourceKey: field.source,
163
489
  parameterFields: field.params ? [...field.params] : []
164
490
  };
165
- return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
491
+ return buildFieldNode(
492
+ field.name,
493
+ resolveFieldMetadata(field.name, field, metadataPolicy),
494
+ type,
495
+ field.required,
496
+ buildAnnotations(getExplicitDisplayName(field))
497
+ );
166
498
  }
167
- function canonicalizeDynamicSchemaField(field) {
499
+ function canonicalizeDynamicSchemaField(field, metadataPolicy) {
168
500
  const type = {
169
501
  kind: "dynamic",
170
502
  dynamicKind: "schema",
171
503
  sourceKey: field.schemaSource,
172
504
  parameterFields: []
173
505
  };
174
- return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
506
+ return buildFieldNode(
507
+ field.name,
508
+ resolveFieldMetadata(field.name, field, metadataPolicy),
509
+ type,
510
+ field.required,
511
+ buildAnnotations(getExplicitDisplayName(field))
512
+ );
175
513
  }
176
- function canonicalizeArrayField(field) {
177
- const itemProperties = buildObjectProperties(field.items);
514
+ function canonicalizeArrayField(field, metadataPolicy) {
515
+ const itemProperties = buildObjectProperties(field.items, metadataPolicy);
178
516
  const itemsType = {
179
517
  kind: "object",
180
518
  properties: itemProperties,
@@ -202,37 +540,44 @@ function canonicalizeArrayField(field) {
202
540
  }
203
541
  return buildFieldNode(
204
542
  field.name,
543
+ resolveFieldMetadata(field.name, field, metadataPolicy),
205
544
  type,
206
545
  field.required,
207
- buildAnnotations(field.label),
546
+ buildAnnotations(getExplicitDisplayName(field)),
208
547
  constraints
209
548
  );
210
549
  }
211
- function canonicalizeObjectField(field) {
212
- const properties = buildObjectProperties(field.properties);
550
+ function canonicalizeObjectField(field, metadataPolicy) {
551
+ const properties = buildObjectProperties(field.properties, metadataPolicy);
213
552
  const type = {
214
553
  kind: "object",
215
554
  properties,
216
555
  additionalProperties: true
217
556
  };
218
- return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
557
+ return buildFieldNode(
558
+ field.name,
559
+ resolveFieldMetadata(field.name, field, metadataPolicy),
560
+ type,
561
+ field.required,
562
+ buildAnnotations(getExplicitDisplayName(field))
563
+ );
219
564
  }
220
- function canonicalizeGroup(g) {
565
+ function canonicalizeGroup(g, metadataPolicy) {
221
566
  return {
222
567
  kind: "group",
223
568
  label: g.label,
224
- elements: canonicalizeElements(g.elements),
569
+ elements: canonicalizeElements(g.elements, metadataPolicy),
225
570
  provenance: CHAIN_DSL_PROVENANCE
226
571
  };
227
572
  }
228
- function canonicalizeConditional(c) {
573
+ function canonicalizeConditional(c, metadataPolicy) {
229
574
  return {
230
575
  kind: "conditional",
231
576
  fieldName: c.field,
232
577
  // Conditional values from the chain DSL are JSON-serializable primitives
233
578
  // (strings, numbers, booleans) produced by the `is()` predicate helper.
234
579
  value: assertJsonValue(c.value),
235
- elements: canonicalizeElements(c.elements),
580
+ elements: canonicalizeElements(c.elements, metadataPolicy),
236
581
  provenance: CHAIN_DSL_PROVENANCE
237
582
  };
238
583
  }
@@ -252,10 +597,11 @@ function assertJsonValue(v) {
252
597
  }
253
598
  throw new TypeError(`Conditional value is not a valid JsonValue: ${typeof v}`);
254
599
  }
255
- function buildFieldNode(name, type, required, annotations, constraints = []) {
600
+ function buildFieldNode(name, metadata, type, required, annotations, constraints = []) {
256
601
  return {
257
602
  kind: "field",
258
603
  name,
604
+ ...metadata !== void 0 && { metadata },
259
605
  type,
260
606
  required: required === true,
261
607
  constraints,
@@ -285,13 +631,14 @@ function buildAnnotations(label, placeholder) {
285
631
  }
286
632
  return annotations;
287
633
  }
288
- function buildObjectProperties(elements, insideConditional = false) {
634
+ function buildObjectProperties(elements, metadataPolicy, insideConditional = false) {
289
635
  const properties = [];
290
636
  for (const el of elements) {
291
637
  if (isField(el)) {
292
- const fieldNode = canonicalizeField(el);
638
+ const fieldNode = canonicalizeField(el, metadataPolicy);
293
639
  properties.push({
294
640
  name: fieldNode.name,
641
+ ...fieldNode.metadata !== void 0 && { metadata: fieldNode.metadata },
295
642
  type: fieldNode.type,
296
643
  // Fields inside a conditional branch are always optional in the
297
644
  // data schema, regardless of their `required` flag — the condition
@@ -302,17 +649,34 @@ function buildObjectProperties(elements, insideConditional = false) {
302
649
  provenance: CHAIN_DSL_PROVENANCE
303
650
  });
304
651
  } else if (isGroup(el)) {
305
- properties.push(...buildObjectProperties(el.elements, insideConditional));
652
+ properties.push(...buildObjectProperties(el.elements, metadataPolicy, insideConditional));
306
653
  } else if (isConditional(el)) {
307
- properties.push(...buildObjectProperties(el.elements, true));
654
+ properties.push(...buildObjectProperties(el.elements, metadataPolicy, true));
308
655
  }
309
656
  }
310
657
  return properties;
311
658
  }
659
+ function getExplicitDisplayName(field) {
660
+ if (field.label !== void 0 && field.displayName !== void 0) {
661
+ throw new Error('Chain DSL fields cannot specify both "label" and "displayName".');
662
+ }
663
+ return field.displayName ?? field.label;
664
+ }
665
+ function resolveFieldMetadata(logicalName, field, metadataPolicy) {
666
+ const displayName = getExplicitDisplayName(field);
667
+ return resolveMetadata(
668
+ {
669
+ ...field.apiName !== void 0 && { apiName: field.apiName },
670
+ ...displayName !== void 0 && { displayName }
671
+ },
672
+ getDeclarationMetadataPolicy(metadataPolicy, "field"),
673
+ makeMetadataContext("chain-dsl", "field", logicalName)
674
+ );
675
+ }
312
676
 
313
677
  // src/canonicalize/tsdoc-canonicalizer.ts
314
678
  import { IR_VERSION as IR_VERSION2 } from "@formspec/core/internals";
315
- function canonicalizeTSDoc(analysis, source) {
679
+ function canonicalizeTSDoc(analysis, source, options) {
316
680
  const file = source?.file ?? "";
317
681
  const provenance = {
318
682
  surface: "tsdoc",
@@ -321,15 +685,21 @@ function canonicalizeTSDoc(analysis, source) {
321
685
  column: 0
322
686
  };
323
687
  const elements = assembleElements(analysis.fields, analysis.fieldLayouts, provenance);
324
- return {
688
+ const ir = {
325
689
  kind: "form-ir",
690
+ name: analysis.name,
326
691
  irVersion: IR_VERSION2,
327
692
  elements,
693
+ ...analysis.metadata !== void 0 && { metadata: analysis.metadata },
328
694
  typeRegistry: analysis.typeRegistry,
329
695
  ...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { rootAnnotations: analysis.annotations },
330
696
  ...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { annotations: analysis.annotations },
331
697
  provenance
332
698
  };
699
+ return resolveFormIRMetadata(ir, {
700
+ policy: normalizeMetadataPolicy(options?.metadata),
701
+ surface: "tsdoc"
702
+ });
333
703
  }
334
704
  function assembleElements(fields, layouts, provenance) {
335
705
  const elements = [];
@@ -386,6 +756,120 @@ function wrapInConditional(field, layout, provenance) {
386
756
  return conditional;
387
757
  }
388
758
 
759
+ // src/metadata/collision-guards.ts
760
+ function assertUniqueSerializedNames(entries, scope) {
761
+ const seen = /* @__PURE__ */ new Map();
762
+ for (const entry of entries) {
763
+ const previous = seen.get(entry.serializedName);
764
+ if (previous !== void 0) {
765
+ if (previous.logicalName === entry.logicalName && previous.category === entry.category) {
766
+ continue;
767
+ }
768
+ throw new Error(
769
+ `Serialized name collision in ${scope}: ${previous.category} "${previous.logicalName}" and ${entry.category} "${entry.logicalName}" both resolve to "${entry.serializedName}".`
770
+ );
771
+ }
772
+ seen.set(entry.serializedName, entry);
773
+ }
774
+ }
775
+ function collectFlattenedFields(elements) {
776
+ const fields = [];
777
+ for (const element of elements) {
778
+ switch (element.kind) {
779
+ case "field":
780
+ fields.push(element);
781
+ break;
782
+ case "group":
783
+ case "conditional":
784
+ fields.push(...collectFlattenedFields(element.elements));
785
+ break;
786
+ default: {
787
+ const exhaustive = element;
788
+ void exhaustive;
789
+ }
790
+ }
791
+ }
792
+ return fields;
793
+ }
794
+ function validateObjectProperties(properties, scope) {
795
+ assertUniqueSerializedNames(
796
+ properties.map((property) => ({
797
+ logicalName: property.name,
798
+ serializedName: getSerializedName(property.name, property.metadata),
799
+ category: "object property"
800
+ })),
801
+ scope
802
+ );
803
+ for (const property of properties) {
804
+ validateTypeNode(
805
+ property.type,
806
+ `${scope}.${getSerializedName(property.name, property.metadata)}`
807
+ );
808
+ }
809
+ }
810
+ function validateTypeNode(type, scope) {
811
+ switch (type.kind) {
812
+ case "array":
813
+ validateTypeNode(type.items, `${scope}[]`);
814
+ break;
815
+ case "object":
816
+ validateObjectProperties(type.properties, scope);
817
+ break;
818
+ case "record":
819
+ validateTypeNode(type.valueType, `${scope}.*`);
820
+ break;
821
+ case "union":
822
+ type.members.forEach((member, index) => {
823
+ validateTypeNode(member, `${scope}|${String(index)}`);
824
+ });
825
+ break;
826
+ case "reference":
827
+ case "primitive":
828
+ case "enum":
829
+ case "dynamic":
830
+ case "custom":
831
+ break;
832
+ default: {
833
+ const exhaustive = type;
834
+ void exhaustive;
835
+ }
836
+ }
837
+ }
838
+ function validateTypeDefinitions(typeRegistry) {
839
+ const definitions = Object.values(typeRegistry);
840
+ assertUniqueSerializedNames(
841
+ definitions.map((definition) => ({
842
+ logicalName: definition.name,
843
+ serializedName: getSerializedName(definition.name, definition.metadata),
844
+ category: "type definition"
845
+ })),
846
+ "$defs"
847
+ );
848
+ for (const definition of definitions) {
849
+ validateTypeDefinition(definition);
850
+ }
851
+ }
852
+ function validateTypeDefinition(definition) {
853
+ validateTypeNode(
854
+ definition.type,
855
+ `type "${getSerializedName(definition.name, definition.metadata)}"`
856
+ );
857
+ }
858
+ function assertNoSerializedNameCollisions(ir) {
859
+ assertUniqueSerializedNames(
860
+ collectFlattenedFields(ir.elements).map((field) => ({
861
+ logicalName: field.name,
862
+ serializedName: getSerializedName(field.name, field.metadata),
863
+ category: "field"
864
+ })),
865
+ "form root"
866
+ );
867
+ for (const field of collectFlattenedFields(ir.elements)) {
868
+ validateTypeNode(field.type, `field "${getSerializedName(field.name, field.metadata)}"`);
869
+ }
870
+ validateTypeDefinitions(ir.typeRegistry);
871
+ }
872
+
389
873
  // src/json-schema/ir-generator.ts
390
874
  function makeContext(options) {
391
875
  const vendorPrefix = options?.vendorPrefix ?? "x-formspec";
@@ -396,19 +880,33 @@ function makeContext(options) {
396
880
  }
397
881
  return {
398
882
  defs: {},
883
+ typeNameMap: {},
884
+ typeRegistry: {},
399
885
  extensionRegistry: options?.extensionRegistry,
400
886
  vendorPrefix
401
887
  };
402
888
  }
403
889
  function generateJsonSchemaFromIR(ir, options) {
404
- const ctx = makeContext(options);
890
+ assertNoSerializedNameCollisions(ir);
891
+ const ctx = {
892
+ ...makeContext(options),
893
+ typeRegistry: ir.typeRegistry,
894
+ typeNameMap: Object.fromEntries(
895
+ Object.entries(ir.typeRegistry).map(([name, typeDef]) => [
896
+ name,
897
+ getSerializedName(name, typeDef.metadata)
898
+ ])
899
+ )
900
+ };
405
901
  for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
406
- ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
902
+ const schemaName = ctx.typeNameMap[name] ?? name;
903
+ ctx.defs[schemaName] = generateTypeNode(typeDef.type, ctx);
904
+ applyResolvedMetadata(ctx.defs[schemaName], typeDef.metadata);
407
905
  if (typeDef.constraints && typeDef.constraints.length > 0) {
408
- applyConstraints(ctx.defs[name], typeDef.constraints, ctx);
906
+ applyConstraints(ctx.defs[schemaName], typeDef.constraints, ctx);
409
907
  }
410
908
  if (typeDef.annotations && typeDef.annotations.length > 0) {
411
- applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
909
+ applyAnnotations(ctx.defs[schemaName], typeDef.annotations, ctx);
412
910
  }
413
911
  }
414
912
  const properties = {};
@@ -421,6 +919,7 @@ function generateJsonSchemaFromIR(ir, options) {
421
919
  properties,
422
920
  ...uniqueRequired.length > 0 && { required: uniqueRequired }
423
921
  };
922
+ applyResolvedMetadata(result, ir.metadata);
424
923
  if (ir.annotations && ir.annotations.length > 0) {
425
924
  applyAnnotations(result, ir.annotations, ctx);
426
925
  }
@@ -433,9 +932,9 @@ function collectFields(elements, properties, required, ctx) {
433
932
  for (const element of elements) {
434
933
  switch (element.kind) {
435
934
  case "field":
436
- properties[element.name] = generateFieldSchema(element, ctx);
935
+ properties[getSerializedName(element.name, element.metadata)] = generateFieldSchema(element, ctx);
437
936
  if (element.required) {
438
- required.push(element.name);
937
+ required.push(getSerializedName(element.name, element.metadata));
439
938
  }
440
939
  break;
441
940
  case "group":
@@ -479,6 +978,7 @@ function generateFieldSchema(field, ctx) {
479
978
  rootAnnotations.push(annotation);
480
979
  }
481
980
  }
981
+ applyResolvedMetadata(schema, field.metadata);
482
982
  applyAnnotations(schema, rootAnnotations, ctx);
483
983
  if (itemStringSchema !== void 0) {
484
984
  applyAnnotations(itemStringSchema, itemAnnotations, ctx);
@@ -486,7 +986,7 @@ function generateFieldSchema(field, ctx) {
486
986
  if (pathConstraints.length === 0) {
487
987
  return schema;
488
988
  }
489
- return applyPathTargetedConstraints(schema, pathConstraints, ctx);
989
+ return applyPathTargetedConstraints(schema, pathConstraints, ctx, field.type);
490
990
  }
491
991
  function isStringItemConstraint(constraint) {
492
992
  switch (constraint.constraintKind) {
@@ -498,9 +998,11 @@ function isStringItemConstraint(constraint) {
498
998
  return false;
499
999
  }
500
1000
  }
501
- function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
1001
+ function applyPathTargetedConstraints(schema, pathConstraints, ctx, typeNode) {
502
1002
  if (schema.type === "array" && schema.items) {
503
- schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx);
1003
+ const referencedType = typeNode?.kind === "reference" ? resolveReferencedType(typeNode, ctx) : void 0;
1004
+ const nestedType = typeNode?.kind === "array" ? typeNode.items : referencedType?.kind === "array" ? referencedType.items : void 0;
1005
+ schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx, nestedType);
504
1006
  return schema;
505
1007
  }
506
1008
  const byTarget = /* @__PURE__ */ new Map();
@@ -515,7 +1017,7 @@ function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
515
1017
  for (const [target, constraints] of byTarget) {
516
1018
  const subSchema = {};
517
1019
  applyConstraints(subSchema, constraints, ctx);
518
- propertyOverrides[target] = subSchema;
1020
+ propertyOverrides[resolveSerializedPropertyName(target, typeNode, ctx)] = subSchema;
519
1021
  }
520
1022
  if (schema.$ref) {
521
1023
  const { $ref, ...rest } = schema;
@@ -563,7 +1065,7 @@ function generateTypeNode(type, ctx) {
563
1065
  case "union":
564
1066
  return generateUnionType(type, ctx);
565
1067
  case "reference":
566
- return generateReferenceType(type);
1068
+ return generateReferenceType(type, ctx);
567
1069
  case "dynamic":
568
1070
  return generateDynamicType(type);
569
1071
  case "custom":
@@ -604,9 +1106,10 @@ function generateObjectType(type, ctx) {
604
1106
  const properties = {};
605
1107
  const required = [];
606
1108
  for (const prop of type.properties) {
607
- properties[prop.name] = generatePropertySchema(prop, ctx);
1109
+ const propertyName = getSerializedName(prop.name, prop.metadata);
1110
+ properties[propertyName] = generatePropertySchema(prop, ctx);
608
1111
  if (!prop.optional) {
609
- required.push(prop.name);
1112
+ required.push(propertyName);
610
1113
  }
611
1114
  }
612
1115
  const schema = { type: "object", properties };
@@ -627,6 +1130,7 @@ function generateRecordType(type, ctx) {
627
1130
  function generatePropertySchema(prop, ctx) {
628
1131
  const schema = generateTypeNode(prop.type, ctx);
629
1132
  applyConstraints(schema, prop.constraints, ctx);
1133
+ applyResolvedMetadata(schema, prop.metadata);
630
1134
  applyAnnotations(schema, prop.annotations, ctx);
631
1135
  return schema;
632
1136
  }
@@ -655,8 +1159,28 @@ function isNullableUnion(type) {
655
1159
  ).length;
656
1160
  return nullCount === 1;
657
1161
  }
658
- function generateReferenceType(type) {
659
- return { $ref: `#/$defs/${type.name}` };
1162
+ function generateReferenceType(type, ctx) {
1163
+ return { $ref: `#/$defs/${ctx.typeNameMap[type.name] ?? type.name}` };
1164
+ }
1165
+ function applyResolvedMetadata(schema, metadata) {
1166
+ const displayName = getDisplayName(metadata);
1167
+ if (displayName !== void 0) {
1168
+ schema.title = displayName;
1169
+ }
1170
+ }
1171
+ function resolveReferencedType(type, ctx) {
1172
+ return ctx.typeRegistry[type.name]?.type;
1173
+ }
1174
+ function resolveSerializedPropertyName(logicalName, typeNode, ctx) {
1175
+ if (typeNode?.kind === "object") {
1176
+ const property = typeNode.properties.find((candidate) => candidate.name === logicalName);
1177
+ return property === void 0 ? logicalName : getSerializedName(property.name, property.metadata);
1178
+ }
1179
+ if (typeNode?.kind === "reference") {
1180
+ const referencedType = resolveReferencedType(typeNode, ctx);
1181
+ return referencedType === void 0 ? logicalName : resolveSerializedPropertyName(logicalName, referencedType, ctx);
1182
+ }
1183
+ return logicalName;
660
1184
  }
661
1185
  function generateDynamicType(type) {
662
1186
  if (type.dynamicKind === "enum") {
@@ -736,7 +1260,7 @@ function applyAnnotations(schema, annotations, ctx) {
736
1260
  for (const annotation of annotations) {
737
1261
  switch (annotation.annotationKind) {
738
1262
  case "displayName":
739
- schema.title = annotation.value;
1263
+ schema.title ??= annotation.value;
740
1264
  break;
741
1265
  case "description":
742
1266
  schema.description = annotation.value;
@@ -823,7 +1347,10 @@ function assignVendorPrefixedExtensionKeywords(schema, extensionSchema, vendorPr
823
1347
 
824
1348
  // src/json-schema/generator.ts
825
1349
  function generateJsonSchema(form, options) {
826
- const ir = canonicalizeChainDSL(form);
1350
+ const ir = canonicalizeChainDSL(
1351
+ form,
1352
+ options?.metadata !== void 0 ? { metadata: options.metadata } : void 0
1353
+ );
827
1354
  const internalOptions = options?.vendorPrefix === void 0 ? void 0 : { vendorPrefix: options.vendorPrefix };
828
1355
  return generateJsonSchemaFromIR(ir, internalOptions);
829
1356
  }
@@ -993,13 +1520,21 @@ function combineRules(parentRule, childRule) {
993
1520
  }
994
1521
  };
995
1522
  }
996
- function fieldNodeToControl(field, parentRule) {
997
- const displayNameAnnotation = field.annotations.find((a) => a.annotationKind === "displayName");
1523
+ function getFieldDisplayName(field) {
1524
+ const resolvedDisplayName = getDisplayName(field.metadata);
1525
+ if (resolvedDisplayName !== void 0) {
1526
+ return resolvedDisplayName;
1527
+ }
1528
+ return field.annotations.find((annotation) => annotation.annotationKind === "displayName")?.value;
1529
+ }
1530
+ function fieldNodeToControl(field, fieldNameMap, parentRule) {
998
1531
  const placeholderAnnotation = field.annotations.find((a) => a.annotationKind === "placeholder");
1532
+ const serializedName = fieldNameMap.get(field.name) ?? getSerializedName(field.name, field.metadata);
1533
+ const displayName = getFieldDisplayName(field);
999
1534
  const control = {
1000
1535
  type: "Control",
1001
- scope: fieldToScope(field.name),
1002
- ...displayNameAnnotation !== void 0 && { label: displayNameAnnotation.value },
1536
+ scope: fieldToScope(serializedName),
1537
+ ...displayName !== void 0 && { label: displayName },
1003
1538
  ...placeholderAnnotation !== void 0 && {
1004
1539
  options: { placeholder: placeholderAnnotation.value }
1005
1540
  },
@@ -1007,30 +1542,30 @@ function fieldNodeToControl(field, parentRule) {
1007
1542
  };
1008
1543
  return control;
1009
1544
  }
1010
- function groupNodeToLayout(group, parentRule) {
1545
+ function groupNodeToLayout(group, fieldNameMap, parentRule) {
1011
1546
  return {
1012
1547
  type: "Group",
1013
1548
  label: group.label,
1014
- elements: irElementsToUiSchema(group.elements, parentRule),
1549
+ elements: irElementsToUiSchema(group.elements, fieldNameMap, parentRule),
1015
1550
  ...parentRule !== void 0 && { rule: parentRule }
1016
1551
  };
1017
1552
  }
1018
- function irElementsToUiSchema(elements, parentRule) {
1553
+ function irElementsToUiSchema(elements, fieldNameMap, parentRule) {
1019
1554
  const result = [];
1020
1555
  for (const element of elements) {
1021
1556
  switch (element.kind) {
1022
1557
  case "field": {
1023
- result.push(fieldNodeToControl(element, parentRule));
1558
+ result.push(fieldNodeToControl(element, fieldNameMap, parentRule));
1024
1559
  break;
1025
1560
  }
1026
1561
  case "group": {
1027
- result.push(groupNodeToLayout(element, parentRule));
1562
+ result.push(groupNodeToLayout(element, fieldNameMap, parentRule));
1028
1563
  break;
1029
1564
  }
1030
1565
  case "conditional": {
1031
- const newRule = createShowRule(element.fieldName, element.value);
1566
+ const newRule = createShowRule(fieldNameMap.get(element.fieldName) ?? element.fieldName, element.value);
1032
1567
  const combinedRule = parentRule !== void 0 ? combineRules(parentRule, newRule) : newRule;
1033
- const childElements = irElementsToUiSchema(element.elements, combinedRule);
1568
+ const childElements = irElementsToUiSchema(element.elements, fieldNameMap, combinedRule);
1034
1569
  result.push(...childElements);
1035
1570
  break;
1036
1571
  }
@@ -1044,16 +1579,42 @@ function irElementsToUiSchema(elements, parentRule) {
1044
1579
  return result;
1045
1580
  }
1046
1581
  function generateUiSchemaFromIR(ir) {
1582
+ assertNoSerializedNameCollisions(ir);
1583
+ const fieldNameMap = collectFieldNameMap(ir.elements);
1047
1584
  const result = {
1048
1585
  type: "VerticalLayout",
1049
- elements: irElementsToUiSchema(ir.elements)
1586
+ elements: irElementsToUiSchema(ir.elements, fieldNameMap)
1050
1587
  };
1051
1588
  return parseOrThrow(uiSchema, result, "UI Schema");
1052
1589
  }
1590
+ function collectFieldNameMap(elements) {
1591
+ const map = /* @__PURE__ */ new Map();
1592
+ for (const element of elements) {
1593
+ switch (element.kind) {
1594
+ case "field":
1595
+ map.set(element.name, getSerializedName(element.name, element.metadata));
1596
+ break;
1597
+ case "group":
1598
+ case "conditional":
1599
+ for (const [key, value] of collectFieldNameMap(element.elements)) {
1600
+ map.set(key, value);
1601
+ }
1602
+ break;
1603
+ default: {
1604
+ const _exhaustive = element;
1605
+ void _exhaustive;
1606
+ }
1607
+ }
1608
+ }
1609
+ return map;
1610
+ }
1053
1611
 
1054
1612
  // src/ui-schema/generator.ts
1055
- function generateUiSchema(form) {
1056
- const ir = canonicalizeChainDSL(form);
1613
+ function generateUiSchema(form, options) {
1614
+ const ir = canonicalizeChainDSL(
1615
+ form,
1616
+ options?.metadata !== void 0 ? { metadata: options.metadata } : void 0
1617
+ );
1057
1618
  return generateUiSchemaFromIR(ir);
1058
1619
  }
1059
1620
 
@@ -1212,6 +1773,9 @@ import * as path from "path";
1212
1773
 
1213
1774
  // src/analyzer/class-analyzer.ts
1214
1775
  import * as ts3 from "typescript";
1776
+ import {
1777
+ parseCommentBlock as parseCommentBlock2
1778
+ } from "@formspec/analysis/internal";
1215
1779
 
1216
1780
  // src/analyzer/jsdoc-constraints.ts
1217
1781
  import * as ts2 from "typescript";
@@ -2114,7 +2678,76 @@ function makeParseOptions(extensionRegistry, fieldType, checker, subjectType, ho
2114
2678
  ...hostType !== void 0 && { hostType }
2115
2679
  };
2116
2680
  }
2117
- function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
2681
+ function makeExplicitScalarMetadata(value) {
2682
+ return value === void 0 || value === "" ? void 0 : { value, source: "explicit" };
2683
+ }
2684
+ function extractExplicitMetadata(node) {
2685
+ let apiName;
2686
+ let displayName;
2687
+ let apiNamePlural;
2688
+ let displayNamePlural;
2689
+ for (const tag of getLeadingParsedTags(node)) {
2690
+ const value = tag.argumentText.trim();
2691
+ if (value === "") {
2692
+ continue;
2693
+ }
2694
+ if (tag.normalizedTagName === "apiName") {
2695
+ if (tag.target === null) {
2696
+ apiName ??= value;
2697
+ } else if (tag.target.kind === "variant") {
2698
+ if (tag.target.rawText === "singular") {
2699
+ apiName ??= value;
2700
+ } else if (tag.target.rawText === "plural") {
2701
+ apiNamePlural ??= value;
2702
+ }
2703
+ }
2704
+ continue;
2705
+ }
2706
+ if (tag.normalizedTagName === "displayName") {
2707
+ if (tag.target === null) {
2708
+ displayName ??= value;
2709
+ } else if (tag.target.kind === "variant") {
2710
+ if (tag.target.rawText === "singular") {
2711
+ displayName ??= value;
2712
+ } else if (tag.target.rawText === "plural") {
2713
+ displayNamePlural ??= value;
2714
+ }
2715
+ }
2716
+ }
2717
+ }
2718
+ const resolvedApiName = makeExplicitScalarMetadata(apiName);
2719
+ const resolvedDisplayName = makeExplicitScalarMetadata(displayName);
2720
+ const resolvedApiNamePlural = makeExplicitScalarMetadata(apiNamePlural);
2721
+ const resolvedDisplayNamePlural = makeExplicitScalarMetadata(displayNamePlural);
2722
+ const metadata = {
2723
+ ...resolvedApiName !== void 0 && { apiName: resolvedApiName },
2724
+ ...resolvedDisplayName !== void 0 && { displayName: resolvedDisplayName },
2725
+ ...resolvedApiNamePlural !== void 0 && { apiNamePlural: resolvedApiNamePlural },
2726
+ ...resolvedDisplayNamePlural !== void 0 && {
2727
+ displayNamePlural: resolvedDisplayNamePlural
2728
+ }
2729
+ };
2730
+ return Object.keys(metadata).length === 0 ? void 0 : metadata;
2731
+ }
2732
+ function resolveNodeMetadata(metadataPolicy, declarationKind, logicalName, node, buildContext) {
2733
+ const explicit = extractExplicitMetadata(node);
2734
+ return resolveMetadata(
2735
+ {
2736
+ ...explicit?.apiName !== void 0 && { apiName: explicit.apiName.value },
2737
+ ...explicit?.displayName !== void 0 && { displayName: explicit.displayName.value },
2738
+ ...explicit?.apiNamePlural !== void 0 && {
2739
+ apiNamePlural: explicit.apiNamePlural.value
2740
+ },
2741
+ ...explicit?.displayNamePlural !== void 0 && {
2742
+ displayNamePlural: explicit.displayNamePlural.value
2743
+ }
2744
+ },
2745
+ getDeclarationMetadataPolicy(metadataPolicy, declarationKind),
2746
+ makeMetadataContext("tsdoc", declarationKind, logicalName, buildContext)
2747
+ );
2748
+ }
2749
+ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry, metadataPolicy) {
2750
+ const normalizedMetadataPolicy = normalizeMetadataPolicy(metadataPolicy);
2118
2751
  const name = classDecl.name?.text ?? "AnonymousClass";
2119
2752
  const fields = [];
2120
2753
  const fieldLayouts = [];
@@ -2141,6 +2774,7 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
2141
2774
  visiting,
2142
2775
  diagnostics,
2143
2776
  classType,
2777
+ normalizedMetadataPolicy,
2144
2778
  extensionRegistry
2145
2779
  );
2146
2780
  if (fieldNode) {
@@ -2159,9 +2793,25 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
2159
2793
  }
2160
2794
  }
2161
2795
  }
2796
+ const specializedFields = applyDeclarationDiscriminatorToFields(
2797
+ fields,
2798
+ classDecl,
2799
+ classType,
2800
+ checker,
2801
+ file,
2802
+ diagnostics,
2803
+ normalizedMetadataPolicy
2804
+ );
2805
+ const metadata = resolveNodeMetadata(normalizedMetadataPolicy, "type", name, classDecl, {
2806
+ checker,
2807
+ declaration: classDecl,
2808
+ subjectType: classType,
2809
+ hostType: classType
2810
+ });
2162
2811
  return {
2163
2812
  name,
2164
- fields,
2813
+ ...metadata !== void 0 && { metadata },
2814
+ fields: specializedFields,
2165
2815
  fieldLayouts,
2166
2816
  typeRegistry,
2167
2817
  ...annotations.length > 0 && { annotations },
@@ -2170,7 +2820,8 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
2170
2820
  staticMethods
2171
2821
  };
2172
2822
  }
2173
- function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegistry) {
2823
+ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegistry, metadataPolicy) {
2824
+ const normalizedMetadataPolicy = normalizeMetadataPolicy(metadataPolicy);
2174
2825
  const name = interfaceDecl.name.text;
2175
2826
  const fields = [];
2176
2827
  const typeRegistry = {};
@@ -2194,6 +2845,7 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
2194
2845
  visiting,
2195
2846
  diagnostics,
2196
2847
  interfaceType,
2848
+ normalizedMetadataPolicy,
2197
2849
  extensionRegistry
2198
2850
  );
2199
2851
  if (fieldNode) {
@@ -2201,10 +2853,26 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
2201
2853
  }
2202
2854
  }
2203
2855
  }
2204
- const fieldLayouts = fields.map(() => ({}));
2856
+ const specializedFields = applyDeclarationDiscriminatorToFields(
2857
+ fields,
2858
+ interfaceDecl,
2859
+ interfaceType,
2860
+ checker,
2861
+ file,
2862
+ diagnostics,
2863
+ normalizedMetadataPolicy
2864
+ );
2865
+ const fieldLayouts = specializedFields.map(() => ({}));
2866
+ const metadata = resolveNodeMetadata(normalizedMetadataPolicy, "type", name, interfaceDecl, {
2867
+ checker,
2868
+ declaration: interfaceDecl,
2869
+ subjectType: interfaceType,
2870
+ hostType: interfaceType
2871
+ });
2205
2872
  return {
2206
2873
  name,
2207
- fields,
2874
+ ...metadata !== void 0 && { metadata },
2875
+ fields: specializedFields,
2208
2876
  fieldLayouts,
2209
2877
  typeRegistry,
2210
2878
  ...annotations.length > 0 && { annotations },
@@ -2213,7 +2881,7 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
2213
2881
  staticMethods: []
2214
2882
  };
2215
2883
  }
2216
- function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry) {
2884
+ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry, metadataPolicy) {
2217
2885
  if (!ts3.isTypeLiteralNode(typeAlias.type)) {
2218
2886
  const sourceFile = typeAlias.getSourceFile();
2219
2887
  const { line } = sourceFile.getLineAndCharacterOfPosition(typeAlias.getStart());
@@ -2223,6 +2891,8 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
2223
2891
  error: `Type alias "${typeAlias.name.text}" at line ${String(line + 1)} is not an object type literal (found ${kindDesc})`
2224
2892
  };
2225
2893
  }
2894
+ const typeLiteral = typeAlias.type;
2895
+ const normalizedMetadataPolicy = normalizeMetadataPolicy(metadataPolicy);
2226
2896
  const name = typeAlias.name.text;
2227
2897
  const fields = [];
2228
2898
  const typeRegistry = {};
@@ -2236,7 +2906,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
2236
2906
  const annotations = [...typeAliasDoc.annotations];
2237
2907
  diagnostics.push(...typeAliasDoc.diagnostics);
2238
2908
  const visiting = /* @__PURE__ */ new Set();
2239
- for (const member of typeAlias.type.members) {
2909
+ for (const member of typeLiteral.members) {
2240
2910
  if (ts3.isPropertySignature(member)) {
2241
2911
  const fieldNode = analyzeInterfacePropertyToIR(
2242
2912
  member,
@@ -2246,6 +2916,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
2246
2916
  visiting,
2247
2917
  diagnostics,
2248
2918
  aliasType,
2919
+ normalizedMetadataPolicy,
2249
2920
  extensionRegistry
2250
2921
  );
2251
2922
  if (fieldNode) {
@@ -2253,12 +2924,28 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
2253
2924
  }
2254
2925
  }
2255
2926
  }
2927
+ const specializedFields = applyDeclarationDiscriminatorToFields(
2928
+ fields,
2929
+ typeAlias,
2930
+ aliasType,
2931
+ checker,
2932
+ file,
2933
+ diagnostics,
2934
+ normalizedMetadataPolicy
2935
+ );
2936
+ const metadata = resolveNodeMetadata(normalizedMetadataPolicy, "type", name, typeAlias, {
2937
+ checker,
2938
+ declaration: typeAlias,
2939
+ subjectType: aliasType,
2940
+ hostType: aliasType
2941
+ });
2256
2942
  return {
2257
2943
  ok: true,
2258
2944
  analysis: {
2259
2945
  name,
2260
- fields,
2261
- fieldLayouts: fields.map(() => ({})),
2946
+ ...metadata !== void 0 && { metadata },
2947
+ fields: specializedFields,
2948
+ fieldLayouts: specializedFields.map(() => ({})),
2262
2949
  typeRegistry,
2263
2950
  ...annotations.length > 0 && { annotations },
2264
2951
  ...diagnostics.length > 0 && { diagnostics },
@@ -2267,7 +2954,444 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
2267
2954
  }
2268
2955
  };
2269
2956
  }
2270
- function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, extensionRegistry) {
2957
+ function makeAnalysisDiagnostic(code, message, primaryLocation, relatedLocations = []) {
2958
+ return {
2959
+ code,
2960
+ message,
2961
+ severity: "error",
2962
+ primaryLocation,
2963
+ relatedLocations
2964
+ };
2965
+ }
2966
+ function getLeadingParsedTags(node) {
2967
+ const sourceFile = node.getSourceFile();
2968
+ const sourceText = sourceFile.getFullText();
2969
+ const commentRanges = ts3.getLeadingCommentRanges(sourceText, node.getFullStart());
2970
+ if (commentRanges === void 0) {
2971
+ return [];
2972
+ }
2973
+ const parsedTags = [];
2974
+ for (const range of commentRanges) {
2975
+ if (range.kind !== ts3.SyntaxKind.MultiLineCommentTrivia) {
2976
+ continue;
2977
+ }
2978
+ const commentText = sourceText.slice(range.pos, range.end);
2979
+ if (!commentText.startsWith("/**")) {
2980
+ continue;
2981
+ }
2982
+ parsedTags.push(...parseCommentBlock2(commentText, { offset: range.pos }).tags);
2983
+ }
2984
+ return parsedTags;
2985
+ }
2986
+ function resolveDiscriminatorProperty(node, checker, fieldName) {
2987
+ const subjectType = checker.getTypeAtLocation(node);
2988
+ const propertySymbol = subjectType.getProperty(fieldName);
2989
+ if (propertySymbol === void 0) {
2990
+ return null;
2991
+ }
2992
+ const declaration = propertySymbol.valueDeclaration ?? propertySymbol.declarations?.find(
2993
+ (candidate) => ts3.isPropertyDeclaration(candidate) || ts3.isPropertySignature(candidate)
2994
+ ) ?? propertySymbol.declarations?.[0];
2995
+ return {
2996
+ declaration,
2997
+ type: checker.getTypeOfSymbolAtLocation(propertySymbol, declaration ?? node),
2998
+ optional: !!(propertySymbol.flags & ts3.SymbolFlags.Optional) || declaration !== void 0 && "questionToken" in declaration && declaration.questionToken !== void 0
2999
+ };
3000
+ }
3001
+ function isLocalTypeParameterName(node, typeParameterName) {
3002
+ return node.typeParameters?.some((typeParameter) => typeParameter.name.text === typeParameterName) ?? false;
3003
+ }
3004
+ function isNullishSemanticType(type) {
3005
+ if (type.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined | ts3.TypeFlags.Void | ts3.TypeFlags.Unknown | ts3.TypeFlags.Any)) {
3006
+ return true;
3007
+ }
3008
+ return type.isUnion() && type.types.some((member) => isNullishSemanticType(member));
3009
+ }
3010
+ function isStringLikeSemanticType(type) {
3011
+ if (type.flags & ts3.TypeFlags.StringLike) {
3012
+ return true;
3013
+ }
3014
+ if (type.isUnion()) {
3015
+ return type.types.length > 0 && type.types.every((member) => isStringLikeSemanticType(member));
3016
+ }
3017
+ return false;
3018
+ }
3019
+ function extractDiscriminatorDirective(node, file, diagnostics) {
3020
+ const discriminatorTags = getLeadingParsedTags(node).filter(
3021
+ (tag) => tag.normalizedTagName === "discriminator"
3022
+ );
3023
+ if (discriminatorTags.length === 0) {
3024
+ return null;
3025
+ }
3026
+ const [firstTag, ...duplicateTags] = discriminatorTags;
3027
+ for (const _duplicateTag of duplicateTags) {
3028
+ diagnostics.push(
3029
+ makeAnalysisDiagnostic(
3030
+ "DUPLICATE_TAG",
3031
+ 'Duplicate "@discriminator" tag. Only one discriminator declaration is allowed per declaration.',
3032
+ provenanceForNode(node, file)
3033
+ )
3034
+ );
3035
+ }
3036
+ if (firstTag === void 0) {
3037
+ return null;
3038
+ }
3039
+ const firstTarget = firstTag.target;
3040
+ if (firstTarget?.path === null || firstTarget?.valid !== true) {
3041
+ diagnostics.push(
3042
+ makeAnalysisDiagnostic(
3043
+ "INVALID_TAG_ARGUMENT",
3044
+ 'Tag "@discriminator" requires a direct path target like ":kind".',
3045
+ provenanceForNode(node, file)
3046
+ )
3047
+ );
3048
+ return null;
3049
+ }
3050
+ if (firstTarget.path.segments.length !== 1) {
3051
+ diagnostics.push(
3052
+ makeAnalysisDiagnostic(
3053
+ "INVALID_TAG_ARGUMENT",
3054
+ 'Tag "@discriminator" only supports direct property targets in v1; nested paths are out of scope.',
3055
+ provenanceForNode(node, file)
3056
+ )
3057
+ );
3058
+ return null;
3059
+ }
3060
+ const typeParameterName = firstTag.argumentText.trim();
3061
+ if (!/^[A-Za-z_$][\w$]*$/u.test(typeParameterName)) {
3062
+ diagnostics.push(
3063
+ makeAnalysisDiagnostic(
3064
+ "INVALID_TAG_ARGUMENT",
3065
+ 'Tag "@discriminator" requires a local type parameter name as its source operand.',
3066
+ provenanceForNode(node, file)
3067
+ )
3068
+ );
3069
+ return null;
3070
+ }
3071
+ return {
3072
+ fieldName: firstTarget.path.segments[0] ?? firstTarget.rawText,
3073
+ typeParameterName,
3074
+ provenance: provenanceForNode(node, file)
3075
+ };
3076
+ }
3077
+ function validateDiscriminatorDirective(node, checker, file, diagnostics) {
3078
+ const directive = extractDiscriminatorDirective(node, file, diagnostics);
3079
+ if (directive === null) {
3080
+ return null;
3081
+ }
3082
+ if (!isLocalTypeParameterName(node, directive.typeParameterName)) {
3083
+ diagnostics.push(
3084
+ makeAnalysisDiagnostic(
3085
+ "INVALID_TAG_ARGUMENT",
3086
+ `Tag "@discriminator" references "${directive.typeParameterName}", but the source operand must be a type parameter declared on the same declaration.`,
3087
+ directive.provenance
3088
+ )
3089
+ );
3090
+ return null;
3091
+ }
3092
+ const property = resolveDiscriminatorProperty(node, checker, directive.fieldName);
3093
+ if (property === null) {
3094
+ diagnostics.push(
3095
+ makeAnalysisDiagnostic(
3096
+ "UNKNOWN_PATH_TARGET",
3097
+ `Tag "@discriminator" targets "${directive.fieldName}", but no direct property with that name exists on this declaration.`,
3098
+ directive.provenance
3099
+ )
3100
+ );
3101
+ return null;
3102
+ }
3103
+ if (property.optional) {
3104
+ diagnostics.push(
3105
+ makeAnalysisDiagnostic(
3106
+ "TYPE_MISMATCH",
3107
+ `Discriminator field "${directive.fieldName}" must be required; optional discriminator fields are not supported.`,
3108
+ directive.provenance,
3109
+ property.declaration !== void 0 ? [provenanceForNode(property.declaration, file)] : []
3110
+ )
3111
+ );
3112
+ return null;
3113
+ }
3114
+ if (isNullishSemanticType(property.type)) {
3115
+ diagnostics.push(
3116
+ makeAnalysisDiagnostic(
3117
+ "TYPE_MISMATCH",
3118
+ `Discriminator field "${directive.fieldName}" must not be nullable.`,
3119
+ directive.provenance,
3120
+ property.declaration !== void 0 ? [provenanceForNode(property.declaration, file)] : []
3121
+ )
3122
+ );
3123
+ return null;
3124
+ }
3125
+ if (!isStringLikeSemanticType(property.type)) {
3126
+ diagnostics.push(
3127
+ makeAnalysisDiagnostic(
3128
+ "TYPE_MISMATCH",
3129
+ `Discriminator field "${directive.fieldName}" must be string-like.`,
3130
+ directive.provenance,
3131
+ property.declaration !== void 0 ? [provenanceForNode(property.declaration, file)] : []
3132
+ )
3133
+ );
3134
+ return null;
3135
+ }
3136
+ return directive;
3137
+ }
3138
+ function getConcreteTypeArgumentForDiscriminator(node, subjectType, checker, typeParameterName) {
3139
+ const typeParameterIndex = node.typeParameters?.findIndex(
3140
+ (typeParameter) => typeParameter.name.text === typeParameterName
3141
+ ) ?? -1;
3142
+ if (typeParameterIndex < 0) {
3143
+ return null;
3144
+ }
3145
+ const referenceTypeArguments = (isTypeReference(subjectType) ? subjectType.typeArguments : void 0) ?? subjectType.aliasTypeArguments;
3146
+ if (referenceTypeArguments?.[typeParameterIndex] !== void 0) {
3147
+ return referenceTypeArguments[typeParameterIndex] ?? null;
3148
+ }
3149
+ const localTypeParameter = node.typeParameters?.[typeParameterIndex];
3150
+ return localTypeParameter === void 0 ? null : checker.getTypeAtLocation(localTypeParameter);
3151
+ }
3152
+ function resolveLiteralDiscriminatorPropertyValue(boundType, fieldName, checker, provenance, diagnostics) {
3153
+ const propertySymbol = boundType.getProperty(fieldName);
3154
+ if (propertySymbol === void 0) {
3155
+ return void 0;
3156
+ }
3157
+ const declaration = propertySymbol.valueDeclaration ?? propertySymbol.declarations?.[0];
3158
+ const anchorNode = declaration ?? boundType.symbol.declarations?.[0] ?? null;
3159
+ const resolvedAnchorNode = anchorNode ?? resolveNamedDiscriminatorDeclaration(boundType, checker);
3160
+ if (resolvedAnchorNode === null) {
3161
+ return void 0;
3162
+ }
3163
+ const propertyType = checker.getTypeOfSymbolAtLocation(
3164
+ propertySymbol,
3165
+ resolvedAnchorNode
3166
+ );
3167
+ if (propertyType.isStringLiteral()) {
3168
+ return propertyType.value;
3169
+ }
3170
+ if (propertyType.isUnion()) {
3171
+ const nonNullMembers = propertyType.types.filter(
3172
+ (member) => !(member.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined))
3173
+ );
3174
+ if (nonNullMembers.length > 0 && nonNullMembers.every((member) => member.isStringLiteral())) {
3175
+ diagnostics.push(
3176
+ makeAnalysisDiagnostic(
3177
+ "INVALID_TAG_ARGUMENT",
3178
+ "Discriminator resolution for union-valued identity properties is out of scope for v1.",
3179
+ provenance
3180
+ )
3181
+ );
3182
+ return null;
3183
+ }
3184
+ }
3185
+ return void 0;
3186
+ }
3187
+ function resolveDiscriminatorApiName(boundType, checker, metadataPolicy) {
3188
+ const declaration = resolveNamedDiscriminatorDeclaration(boundType, checker);
3189
+ if (declaration === null) {
3190
+ return void 0;
3191
+ }
3192
+ const metadata = resolveNodeMetadata(
3193
+ metadataPolicy,
3194
+ "type",
3195
+ getDiscriminatorLogicalName(boundType, declaration, checker),
3196
+ declaration,
3197
+ {
3198
+ checker,
3199
+ declaration,
3200
+ subjectType: boundType
3201
+ }
3202
+ );
3203
+ return metadata?.apiName;
3204
+ }
3205
+ function resolveNamedDiscriminatorDeclaration(type, checker, seen = /* @__PURE__ */ new Set()) {
3206
+ if (seen.has(type)) {
3207
+ return null;
3208
+ }
3209
+ seen.add(type);
3210
+ const symbol = type.aliasSymbol ?? type.getSymbol();
3211
+ if (symbol !== void 0) {
3212
+ const aliased = symbol.flags & ts3.SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : void 0;
3213
+ const targetSymbol = aliased ?? symbol;
3214
+ const declaration = targetSymbol.declarations?.find(
3215
+ (candidate) => ts3.isClassDeclaration(candidate) || ts3.isInterfaceDeclaration(candidate) || ts3.isTypeAliasDeclaration(candidate) || ts3.isEnumDeclaration(candidate)
3216
+ );
3217
+ if (declaration !== void 0) {
3218
+ if (ts3.isTypeAliasDeclaration(declaration) && ts3.isTypeReferenceNode(declaration.type) && checker.getTypeFromTypeNode(declaration.type) !== type) {
3219
+ return resolveNamedDiscriminatorDeclaration(
3220
+ checker.getTypeFromTypeNode(declaration.type),
3221
+ checker,
3222
+ seen
3223
+ );
3224
+ }
3225
+ return declaration;
3226
+ }
3227
+ }
3228
+ return null;
3229
+ }
3230
+ function resolveDiscriminatorValue(boundType, fieldName, checker, provenance, diagnostics, metadataPolicy) {
3231
+ if (boundType === null) {
3232
+ diagnostics.push(
3233
+ makeAnalysisDiagnostic(
3234
+ "INVALID_TAG_ARGUMENT",
3235
+ "Discriminator resolution failed because no concrete type argument is available for the referenced type parameter.",
3236
+ provenance
3237
+ )
3238
+ );
3239
+ return null;
3240
+ }
3241
+ if (boundType.isStringLiteral()) {
3242
+ return boundType.value;
3243
+ }
3244
+ if (boundType.isUnion()) {
3245
+ const nonNullMembers = boundType.types.filter(
3246
+ (member) => !(member.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined))
3247
+ );
3248
+ if (nonNullMembers.every((member) => member.isStringLiteral())) {
3249
+ diagnostics.push(
3250
+ makeAnalysisDiagnostic(
3251
+ "INVALID_TAG_ARGUMENT",
3252
+ "Discriminator resolution for unions of string literals is out of scope for v1.",
3253
+ provenance
3254
+ )
3255
+ );
3256
+ return null;
3257
+ }
3258
+ }
3259
+ const literalIdentityValue = resolveLiteralDiscriminatorPropertyValue(
3260
+ boundType,
3261
+ fieldName,
3262
+ checker,
3263
+ provenance,
3264
+ diagnostics
3265
+ );
3266
+ if (literalIdentityValue !== void 0) {
3267
+ return literalIdentityValue;
3268
+ }
3269
+ const apiName = resolveDiscriminatorApiName(boundType, checker, metadataPolicy);
3270
+ if (apiName?.source === "explicit") {
3271
+ return apiName.value;
3272
+ }
3273
+ if (apiName?.source === "inferred") {
3274
+ return apiName.value;
3275
+ }
3276
+ diagnostics.push(
3277
+ makeAnalysisDiagnostic(
3278
+ "INVALID_TAG_ARGUMENT",
3279
+ "Discriminator resolution could not derive a JSON-facing discriminator value from the referenced type argument.",
3280
+ provenance
3281
+ )
3282
+ );
3283
+ return null;
3284
+ }
3285
+ function getDeclarationName(node) {
3286
+ if (ts3.isClassDeclaration(node) || ts3.isInterfaceDeclaration(node) || ts3.isTypeAliasDeclaration(node) || ts3.isEnumDeclaration(node)) {
3287
+ return node.name?.text ?? "anonymous";
3288
+ }
3289
+ return "anonymous";
3290
+ }
3291
+ function getResolvedTypeArguments(type) {
3292
+ return (isTypeReference(type) ? type.typeArguments : void 0) ?? type.aliasTypeArguments ?? [];
3293
+ }
3294
+ function getDiscriminatorLogicalName(type, declaration, checker) {
3295
+ const baseName = getDeclarationName(declaration);
3296
+ const typeArguments = getResolvedTypeArguments(type);
3297
+ return typeArguments.length === 0 ? baseName : buildInstantiatedReferenceName(baseName, typeArguments, checker);
3298
+ }
3299
+ function applyDeclarationDiscriminatorToFields(fields, node, subjectType, checker, file, diagnostics, metadataPolicy) {
3300
+ const directive = validateDiscriminatorDirective(node, checker, file, diagnostics);
3301
+ if (directive === null) {
3302
+ return [...fields];
3303
+ }
3304
+ const discriminatorValue = resolveDiscriminatorValue(
3305
+ getConcreteTypeArgumentForDiscriminator(
3306
+ node,
3307
+ subjectType,
3308
+ checker,
3309
+ directive.typeParameterName
3310
+ ),
3311
+ directive.fieldName,
3312
+ checker,
3313
+ directive.provenance,
3314
+ diagnostics,
3315
+ metadataPolicy
3316
+ );
3317
+ if (discriminatorValue === null) {
3318
+ return [...fields];
3319
+ }
3320
+ return fields.map(
3321
+ (field) => field.name === directive.fieldName ? {
3322
+ ...field,
3323
+ type: {
3324
+ kind: "enum",
3325
+ members: [{ value: discriminatorValue }]
3326
+ }
3327
+ } : field
3328
+ );
3329
+ }
3330
+ function buildInstantiatedReferenceName(baseName, typeArguments, checker) {
3331
+ const renderedArguments = typeArguments.map(
3332
+ (typeArgument) => checker.typeToString(typeArgument).replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "")
3333
+ ).filter((value) => value !== "");
3334
+ return renderedArguments.length === 0 ? baseName : `${baseName}__${renderedArguments.join("__")}`;
3335
+ }
3336
+ function extractReferenceTypeArguments(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy, extensionRegistry, diagnostics) {
3337
+ const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
3338
+ if (typeNode === void 0) {
3339
+ return [];
3340
+ }
3341
+ const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
3342
+ if (!ts3.isTypeReferenceNode(resolvedTypeNode) || resolvedTypeNode.typeArguments === void 0) {
3343
+ return [];
3344
+ }
3345
+ return resolvedTypeNode.typeArguments.map((argumentNode) => {
3346
+ const argumentType = checker.getTypeFromTypeNode(argumentNode);
3347
+ return {
3348
+ tsType: argumentType,
3349
+ typeNode: resolveTypeNode(
3350
+ argumentType,
3351
+ checker,
3352
+ file,
3353
+ typeRegistry,
3354
+ visiting,
3355
+ argumentNode,
3356
+ metadataPolicy,
3357
+ extensionRegistry,
3358
+ diagnostics
3359
+ )
3360
+ };
3361
+ });
3362
+ }
3363
+ function applyDiscriminatorToObjectProperties(properties, node, subjectType, checker, file, diagnostics, metadataPolicy) {
3364
+ const directive = validateDiscriminatorDirective(node, checker, file, diagnostics);
3365
+ if (directive === null) {
3366
+ return properties;
3367
+ }
3368
+ const discriminatorValue = resolveDiscriminatorValue(
3369
+ getConcreteTypeArgumentForDiscriminator(
3370
+ node,
3371
+ subjectType,
3372
+ checker,
3373
+ directive.typeParameterName
3374
+ ),
3375
+ directive.fieldName,
3376
+ checker,
3377
+ directive.provenance,
3378
+ diagnostics,
3379
+ metadataPolicy
3380
+ );
3381
+ if (discriminatorValue === null) {
3382
+ return properties;
3383
+ }
3384
+ return properties.map(
3385
+ (property) => property.name === directive.fieldName ? {
3386
+ ...property,
3387
+ type: {
3388
+ kind: "enum",
3389
+ members: [{ value: discriminatorValue }]
3390
+ }
3391
+ } : property
3392
+ );
3393
+ }
3394
+ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, metadataPolicy, extensionRegistry) {
2271
3395
  if (!ts3.isIdentifier(prop.name)) {
2272
3396
  return null;
2273
3397
  }
@@ -2282,6 +3406,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnosti
2282
3406
  typeRegistry,
2283
3407
  visiting,
2284
3408
  prop,
3409
+ metadataPolicy,
2285
3410
  extensionRegistry,
2286
3411
  diagnostics
2287
3412
  );
@@ -2305,9 +3430,16 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnosti
2305
3430
  annotations.push(defaultAnnotation);
2306
3431
  }
2307
3432
  ({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
3433
+ const metadata = resolveNodeMetadata(metadataPolicy, "field", name, prop, {
3434
+ checker,
3435
+ declaration: prop,
3436
+ subjectType: tsType,
3437
+ hostType
3438
+ });
2308
3439
  return {
2309
3440
  kind: "field",
2310
3441
  name,
3442
+ ...metadata !== void 0 && { metadata },
2311
3443
  type,
2312
3444
  required: !optional,
2313
3445
  constraints,
@@ -2315,7 +3447,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnosti
2315
3447
  provenance
2316
3448
  };
2317
3449
  }
2318
- function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, extensionRegistry) {
3450
+ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, metadataPolicy, extensionRegistry) {
2319
3451
  if (!ts3.isIdentifier(prop.name)) {
2320
3452
  return null;
2321
3453
  }
@@ -2330,6 +3462,7 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
2330
3462
  typeRegistry,
2331
3463
  visiting,
2332
3464
  prop,
3465
+ metadataPolicy,
2333
3466
  extensionRegistry,
2334
3467
  diagnostics
2335
3468
  );
@@ -2349,9 +3482,16 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
2349
3482
  let annotations = [];
2350
3483
  annotations.push(...docResult.annotations);
2351
3484
  ({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
3485
+ const metadata = resolveNodeMetadata(metadataPolicy, "field", name, prop, {
3486
+ checker,
3487
+ declaration: prop,
3488
+ subjectType: tsType,
3489
+ hostType
3490
+ });
2352
3491
  return {
2353
3492
  kind: "field",
2354
3493
  name,
3494
+ ...metadata !== void 0 && { metadata },
2355
3495
  type,
2356
3496
  required: !optional,
2357
3497
  constraints,
@@ -2476,7 +3616,7 @@ function getTypeNodeRegistrationName(typeNode) {
2476
3616
  }
2477
3617
  return null;
2478
3618
  }
2479
- function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
3619
+ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
2480
3620
  const customType = resolveRegisteredCustomType(sourceNode, extensionRegistry, checker);
2481
3621
  if (customType) {
2482
3622
  return customType;
@@ -2488,6 +3628,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
2488
3628
  typeRegistry,
2489
3629
  visiting,
2490
3630
  sourceNode,
3631
+ metadataPolicy,
2491
3632
  extensionRegistry,
2492
3633
  diagnostics
2493
3634
  );
@@ -2532,6 +3673,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
2532
3673
  typeRegistry,
2533
3674
  visiting,
2534
3675
  sourceNode,
3676
+ metadataPolicy,
2535
3677
  extensionRegistry,
2536
3678
  diagnostics
2537
3679
  );
@@ -2544,6 +3686,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
2544
3686
  typeRegistry,
2545
3687
  visiting,
2546
3688
  sourceNode,
3689
+ metadataPolicy,
2547
3690
  extensionRegistry,
2548
3691
  diagnostics
2549
3692
  );
@@ -2555,13 +3698,15 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
2555
3698
  file,
2556
3699
  typeRegistry,
2557
3700
  visiting,
3701
+ sourceNode,
3702
+ metadataPolicy,
2558
3703
  extensionRegistry,
2559
3704
  diagnostics
2560
3705
  );
2561
3706
  }
2562
3707
  return { kind: "primitive", primitiveKind: "string" };
2563
3708
  }
2564
- function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
3709
+ function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
2565
3710
  if (!(type.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null))) {
2566
3711
  return null;
2567
3712
  }
@@ -2581,14 +3726,21 @@ function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiti
2581
3726
  file,
2582
3727
  makeParseOptions(extensionRegistry)
2583
3728
  );
3729
+ const metadata = resolveNodeMetadata(metadataPolicy, "type", aliasName, aliasDecl, {
3730
+ checker,
3731
+ declaration: aliasDecl,
3732
+ subjectType: aliasType
3733
+ });
2584
3734
  typeRegistry[aliasName] = {
2585
3735
  name: aliasName,
3736
+ ...metadata !== void 0 && { metadata },
2586
3737
  type: resolveAliasedPrimitiveTarget(
2587
3738
  aliasType,
2588
3739
  checker,
2589
3740
  file,
2590
3741
  typeRegistry,
2591
3742
  visiting,
3743
+ metadataPolicy,
2592
3744
  extensionRegistry,
2593
3745
  diagnostics
2594
3746
  ),
@@ -2617,7 +3769,7 @@ function shouldEmitPrimitiveAliasDefinition(typeNode, checker) {
2617
3769
  const resolved = checker.getTypeFromTypeNode(aliasDecl.type);
2618
3770
  return !!(resolved.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null));
2619
3771
  }
2620
- function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
3772
+ function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
2621
3773
  const nestedAliasDecl = type.aliasSymbol?.declarations?.find(ts3.isTypeAliasDeclaration);
2622
3774
  if (nestedAliasDecl !== void 0) {
2623
3775
  return resolveAliasedPrimitiveTarget(
@@ -2626,6 +3778,7 @@ function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiti
2626
3778
  file,
2627
3779
  typeRegistry,
2628
3780
  visiting,
3781
+ metadataPolicy,
2629
3782
  extensionRegistry,
2630
3783
  diagnostics
2631
3784
  );
@@ -2637,11 +3790,12 @@ function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiti
2637
3790
  typeRegistry,
2638
3791
  visiting,
2639
3792
  void 0,
3793
+ metadataPolicy,
2640
3794
  extensionRegistry,
2641
3795
  diagnostics
2642
3796
  );
2643
3797
  }
2644
- function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
3798
+ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
2645
3799
  const typeName = getNamedTypeName(type);
2646
3800
  const namedDecl = getNamedTypeDeclaration(type);
2647
3801
  if (typeName && typeName in typeRegistry) {
@@ -2676,8 +3830,14 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
2676
3830
  return result;
2677
3831
  }
2678
3832
  const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
3833
+ const metadata = namedDecl !== void 0 ? resolveNodeMetadata(metadataPolicy, "type", typeName, namedDecl, {
3834
+ checker,
3835
+ declaration: namedDecl,
3836
+ subjectType: type
3837
+ }) : void 0;
2679
3838
  typeRegistry[typeName] = {
2680
3839
  name: typeName,
3840
+ ...metadata !== void 0 && { metadata },
2681
3841
  type: result,
2682
3842
  ...annotations !== void 0 && annotations.length > 0 && { annotations },
2683
3843
  provenance: provenanceForDeclaration(namedDecl ?? sourceNode, file)
@@ -2731,6 +3891,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
2731
3891
  typeRegistry,
2732
3892
  visiting,
2733
3893
  nonNullMembers[0].sourceNode ?? sourceNode,
3894
+ metadataPolicy,
2734
3895
  extensionRegistry,
2735
3896
  diagnostics
2736
3897
  );
@@ -2748,6 +3909,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
2748
3909
  typeRegistry,
2749
3910
  visiting,
2750
3911
  memberSourceNode ?? sourceNode,
3912
+ metadataPolicy,
2751
3913
  extensionRegistry,
2752
3914
  diagnostics
2753
3915
  )
@@ -2757,7 +3919,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
2757
3919
  }
2758
3920
  return registerNamed({ kind: "union", members });
2759
3921
  }
2760
- function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
3922
+ function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
2761
3923
  const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
2762
3924
  const elementType = typeArgs?.[0];
2763
3925
  const elementSourceNode = extractArrayElementTypeNode(sourceNode, checker);
@@ -2768,12 +3930,13 @@ function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNod
2768
3930
  typeRegistry,
2769
3931
  visiting,
2770
3932
  elementSourceNode,
3933
+ metadataPolicy,
2771
3934
  extensionRegistry,
2772
3935
  diagnostics
2773
3936
  ) : { kind: "primitive", primitiveKind: "string" };
2774
3937
  return { kind: "array", items };
2775
3938
  }
2776
- function tryResolveRecordType(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
3939
+ function tryResolveRecordType(type, checker, file, typeRegistry, visiting, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
2777
3940
  if (type.getProperties().length > 0) {
2778
3941
  return null;
2779
3942
  }
@@ -2788,6 +3951,7 @@ function tryResolveRecordType(type, checker, file, typeRegistry, visiting, exten
2788
3951
  typeRegistry,
2789
3952
  visiting,
2790
3953
  void 0,
3954
+ metadataPolicy,
2791
3955
  extensionRegistry,
2792
3956
  diagnostics
2793
3957
  );
@@ -2818,35 +3982,76 @@ function typeNodeContainsReference(type, targetName) {
2818
3982
  }
2819
3983
  }
2820
3984
  }
2821
- function resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
3985
+ function shouldEmitResolvedObjectProperty(property, declaration) {
3986
+ if (property.name.startsWith("__@")) {
3987
+ return false;
3988
+ }
3989
+ if (declaration !== void 0 && "name" in declaration && declaration.name !== void 0) {
3990
+ const name = declaration.name;
3991
+ if (ts3.isComputedPropertyName(name) || ts3.isPrivateIdentifier(name)) {
3992
+ return false;
3993
+ }
3994
+ if (!ts3.isIdentifier(name) && !ts3.isStringLiteral(name) && !ts3.isNumericLiteral(name)) {
3995
+ return false;
3996
+ }
3997
+ }
3998
+ return true;
3999
+ }
4000
+ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
4001
+ const collectedDiagnostics = diagnostics ?? [];
2822
4002
  const typeName = getNamedTypeName(type);
2823
4003
  const namedTypeName = typeName ?? void 0;
2824
4004
  const namedDecl = getNamedTypeDeclaration(type);
2825
- const shouldRegisterNamedType = namedTypeName !== void 0 && !(namedTypeName === "Record" && namedDecl?.getSourceFile().fileName !== file);
4005
+ const referenceTypeArguments = extractReferenceTypeArguments(
4006
+ type,
4007
+ checker,
4008
+ file,
4009
+ typeRegistry,
4010
+ visiting,
4011
+ sourceNode,
4012
+ metadataPolicy,
4013
+ extensionRegistry,
4014
+ collectedDiagnostics
4015
+ );
4016
+ const instantiatedTypeName = namedTypeName !== void 0 && referenceTypeArguments.length > 0 ? buildInstantiatedReferenceName(
4017
+ namedTypeName,
4018
+ referenceTypeArguments.map((argument) => argument.tsType),
4019
+ checker
4020
+ ) : void 0;
4021
+ const registryTypeName = instantiatedTypeName ?? namedTypeName;
4022
+ const shouldRegisterNamedType = registryTypeName !== void 0 && !(registryTypeName === "Record" && namedDecl?.getSourceFile().fileName !== file);
2826
4023
  const clearNamedTypeRegistration = () => {
2827
- if (namedTypeName === void 0 || !shouldRegisterNamedType) {
4024
+ if (registryTypeName === void 0 || !shouldRegisterNamedType) {
2828
4025
  return;
2829
4026
  }
2830
- Reflect.deleteProperty(typeRegistry, namedTypeName);
4027
+ Reflect.deleteProperty(typeRegistry, registryTypeName);
2831
4028
  };
2832
4029
  if (visiting.has(type)) {
2833
- if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2834
- return { kind: "reference", name: namedTypeName, typeArguments: [] };
4030
+ if (registryTypeName !== void 0 && shouldRegisterNamedType) {
4031
+ return {
4032
+ kind: "reference",
4033
+ name: registryTypeName,
4034
+ typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
4035
+ };
2835
4036
  }
2836
4037
  return { kind: "object", properties: [], additionalProperties: false };
2837
4038
  }
2838
- if (namedTypeName !== void 0 && shouldRegisterNamedType && !typeRegistry[namedTypeName]) {
2839
- typeRegistry[namedTypeName] = {
2840
- name: namedTypeName,
4039
+ if (registryTypeName !== void 0 && shouldRegisterNamedType && !typeRegistry[registryTypeName]) {
4040
+ typeRegistry[registryTypeName] = {
4041
+ name: registryTypeName,
2841
4042
  type: RESOLVING_TYPE_PLACEHOLDER,
2842
4043
  provenance: provenanceForDeclaration(namedDecl, file)
2843
4044
  };
2844
4045
  }
2845
4046
  visiting.add(type);
2846
- if (namedTypeName !== void 0 && shouldRegisterNamedType && typeRegistry[namedTypeName]?.type !== void 0) {
2847
- if (typeRegistry[namedTypeName].type !== RESOLVING_TYPE_PLACEHOLDER) {
4047
+ if (registryTypeName !== void 0 && shouldRegisterNamedType && typeRegistry[registryTypeName]?.type !== void 0) {
4048
+ if (typeRegistry[registryTypeName].type !== RESOLVING_TYPE_PLACEHOLDER) {
2848
4049
  visiting.delete(type);
2849
- return { kind: "reference", name: namedTypeName, typeArguments: [] };
4050
+ return {
4051
+ kind: "reference",
4052
+ name: registryTypeName,
4053
+ typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
4054
+ };
2850
4055
  }
2851
4056
  }
2852
4057
  const recordNode = tryResolveRecordType(
@@ -2855,25 +4060,36 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
2855
4060
  file,
2856
4061
  typeRegistry,
2857
4062
  visiting,
4063
+ metadataPolicy,
2858
4064
  extensionRegistry,
2859
- diagnostics
4065
+ collectedDiagnostics
2860
4066
  );
2861
4067
  if (recordNode) {
2862
4068
  visiting.delete(type);
2863
- if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2864
- const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType, namedTypeName);
4069
+ if (registryTypeName !== void 0 && shouldRegisterNamedType) {
4070
+ const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType, registryTypeName);
2865
4071
  if (!isRecursiveRecord) {
2866
4072
  clearNamedTypeRegistration();
2867
4073
  return recordNode;
2868
4074
  }
2869
4075
  const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
2870
- typeRegistry[namedTypeName] = {
2871
- name: namedTypeName,
4076
+ const metadata = namedDecl !== void 0 ? resolveNodeMetadata(metadataPolicy, "type", registryTypeName, namedDecl, {
4077
+ checker,
4078
+ declaration: namedDecl,
4079
+ subjectType: type
4080
+ }) : void 0;
4081
+ typeRegistry[registryTypeName] = {
4082
+ name: registryTypeName,
4083
+ ...metadata !== void 0 && { metadata },
2872
4084
  type: recordNode,
2873
4085
  ...annotations !== void 0 && annotations.length > 0 && { annotations },
2874
4086
  provenance: provenanceForDeclaration(namedDecl, file)
2875
4087
  };
2876
- return { kind: "reference", name: namedTypeName, typeArguments: [] };
4088
+ return {
4089
+ kind: "reference",
4090
+ name: registryTypeName,
4091
+ typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
4092
+ };
2877
4093
  }
2878
4094
  return recordNode;
2879
4095
  }
@@ -2884,12 +4100,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
2884
4100
  file,
2885
4101
  typeRegistry,
2886
4102
  visiting,
2887
- diagnostics ?? [],
4103
+ metadataPolicy,
4104
+ collectedDiagnostics,
2888
4105
  extensionRegistry
2889
4106
  );
2890
4107
  for (const prop of type.getProperties()) {
2891
4108
  const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
2892
4109
  if (!declaration) continue;
4110
+ if (!shouldEmitResolvedObjectProperty(prop, declaration)) continue;
2893
4111
  const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
2894
4112
  const optional = !!(prop.flags & ts3.SymbolFlags.Optional);
2895
4113
  const propTypeNode = resolveTypeNode(
@@ -2899,12 +4117,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
2899
4117
  typeRegistry,
2900
4118
  visiting,
2901
4119
  declaration,
4120
+ metadataPolicy,
2902
4121
  extensionRegistry,
2903
- diagnostics
4122
+ collectedDiagnostics
2904
4123
  );
2905
4124
  const fieldNodeInfo = fieldInfoMap?.get(prop.name);
2906
4125
  properties.push({
2907
4126
  name: prop.name,
4127
+ ...fieldNodeInfo?.metadata !== void 0 && { metadata: fieldNodeInfo.metadata },
2908
4128
  type: propTypeNode,
2909
4129
  optional,
2910
4130
  constraints: fieldNodeInfo?.constraints ?? [],
@@ -2915,22 +4135,40 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
2915
4135
  visiting.delete(type);
2916
4136
  const objectNode = {
2917
4137
  kind: "object",
2918
- properties,
4138
+ properties: namedDecl !== void 0 && (ts3.isClassDeclaration(namedDecl) || ts3.isInterfaceDeclaration(namedDecl) || ts3.isTypeAliasDeclaration(namedDecl)) ? applyDiscriminatorToObjectProperties(
4139
+ properties,
4140
+ namedDecl,
4141
+ type,
4142
+ checker,
4143
+ file,
4144
+ collectedDiagnostics,
4145
+ metadataPolicy
4146
+ ) : properties,
2919
4147
  additionalProperties: true
2920
4148
  };
2921
- if (namedTypeName !== void 0 && shouldRegisterNamedType) {
4149
+ if (registryTypeName !== void 0 && shouldRegisterNamedType) {
2922
4150
  const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
2923
- typeRegistry[namedTypeName] = {
2924
- name: namedTypeName,
4151
+ const metadata = namedDecl !== void 0 ? resolveNodeMetadata(metadataPolicy, "type", registryTypeName, namedDecl, {
4152
+ checker,
4153
+ declaration: namedDecl,
4154
+ subjectType: type
4155
+ }) : void 0;
4156
+ typeRegistry[registryTypeName] = {
4157
+ name: registryTypeName,
4158
+ ...metadata !== void 0 && { metadata },
2925
4159
  type: objectNode,
2926
4160
  ...annotations !== void 0 && annotations.length > 0 && { annotations },
2927
4161
  provenance: provenanceForDeclaration(namedDecl, file)
2928
4162
  };
2929
- return { kind: "reference", name: namedTypeName, typeArguments: [] };
4163
+ return {
4164
+ kind: "reference",
4165
+ name: registryTypeName,
4166
+ typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
4167
+ };
2930
4168
  }
2931
4169
  return objectNode;
2932
4170
  }
2933
- function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, diagnostics, extensionRegistry) {
4171
+ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, metadataPolicy, diagnostics, extensionRegistry) {
2934
4172
  const symbols = [type.getSymbol(), type.aliasSymbol].filter(
2935
4173
  (s) => s?.declarations != null && s.declarations.length > 0
2936
4174
  );
@@ -2951,10 +4189,12 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
2951
4189
  visiting,
2952
4190
  diagnostics,
2953
4191
  hostType,
4192
+ metadataPolicy,
2954
4193
  extensionRegistry
2955
4194
  );
2956
4195
  if (fieldNode) {
2957
4196
  map.set(fieldNode.name, {
4197
+ ...fieldNode.metadata !== void 0 && { metadata: fieldNode.metadata },
2958
4198
  constraints: [...fieldNode.constraints],
2959
4199
  annotations: [...fieldNode.annotations],
2960
4200
  provenance: fieldNode.provenance
@@ -2972,6 +4212,7 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
2972
4212
  file,
2973
4213
  typeRegistry,
2974
4214
  visiting,
4215
+ metadataPolicy,
2975
4216
  checker.getTypeAtLocation(interfaceDecl),
2976
4217
  diagnostics,
2977
4218
  extensionRegistry
@@ -2985,6 +4226,7 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
2985
4226
  file,
2986
4227
  typeRegistry,
2987
4228
  visiting,
4229
+ metadataPolicy,
2988
4230
  checker.getTypeAtLocation(typeAliasDecl),
2989
4231
  diagnostics,
2990
4232
  extensionRegistry
@@ -3036,7 +4278,7 @@ function isNullishTypeNode(typeNode) {
3036
4278
  }
3037
4279
  return ts3.isLiteralTypeNode(typeNode) && (typeNode.literal.kind === ts3.SyntaxKind.NullKeyword || typeNode.literal.kind === ts3.SyntaxKind.UndefinedKeyword);
3038
4280
  }
3039
- function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, hostType, diagnostics, extensionRegistry) {
4281
+ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, metadataPolicy, hostType, diagnostics, extensionRegistry) {
3040
4282
  const map = /* @__PURE__ */ new Map();
3041
4283
  for (const member of members) {
3042
4284
  if (ts3.isPropertySignature(member)) {
@@ -3048,10 +4290,12 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, h
3048
4290
  visiting,
3049
4291
  diagnostics,
3050
4292
  hostType,
4293
+ metadataPolicy,
3051
4294
  extensionRegistry
3052
4295
  );
3053
4296
  if (fieldNode) {
3054
4297
  map.set(fieldNode.name, {
4298
+ ...fieldNode.metadata !== void 0 && { metadata: fieldNode.metadata },
3055
4299
  constraints: [...fieldNode.constraints],
3056
4300
  annotations: [...fieldNode.annotations],
3057
4301
  provenance: fieldNode.provenance
@@ -3082,6 +4326,7 @@ function extractTypeAliasConstraintNodes(typeNode, checker, file, extensionRegis
3082
4326
  {},
3083
4327
  /* @__PURE__ */ new Set(),
3084
4328
  aliasDecl.type,
4329
+ void 0,
3085
4330
  extensionRegistry
3086
4331
  );
3087
4332
  const constraints = extractJSDocConstraintNodes(
@@ -3267,19 +4512,37 @@ function findInterfaceByName(sourceFile, interfaceName) {
3267
4512
  function findTypeAliasByName(sourceFile, aliasName) {
3268
4513
  return findNodeByName(sourceFile, aliasName, ts4.isTypeAliasDeclaration, (n) => n.name.text);
3269
4514
  }
3270
- function analyzeNamedTypeToIR(filePath, typeName, extensionRegistry) {
4515
+ function analyzeNamedTypeToIR(filePath, typeName, extensionRegistry, metadataPolicy) {
3271
4516
  const ctx = createProgramContext(filePath);
3272
- return analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensionRegistry);
4517
+ return analyzeNamedTypeToIRFromProgramContext(
4518
+ ctx,
4519
+ filePath,
4520
+ typeName,
4521
+ extensionRegistry,
4522
+ metadataPolicy
4523
+ );
3273
4524
  }
3274
- function analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensionRegistry) {
4525
+ function analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensionRegistry, metadataPolicy) {
3275
4526
  const analysisFilePath = path.resolve(filePath);
3276
4527
  const classDecl = findClassByName(ctx.sourceFile, typeName);
3277
4528
  if (classDecl !== null) {
3278
- return analyzeClassToIR(classDecl, ctx.checker, analysisFilePath, extensionRegistry);
4529
+ return analyzeClassToIR(
4530
+ classDecl,
4531
+ ctx.checker,
4532
+ analysisFilePath,
4533
+ extensionRegistry,
4534
+ metadataPolicy
4535
+ );
3279
4536
  }
3280
4537
  const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
3281
4538
  if (interfaceDecl !== null) {
3282
- return analyzeInterfaceToIR(interfaceDecl, ctx.checker, analysisFilePath, extensionRegistry);
4539
+ return analyzeInterfaceToIR(
4540
+ interfaceDecl,
4541
+ ctx.checker,
4542
+ analysisFilePath,
4543
+ extensionRegistry,
4544
+ metadataPolicy
4545
+ );
3283
4546
  }
3284
4547
  const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
3285
4548
  if (typeAlias !== null) {
@@ -3287,7 +4550,8 @@ function analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensi
3287
4550
  typeAlias,
3288
4551
  ctx.checker,
3289
4552
  analysisFilePath,
3290
- extensionRegistry
4553
+ extensionRegistry,
4554
+ metadataPolicy
3291
4555
  );
3292
4556
  if (result.ok) {
3293
4557
  return result.analysis;
@@ -3382,7 +4646,11 @@ function generateClassSchemas(analysis, source, options) {
3382
4646
  if (errorDiagnostics !== void 0 && errorDiagnostics.length > 0) {
3383
4647
  throw new Error(formatValidationError(errorDiagnostics));
3384
4648
  }
3385
- const ir = canonicalizeTSDoc(analysis, source);
4649
+ const ir = canonicalizeTSDoc(
4650
+ analysis,
4651
+ source,
4652
+ options?.metadata !== void 0 ? { metadata: options.metadata } : void 0
4653
+ );
3386
4654
  const validationResult = validateIR(ir, {
3387
4655
  ...options?.extensionRegistry !== void 0 && {
3388
4656
  extensionRegistry: options.extensionRegistry
@@ -3419,13 +4687,15 @@ function generateSchemasFromClass(options) {
3419
4687
  classDecl,
3420
4688
  ctx.checker,
3421
4689
  options.filePath,
3422
- options.extensionRegistry
4690
+ options.extensionRegistry,
4691
+ options.metadata
3423
4692
  );
3424
4693
  return generateClassSchemas(
3425
4694
  analysis,
3426
4695
  { file: options.filePath },
3427
4696
  {
3428
4697
  extensionRegistry: options.extensionRegistry,
4698
+ metadata: options.metadata,
3429
4699
  vendorPrefix: options.vendorPrefix
3430
4700
  }
3431
4701
  );
@@ -3443,13 +4713,15 @@ function generateSchemasFromProgram(options) {
3443
4713
  ctx,
3444
4714
  options.filePath,
3445
4715
  options.typeName,
3446
- options.extensionRegistry
4716
+ options.extensionRegistry,
4717
+ options.metadata
3447
4718
  );
3448
4719
  return generateClassSchemas(
3449
4720
  analysis,
3450
4721
  { file: options.filePath },
3451
4722
  {
3452
4723
  extensionRegistry: options.extensionRegistry,
4724
+ metadata: options.metadata,
3453
4725
  vendorPrefix: options.vendorPrefix
3454
4726
  }
3455
4727
  );
@@ -3458,16 +4730,28 @@ function generateSchemasFromProgram(options) {
3458
4730
  // src/generators/mixed-authoring.ts
3459
4731
  function buildMixedAuthoringSchemas(options) {
3460
4732
  const { filePath, typeName, overlays, ...schemaOptions } = options;
3461
- const analysis = analyzeNamedTypeToIR(filePath, typeName, schemaOptions.extensionRegistry);
3462
- const composedAnalysis = composeAnalysisWithOverlays(analysis, overlays);
3463
- const ir = canonicalizeTSDoc(composedAnalysis, { file: filePath });
4733
+ const analysis = analyzeNamedTypeToIR(
4734
+ filePath,
4735
+ typeName,
4736
+ schemaOptions.extensionRegistry,
4737
+ schemaOptions.metadata
4738
+ );
4739
+ const composedAnalysis = composeAnalysisWithOverlays(analysis, overlays, schemaOptions.metadata);
4740
+ const ir = canonicalizeTSDoc(
4741
+ composedAnalysis,
4742
+ { file: filePath },
4743
+ schemaOptions.metadata !== void 0 ? { metadata: schemaOptions.metadata } : void 0
4744
+ );
3464
4745
  return {
3465
4746
  jsonSchema: generateJsonSchemaFromIR(ir, schemaOptions),
3466
4747
  uiSchema: generateUiSchemaFromIR(ir)
3467
4748
  };
3468
4749
  }
3469
- function composeAnalysisWithOverlays(analysis, overlays) {
3470
- const overlayIR = canonicalizeChainDSL(overlays);
4750
+ function composeAnalysisWithOverlays(analysis, overlays, metadata) {
4751
+ const overlayIR = canonicalizeChainDSL(
4752
+ overlays,
4753
+ metadata !== void 0 ? { metadata } : void 0
4754
+ );
3471
4755
  const overlayFields = collectOverlayFields(overlayIR.elements);
3472
4756
  if (overlayFields.length === 0) {
3473
4757
  return analysis;
@@ -3523,8 +4807,10 @@ function collectOverlayFields(elements) {
3523
4807
  }
3524
4808
  function mergeFieldOverlay(baseField, overlayField, typeRegistry) {
3525
4809
  assertSupportedOverlayField(baseField, overlayField);
4810
+ const metadata = mergeResolvedMetadata(baseField.metadata, overlayField.metadata);
3526
4811
  return {
3527
4812
  ...baseField,
4813
+ ...metadata !== void 0 && { metadata },
3528
4814
  type: mergeFieldType(baseField, overlayField, typeRegistry),
3529
4815
  annotations: mergeAnnotations(baseField.annotations, overlayField.annotations)
3530
4816
  };
@@ -3635,12 +4921,12 @@ function annotationKey(annotation) {
3635
4921
  function buildFormSchemas(form, options) {
3636
4922
  return {
3637
4923
  jsonSchema: generateJsonSchema(form, options),
3638
- uiSchema: generateUiSchema(form)
4924
+ uiSchema: generateUiSchema(form, options)
3639
4925
  };
3640
4926
  }
3641
4927
  function writeSchemas(form, options) {
3642
- const { outDir, name = "schema", indent = 2, vendorPrefix } = options;
3643
- const buildOptions = vendorPrefix === void 0 ? void 0 : { vendorPrefix };
4928
+ const { outDir, name = "schema", indent = 2, vendorPrefix, metadata } = options;
4929
+ const buildOptions = vendorPrefix === void 0 && metadata === void 0 ? void 0 : { vendorPrefix, metadata };
3644
4930
  const { jsonSchema, uiSchema: uiSchema2 } = buildFormSchemas(form, buildOptions);
3645
4931
  if (!fs.existsSync(outDir)) {
3646
4932
  fs.mkdirSync(outDir, { recursive: true });