@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.
- package/LICENSE +21 -0
- package/README.md +3 -2
- package/dist/analyzer/class-analyzer.d.ts +12 -6
- package/dist/analyzer/class-analyzer.d.ts.map +1 -1
- package/dist/analyzer/program.d.ts +3 -2
- package/dist/analyzer/program.d.ts.map +1 -1
- package/dist/browser.cjs +485 -76
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.js +486 -77
- package/dist/browser.js.map +1 -1
- package/dist/build-alpha.d.ts +18 -2
- package/dist/build-beta.d.ts +18 -2
- package/dist/build-internal.d.ts +18 -2
- package/dist/build.d.ts +18 -2
- package/dist/canonicalize/chain-dsl-canonicalizer.d.ts +5 -2
- package/dist/canonicalize/chain-dsl-canonicalizer.d.ts.map +1 -1
- package/dist/canonicalize/tsdoc-canonicalizer.d.ts +5 -1
- package/dist/canonicalize/tsdoc-canonicalizer.d.ts.map +1 -1
- package/dist/cli.cjs +1460 -143
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1458 -139
- package/dist/cli.js.map +1 -1
- package/dist/generators/class-schema.d.ts +6 -1
- package/dist/generators/class-schema.d.ts.map +1 -1
- package/dist/generators/method-schema.d.ts.map +1 -1
- package/dist/generators/mixed-authoring.d.ts.map +1 -1
- package/dist/index.cjs +1425 -141
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1425 -139
- package/dist/index.js.map +1 -1
- package/dist/internals.cjs +1416 -178
- package/dist/internals.cjs.map +1 -1
- package/dist/internals.js +1416 -176
- package/dist/internals.js.map +1 -1
- package/dist/json-schema/generator.d.ts +3 -1
- package/dist/json-schema/generator.d.ts.map +1 -1
- package/dist/json-schema/ir-generator.d.ts.map +1 -1
- package/dist/metadata/collision-guards.d.ts +3 -0
- package/dist/metadata/collision-guards.d.ts.map +1 -0
- package/dist/metadata/index.d.ts +7 -0
- package/dist/metadata/index.d.ts.map +1 -0
- package/dist/metadata/policy.d.ts +11 -0
- package/dist/metadata/policy.d.ts.map +1 -0
- package/dist/metadata/resolve.d.ts +20 -0
- package/dist/metadata/resolve.d.ts.map +1 -0
- package/dist/ui-schema/generator.d.ts +11 -2
- package/dist/ui-schema/generator.d.ts.map +1 -1
- package/dist/ui-schema/ir-generator.d.ts +2 -1
- package/dist/ui-schema/ir-generator.d.ts.map +1 -1
- package/package.json +7 -6
package/dist/internals.cjs
CHANGED
|
@@ -53,6 +53,282 @@ module.exports = __toCommonJS(internals_exports);
|
|
|
53
53
|
|
|
54
54
|
// src/canonicalize/chain-dsl-canonicalizer.ts
|
|
55
55
|
var import_internals = require("@formspec/core/internals");
|
|
56
|
+
|
|
57
|
+
// src/metadata/policy.ts
|
|
58
|
+
var NOOP_INFLECT = () => "";
|
|
59
|
+
function normalizePluralization(input) {
|
|
60
|
+
if (input?.mode === "infer-if-missing") {
|
|
61
|
+
return {
|
|
62
|
+
mode: "infer-if-missing",
|
|
63
|
+
infer: () => "",
|
|
64
|
+
inflect: input.inflect
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
if (input?.mode === "require-explicit") {
|
|
68
|
+
return {
|
|
69
|
+
mode: "require-explicit",
|
|
70
|
+
infer: () => "",
|
|
71
|
+
inflect: NOOP_INFLECT
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
mode: "disabled",
|
|
76
|
+
infer: () => "",
|
|
77
|
+
inflect: NOOP_INFLECT
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function normalizeScalarPolicy(input) {
|
|
81
|
+
if (input?.mode === "infer-if-missing") {
|
|
82
|
+
return {
|
|
83
|
+
mode: "infer-if-missing",
|
|
84
|
+
infer: input.infer,
|
|
85
|
+
pluralization: normalizePluralization(input.pluralization)
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
if (input?.mode === "require-explicit") {
|
|
89
|
+
return {
|
|
90
|
+
mode: "require-explicit",
|
|
91
|
+
infer: () => "",
|
|
92
|
+
pluralization: normalizePluralization(input.pluralization)
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
mode: "disabled",
|
|
97
|
+
infer: () => "",
|
|
98
|
+
pluralization: normalizePluralization(input?.pluralization)
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function normalizeDeclarationPolicy(input) {
|
|
102
|
+
return {
|
|
103
|
+
apiName: normalizeScalarPolicy(input?.apiName),
|
|
104
|
+
displayName: normalizeScalarPolicy(input?.displayName)
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function normalizeMetadataPolicy(input) {
|
|
108
|
+
return {
|
|
109
|
+
type: normalizeDeclarationPolicy(input?.type),
|
|
110
|
+
field: normalizeDeclarationPolicy(input?.field),
|
|
111
|
+
method: normalizeDeclarationPolicy(input?.method)
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function getDeclarationMetadataPolicy(policy, declarationKind) {
|
|
115
|
+
return policy[declarationKind];
|
|
116
|
+
}
|
|
117
|
+
function makeMetadataContext(surface, declarationKind, logicalName, buildContext) {
|
|
118
|
+
return {
|
|
119
|
+
surface,
|
|
120
|
+
declarationKind,
|
|
121
|
+
logicalName,
|
|
122
|
+
...buildContext !== void 0 && { buildContext }
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// src/metadata/resolve.ts
|
|
127
|
+
function toExplicitScalar(value) {
|
|
128
|
+
return value !== void 0 && value.trim() !== "" ? { value, source: "explicit" } : void 0;
|
|
129
|
+
}
|
|
130
|
+
function toExplicitResolvedMetadata(explicit) {
|
|
131
|
+
if (explicit === void 0) {
|
|
132
|
+
return void 0;
|
|
133
|
+
}
|
|
134
|
+
const apiName = toExplicitScalar(explicit.apiName);
|
|
135
|
+
const displayName = toExplicitScalar(explicit.displayName);
|
|
136
|
+
const apiNamePlural = toExplicitScalar(explicit.apiNamePlural);
|
|
137
|
+
const displayNamePlural = toExplicitScalar(explicit.displayNamePlural);
|
|
138
|
+
const metadata = {
|
|
139
|
+
...apiName !== void 0 && { apiName },
|
|
140
|
+
...displayName !== void 0 && { displayName },
|
|
141
|
+
...apiNamePlural !== void 0 && { apiNamePlural },
|
|
142
|
+
...displayNamePlural !== void 0 && { displayNamePlural }
|
|
143
|
+
};
|
|
144
|
+
return Object.keys(metadata).length > 0 ? metadata : void 0;
|
|
145
|
+
}
|
|
146
|
+
function resolveScalar(current, policy, context, metadataLabel) {
|
|
147
|
+
if (current !== void 0) {
|
|
148
|
+
return current;
|
|
149
|
+
}
|
|
150
|
+
if (policy.mode === "require-explicit") {
|
|
151
|
+
throw new Error(
|
|
152
|
+
`Metadata policy requires explicit ${metadataLabel} for ${context.declarationKind} "${context.logicalName}" on the ${context.surface} surface.`
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
if (policy.mode !== "infer-if-missing") {
|
|
156
|
+
return void 0;
|
|
157
|
+
}
|
|
158
|
+
const inferredValue = policy.infer(context);
|
|
159
|
+
return inferredValue.trim() !== "" ? { value: inferredValue, source: "inferred" } : void 0;
|
|
160
|
+
}
|
|
161
|
+
function resolvePlural(current, singular, policy, context, metadataLabel) {
|
|
162
|
+
if (current !== void 0) {
|
|
163
|
+
return current;
|
|
164
|
+
}
|
|
165
|
+
if (policy.mode === "require-explicit") {
|
|
166
|
+
throw new Error(
|
|
167
|
+
`Metadata policy requires explicit ${metadataLabel} for ${context.declarationKind} "${context.logicalName}" on the ${context.surface} surface.`
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
if (singular === void 0 || policy.mode !== "infer-if-missing") {
|
|
171
|
+
return void 0;
|
|
172
|
+
}
|
|
173
|
+
const pluralValue = policy.inflect({ ...context, singular: singular.value });
|
|
174
|
+
return pluralValue.trim() !== "" ? { value: pluralValue, source: "inferred" } : void 0;
|
|
175
|
+
}
|
|
176
|
+
function resolveResolvedMetadata(current, policy, context) {
|
|
177
|
+
const apiName = resolveScalar(current?.apiName, policy.apiName, context, "apiName");
|
|
178
|
+
const displayName = resolveScalar(
|
|
179
|
+
current?.displayName,
|
|
180
|
+
policy.displayName,
|
|
181
|
+
context,
|
|
182
|
+
"displayName"
|
|
183
|
+
);
|
|
184
|
+
const apiNamePlural = resolvePlural(
|
|
185
|
+
current?.apiNamePlural,
|
|
186
|
+
apiName,
|
|
187
|
+
policy.apiName.pluralization,
|
|
188
|
+
context,
|
|
189
|
+
"apiNamePlural"
|
|
190
|
+
);
|
|
191
|
+
const displayNamePlural = resolvePlural(
|
|
192
|
+
current?.displayNamePlural,
|
|
193
|
+
displayName,
|
|
194
|
+
policy.displayName.pluralization,
|
|
195
|
+
context,
|
|
196
|
+
"displayNamePlural"
|
|
197
|
+
);
|
|
198
|
+
if (apiName === void 0 && displayName === void 0 && apiNamePlural === void 0 && displayNamePlural === void 0) {
|
|
199
|
+
return void 0;
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
...apiName !== void 0 && { apiName },
|
|
203
|
+
...displayName !== void 0 && { displayName },
|
|
204
|
+
...apiNamePlural !== void 0 && { apiNamePlural },
|
|
205
|
+
...displayNamePlural !== void 0 && { displayNamePlural }
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
function resolveTypeNodeMetadata(type, options) {
|
|
209
|
+
switch (type.kind) {
|
|
210
|
+
case "array":
|
|
211
|
+
return {
|
|
212
|
+
...type,
|
|
213
|
+
items: resolveTypeNodeMetadata(type.items, options)
|
|
214
|
+
};
|
|
215
|
+
case "object":
|
|
216
|
+
return {
|
|
217
|
+
...type,
|
|
218
|
+
properties: type.properties.map((property) => resolveObjectPropertyMetadata(property, options))
|
|
219
|
+
};
|
|
220
|
+
case "record":
|
|
221
|
+
return {
|
|
222
|
+
...type,
|
|
223
|
+
valueType: resolveTypeNodeMetadata(type.valueType, options)
|
|
224
|
+
};
|
|
225
|
+
case "union":
|
|
226
|
+
return {
|
|
227
|
+
...type,
|
|
228
|
+
members: type.members.map((member) => resolveTypeNodeMetadata(member, options))
|
|
229
|
+
};
|
|
230
|
+
case "reference":
|
|
231
|
+
case "primitive":
|
|
232
|
+
case "enum":
|
|
233
|
+
case "dynamic":
|
|
234
|
+
case "custom":
|
|
235
|
+
return type;
|
|
236
|
+
default: {
|
|
237
|
+
const _exhaustive = type;
|
|
238
|
+
return _exhaustive;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
function resolveObjectPropertyMetadata(property, options) {
|
|
243
|
+
const metadata = resolveResolvedMetadata(property.metadata, options.policy.field, {
|
|
244
|
+
surface: options.surface,
|
|
245
|
+
declarationKind: "field",
|
|
246
|
+
logicalName: property.name,
|
|
247
|
+
...options.buildContext !== void 0 && { buildContext: options.buildContext }
|
|
248
|
+
});
|
|
249
|
+
return {
|
|
250
|
+
...property,
|
|
251
|
+
...metadata !== void 0 && { metadata },
|
|
252
|
+
type: resolveTypeNodeMetadata(property.type, options)
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
function resolveFieldMetadataNode(field, options) {
|
|
256
|
+
const metadata = resolveResolvedMetadata(field.metadata, options.policy.field, {
|
|
257
|
+
surface: options.surface,
|
|
258
|
+
declarationKind: "field",
|
|
259
|
+
logicalName: field.name,
|
|
260
|
+
...options.buildContext !== void 0 && { buildContext: options.buildContext }
|
|
261
|
+
});
|
|
262
|
+
return {
|
|
263
|
+
...field,
|
|
264
|
+
...metadata !== void 0 && { metadata },
|
|
265
|
+
type: resolveTypeNodeMetadata(field.type, options)
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
function resolveFormElementMetadata(element, options) {
|
|
269
|
+
switch (element.kind) {
|
|
270
|
+
case "field":
|
|
271
|
+
return resolveFieldMetadataNode(element, options);
|
|
272
|
+
case "group":
|
|
273
|
+
return {
|
|
274
|
+
...element,
|
|
275
|
+
elements: element.elements.map((child) => resolveFormElementMetadata(child, options))
|
|
276
|
+
};
|
|
277
|
+
case "conditional":
|
|
278
|
+
return {
|
|
279
|
+
...element,
|
|
280
|
+
elements: element.elements.map((child) => resolveFormElementMetadata(child, options))
|
|
281
|
+
};
|
|
282
|
+
default: {
|
|
283
|
+
const _exhaustive = element;
|
|
284
|
+
return _exhaustive;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
function resolveTypeDefinitionMetadata(typeDefinition, options) {
|
|
289
|
+
const metadata = resolveResolvedMetadata(typeDefinition.metadata, options.policy.type, {
|
|
290
|
+
surface: options.surface,
|
|
291
|
+
declarationKind: "type",
|
|
292
|
+
logicalName: typeDefinition.name,
|
|
293
|
+
...options.buildContext !== void 0 && { buildContext: options.buildContext }
|
|
294
|
+
});
|
|
295
|
+
return {
|
|
296
|
+
...typeDefinition,
|
|
297
|
+
...metadata !== void 0 && { metadata },
|
|
298
|
+
type: resolveTypeNodeMetadata(typeDefinition.type, options)
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
function resolveMetadata(explicit, policy, context) {
|
|
302
|
+
return resolveResolvedMetadata(toExplicitResolvedMetadata(explicit), policy, context);
|
|
303
|
+
}
|
|
304
|
+
function getSerializedName(logicalName, metadata) {
|
|
305
|
+
return metadata?.apiName?.value ?? logicalName;
|
|
306
|
+
}
|
|
307
|
+
function getDisplayName(metadata) {
|
|
308
|
+
return metadata?.displayName?.value;
|
|
309
|
+
}
|
|
310
|
+
function resolveFormIRMetadata(ir, options) {
|
|
311
|
+
const rootLogicalName = options.rootLogicalName ?? ir.name ?? "FormSpec";
|
|
312
|
+
const metadata = resolveResolvedMetadata(ir.metadata, options.policy.type, {
|
|
313
|
+
surface: options.surface,
|
|
314
|
+
declarationKind: "type",
|
|
315
|
+
logicalName: rootLogicalName,
|
|
316
|
+
...options.buildContext !== void 0 && { buildContext: options.buildContext }
|
|
317
|
+
});
|
|
318
|
+
return {
|
|
319
|
+
...ir,
|
|
320
|
+
...metadata !== void 0 && { metadata },
|
|
321
|
+
elements: ir.elements.map((element) => resolveFormElementMetadata(element, options)),
|
|
322
|
+
typeRegistry: Object.fromEntries(
|
|
323
|
+
Object.entries(ir.typeRegistry).map(([name, definition]) => [
|
|
324
|
+
name,
|
|
325
|
+
resolveTypeDefinitionMetadata(definition, options)
|
|
326
|
+
])
|
|
327
|
+
)
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// src/canonicalize/chain-dsl-canonicalizer.ts
|
|
56
332
|
var CHAIN_DSL_PROVENANCE = {
|
|
57
333
|
surface: "chain-dsl",
|
|
58
334
|
file: "",
|
|
@@ -68,57 +344,60 @@ function isConditional(el) {
|
|
|
68
344
|
function isField(el) {
|
|
69
345
|
return el._type === "field";
|
|
70
346
|
}
|
|
71
|
-
function canonicalizeChainDSL(form) {
|
|
347
|
+
function canonicalizeChainDSL(form, options) {
|
|
348
|
+
const metadataPolicy = normalizeMetadataPolicy(
|
|
349
|
+
options?.metadata ?? (0, import_internals._getFormSpecMetadataPolicy)(form)
|
|
350
|
+
);
|
|
72
351
|
return {
|
|
73
352
|
kind: "form-ir",
|
|
74
353
|
irVersion: import_internals.IR_VERSION,
|
|
75
|
-
elements: canonicalizeElements(form.elements),
|
|
354
|
+
elements: canonicalizeElements(form.elements, metadataPolicy),
|
|
76
355
|
rootAnnotations: [],
|
|
77
356
|
typeRegistry: {},
|
|
78
357
|
provenance: CHAIN_DSL_PROVENANCE
|
|
79
358
|
};
|
|
80
359
|
}
|
|
81
|
-
function canonicalizeElements(elements) {
|
|
82
|
-
return elements.map(canonicalizeElement);
|
|
360
|
+
function canonicalizeElements(elements, metadataPolicy) {
|
|
361
|
+
return elements.map((element) => canonicalizeElement(element, metadataPolicy));
|
|
83
362
|
}
|
|
84
|
-
function canonicalizeElement(element) {
|
|
363
|
+
function canonicalizeElement(element, metadataPolicy) {
|
|
85
364
|
if (isField(element)) {
|
|
86
|
-
return canonicalizeField(element);
|
|
365
|
+
return canonicalizeField(element, metadataPolicy);
|
|
87
366
|
}
|
|
88
367
|
if (isGroup(element)) {
|
|
89
|
-
return canonicalizeGroup(element);
|
|
368
|
+
return canonicalizeGroup(element, metadataPolicy);
|
|
90
369
|
}
|
|
91
370
|
if (isConditional(element)) {
|
|
92
|
-
return canonicalizeConditional(element);
|
|
371
|
+
return canonicalizeConditional(element, metadataPolicy);
|
|
93
372
|
}
|
|
94
373
|
const _exhaustive = element;
|
|
95
374
|
throw new Error(`Unknown element type: ${JSON.stringify(_exhaustive)}`);
|
|
96
375
|
}
|
|
97
|
-
function canonicalizeField(field) {
|
|
376
|
+
function canonicalizeField(field, metadataPolicy) {
|
|
98
377
|
switch (field._field) {
|
|
99
378
|
case "text":
|
|
100
|
-
return canonicalizeTextField(field);
|
|
379
|
+
return canonicalizeTextField(field, metadataPolicy);
|
|
101
380
|
case "number":
|
|
102
|
-
return canonicalizeNumberField(field);
|
|
381
|
+
return canonicalizeNumberField(field, metadataPolicy);
|
|
103
382
|
case "boolean":
|
|
104
|
-
return canonicalizeBooleanField(field);
|
|
383
|
+
return canonicalizeBooleanField(field, metadataPolicy);
|
|
105
384
|
case "enum":
|
|
106
|
-
return canonicalizeStaticEnumField(field);
|
|
385
|
+
return canonicalizeStaticEnumField(field, metadataPolicy);
|
|
107
386
|
case "dynamic_enum":
|
|
108
|
-
return canonicalizeDynamicEnumField(field);
|
|
387
|
+
return canonicalizeDynamicEnumField(field, metadataPolicy);
|
|
109
388
|
case "dynamic_schema":
|
|
110
|
-
return canonicalizeDynamicSchemaField(field);
|
|
389
|
+
return canonicalizeDynamicSchemaField(field, metadataPolicy);
|
|
111
390
|
case "array":
|
|
112
|
-
return canonicalizeArrayField(field);
|
|
391
|
+
return canonicalizeArrayField(field, metadataPolicy);
|
|
113
392
|
case "object":
|
|
114
|
-
return canonicalizeObjectField(field);
|
|
393
|
+
return canonicalizeObjectField(field, metadataPolicy);
|
|
115
394
|
default: {
|
|
116
395
|
const _exhaustive = field;
|
|
117
396
|
throw new Error(`Unknown field type: ${JSON.stringify(_exhaustive)}`);
|
|
118
397
|
}
|
|
119
398
|
}
|
|
120
399
|
}
|
|
121
|
-
function canonicalizeTextField(field) {
|
|
400
|
+
function canonicalizeTextField(field, metadataPolicy) {
|
|
122
401
|
const type = { kind: "primitive", primitiveKind: "string" };
|
|
123
402
|
const constraints = [];
|
|
124
403
|
if (field.minLength !== void 0) {
|
|
@@ -150,13 +429,14 @@ function canonicalizeTextField(field) {
|
|
|
150
429
|
}
|
|
151
430
|
return buildFieldNode(
|
|
152
431
|
field.name,
|
|
432
|
+
resolveFieldMetadata(field.name, field, metadataPolicy),
|
|
153
433
|
type,
|
|
154
434
|
field.required,
|
|
155
|
-
buildAnnotations(field
|
|
435
|
+
buildAnnotations(getExplicitDisplayName(field), field.placeholder),
|
|
156
436
|
constraints
|
|
157
437
|
);
|
|
158
438
|
}
|
|
159
|
-
function canonicalizeNumberField(field) {
|
|
439
|
+
function canonicalizeNumberField(field, metadataPolicy) {
|
|
160
440
|
const type = { kind: "primitive", primitiveKind: "number" };
|
|
161
441
|
const constraints = [];
|
|
162
442
|
if (field.min !== void 0) {
|
|
@@ -188,17 +468,24 @@ function canonicalizeNumberField(field) {
|
|
|
188
468
|
}
|
|
189
469
|
return buildFieldNode(
|
|
190
470
|
field.name,
|
|
471
|
+
resolveFieldMetadata(field.name, field, metadataPolicy),
|
|
191
472
|
type,
|
|
192
473
|
field.required,
|
|
193
|
-
buildAnnotations(field
|
|
474
|
+
buildAnnotations(getExplicitDisplayName(field)),
|
|
194
475
|
constraints
|
|
195
476
|
);
|
|
196
477
|
}
|
|
197
|
-
function canonicalizeBooleanField(field) {
|
|
478
|
+
function canonicalizeBooleanField(field, metadataPolicy) {
|
|
198
479
|
const type = { kind: "primitive", primitiveKind: "boolean" };
|
|
199
|
-
return buildFieldNode(
|
|
480
|
+
return buildFieldNode(
|
|
481
|
+
field.name,
|
|
482
|
+
resolveFieldMetadata(field.name, field, metadataPolicy),
|
|
483
|
+
type,
|
|
484
|
+
field.required,
|
|
485
|
+
buildAnnotations(getExplicitDisplayName(field))
|
|
486
|
+
);
|
|
200
487
|
}
|
|
201
|
-
function canonicalizeStaticEnumField(field) {
|
|
488
|
+
function canonicalizeStaticEnumField(field, metadataPolicy) {
|
|
202
489
|
const members = field.options.map((opt) => {
|
|
203
490
|
if (typeof opt === "string") {
|
|
204
491
|
return { value: opt };
|
|
@@ -206,28 +493,46 @@ function canonicalizeStaticEnumField(field) {
|
|
|
206
493
|
return { value: opt.id, displayName: opt.label };
|
|
207
494
|
});
|
|
208
495
|
const type = { kind: "enum", members };
|
|
209
|
-
return buildFieldNode(
|
|
496
|
+
return buildFieldNode(
|
|
497
|
+
field.name,
|
|
498
|
+
resolveFieldMetadata(field.name, field, metadataPolicy),
|
|
499
|
+
type,
|
|
500
|
+
field.required,
|
|
501
|
+
buildAnnotations(getExplicitDisplayName(field))
|
|
502
|
+
);
|
|
210
503
|
}
|
|
211
|
-
function canonicalizeDynamicEnumField(field) {
|
|
504
|
+
function canonicalizeDynamicEnumField(field, metadataPolicy) {
|
|
212
505
|
const type = {
|
|
213
506
|
kind: "dynamic",
|
|
214
507
|
dynamicKind: "enum",
|
|
215
508
|
sourceKey: field.source,
|
|
216
509
|
parameterFields: field.params ? [...field.params] : []
|
|
217
510
|
};
|
|
218
|
-
return buildFieldNode(
|
|
511
|
+
return buildFieldNode(
|
|
512
|
+
field.name,
|
|
513
|
+
resolveFieldMetadata(field.name, field, metadataPolicy),
|
|
514
|
+
type,
|
|
515
|
+
field.required,
|
|
516
|
+
buildAnnotations(getExplicitDisplayName(field))
|
|
517
|
+
);
|
|
219
518
|
}
|
|
220
|
-
function canonicalizeDynamicSchemaField(field) {
|
|
519
|
+
function canonicalizeDynamicSchemaField(field, metadataPolicy) {
|
|
221
520
|
const type = {
|
|
222
521
|
kind: "dynamic",
|
|
223
522
|
dynamicKind: "schema",
|
|
224
523
|
sourceKey: field.schemaSource,
|
|
225
524
|
parameterFields: []
|
|
226
525
|
};
|
|
227
|
-
return buildFieldNode(
|
|
526
|
+
return buildFieldNode(
|
|
527
|
+
field.name,
|
|
528
|
+
resolveFieldMetadata(field.name, field, metadataPolicy),
|
|
529
|
+
type,
|
|
530
|
+
field.required,
|
|
531
|
+
buildAnnotations(getExplicitDisplayName(field))
|
|
532
|
+
);
|
|
228
533
|
}
|
|
229
|
-
function canonicalizeArrayField(field) {
|
|
230
|
-
const itemProperties = buildObjectProperties(field.items);
|
|
534
|
+
function canonicalizeArrayField(field, metadataPolicy) {
|
|
535
|
+
const itemProperties = buildObjectProperties(field.items, metadataPolicy);
|
|
231
536
|
const itemsType = {
|
|
232
537
|
kind: "object",
|
|
233
538
|
properties: itemProperties,
|
|
@@ -255,37 +560,44 @@ function canonicalizeArrayField(field) {
|
|
|
255
560
|
}
|
|
256
561
|
return buildFieldNode(
|
|
257
562
|
field.name,
|
|
563
|
+
resolveFieldMetadata(field.name, field, metadataPolicy),
|
|
258
564
|
type,
|
|
259
565
|
field.required,
|
|
260
|
-
buildAnnotations(field
|
|
566
|
+
buildAnnotations(getExplicitDisplayName(field)),
|
|
261
567
|
constraints
|
|
262
568
|
);
|
|
263
569
|
}
|
|
264
|
-
function canonicalizeObjectField(field) {
|
|
265
|
-
const properties = buildObjectProperties(field.properties);
|
|
570
|
+
function canonicalizeObjectField(field, metadataPolicy) {
|
|
571
|
+
const properties = buildObjectProperties(field.properties, metadataPolicy);
|
|
266
572
|
const type = {
|
|
267
573
|
kind: "object",
|
|
268
574
|
properties,
|
|
269
575
|
additionalProperties: true
|
|
270
576
|
};
|
|
271
|
-
return buildFieldNode(
|
|
577
|
+
return buildFieldNode(
|
|
578
|
+
field.name,
|
|
579
|
+
resolveFieldMetadata(field.name, field, metadataPolicy),
|
|
580
|
+
type,
|
|
581
|
+
field.required,
|
|
582
|
+
buildAnnotations(getExplicitDisplayName(field))
|
|
583
|
+
);
|
|
272
584
|
}
|
|
273
|
-
function canonicalizeGroup(g) {
|
|
585
|
+
function canonicalizeGroup(g, metadataPolicy) {
|
|
274
586
|
return {
|
|
275
587
|
kind: "group",
|
|
276
588
|
label: g.label,
|
|
277
|
-
elements: canonicalizeElements(g.elements),
|
|
589
|
+
elements: canonicalizeElements(g.elements, metadataPolicy),
|
|
278
590
|
provenance: CHAIN_DSL_PROVENANCE
|
|
279
591
|
};
|
|
280
592
|
}
|
|
281
|
-
function canonicalizeConditional(c) {
|
|
593
|
+
function canonicalizeConditional(c, metadataPolicy) {
|
|
282
594
|
return {
|
|
283
595
|
kind: "conditional",
|
|
284
596
|
fieldName: c.field,
|
|
285
597
|
// Conditional values from the chain DSL are JSON-serializable primitives
|
|
286
598
|
// (strings, numbers, booleans) produced by the `is()` predicate helper.
|
|
287
599
|
value: assertJsonValue(c.value),
|
|
288
|
-
elements: canonicalizeElements(c.elements),
|
|
600
|
+
elements: canonicalizeElements(c.elements, metadataPolicy),
|
|
289
601
|
provenance: CHAIN_DSL_PROVENANCE
|
|
290
602
|
};
|
|
291
603
|
}
|
|
@@ -305,10 +617,11 @@ function assertJsonValue(v) {
|
|
|
305
617
|
}
|
|
306
618
|
throw new TypeError(`Conditional value is not a valid JsonValue: ${typeof v}`);
|
|
307
619
|
}
|
|
308
|
-
function buildFieldNode(name, type, required, annotations, constraints = []) {
|
|
620
|
+
function buildFieldNode(name, metadata, type, required, annotations, constraints = []) {
|
|
309
621
|
return {
|
|
310
622
|
kind: "field",
|
|
311
623
|
name,
|
|
624
|
+
...metadata !== void 0 && { metadata },
|
|
312
625
|
type,
|
|
313
626
|
required: required === true,
|
|
314
627
|
constraints,
|
|
@@ -338,13 +651,14 @@ function buildAnnotations(label, placeholder) {
|
|
|
338
651
|
}
|
|
339
652
|
return annotations;
|
|
340
653
|
}
|
|
341
|
-
function buildObjectProperties(elements, insideConditional = false) {
|
|
654
|
+
function buildObjectProperties(elements, metadataPolicy, insideConditional = false) {
|
|
342
655
|
const properties = [];
|
|
343
656
|
for (const el of elements) {
|
|
344
657
|
if (isField(el)) {
|
|
345
|
-
const fieldNode = canonicalizeField(el);
|
|
658
|
+
const fieldNode = canonicalizeField(el, metadataPolicy);
|
|
346
659
|
properties.push({
|
|
347
660
|
name: fieldNode.name,
|
|
661
|
+
...fieldNode.metadata !== void 0 && { metadata: fieldNode.metadata },
|
|
348
662
|
type: fieldNode.type,
|
|
349
663
|
// Fields inside a conditional branch are always optional in the
|
|
350
664
|
// data schema, regardless of their `required` flag — the condition
|
|
@@ -355,17 +669,34 @@ function buildObjectProperties(elements, insideConditional = false) {
|
|
|
355
669
|
provenance: CHAIN_DSL_PROVENANCE
|
|
356
670
|
});
|
|
357
671
|
} else if (isGroup(el)) {
|
|
358
|
-
properties.push(...buildObjectProperties(el.elements, insideConditional));
|
|
672
|
+
properties.push(...buildObjectProperties(el.elements, metadataPolicy, insideConditional));
|
|
359
673
|
} else if (isConditional(el)) {
|
|
360
|
-
properties.push(...buildObjectProperties(el.elements, true));
|
|
674
|
+
properties.push(...buildObjectProperties(el.elements, metadataPolicy, true));
|
|
361
675
|
}
|
|
362
676
|
}
|
|
363
677
|
return properties;
|
|
364
678
|
}
|
|
679
|
+
function getExplicitDisplayName(field) {
|
|
680
|
+
if (field.label !== void 0 && field.displayName !== void 0) {
|
|
681
|
+
throw new Error('Chain DSL fields cannot specify both "label" and "displayName".');
|
|
682
|
+
}
|
|
683
|
+
return field.displayName ?? field.label;
|
|
684
|
+
}
|
|
685
|
+
function resolveFieldMetadata(logicalName, field, metadataPolicy) {
|
|
686
|
+
const displayName = getExplicitDisplayName(field);
|
|
687
|
+
return resolveMetadata(
|
|
688
|
+
{
|
|
689
|
+
...field.apiName !== void 0 && { apiName: field.apiName },
|
|
690
|
+
...displayName !== void 0 && { displayName }
|
|
691
|
+
},
|
|
692
|
+
getDeclarationMetadataPolicy(metadataPolicy, "field"),
|
|
693
|
+
makeMetadataContext("chain-dsl", "field", logicalName)
|
|
694
|
+
);
|
|
695
|
+
}
|
|
365
696
|
|
|
366
697
|
// src/canonicalize/tsdoc-canonicalizer.ts
|
|
367
698
|
var import_internals2 = require("@formspec/core/internals");
|
|
368
|
-
function canonicalizeTSDoc(analysis, source) {
|
|
699
|
+
function canonicalizeTSDoc(analysis, source, options) {
|
|
369
700
|
const file = source?.file ?? "";
|
|
370
701
|
const provenance = {
|
|
371
702
|
surface: "tsdoc",
|
|
@@ -374,15 +705,21 @@ function canonicalizeTSDoc(analysis, source) {
|
|
|
374
705
|
column: 0
|
|
375
706
|
};
|
|
376
707
|
const elements = assembleElements(analysis.fields, analysis.fieldLayouts, provenance);
|
|
377
|
-
|
|
708
|
+
const ir = {
|
|
378
709
|
kind: "form-ir",
|
|
710
|
+
name: analysis.name,
|
|
379
711
|
irVersion: import_internals2.IR_VERSION,
|
|
380
712
|
elements,
|
|
713
|
+
...analysis.metadata !== void 0 && { metadata: analysis.metadata },
|
|
381
714
|
typeRegistry: analysis.typeRegistry,
|
|
382
715
|
...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { rootAnnotations: analysis.annotations },
|
|
383
716
|
...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { annotations: analysis.annotations },
|
|
384
717
|
provenance
|
|
385
718
|
};
|
|
719
|
+
return resolveFormIRMetadata(ir, {
|
|
720
|
+
policy: normalizeMetadataPolicy(options?.metadata),
|
|
721
|
+
surface: "tsdoc"
|
|
722
|
+
});
|
|
386
723
|
}
|
|
387
724
|
function assembleElements(fields, layouts, provenance) {
|
|
388
725
|
const elements = [];
|
|
@@ -445,6 +782,7 @@ var path = __toESM(require("path"), 1);
|
|
|
445
782
|
|
|
446
783
|
// src/analyzer/class-analyzer.ts
|
|
447
784
|
var ts3 = __toESM(require("typescript"), 1);
|
|
785
|
+
var import_internal2 = require("@formspec/analysis/internal");
|
|
448
786
|
|
|
449
787
|
// src/analyzer/jsdoc-constraints.ts
|
|
450
788
|
var ts2 = __toESM(require("typescript"), 1);
|
|
@@ -1322,7 +1660,76 @@ function makeParseOptions(extensionRegistry, fieldType, checker, subjectType, ho
|
|
|
1322
1660
|
...hostType !== void 0 && { hostType }
|
|
1323
1661
|
};
|
|
1324
1662
|
}
|
|
1325
|
-
function
|
|
1663
|
+
function makeExplicitScalarMetadata(value) {
|
|
1664
|
+
return value === void 0 || value === "" ? void 0 : { value, source: "explicit" };
|
|
1665
|
+
}
|
|
1666
|
+
function extractExplicitMetadata(node) {
|
|
1667
|
+
let apiName;
|
|
1668
|
+
let displayName;
|
|
1669
|
+
let apiNamePlural;
|
|
1670
|
+
let displayNamePlural;
|
|
1671
|
+
for (const tag of getLeadingParsedTags(node)) {
|
|
1672
|
+
const value = tag.argumentText.trim();
|
|
1673
|
+
if (value === "") {
|
|
1674
|
+
continue;
|
|
1675
|
+
}
|
|
1676
|
+
if (tag.normalizedTagName === "apiName") {
|
|
1677
|
+
if (tag.target === null) {
|
|
1678
|
+
apiName ??= value;
|
|
1679
|
+
} else if (tag.target.kind === "variant") {
|
|
1680
|
+
if (tag.target.rawText === "singular") {
|
|
1681
|
+
apiName ??= value;
|
|
1682
|
+
} else if (tag.target.rawText === "plural") {
|
|
1683
|
+
apiNamePlural ??= value;
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
continue;
|
|
1687
|
+
}
|
|
1688
|
+
if (tag.normalizedTagName === "displayName") {
|
|
1689
|
+
if (tag.target === null) {
|
|
1690
|
+
displayName ??= value;
|
|
1691
|
+
} else if (tag.target.kind === "variant") {
|
|
1692
|
+
if (tag.target.rawText === "singular") {
|
|
1693
|
+
displayName ??= value;
|
|
1694
|
+
} else if (tag.target.rawText === "plural") {
|
|
1695
|
+
displayNamePlural ??= value;
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
const resolvedApiName = makeExplicitScalarMetadata(apiName);
|
|
1701
|
+
const resolvedDisplayName = makeExplicitScalarMetadata(displayName);
|
|
1702
|
+
const resolvedApiNamePlural = makeExplicitScalarMetadata(apiNamePlural);
|
|
1703
|
+
const resolvedDisplayNamePlural = makeExplicitScalarMetadata(displayNamePlural);
|
|
1704
|
+
const metadata = {
|
|
1705
|
+
...resolvedApiName !== void 0 && { apiName: resolvedApiName },
|
|
1706
|
+
...resolvedDisplayName !== void 0 && { displayName: resolvedDisplayName },
|
|
1707
|
+
...resolvedApiNamePlural !== void 0 && { apiNamePlural: resolvedApiNamePlural },
|
|
1708
|
+
...resolvedDisplayNamePlural !== void 0 && {
|
|
1709
|
+
displayNamePlural: resolvedDisplayNamePlural
|
|
1710
|
+
}
|
|
1711
|
+
};
|
|
1712
|
+
return Object.keys(metadata).length === 0 ? void 0 : metadata;
|
|
1713
|
+
}
|
|
1714
|
+
function resolveNodeMetadata(metadataPolicy, declarationKind, logicalName, node, buildContext) {
|
|
1715
|
+
const explicit = extractExplicitMetadata(node);
|
|
1716
|
+
return resolveMetadata(
|
|
1717
|
+
{
|
|
1718
|
+
...explicit?.apiName !== void 0 && { apiName: explicit.apiName.value },
|
|
1719
|
+
...explicit?.displayName !== void 0 && { displayName: explicit.displayName.value },
|
|
1720
|
+
...explicit?.apiNamePlural !== void 0 && {
|
|
1721
|
+
apiNamePlural: explicit.apiNamePlural.value
|
|
1722
|
+
},
|
|
1723
|
+
...explicit?.displayNamePlural !== void 0 && {
|
|
1724
|
+
displayNamePlural: explicit.displayNamePlural.value
|
|
1725
|
+
}
|
|
1726
|
+
},
|
|
1727
|
+
getDeclarationMetadataPolicy(metadataPolicy, declarationKind),
|
|
1728
|
+
makeMetadataContext("tsdoc", declarationKind, logicalName, buildContext)
|
|
1729
|
+
);
|
|
1730
|
+
}
|
|
1731
|
+
function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry, metadataPolicy) {
|
|
1732
|
+
const normalizedMetadataPolicy = normalizeMetadataPolicy(metadataPolicy);
|
|
1326
1733
|
const name = classDecl.name?.text ?? "AnonymousClass";
|
|
1327
1734
|
const fields = [];
|
|
1328
1735
|
const fieldLayouts = [];
|
|
@@ -1349,6 +1756,7 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
|
|
|
1349
1756
|
visiting,
|
|
1350
1757
|
diagnostics,
|
|
1351
1758
|
classType,
|
|
1759
|
+
normalizedMetadataPolicy,
|
|
1352
1760
|
extensionRegistry
|
|
1353
1761
|
);
|
|
1354
1762
|
if (fieldNode) {
|
|
@@ -1367,9 +1775,25 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
|
|
|
1367
1775
|
}
|
|
1368
1776
|
}
|
|
1369
1777
|
}
|
|
1778
|
+
const specializedFields = applyDeclarationDiscriminatorToFields(
|
|
1779
|
+
fields,
|
|
1780
|
+
classDecl,
|
|
1781
|
+
classType,
|
|
1782
|
+
checker,
|
|
1783
|
+
file,
|
|
1784
|
+
diagnostics,
|
|
1785
|
+
normalizedMetadataPolicy
|
|
1786
|
+
);
|
|
1787
|
+
const metadata = resolveNodeMetadata(normalizedMetadataPolicy, "type", name, classDecl, {
|
|
1788
|
+
checker,
|
|
1789
|
+
declaration: classDecl,
|
|
1790
|
+
subjectType: classType,
|
|
1791
|
+
hostType: classType
|
|
1792
|
+
});
|
|
1370
1793
|
return {
|
|
1371
1794
|
name,
|
|
1372
|
-
|
|
1795
|
+
...metadata !== void 0 && { metadata },
|
|
1796
|
+
fields: specializedFields,
|
|
1373
1797
|
fieldLayouts,
|
|
1374
1798
|
typeRegistry,
|
|
1375
1799
|
...annotations.length > 0 && { annotations },
|
|
@@ -1378,7 +1802,8 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
|
|
|
1378
1802
|
staticMethods
|
|
1379
1803
|
};
|
|
1380
1804
|
}
|
|
1381
|
-
function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegistry) {
|
|
1805
|
+
function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegistry, metadataPolicy) {
|
|
1806
|
+
const normalizedMetadataPolicy = normalizeMetadataPolicy(metadataPolicy);
|
|
1382
1807
|
const name = interfaceDecl.name.text;
|
|
1383
1808
|
const fields = [];
|
|
1384
1809
|
const typeRegistry = {};
|
|
@@ -1402,6 +1827,78 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
|
|
|
1402
1827
|
visiting,
|
|
1403
1828
|
diagnostics,
|
|
1404
1829
|
interfaceType,
|
|
1830
|
+
normalizedMetadataPolicy,
|
|
1831
|
+
extensionRegistry
|
|
1832
|
+
);
|
|
1833
|
+
if (fieldNode) {
|
|
1834
|
+
fields.push(fieldNode);
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
const specializedFields = applyDeclarationDiscriminatorToFields(
|
|
1839
|
+
fields,
|
|
1840
|
+
interfaceDecl,
|
|
1841
|
+
interfaceType,
|
|
1842
|
+
checker,
|
|
1843
|
+
file,
|
|
1844
|
+
diagnostics,
|
|
1845
|
+
normalizedMetadataPolicy
|
|
1846
|
+
);
|
|
1847
|
+
const fieldLayouts = specializedFields.map(() => ({}));
|
|
1848
|
+
const metadata = resolveNodeMetadata(normalizedMetadataPolicy, "type", name, interfaceDecl, {
|
|
1849
|
+
checker,
|
|
1850
|
+
declaration: interfaceDecl,
|
|
1851
|
+
subjectType: interfaceType,
|
|
1852
|
+
hostType: interfaceType
|
|
1853
|
+
});
|
|
1854
|
+
return {
|
|
1855
|
+
name,
|
|
1856
|
+
...metadata !== void 0 && { metadata },
|
|
1857
|
+
fields: specializedFields,
|
|
1858
|
+
fieldLayouts,
|
|
1859
|
+
typeRegistry,
|
|
1860
|
+
...annotations.length > 0 && { annotations },
|
|
1861
|
+
...diagnostics.length > 0 && { diagnostics },
|
|
1862
|
+
instanceMethods: [],
|
|
1863
|
+
staticMethods: []
|
|
1864
|
+
};
|
|
1865
|
+
}
|
|
1866
|
+
function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry, metadataPolicy) {
|
|
1867
|
+
if (!ts3.isTypeLiteralNode(typeAlias.type)) {
|
|
1868
|
+
const sourceFile = typeAlias.getSourceFile();
|
|
1869
|
+
const { line } = sourceFile.getLineAndCharacterOfPosition(typeAlias.getStart());
|
|
1870
|
+
const kindDesc = ts3.SyntaxKind[typeAlias.type.kind] ?? "unknown";
|
|
1871
|
+
return {
|
|
1872
|
+
ok: false,
|
|
1873
|
+
error: `Type alias "${typeAlias.name.text}" at line ${String(line + 1)} is not an object type literal (found ${kindDesc})`
|
|
1874
|
+
};
|
|
1875
|
+
}
|
|
1876
|
+
const typeLiteral = typeAlias.type;
|
|
1877
|
+
const normalizedMetadataPolicy = normalizeMetadataPolicy(metadataPolicy);
|
|
1878
|
+
const name = typeAlias.name.text;
|
|
1879
|
+
const fields = [];
|
|
1880
|
+
const typeRegistry = {};
|
|
1881
|
+
const diagnostics = [];
|
|
1882
|
+
const aliasType = checker.getTypeAtLocation(typeAlias);
|
|
1883
|
+
const typeAliasDoc = extractJSDocParseResult(
|
|
1884
|
+
typeAlias,
|
|
1885
|
+
file,
|
|
1886
|
+
makeParseOptions(extensionRegistry, void 0, checker, aliasType, aliasType)
|
|
1887
|
+
);
|
|
1888
|
+
const annotations = [...typeAliasDoc.annotations];
|
|
1889
|
+
diagnostics.push(...typeAliasDoc.diagnostics);
|
|
1890
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
1891
|
+
for (const member of typeLiteral.members) {
|
|
1892
|
+
if (ts3.isPropertySignature(member)) {
|
|
1893
|
+
const fieldNode = analyzeInterfacePropertyToIR(
|
|
1894
|
+
member,
|
|
1895
|
+
checker,
|
|
1896
|
+
file,
|
|
1897
|
+
typeRegistry,
|
|
1898
|
+
visiting,
|
|
1899
|
+
diagnostics,
|
|
1900
|
+
aliasType,
|
|
1901
|
+
normalizedMetadataPolicy,
|
|
1405
1902
|
extensionRegistry
|
|
1406
1903
|
);
|
|
1407
1904
|
if (fieldNode) {
|
|
@@ -1409,73 +1906,474 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
|
|
|
1409
1906
|
}
|
|
1410
1907
|
}
|
|
1411
1908
|
}
|
|
1412
|
-
const
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1909
|
+
const specializedFields = applyDeclarationDiscriminatorToFields(
|
|
1910
|
+
fields,
|
|
1911
|
+
typeAlias,
|
|
1912
|
+
aliasType,
|
|
1913
|
+
checker,
|
|
1914
|
+
file,
|
|
1915
|
+
diagnostics,
|
|
1916
|
+
normalizedMetadataPolicy
|
|
1917
|
+
);
|
|
1918
|
+
const metadata = resolveNodeMetadata(normalizedMetadataPolicy, "type", name, typeAlias, {
|
|
1919
|
+
checker,
|
|
1920
|
+
declaration: typeAlias,
|
|
1921
|
+
subjectType: aliasType,
|
|
1922
|
+
hostType: aliasType
|
|
1923
|
+
});
|
|
1924
|
+
return {
|
|
1925
|
+
ok: true,
|
|
1926
|
+
analysis: {
|
|
1927
|
+
name,
|
|
1928
|
+
...metadata !== void 0 && { metadata },
|
|
1929
|
+
fields: specializedFields,
|
|
1930
|
+
fieldLayouts: specializedFields.map(() => ({})),
|
|
1931
|
+
typeRegistry,
|
|
1932
|
+
...annotations.length > 0 && { annotations },
|
|
1933
|
+
...diagnostics.length > 0 && { diagnostics },
|
|
1934
|
+
instanceMethods: [],
|
|
1935
|
+
staticMethods: []
|
|
1936
|
+
}
|
|
1937
|
+
};
|
|
1938
|
+
}
|
|
1939
|
+
function makeAnalysisDiagnostic(code, message, primaryLocation, relatedLocations = []) {
|
|
1940
|
+
return {
|
|
1941
|
+
code,
|
|
1942
|
+
message,
|
|
1943
|
+
severity: "error",
|
|
1944
|
+
primaryLocation,
|
|
1945
|
+
relatedLocations
|
|
1946
|
+
};
|
|
1947
|
+
}
|
|
1948
|
+
function getLeadingParsedTags(node) {
|
|
1949
|
+
const sourceFile = node.getSourceFile();
|
|
1950
|
+
const sourceText = sourceFile.getFullText();
|
|
1951
|
+
const commentRanges = ts3.getLeadingCommentRanges(sourceText, node.getFullStart());
|
|
1952
|
+
if (commentRanges === void 0) {
|
|
1953
|
+
return [];
|
|
1954
|
+
}
|
|
1955
|
+
const parsedTags = [];
|
|
1956
|
+
for (const range of commentRanges) {
|
|
1957
|
+
if (range.kind !== ts3.SyntaxKind.MultiLineCommentTrivia) {
|
|
1958
|
+
continue;
|
|
1959
|
+
}
|
|
1960
|
+
const commentText = sourceText.slice(range.pos, range.end);
|
|
1961
|
+
if (!commentText.startsWith("/**")) {
|
|
1962
|
+
continue;
|
|
1963
|
+
}
|
|
1964
|
+
parsedTags.push(...(0, import_internal2.parseCommentBlock)(commentText, { offset: range.pos }).tags);
|
|
1965
|
+
}
|
|
1966
|
+
return parsedTags;
|
|
1967
|
+
}
|
|
1968
|
+
function resolveDiscriminatorProperty(node, checker, fieldName) {
|
|
1969
|
+
const subjectType = checker.getTypeAtLocation(node);
|
|
1970
|
+
const propertySymbol = subjectType.getProperty(fieldName);
|
|
1971
|
+
if (propertySymbol === void 0) {
|
|
1972
|
+
return null;
|
|
1973
|
+
}
|
|
1974
|
+
const declaration = propertySymbol.valueDeclaration ?? propertySymbol.declarations?.find(
|
|
1975
|
+
(candidate) => ts3.isPropertyDeclaration(candidate) || ts3.isPropertySignature(candidate)
|
|
1976
|
+
) ?? propertySymbol.declarations?.[0];
|
|
1977
|
+
return {
|
|
1978
|
+
declaration,
|
|
1979
|
+
type: checker.getTypeOfSymbolAtLocation(propertySymbol, declaration ?? node),
|
|
1980
|
+
optional: !!(propertySymbol.flags & ts3.SymbolFlags.Optional) || declaration !== void 0 && "questionToken" in declaration && declaration.questionToken !== void 0
|
|
1981
|
+
};
|
|
1982
|
+
}
|
|
1983
|
+
function isLocalTypeParameterName(node, typeParameterName) {
|
|
1984
|
+
return node.typeParameters?.some((typeParameter) => typeParameter.name.text === typeParameterName) ?? false;
|
|
1985
|
+
}
|
|
1986
|
+
function isNullishSemanticType(type) {
|
|
1987
|
+
if (type.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined | ts3.TypeFlags.Void | ts3.TypeFlags.Unknown | ts3.TypeFlags.Any)) {
|
|
1988
|
+
return true;
|
|
1989
|
+
}
|
|
1990
|
+
return type.isUnion() && type.types.some((member) => isNullishSemanticType(member));
|
|
1991
|
+
}
|
|
1992
|
+
function isStringLikeSemanticType(type) {
|
|
1993
|
+
if (type.flags & ts3.TypeFlags.StringLike) {
|
|
1994
|
+
return true;
|
|
1995
|
+
}
|
|
1996
|
+
if (type.isUnion()) {
|
|
1997
|
+
return type.types.length > 0 && type.types.every((member) => isStringLikeSemanticType(member));
|
|
1998
|
+
}
|
|
1999
|
+
return false;
|
|
2000
|
+
}
|
|
2001
|
+
function extractDiscriminatorDirective(node, file, diagnostics) {
|
|
2002
|
+
const discriminatorTags = getLeadingParsedTags(node).filter(
|
|
2003
|
+
(tag) => tag.normalizedTagName === "discriminator"
|
|
2004
|
+
);
|
|
2005
|
+
if (discriminatorTags.length === 0) {
|
|
2006
|
+
return null;
|
|
2007
|
+
}
|
|
2008
|
+
const [firstTag, ...duplicateTags] = discriminatorTags;
|
|
2009
|
+
for (const _duplicateTag of duplicateTags) {
|
|
2010
|
+
diagnostics.push(
|
|
2011
|
+
makeAnalysisDiagnostic(
|
|
2012
|
+
"DUPLICATE_TAG",
|
|
2013
|
+
'Duplicate "@discriminator" tag. Only one discriminator declaration is allowed per declaration.',
|
|
2014
|
+
provenanceForNode(node, file)
|
|
2015
|
+
)
|
|
2016
|
+
);
|
|
2017
|
+
}
|
|
2018
|
+
if (firstTag === void 0) {
|
|
2019
|
+
return null;
|
|
2020
|
+
}
|
|
2021
|
+
const firstTarget = firstTag.target;
|
|
2022
|
+
if (firstTarget?.path === null || firstTarget?.valid !== true) {
|
|
2023
|
+
diagnostics.push(
|
|
2024
|
+
makeAnalysisDiagnostic(
|
|
2025
|
+
"INVALID_TAG_ARGUMENT",
|
|
2026
|
+
'Tag "@discriminator" requires a direct path target like ":kind".',
|
|
2027
|
+
provenanceForNode(node, file)
|
|
2028
|
+
)
|
|
2029
|
+
);
|
|
2030
|
+
return null;
|
|
2031
|
+
}
|
|
2032
|
+
if (firstTarget.path.segments.length !== 1) {
|
|
2033
|
+
diagnostics.push(
|
|
2034
|
+
makeAnalysisDiagnostic(
|
|
2035
|
+
"INVALID_TAG_ARGUMENT",
|
|
2036
|
+
'Tag "@discriminator" only supports direct property targets in v1; nested paths are out of scope.',
|
|
2037
|
+
provenanceForNode(node, file)
|
|
2038
|
+
)
|
|
2039
|
+
);
|
|
2040
|
+
return null;
|
|
2041
|
+
}
|
|
2042
|
+
const typeParameterName = firstTag.argumentText.trim();
|
|
2043
|
+
if (!/^[A-Za-z_$][\w$]*$/u.test(typeParameterName)) {
|
|
2044
|
+
diagnostics.push(
|
|
2045
|
+
makeAnalysisDiagnostic(
|
|
2046
|
+
"INVALID_TAG_ARGUMENT",
|
|
2047
|
+
'Tag "@discriminator" requires a local type parameter name as its source operand.',
|
|
2048
|
+
provenanceForNode(node, file)
|
|
2049
|
+
)
|
|
2050
|
+
);
|
|
2051
|
+
return null;
|
|
2052
|
+
}
|
|
2053
|
+
return {
|
|
2054
|
+
fieldName: firstTarget.path.segments[0] ?? firstTarget.rawText,
|
|
2055
|
+
typeParameterName,
|
|
2056
|
+
provenance: provenanceForNode(node, file)
|
|
2057
|
+
};
|
|
2058
|
+
}
|
|
2059
|
+
function validateDiscriminatorDirective(node, checker, file, diagnostics) {
|
|
2060
|
+
const directive = extractDiscriminatorDirective(node, file, diagnostics);
|
|
2061
|
+
if (directive === null) {
|
|
2062
|
+
return null;
|
|
2063
|
+
}
|
|
2064
|
+
if (!isLocalTypeParameterName(node, directive.typeParameterName)) {
|
|
2065
|
+
diagnostics.push(
|
|
2066
|
+
makeAnalysisDiagnostic(
|
|
2067
|
+
"INVALID_TAG_ARGUMENT",
|
|
2068
|
+
`Tag "@discriminator" references "${directive.typeParameterName}", but the source operand must be a type parameter declared on the same declaration.`,
|
|
2069
|
+
directive.provenance
|
|
2070
|
+
)
|
|
2071
|
+
);
|
|
2072
|
+
return null;
|
|
2073
|
+
}
|
|
2074
|
+
const property = resolveDiscriminatorProperty(node, checker, directive.fieldName);
|
|
2075
|
+
if (property === null) {
|
|
2076
|
+
diagnostics.push(
|
|
2077
|
+
makeAnalysisDiagnostic(
|
|
2078
|
+
"UNKNOWN_PATH_TARGET",
|
|
2079
|
+
`Tag "@discriminator" targets "${directive.fieldName}", but no direct property with that name exists on this declaration.`,
|
|
2080
|
+
directive.provenance
|
|
2081
|
+
)
|
|
2082
|
+
);
|
|
2083
|
+
return null;
|
|
2084
|
+
}
|
|
2085
|
+
if (property.optional) {
|
|
2086
|
+
diagnostics.push(
|
|
2087
|
+
makeAnalysisDiagnostic(
|
|
2088
|
+
"TYPE_MISMATCH",
|
|
2089
|
+
`Discriminator field "${directive.fieldName}" must be required; optional discriminator fields are not supported.`,
|
|
2090
|
+
directive.provenance,
|
|
2091
|
+
property.declaration !== void 0 ? [provenanceForNode(property.declaration, file)] : []
|
|
2092
|
+
)
|
|
2093
|
+
);
|
|
2094
|
+
return null;
|
|
2095
|
+
}
|
|
2096
|
+
if (isNullishSemanticType(property.type)) {
|
|
2097
|
+
diagnostics.push(
|
|
2098
|
+
makeAnalysisDiagnostic(
|
|
2099
|
+
"TYPE_MISMATCH",
|
|
2100
|
+
`Discriminator field "${directive.fieldName}" must not be nullable.`,
|
|
2101
|
+
directive.provenance,
|
|
2102
|
+
property.declaration !== void 0 ? [provenanceForNode(property.declaration, file)] : []
|
|
2103
|
+
)
|
|
2104
|
+
);
|
|
2105
|
+
return null;
|
|
2106
|
+
}
|
|
2107
|
+
if (!isStringLikeSemanticType(property.type)) {
|
|
2108
|
+
diagnostics.push(
|
|
2109
|
+
makeAnalysisDiagnostic(
|
|
2110
|
+
"TYPE_MISMATCH",
|
|
2111
|
+
`Discriminator field "${directive.fieldName}" must be string-like.`,
|
|
2112
|
+
directive.provenance,
|
|
2113
|
+
property.declaration !== void 0 ? [provenanceForNode(property.declaration, file)] : []
|
|
2114
|
+
)
|
|
2115
|
+
);
|
|
2116
|
+
return null;
|
|
2117
|
+
}
|
|
2118
|
+
return directive;
|
|
2119
|
+
}
|
|
2120
|
+
function getConcreteTypeArgumentForDiscriminator(node, subjectType, checker, typeParameterName) {
|
|
2121
|
+
const typeParameterIndex = node.typeParameters?.findIndex(
|
|
2122
|
+
(typeParameter) => typeParameter.name.text === typeParameterName
|
|
2123
|
+
) ?? -1;
|
|
2124
|
+
if (typeParameterIndex < 0) {
|
|
2125
|
+
return null;
|
|
2126
|
+
}
|
|
2127
|
+
const referenceTypeArguments = (isTypeReference(subjectType) ? subjectType.typeArguments : void 0) ?? subjectType.aliasTypeArguments;
|
|
2128
|
+
if (referenceTypeArguments?.[typeParameterIndex] !== void 0) {
|
|
2129
|
+
return referenceTypeArguments[typeParameterIndex] ?? null;
|
|
2130
|
+
}
|
|
2131
|
+
const localTypeParameter = node.typeParameters?.[typeParameterIndex];
|
|
2132
|
+
return localTypeParameter === void 0 ? null : checker.getTypeAtLocation(localTypeParameter);
|
|
2133
|
+
}
|
|
2134
|
+
function resolveLiteralDiscriminatorPropertyValue(boundType, fieldName, checker, provenance, diagnostics) {
|
|
2135
|
+
const propertySymbol = boundType.getProperty(fieldName);
|
|
2136
|
+
if (propertySymbol === void 0) {
|
|
2137
|
+
return void 0;
|
|
2138
|
+
}
|
|
2139
|
+
const declaration = propertySymbol.valueDeclaration ?? propertySymbol.declarations?.[0];
|
|
2140
|
+
const anchorNode = declaration ?? boundType.symbol.declarations?.[0] ?? null;
|
|
2141
|
+
const resolvedAnchorNode = anchorNode ?? resolveNamedDiscriminatorDeclaration(boundType, checker);
|
|
2142
|
+
if (resolvedAnchorNode === null) {
|
|
2143
|
+
return void 0;
|
|
2144
|
+
}
|
|
2145
|
+
const propertyType = checker.getTypeOfSymbolAtLocation(
|
|
2146
|
+
propertySymbol,
|
|
2147
|
+
resolvedAnchorNode
|
|
2148
|
+
);
|
|
2149
|
+
if (propertyType.isStringLiteral()) {
|
|
2150
|
+
return propertyType.value;
|
|
2151
|
+
}
|
|
2152
|
+
if (propertyType.isUnion()) {
|
|
2153
|
+
const nonNullMembers = propertyType.types.filter(
|
|
2154
|
+
(member) => !(member.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined))
|
|
2155
|
+
);
|
|
2156
|
+
if (nonNullMembers.length > 0 && nonNullMembers.every((member) => member.isStringLiteral())) {
|
|
2157
|
+
diagnostics.push(
|
|
2158
|
+
makeAnalysisDiagnostic(
|
|
2159
|
+
"INVALID_TAG_ARGUMENT",
|
|
2160
|
+
"Discriminator resolution for union-valued identity properties is out of scope for v1.",
|
|
2161
|
+
provenance
|
|
2162
|
+
)
|
|
2163
|
+
);
|
|
2164
|
+
return null;
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
return void 0;
|
|
2168
|
+
}
|
|
2169
|
+
function resolveDiscriminatorApiName(boundType, checker, metadataPolicy) {
|
|
2170
|
+
const declaration = resolveNamedDiscriminatorDeclaration(boundType, checker);
|
|
2171
|
+
if (declaration === null) {
|
|
2172
|
+
return void 0;
|
|
2173
|
+
}
|
|
2174
|
+
const metadata = resolveNodeMetadata(
|
|
2175
|
+
metadataPolicy,
|
|
2176
|
+
"type",
|
|
2177
|
+
getDiscriminatorLogicalName(boundType, declaration, checker),
|
|
2178
|
+
declaration,
|
|
2179
|
+
{
|
|
2180
|
+
checker,
|
|
2181
|
+
declaration,
|
|
2182
|
+
subjectType: boundType
|
|
2183
|
+
}
|
|
2184
|
+
);
|
|
2185
|
+
return metadata?.apiName;
|
|
2186
|
+
}
|
|
2187
|
+
function resolveNamedDiscriminatorDeclaration(type, checker, seen = /* @__PURE__ */ new Set()) {
|
|
2188
|
+
if (seen.has(type)) {
|
|
2189
|
+
return null;
|
|
2190
|
+
}
|
|
2191
|
+
seen.add(type);
|
|
2192
|
+
const symbol = type.aliasSymbol ?? type.getSymbol();
|
|
2193
|
+
if (symbol !== void 0) {
|
|
2194
|
+
const aliased = symbol.flags & ts3.SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : void 0;
|
|
2195
|
+
const targetSymbol = aliased ?? symbol;
|
|
2196
|
+
const declaration = targetSymbol.declarations?.find(
|
|
2197
|
+
(candidate) => ts3.isClassDeclaration(candidate) || ts3.isInterfaceDeclaration(candidate) || ts3.isTypeAliasDeclaration(candidate) || ts3.isEnumDeclaration(candidate)
|
|
2198
|
+
);
|
|
2199
|
+
if (declaration !== void 0) {
|
|
2200
|
+
if (ts3.isTypeAliasDeclaration(declaration) && ts3.isTypeReferenceNode(declaration.type) && checker.getTypeFromTypeNode(declaration.type) !== type) {
|
|
2201
|
+
return resolveNamedDiscriminatorDeclaration(
|
|
2202
|
+
checker.getTypeFromTypeNode(declaration.type),
|
|
2203
|
+
checker,
|
|
2204
|
+
seen
|
|
2205
|
+
);
|
|
2206
|
+
}
|
|
2207
|
+
return declaration;
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
return null;
|
|
1423
2211
|
}
|
|
1424
|
-
function
|
|
1425
|
-
if (
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
2212
|
+
function resolveDiscriminatorValue(boundType, fieldName, checker, provenance, diagnostics, metadataPolicy) {
|
|
2213
|
+
if (boundType === null) {
|
|
2214
|
+
diagnostics.push(
|
|
2215
|
+
makeAnalysisDiagnostic(
|
|
2216
|
+
"INVALID_TAG_ARGUMENT",
|
|
2217
|
+
"Discriminator resolution failed because no concrete type argument is available for the referenced type parameter.",
|
|
2218
|
+
provenance
|
|
2219
|
+
)
|
|
2220
|
+
);
|
|
2221
|
+
return null;
|
|
1433
2222
|
}
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
2223
|
+
if (boundType.isStringLiteral()) {
|
|
2224
|
+
return boundType.value;
|
|
2225
|
+
}
|
|
2226
|
+
if (boundType.isUnion()) {
|
|
2227
|
+
const nonNullMembers = boundType.types.filter(
|
|
2228
|
+
(member) => !(member.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined))
|
|
2229
|
+
);
|
|
2230
|
+
if (nonNullMembers.every((member) => member.isStringLiteral())) {
|
|
2231
|
+
diagnostics.push(
|
|
2232
|
+
makeAnalysisDiagnostic(
|
|
2233
|
+
"INVALID_TAG_ARGUMENT",
|
|
2234
|
+
"Discriminator resolution for unions of string literals is out of scope for v1.",
|
|
2235
|
+
provenance
|
|
2236
|
+
)
|
|
2237
|
+
);
|
|
2238
|
+
return null;
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
const literalIdentityValue = resolveLiteralDiscriminatorPropertyValue(
|
|
2242
|
+
boundType,
|
|
2243
|
+
fieldName,
|
|
2244
|
+
checker,
|
|
2245
|
+
provenance,
|
|
2246
|
+
diagnostics
|
|
1443
2247
|
);
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
2248
|
+
if (literalIdentityValue !== void 0) {
|
|
2249
|
+
return literalIdentityValue;
|
|
2250
|
+
}
|
|
2251
|
+
const apiName = resolveDiscriminatorApiName(boundType, checker, metadataPolicy);
|
|
2252
|
+
if (apiName?.source === "explicit") {
|
|
2253
|
+
return apiName.value;
|
|
2254
|
+
}
|
|
2255
|
+
if (apiName?.source === "inferred") {
|
|
2256
|
+
return apiName.value;
|
|
2257
|
+
}
|
|
2258
|
+
diagnostics.push(
|
|
2259
|
+
makeAnalysisDiagnostic(
|
|
2260
|
+
"INVALID_TAG_ARGUMENT",
|
|
2261
|
+
"Discriminator resolution could not derive a JSON-facing discriminator value from the referenced type argument.",
|
|
2262
|
+
provenance
|
|
2263
|
+
)
|
|
2264
|
+
);
|
|
2265
|
+
return null;
|
|
2266
|
+
}
|
|
2267
|
+
function getDeclarationName(node) {
|
|
2268
|
+
if (ts3.isClassDeclaration(node) || ts3.isInterfaceDeclaration(node) || ts3.isTypeAliasDeclaration(node) || ts3.isEnumDeclaration(node)) {
|
|
2269
|
+
return node.name?.text ?? "anonymous";
|
|
2270
|
+
}
|
|
2271
|
+
return "anonymous";
|
|
2272
|
+
}
|
|
2273
|
+
function getResolvedTypeArguments(type) {
|
|
2274
|
+
return (isTypeReference(type) ? type.typeArguments : void 0) ?? type.aliasTypeArguments ?? [];
|
|
2275
|
+
}
|
|
2276
|
+
function getDiscriminatorLogicalName(type, declaration, checker) {
|
|
2277
|
+
const baseName = getDeclarationName(declaration);
|
|
2278
|
+
const typeArguments = getResolvedTypeArguments(type);
|
|
2279
|
+
return typeArguments.length === 0 ? baseName : buildInstantiatedReferenceName(baseName, typeArguments, checker);
|
|
2280
|
+
}
|
|
2281
|
+
function applyDeclarationDiscriminatorToFields(fields, node, subjectType, checker, file, diagnostics, metadataPolicy) {
|
|
2282
|
+
const directive = validateDiscriminatorDirective(node, checker, file, diagnostics);
|
|
2283
|
+
if (directive === null) {
|
|
2284
|
+
return [...fields];
|
|
2285
|
+
}
|
|
2286
|
+
const discriminatorValue = resolveDiscriminatorValue(
|
|
2287
|
+
getConcreteTypeArgumentForDiscriminator(
|
|
2288
|
+
node,
|
|
2289
|
+
subjectType,
|
|
2290
|
+
checker,
|
|
2291
|
+
directive.typeParameterName
|
|
2292
|
+
),
|
|
2293
|
+
directive.fieldName,
|
|
2294
|
+
checker,
|
|
2295
|
+
directive.provenance,
|
|
2296
|
+
diagnostics,
|
|
2297
|
+
metadataPolicy
|
|
2298
|
+
);
|
|
2299
|
+
if (discriminatorValue === null) {
|
|
2300
|
+
return [...fields];
|
|
2301
|
+
}
|
|
2302
|
+
return fields.map(
|
|
2303
|
+
(field) => field.name === directive.fieldName ? {
|
|
2304
|
+
...field,
|
|
2305
|
+
type: {
|
|
2306
|
+
kind: "enum",
|
|
2307
|
+
members: [{ value: discriminatorValue }]
|
|
2308
|
+
}
|
|
2309
|
+
} : field
|
|
2310
|
+
);
|
|
2311
|
+
}
|
|
2312
|
+
function buildInstantiatedReferenceName(baseName, typeArguments, checker) {
|
|
2313
|
+
const renderedArguments = typeArguments.map(
|
|
2314
|
+
(typeArgument) => checker.typeToString(typeArgument).replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "")
|
|
2315
|
+
).filter((value) => value !== "");
|
|
2316
|
+
return renderedArguments.length === 0 ? baseName : `${baseName}__${renderedArguments.join("__")}`;
|
|
2317
|
+
}
|
|
2318
|
+
function extractReferenceTypeArguments(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy, extensionRegistry, diagnostics) {
|
|
2319
|
+
const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
|
|
2320
|
+
if (typeNode === void 0) {
|
|
2321
|
+
return [];
|
|
2322
|
+
}
|
|
2323
|
+
const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
|
|
2324
|
+
if (!ts3.isTypeReferenceNode(resolvedTypeNode) || resolvedTypeNode.typeArguments === void 0) {
|
|
2325
|
+
return [];
|
|
2326
|
+
}
|
|
2327
|
+
return resolvedTypeNode.typeArguments.map((argumentNode) => {
|
|
2328
|
+
const argumentType = checker.getTypeFromTypeNode(argumentNode);
|
|
2329
|
+
return {
|
|
2330
|
+
tsType: argumentType,
|
|
2331
|
+
typeNode: resolveTypeNode(
|
|
2332
|
+
argumentType,
|
|
1451
2333
|
checker,
|
|
1452
2334
|
file,
|
|
1453
2335
|
typeRegistry,
|
|
1454
2336
|
visiting,
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
extensionRegistry
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
2337
|
+
argumentNode,
|
|
2338
|
+
metadataPolicy,
|
|
2339
|
+
extensionRegistry,
|
|
2340
|
+
diagnostics
|
|
2341
|
+
)
|
|
2342
|
+
};
|
|
2343
|
+
});
|
|
2344
|
+
}
|
|
2345
|
+
function applyDiscriminatorToObjectProperties(properties, node, subjectType, checker, file, diagnostics, metadataPolicy) {
|
|
2346
|
+
const directive = validateDiscriminatorDirective(node, checker, file, diagnostics);
|
|
2347
|
+
if (directive === null) {
|
|
2348
|
+
return properties;
|
|
1463
2349
|
}
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
2350
|
+
const discriminatorValue = resolveDiscriminatorValue(
|
|
2351
|
+
getConcreteTypeArgumentForDiscriminator(
|
|
2352
|
+
node,
|
|
2353
|
+
subjectType,
|
|
2354
|
+
checker,
|
|
2355
|
+
directive.typeParameterName
|
|
2356
|
+
),
|
|
2357
|
+
directive.fieldName,
|
|
2358
|
+
checker,
|
|
2359
|
+
directive.provenance,
|
|
2360
|
+
diagnostics,
|
|
2361
|
+
metadataPolicy
|
|
2362
|
+
);
|
|
2363
|
+
if (discriminatorValue === null) {
|
|
2364
|
+
return properties;
|
|
2365
|
+
}
|
|
2366
|
+
return properties.map(
|
|
2367
|
+
(property) => property.name === directive.fieldName ? {
|
|
2368
|
+
...property,
|
|
2369
|
+
type: {
|
|
2370
|
+
kind: "enum",
|
|
2371
|
+
members: [{ value: discriminatorValue }]
|
|
2372
|
+
}
|
|
2373
|
+
} : property
|
|
2374
|
+
);
|
|
1477
2375
|
}
|
|
1478
|
-
function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, extensionRegistry) {
|
|
2376
|
+
function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, metadataPolicy, extensionRegistry) {
|
|
1479
2377
|
if (!ts3.isIdentifier(prop.name)) {
|
|
1480
2378
|
return null;
|
|
1481
2379
|
}
|
|
@@ -1490,6 +2388,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnosti
|
|
|
1490
2388
|
typeRegistry,
|
|
1491
2389
|
visiting,
|
|
1492
2390
|
prop,
|
|
2391
|
+
metadataPolicy,
|
|
1493
2392
|
extensionRegistry,
|
|
1494
2393
|
diagnostics
|
|
1495
2394
|
);
|
|
@@ -1513,9 +2412,16 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnosti
|
|
|
1513
2412
|
annotations.push(defaultAnnotation);
|
|
1514
2413
|
}
|
|
1515
2414
|
({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
|
|
2415
|
+
const metadata = resolveNodeMetadata(metadataPolicy, "field", name, prop, {
|
|
2416
|
+
checker,
|
|
2417
|
+
declaration: prop,
|
|
2418
|
+
subjectType: tsType,
|
|
2419
|
+
hostType
|
|
2420
|
+
});
|
|
1516
2421
|
return {
|
|
1517
2422
|
kind: "field",
|
|
1518
2423
|
name,
|
|
2424
|
+
...metadata !== void 0 && { metadata },
|
|
1519
2425
|
type,
|
|
1520
2426
|
required: !optional,
|
|
1521
2427
|
constraints,
|
|
@@ -1523,7 +2429,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnosti
|
|
|
1523
2429
|
provenance
|
|
1524
2430
|
};
|
|
1525
2431
|
}
|
|
1526
|
-
function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, extensionRegistry) {
|
|
2432
|
+
function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, metadataPolicy, extensionRegistry) {
|
|
1527
2433
|
if (!ts3.isIdentifier(prop.name)) {
|
|
1528
2434
|
return null;
|
|
1529
2435
|
}
|
|
@@ -1538,6 +2444,7 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
|
|
|
1538
2444
|
typeRegistry,
|
|
1539
2445
|
visiting,
|
|
1540
2446
|
prop,
|
|
2447
|
+
metadataPolicy,
|
|
1541
2448
|
extensionRegistry,
|
|
1542
2449
|
diagnostics
|
|
1543
2450
|
);
|
|
@@ -1557,9 +2464,16 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
|
|
|
1557
2464
|
let annotations = [];
|
|
1558
2465
|
annotations.push(...docResult.annotations);
|
|
1559
2466
|
({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
|
|
2467
|
+
const metadata = resolveNodeMetadata(metadataPolicy, "field", name, prop, {
|
|
2468
|
+
checker,
|
|
2469
|
+
declaration: prop,
|
|
2470
|
+
subjectType: tsType,
|
|
2471
|
+
hostType
|
|
2472
|
+
});
|
|
1560
2473
|
return {
|
|
1561
2474
|
kind: "field",
|
|
1562
2475
|
name,
|
|
2476
|
+
...metadata !== void 0 && { metadata },
|
|
1563
2477
|
type,
|
|
1564
2478
|
required: !optional,
|
|
1565
2479
|
constraints,
|
|
@@ -1684,7 +2598,7 @@ function getTypeNodeRegistrationName(typeNode) {
|
|
|
1684
2598
|
}
|
|
1685
2599
|
return null;
|
|
1686
2600
|
}
|
|
1687
|
-
function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
|
|
2601
|
+
function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
|
|
1688
2602
|
const customType = resolveRegisteredCustomType(sourceNode, extensionRegistry, checker);
|
|
1689
2603
|
if (customType) {
|
|
1690
2604
|
return customType;
|
|
@@ -1696,6 +2610,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
|
|
|
1696
2610
|
typeRegistry,
|
|
1697
2611
|
visiting,
|
|
1698
2612
|
sourceNode,
|
|
2613
|
+
metadataPolicy,
|
|
1699
2614
|
extensionRegistry,
|
|
1700
2615
|
diagnostics
|
|
1701
2616
|
);
|
|
@@ -1740,6 +2655,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
|
|
|
1740
2655
|
typeRegistry,
|
|
1741
2656
|
visiting,
|
|
1742
2657
|
sourceNode,
|
|
2658
|
+
metadataPolicy,
|
|
1743
2659
|
extensionRegistry,
|
|
1744
2660
|
diagnostics
|
|
1745
2661
|
);
|
|
@@ -1752,6 +2668,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
|
|
|
1752
2668
|
typeRegistry,
|
|
1753
2669
|
visiting,
|
|
1754
2670
|
sourceNode,
|
|
2671
|
+
metadataPolicy,
|
|
1755
2672
|
extensionRegistry,
|
|
1756
2673
|
diagnostics
|
|
1757
2674
|
);
|
|
@@ -1763,13 +2680,15 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
|
|
|
1763
2680
|
file,
|
|
1764
2681
|
typeRegistry,
|
|
1765
2682
|
visiting,
|
|
2683
|
+
sourceNode,
|
|
2684
|
+
metadataPolicy,
|
|
1766
2685
|
extensionRegistry,
|
|
1767
2686
|
diagnostics
|
|
1768
2687
|
);
|
|
1769
2688
|
}
|
|
1770
2689
|
return { kind: "primitive", primitiveKind: "string" };
|
|
1771
2690
|
}
|
|
1772
|
-
function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
|
|
2691
|
+
function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
|
|
1773
2692
|
if (!(type.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null))) {
|
|
1774
2693
|
return null;
|
|
1775
2694
|
}
|
|
@@ -1789,14 +2708,21 @@ function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiti
|
|
|
1789
2708
|
file,
|
|
1790
2709
|
makeParseOptions(extensionRegistry)
|
|
1791
2710
|
);
|
|
2711
|
+
const metadata = resolveNodeMetadata(metadataPolicy, "type", aliasName, aliasDecl, {
|
|
2712
|
+
checker,
|
|
2713
|
+
declaration: aliasDecl,
|
|
2714
|
+
subjectType: aliasType
|
|
2715
|
+
});
|
|
1792
2716
|
typeRegistry[aliasName] = {
|
|
1793
2717
|
name: aliasName,
|
|
2718
|
+
...metadata !== void 0 && { metadata },
|
|
1794
2719
|
type: resolveAliasedPrimitiveTarget(
|
|
1795
2720
|
aliasType,
|
|
1796
2721
|
checker,
|
|
1797
2722
|
file,
|
|
1798
2723
|
typeRegistry,
|
|
1799
2724
|
visiting,
|
|
2725
|
+
metadataPolicy,
|
|
1800
2726
|
extensionRegistry,
|
|
1801
2727
|
diagnostics
|
|
1802
2728
|
),
|
|
@@ -1825,7 +2751,7 @@ function shouldEmitPrimitiveAliasDefinition(typeNode, checker) {
|
|
|
1825
2751
|
const resolved = checker.getTypeFromTypeNode(aliasDecl.type);
|
|
1826
2752
|
return !!(resolved.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null));
|
|
1827
2753
|
}
|
|
1828
|
-
function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
|
|
2754
|
+
function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
|
|
1829
2755
|
const nestedAliasDecl = type.aliasSymbol?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
1830
2756
|
if (nestedAliasDecl !== void 0) {
|
|
1831
2757
|
return resolveAliasedPrimitiveTarget(
|
|
@@ -1834,6 +2760,7 @@ function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiti
|
|
|
1834
2760
|
file,
|
|
1835
2761
|
typeRegistry,
|
|
1836
2762
|
visiting,
|
|
2763
|
+
metadataPolicy,
|
|
1837
2764
|
extensionRegistry,
|
|
1838
2765
|
diagnostics
|
|
1839
2766
|
);
|
|
@@ -1845,11 +2772,12 @@ function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiti
|
|
|
1845
2772
|
typeRegistry,
|
|
1846
2773
|
visiting,
|
|
1847
2774
|
void 0,
|
|
2775
|
+
metadataPolicy,
|
|
1848
2776
|
extensionRegistry,
|
|
1849
2777
|
diagnostics
|
|
1850
2778
|
);
|
|
1851
2779
|
}
|
|
1852
|
-
function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
|
|
2780
|
+
function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
|
|
1853
2781
|
const typeName = getNamedTypeName(type);
|
|
1854
2782
|
const namedDecl = getNamedTypeDeclaration(type);
|
|
1855
2783
|
if (typeName && typeName in typeRegistry) {
|
|
@@ -1884,8 +2812,14 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
1884
2812
|
return result;
|
|
1885
2813
|
}
|
|
1886
2814
|
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
2815
|
+
const metadata = namedDecl !== void 0 ? resolveNodeMetadata(metadataPolicy, "type", typeName, namedDecl, {
|
|
2816
|
+
checker,
|
|
2817
|
+
declaration: namedDecl,
|
|
2818
|
+
subjectType: type
|
|
2819
|
+
}) : void 0;
|
|
1887
2820
|
typeRegistry[typeName] = {
|
|
1888
2821
|
name: typeName,
|
|
2822
|
+
...metadata !== void 0 && { metadata },
|
|
1889
2823
|
type: result,
|
|
1890
2824
|
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
1891
2825
|
provenance: provenanceForDeclaration(namedDecl ?? sourceNode, file)
|
|
@@ -1939,6 +2873,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
1939
2873
|
typeRegistry,
|
|
1940
2874
|
visiting,
|
|
1941
2875
|
nonNullMembers[0].sourceNode ?? sourceNode,
|
|
2876
|
+
metadataPolicy,
|
|
1942
2877
|
extensionRegistry,
|
|
1943
2878
|
diagnostics
|
|
1944
2879
|
);
|
|
@@ -1956,6 +2891,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
1956
2891
|
typeRegistry,
|
|
1957
2892
|
visiting,
|
|
1958
2893
|
memberSourceNode ?? sourceNode,
|
|
2894
|
+
metadataPolicy,
|
|
1959
2895
|
extensionRegistry,
|
|
1960
2896
|
diagnostics
|
|
1961
2897
|
)
|
|
@@ -1965,7 +2901,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
1965
2901
|
}
|
|
1966
2902
|
return registerNamed({ kind: "union", members });
|
|
1967
2903
|
}
|
|
1968
|
-
function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
|
|
2904
|
+
function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
|
|
1969
2905
|
const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
|
|
1970
2906
|
const elementType = typeArgs?.[0];
|
|
1971
2907
|
const elementSourceNode = extractArrayElementTypeNode(sourceNode, checker);
|
|
@@ -1976,12 +2912,13 @@ function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
1976
2912
|
typeRegistry,
|
|
1977
2913
|
visiting,
|
|
1978
2914
|
elementSourceNode,
|
|
2915
|
+
metadataPolicy,
|
|
1979
2916
|
extensionRegistry,
|
|
1980
2917
|
diagnostics
|
|
1981
2918
|
) : { kind: "primitive", primitiveKind: "string" };
|
|
1982
2919
|
return { kind: "array", items };
|
|
1983
2920
|
}
|
|
1984
|
-
function tryResolveRecordType(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
|
|
2921
|
+
function tryResolveRecordType(type, checker, file, typeRegistry, visiting, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
|
|
1985
2922
|
if (type.getProperties().length > 0) {
|
|
1986
2923
|
return null;
|
|
1987
2924
|
}
|
|
@@ -1996,6 +2933,7 @@ function tryResolveRecordType(type, checker, file, typeRegistry, visiting, exten
|
|
|
1996
2933
|
typeRegistry,
|
|
1997
2934
|
visiting,
|
|
1998
2935
|
void 0,
|
|
2936
|
+
metadataPolicy,
|
|
1999
2937
|
extensionRegistry,
|
|
2000
2938
|
diagnostics
|
|
2001
2939
|
);
|
|
@@ -2026,35 +2964,76 @@ function typeNodeContainsReference(type, targetName) {
|
|
|
2026
2964
|
}
|
|
2027
2965
|
}
|
|
2028
2966
|
}
|
|
2029
|
-
function
|
|
2967
|
+
function shouldEmitResolvedObjectProperty(property, declaration) {
|
|
2968
|
+
if (property.name.startsWith("__@")) {
|
|
2969
|
+
return false;
|
|
2970
|
+
}
|
|
2971
|
+
if (declaration !== void 0 && "name" in declaration && declaration.name !== void 0) {
|
|
2972
|
+
const name = declaration.name;
|
|
2973
|
+
if (ts3.isComputedPropertyName(name) || ts3.isPrivateIdentifier(name)) {
|
|
2974
|
+
return false;
|
|
2975
|
+
}
|
|
2976
|
+
if (!ts3.isIdentifier(name) && !ts3.isStringLiteral(name) && !ts3.isNumericLiteral(name)) {
|
|
2977
|
+
return false;
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
return true;
|
|
2981
|
+
}
|
|
2982
|
+
function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
|
|
2983
|
+
const collectedDiagnostics = diagnostics ?? [];
|
|
2030
2984
|
const typeName = getNamedTypeName(type);
|
|
2031
2985
|
const namedTypeName = typeName ?? void 0;
|
|
2032
2986
|
const namedDecl = getNamedTypeDeclaration(type);
|
|
2033
|
-
const
|
|
2987
|
+
const referenceTypeArguments = extractReferenceTypeArguments(
|
|
2988
|
+
type,
|
|
2989
|
+
checker,
|
|
2990
|
+
file,
|
|
2991
|
+
typeRegistry,
|
|
2992
|
+
visiting,
|
|
2993
|
+
sourceNode,
|
|
2994
|
+
metadataPolicy,
|
|
2995
|
+
extensionRegistry,
|
|
2996
|
+
collectedDiagnostics
|
|
2997
|
+
);
|
|
2998
|
+
const instantiatedTypeName = namedTypeName !== void 0 && referenceTypeArguments.length > 0 ? buildInstantiatedReferenceName(
|
|
2999
|
+
namedTypeName,
|
|
3000
|
+
referenceTypeArguments.map((argument) => argument.tsType),
|
|
3001
|
+
checker
|
|
3002
|
+
) : void 0;
|
|
3003
|
+
const registryTypeName = instantiatedTypeName ?? namedTypeName;
|
|
3004
|
+
const shouldRegisterNamedType = registryTypeName !== void 0 && !(registryTypeName === "Record" && namedDecl?.getSourceFile().fileName !== file);
|
|
2034
3005
|
const clearNamedTypeRegistration = () => {
|
|
2035
|
-
if (
|
|
3006
|
+
if (registryTypeName === void 0 || !shouldRegisterNamedType) {
|
|
2036
3007
|
return;
|
|
2037
3008
|
}
|
|
2038
|
-
Reflect.deleteProperty(typeRegistry,
|
|
3009
|
+
Reflect.deleteProperty(typeRegistry, registryTypeName);
|
|
2039
3010
|
};
|
|
2040
3011
|
if (visiting.has(type)) {
|
|
2041
|
-
if (
|
|
2042
|
-
return {
|
|
3012
|
+
if (registryTypeName !== void 0 && shouldRegisterNamedType) {
|
|
3013
|
+
return {
|
|
3014
|
+
kind: "reference",
|
|
3015
|
+
name: registryTypeName,
|
|
3016
|
+
typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
|
|
3017
|
+
};
|
|
2043
3018
|
}
|
|
2044
3019
|
return { kind: "object", properties: [], additionalProperties: false };
|
|
2045
3020
|
}
|
|
2046
|
-
if (
|
|
2047
|
-
typeRegistry[
|
|
2048
|
-
name:
|
|
3021
|
+
if (registryTypeName !== void 0 && shouldRegisterNamedType && !typeRegistry[registryTypeName]) {
|
|
3022
|
+
typeRegistry[registryTypeName] = {
|
|
3023
|
+
name: registryTypeName,
|
|
2049
3024
|
type: RESOLVING_TYPE_PLACEHOLDER,
|
|
2050
3025
|
provenance: provenanceForDeclaration(namedDecl, file)
|
|
2051
3026
|
};
|
|
2052
3027
|
}
|
|
2053
3028
|
visiting.add(type);
|
|
2054
|
-
if (
|
|
2055
|
-
if (typeRegistry[
|
|
3029
|
+
if (registryTypeName !== void 0 && shouldRegisterNamedType && typeRegistry[registryTypeName]?.type !== void 0) {
|
|
3030
|
+
if (typeRegistry[registryTypeName].type !== RESOLVING_TYPE_PLACEHOLDER) {
|
|
2056
3031
|
visiting.delete(type);
|
|
2057
|
-
return {
|
|
3032
|
+
return {
|
|
3033
|
+
kind: "reference",
|
|
3034
|
+
name: registryTypeName,
|
|
3035
|
+
typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
|
|
3036
|
+
};
|
|
2058
3037
|
}
|
|
2059
3038
|
}
|
|
2060
3039
|
const recordNode = tryResolveRecordType(
|
|
@@ -2063,25 +3042,36 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
2063
3042
|
file,
|
|
2064
3043
|
typeRegistry,
|
|
2065
3044
|
visiting,
|
|
3045
|
+
metadataPolicy,
|
|
2066
3046
|
extensionRegistry,
|
|
2067
|
-
|
|
3047
|
+
collectedDiagnostics
|
|
2068
3048
|
);
|
|
2069
3049
|
if (recordNode) {
|
|
2070
3050
|
visiting.delete(type);
|
|
2071
|
-
if (
|
|
2072
|
-
const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType,
|
|
3051
|
+
if (registryTypeName !== void 0 && shouldRegisterNamedType) {
|
|
3052
|
+
const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType, registryTypeName);
|
|
2073
3053
|
if (!isRecursiveRecord) {
|
|
2074
3054
|
clearNamedTypeRegistration();
|
|
2075
3055
|
return recordNode;
|
|
2076
3056
|
}
|
|
2077
3057
|
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
2078
|
-
|
|
2079
|
-
|
|
3058
|
+
const metadata = namedDecl !== void 0 ? resolveNodeMetadata(metadataPolicy, "type", registryTypeName, namedDecl, {
|
|
3059
|
+
checker,
|
|
3060
|
+
declaration: namedDecl,
|
|
3061
|
+
subjectType: type
|
|
3062
|
+
}) : void 0;
|
|
3063
|
+
typeRegistry[registryTypeName] = {
|
|
3064
|
+
name: registryTypeName,
|
|
3065
|
+
...metadata !== void 0 && { metadata },
|
|
2080
3066
|
type: recordNode,
|
|
2081
3067
|
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
2082
3068
|
provenance: provenanceForDeclaration(namedDecl, file)
|
|
2083
3069
|
};
|
|
2084
|
-
return {
|
|
3070
|
+
return {
|
|
3071
|
+
kind: "reference",
|
|
3072
|
+
name: registryTypeName,
|
|
3073
|
+
typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
|
|
3074
|
+
};
|
|
2085
3075
|
}
|
|
2086
3076
|
return recordNode;
|
|
2087
3077
|
}
|
|
@@ -2092,12 +3082,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
2092
3082
|
file,
|
|
2093
3083
|
typeRegistry,
|
|
2094
3084
|
visiting,
|
|
2095
|
-
|
|
3085
|
+
metadataPolicy,
|
|
3086
|
+
collectedDiagnostics,
|
|
2096
3087
|
extensionRegistry
|
|
2097
3088
|
);
|
|
2098
3089
|
for (const prop of type.getProperties()) {
|
|
2099
3090
|
const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
|
|
2100
3091
|
if (!declaration) continue;
|
|
3092
|
+
if (!shouldEmitResolvedObjectProperty(prop, declaration)) continue;
|
|
2101
3093
|
const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
|
|
2102
3094
|
const optional = !!(prop.flags & ts3.SymbolFlags.Optional);
|
|
2103
3095
|
const propTypeNode = resolveTypeNode(
|
|
@@ -2107,12 +3099,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
2107
3099
|
typeRegistry,
|
|
2108
3100
|
visiting,
|
|
2109
3101
|
declaration,
|
|
3102
|
+
metadataPolicy,
|
|
2110
3103
|
extensionRegistry,
|
|
2111
|
-
|
|
3104
|
+
collectedDiagnostics
|
|
2112
3105
|
);
|
|
2113
3106
|
const fieldNodeInfo = fieldInfoMap?.get(prop.name);
|
|
2114
3107
|
properties.push({
|
|
2115
3108
|
name: prop.name,
|
|
3109
|
+
...fieldNodeInfo?.metadata !== void 0 && { metadata: fieldNodeInfo.metadata },
|
|
2116
3110
|
type: propTypeNode,
|
|
2117
3111
|
optional,
|
|
2118
3112
|
constraints: fieldNodeInfo?.constraints ?? [],
|
|
@@ -2123,22 +3117,40 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
2123
3117
|
visiting.delete(type);
|
|
2124
3118
|
const objectNode = {
|
|
2125
3119
|
kind: "object",
|
|
2126
|
-
properties
|
|
3120
|
+
properties: namedDecl !== void 0 && (ts3.isClassDeclaration(namedDecl) || ts3.isInterfaceDeclaration(namedDecl) || ts3.isTypeAliasDeclaration(namedDecl)) ? applyDiscriminatorToObjectProperties(
|
|
3121
|
+
properties,
|
|
3122
|
+
namedDecl,
|
|
3123
|
+
type,
|
|
3124
|
+
checker,
|
|
3125
|
+
file,
|
|
3126
|
+
collectedDiagnostics,
|
|
3127
|
+
metadataPolicy
|
|
3128
|
+
) : properties,
|
|
2127
3129
|
additionalProperties: true
|
|
2128
3130
|
};
|
|
2129
|
-
if (
|
|
3131
|
+
if (registryTypeName !== void 0 && shouldRegisterNamedType) {
|
|
2130
3132
|
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
2131
|
-
|
|
2132
|
-
|
|
3133
|
+
const metadata = namedDecl !== void 0 ? resolveNodeMetadata(metadataPolicy, "type", registryTypeName, namedDecl, {
|
|
3134
|
+
checker,
|
|
3135
|
+
declaration: namedDecl,
|
|
3136
|
+
subjectType: type
|
|
3137
|
+
}) : void 0;
|
|
3138
|
+
typeRegistry[registryTypeName] = {
|
|
3139
|
+
name: registryTypeName,
|
|
3140
|
+
...metadata !== void 0 && { metadata },
|
|
2133
3141
|
type: objectNode,
|
|
2134
3142
|
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
2135
3143
|
provenance: provenanceForDeclaration(namedDecl, file)
|
|
2136
3144
|
};
|
|
2137
|
-
return {
|
|
3145
|
+
return {
|
|
3146
|
+
kind: "reference",
|
|
3147
|
+
name: registryTypeName,
|
|
3148
|
+
typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
|
|
3149
|
+
};
|
|
2138
3150
|
}
|
|
2139
3151
|
return objectNode;
|
|
2140
3152
|
}
|
|
2141
|
-
function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, diagnostics, extensionRegistry) {
|
|
3153
|
+
function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, metadataPolicy, diagnostics, extensionRegistry) {
|
|
2142
3154
|
const symbols = [type.getSymbol(), type.aliasSymbol].filter(
|
|
2143
3155
|
(s) => s?.declarations != null && s.declarations.length > 0
|
|
2144
3156
|
);
|
|
@@ -2159,10 +3171,12 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
2159
3171
|
visiting,
|
|
2160
3172
|
diagnostics,
|
|
2161
3173
|
hostType,
|
|
3174
|
+
metadataPolicy,
|
|
2162
3175
|
extensionRegistry
|
|
2163
3176
|
);
|
|
2164
3177
|
if (fieldNode) {
|
|
2165
3178
|
map.set(fieldNode.name, {
|
|
3179
|
+
...fieldNode.metadata !== void 0 && { metadata: fieldNode.metadata },
|
|
2166
3180
|
constraints: [...fieldNode.constraints],
|
|
2167
3181
|
annotations: [...fieldNode.annotations],
|
|
2168
3182
|
provenance: fieldNode.provenance
|
|
@@ -2180,6 +3194,7 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
2180
3194
|
file,
|
|
2181
3195
|
typeRegistry,
|
|
2182
3196
|
visiting,
|
|
3197
|
+
metadataPolicy,
|
|
2183
3198
|
checker.getTypeAtLocation(interfaceDecl),
|
|
2184
3199
|
diagnostics,
|
|
2185
3200
|
extensionRegistry
|
|
@@ -2193,6 +3208,7 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
2193
3208
|
file,
|
|
2194
3209
|
typeRegistry,
|
|
2195
3210
|
visiting,
|
|
3211
|
+
metadataPolicy,
|
|
2196
3212
|
checker.getTypeAtLocation(typeAliasDecl),
|
|
2197
3213
|
diagnostics,
|
|
2198
3214
|
extensionRegistry
|
|
@@ -2244,7 +3260,7 @@ function isNullishTypeNode(typeNode) {
|
|
|
2244
3260
|
}
|
|
2245
3261
|
return ts3.isLiteralTypeNode(typeNode) && (typeNode.literal.kind === ts3.SyntaxKind.NullKeyword || typeNode.literal.kind === ts3.SyntaxKind.UndefinedKeyword);
|
|
2246
3262
|
}
|
|
2247
|
-
function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, hostType, diagnostics, extensionRegistry) {
|
|
3263
|
+
function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, metadataPolicy, hostType, diagnostics, extensionRegistry) {
|
|
2248
3264
|
const map = /* @__PURE__ */ new Map();
|
|
2249
3265
|
for (const member of members) {
|
|
2250
3266
|
if (ts3.isPropertySignature(member)) {
|
|
@@ -2256,10 +3272,12 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, h
|
|
|
2256
3272
|
visiting,
|
|
2257
3273
|
diagnostics,
|
|
2258
3274
|
hostType,
|
|
3275
|
+
metadataPolicy,
|
|
2259
3276
|
extensionRegistry
|
|
2260
3277
|
);
|
|
2261
3278
|
if (fieldNode) {
|
|
2262
3279
|
map.set(fieldNode.name, {
|
|
3280
|
+
...fieldNode.metadata !== void 0 && { metadata: fieldNode.metadata },
|
|
2263
3281
|
constraints: [...fieldNode.constraints],
|
|
2264
3282
|
annotations: [...fieldNode.annotations],
|
|
2265
3283
|
provenance: fieldNode.provenance
|
|
@@ -2290,6 +3308,7 @@ function extractTypeAliasConstraintNodes(typeNode, checker, file, extensionRegis
|
|
|
2290
3308
|
{},
|
|
2291
3309
|
/* @__PURE__ */ new Set(),
|
|
2292
3310
|
aliasDecl.type,
|
|
3311
|
+
void 0,
|
|
2293
3312
|
extensionRegistry
|
|
2294
3313
|
);
|
|
2295
3314
|
const constraints = extractJSDocConstraintNodes(
|
|
@@ -2475,15 +3494,27 @@ function findInterfaceByName(sourceFile, interfaceName) {
|
|
|
2475
3494
|
function findTypeAliasByName(sourceFile, aliasName) {
|
|
2476
3495
|
return findNodeByName(sourceFile, aliasName, ts4.isTypeAliasDeclaration, (n) => n.name.text);
|
|
2477
3496
|
}
|
|
2478
|
-
function analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensionRegistry) {
|
|
3497
|
+
function analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensionRegistry, metadataPolicy) {
|
|
2479
3498
|
const analysisFilePath = path.resolve(filePath);
|
|
2480
3499
|
const classDecl = findClassByName(ctx.sourceFile, typeName);
|
|
2481
3500
|
if (classDecl !== null) {
|
|
2482
|
-
return analyzeClassToIR(
|
|
3501
|
+
return analyzeClassToIR(
|
|
3502
|
+
classDecl,
|
|
3503
|
+
ctx.checker,
|
|
3504
|
+
analysisFilePath,
|
|
3505
|
+
extensionRegistry,
|
|
3506
|
+
metadataPolicy
|
|
3507
|
+
);
|
|
2483
3508
|
}
|
|
2484
3509
|
const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
|
|
2485
3510
|
if (interfaceDecl !== null) {
|
|
2486
|
-
return analyzeInterfaceToIR(
|
|
3511
|
+
return analyzeInterfaceToIR(
|
|
3512
|
+
interfaceDecl,
|
|
3513
|
+
ctx.checker,
|
|
3514
|
+
analysisFilePath,
|
|
3515
|
+
extensionRegistry,
|
|
3516
|
+
metadataPolicy
|
|
3517
|
+
);
|
|
2487
3518
|
}
|
|
2488
3519
|
const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
|
|
2489
3520
|
if (typeAlias !== null) {
|
|
@@ -2491,7 +3522,8 @@ function analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensi
|
|
|
2491
3522
|
typeAlias,
|
|
2492
3523
|
ctx.checker,
|
|
2493
3524
|
analysisFilePath,
|
|
2494
|
-
extensionRegistry
|
|
3525
|
+
extensionRegistry,
|
|
3526
|
+
metadataPolicy
|
|
2495
3527
|
);
|
|
2496
3528
|
if (result.ok) {
|
|
2497
3529
|
return result.analysis;
|
|
@@ -2506,6 +3538,120 @@ function analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensi
|
|
|
2506
3538
|
// src/generators/class-schema.ts
|
|
2507
3539
|
var ts5 = require("typescript");
|
|
2508
3540
|
|
|
3541
|
+
// src/metadata/collision-guards.ts
|
|
3542
|
+
function assertUniqueSerializedNames(entries, scope) {
|
|
3543
|
+
const seen = /* @__PURE__ */ new Map();
|
|
3544
|
+
for (const entry of entries) {
|
|
3545
|
+
const previous = seen.get(entry.serializedName);
|
|
3546
|
+
if (previous !== void 0) {
|
|
3547
|
+
if (previous.logicalName === entry.logicalName && previous.category === entry.category) {
|
|
3548
|
+
continue;
|
|
3549
|
+
}
|
|
3550
|
+
throw new Error(
|
|
3551
|
+
`Serialized name collision in ${scope}: ${previous.category} "${previous.logicalName}" and ${entry.category} "${entry.logicalName}" both resolve to "${entry.serializedName}".`
|
|
3552
|
+
);
|
|
3553
|
+
}
|
|
3554
|
+
seen.set(entry.serializedName, entry);
|
|
3555
|
+
}
|
|
3556
|
+
}
|
|
3557
|
+
function collectFlattenedFields(elements) {
|
|
3558
|
+
const fields = [];
|
|
3559
|
+
for (const element of elements) {
|
|
3560
|
+
switch (element.kind) {
|
|
3561
|
+
case "field":
|
|
3562
|
+
fields.push(element);
|
|
3563
|
+
break;
|
|
3564
|
+
case "group":
|
|
3565
|
+
case "conditional":
|
|
3566
|
+
fields.push(...collectFlattenedFields(element.elements));
|
|
3567
|
+
break;
|
|
3568
|
+
default: {
|
|
3569
|
+
const exhaustive = element;
|
|
3570
|
+
void exhaustive;
|
|
3571
|
+
}
|
|
3572
|
+
}
|
|
3573
|
+
}
|
|
3574
|
+
return fields;
|
|
3575
|
+
}
|
|
3576
|
+
function validateObjectProperties(properties, scope) {
|
|
3577
|
+
assertUniqueSerializedNames(
|
|
3578
|
+
properties.map((property) => ({
|
|
3579
|
+
logicalName: property.name,
|
|
3580
|
+
serializedName: getSerializedName(property.name, property.metadata),
|
|
3581
|
+
category: "object property"
|
|
3582
|
+
})),
|
|
3583
|
+
scope
|
|
3584
|
+
);
|
|
3585
|
+
for (const property of properties) {
|
|
3586
|
+
validateTypeNode(
|
|
3587
|
+
property.type,
|
|
3588
|
+
`${scope}.${getSerializedName(property.name, property.metadata)}`
|
|
3589
|
+
);
|
|
3590
|
+
}
|
|
3591
|
+
}
|
|
3592
|
+
function validateTypeNode(type, scope) {
|
|
3593
|
+
switch (type.kind) {
|
|
3594
|
+
case "array":
|
|
3595
|
+
validateTypeNode(type.items, `${scope}[]`);
|
|
3596
|
+
break;
|
|
3597
|
+
case "object":
|
|
3598
|
+
validateObjectProperties(type.properties, scope);
|
|
3599
|
+
break;
|
|
3600
|
+
case "record":
|
|
3601
|
+
validateTypeNode(type.valueType, `${scope}.*`);
|
|
3602
|
+
break;
|
|
3603
|
+
case "union":
|
|
3604
|
+
type.members.forEach((member, index) => {
|
|
3605
|
+
validateTypeNode(member, `${scope}|${String(index)}`);
|
|
3606
|
+
});
|
|
3607
|
+
break;
|
|
3608
|
+
case "reference":
|
|
3609
|
+
case "primitive":
|
|
3610
|
+
case "enum":
|
|
3611
|
+
case "dynamic":
|
|
3612
|
+
case "custom":
|
|
3613
|
+
break;
|
|
3614
|
+
default: {
|
|
3615
|
+
const exhaustive = type;
|
|
3616
|
+
void exhaustive;
|
|
3617
|
+
}
|
|
3618
|
+
}
|
|
3619
|
+
}
|
|
3620
|
+
function validateTypeDefinitions(typeRegistry) {
|
|
3621
|
+
const definitions = Object.values(typeRegistry);
|
|
3622
|
+
assertUniqueSerializedNames(
|
|
3623
|
+
definitions.map((definition) => ({
|
|
3624
|
+
logicalName: definition.name,
|
|
3625
|
+
serializedName: getSerializedName(definition.name, definition.metadata),
|
|
3626
|
+
category: "type definition"
|
|
3627
|
+
})),
|
|
3628
|
+
"$defs"
|
|
3629
|
+
);
|
|
3630
|
+
for (const definition of definitions) {
|
|
3631
|
+
validateTypeDefinition(definition);
|
|
3632
|
+
}
|
|
3633
|
+
}
|
|
3634
|
+
function validateTypeDefinition(definition) {
|
|
3635
|
+
validateTypeNode(
|
|
3636
|
+
definition.type,
|
|
3637
|
+
`type "${getSerializedName(definition.name, definition.metadata)}"`
|
|
3638
|
+
);
|
|
3639
|
+
}
|
|
3640
|
+
function assertNoSerializedNameCollisions(ir) {
|
|
3641
|
+
assertUniqueSerializedNames(
|
|
3642
|
+
collectFlattenedFields(ir.elements).map((field) => ({
|
|
3643
|
+
logicalName: field.name,
|
|
3644
|
+
serializedName: getSerializedName(field.name, field.metadata),
|
|
3645
|
+
category: "field"
|
|
3646
|
+
})),
|
|
3647
|
+
"form root"
|
|
3648
|
+
);
|
|
3649
|
+
for (const field of collectFlattenedFields(ir.elements)) {
|
|
3650
|
+
validateTypeNode(field.type, `field "${getSerializedName(field.name, field.metadata)}"`);
|
|
3651
|
+
}
|
|
3652
|
+
validateTypeDefinitions(ir.typeRegistry);
|
|
3653
|
+
}
|
|
3654
|
+
|
|
2509
3655
|
// src/json-schema/ir-generator.ts
|
|
2510
3656
|
function makeContext(options) {
|
|
2511
3657
|
const vendorPrefix = options?.vendorPrefix ?? "x-formspec";
|
|
@@ -2516,19 +3662,33 @@ function makeContext(options) {
|
|
|
2516
3662
|
}
|
|
2517
3663
|
return {
|
|
2518
3664
|
defs: {},
|
|
3665
|
+
typeNameMap: {},
|
|
3666
|
+
typeRegistry: {},
|
|
2519
3667
|
extensionRegistry: options?.extensionRegistry,
|
|
2520
3668
|
vendorPrefix
|
|
2521
3669
|
};
|
|
2522
3670
|
}
|
|
2523
3671
|
function generateJsonSchemaFromIR(ir, options) {
|
|
2524
|
-
|
|
3672
|
+
assertNoSerializedNameCollisions(ir);
|
|
3673
|
+
const ctx = {
|
|
3674
|
+
...makeContext(options),
|
|
3675
|
+
typeRegistry: ir.typeRegistry,
|
|
3676
|
+
typeNameMap: Object.fromEntries(
|
|
3677
|
+
Object.entries(ir.typeRegistry).map(([name, typeDef]) => [
|
|
3678
|
+
name,
|
|
3679
|
+
getSerializedName(name, typeDef.metadata)
|
|
3680
|
+
])
|
|
3681
|
+
)
|
|
3682
|
+
};
|
|
2525
3683
|
for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
|
|
2526
|
-
ctx.
|
|
3684
|
+
const schemaName = ctx.typeNameMap[name] ?? name;
|
|
3685
|
+
ctx.defs[schemaName] = generateTypeNode(typeDef.type, ctx);
|
|
3686
|
+
applyResolvedMetadata(ctx.defs[schemaName], typeDef.metadata);
|
|
2527
3687
|
if (typeDef.constraints && typeDef.constraints.length > 0) {
|
|
2528
|
-
applyConstraints(ctx.defs[
|
|
3688
|
+
applyConstraints(ctx.defs[schemaName], typeDef.constraints, ctx);
|
|
2529
3689
|
}
|
|
2530
3690
|
if (typeDef.annotations && typeDef.annotations.length > 0) {
|
|
2531
|
-
applyAnnotations(ctx.defs[
|
|
3691
|
+
applyAnnotations(ctx.defs[schemaName], typeDef.annotations, ctx);
|
|
2532
3692
|
}
|
|
2533
3693
|
}
|
|
2534
3694
|
const properties = {};
|
|
@@ -2541,6 +3701,7 @@ function generateJsonSchemaFromIR(ir, options) {
|
|
|
2541
3701
|
properties,
|
|
2542
3702
|
...uniqueRequired.length > 0 && { required: uniqueRequired }
|
|
2543
3703
|
};
|
|
3704
|
+
applyResolvedMetadata(result, ir.metadata);
|
|
2544
3705
|
if (ir.annotations && ir.annotations.length > 0) {
|
|
2545
3706
|
applyAnnotations(result, ir.annotations, ctx);
|
|
2546
3707
|
}
|
|
@@ -2553,9 +3714,9 @@ function collectFields(elements, properties, required, ctx) {
|
|
|
2553
3714
|
for (const element of elements) {
|
|
2554
3715
|
switch (element.kind) {
|
|
2555
3716
|
case "field":
|
|
2556
|
-
properties[element.name] = generateFieldSchema(element, ctx);
|
|
3717
|
+
properties[getSerializedName(element.name, element.metadata)] = generateFieldSchema(element, ctx);
|
|
2557
3718
|
if (element.required) {
|
|
2558
|
-
required.push(element.name);
|
|
3719
|
+
required.push(getSerializedName(element.name, element.metadata));
|
|
2559
3720
|
}
|
|
2560
3721
|
break;
|
|
2561
3722
|
case "group":
|
|
@@ -2599,6 +3760,7 @@ function generateFieldSchema(field, ctx) {
|
|
|
2599
3760
|
rootAnnotations.push(annotation);
|
|
2600
3761
|
}
|
|
2601
3762
|
}
|
|
3763
|
+
applyResolvedMetadata(schema, field.metadata);
|
|
2602
3764
|
applyAnnotations(schema, rootAnnotations, ctx);
|
|
2603
3765
|
if (itemStringSchema !== void 0) {
|
|
2604
3766
|
applyAnnotations(itemStringSchema, itemAnnotations, ctx);
|
|
@@ -2606,7 +3768,7 @@ function generateFieldSchema(field, ctx) {
|
|
|
2606
3768
|
if (pathConstraints.length === 0) {
|
|
2607
3769
|
return schema;
|
|
2608
3770
|
}
|
|
2609
|
-
return applyPathTargetedConstraints(schema, pathConstraints, ctx);
|
|
3771
|
+
return applyPathTargetedConstraints(schema, pathConstraints, ctx, field.type);
|
|
2610
3772
|
}
|
|
2611
3773
|
function isStringItemConstraint(constraint) {
|
|
2612
3774
|
switch (constraint.constraintKind) {
|
|
@@ -2618,9 +3780,11 @@ function isStringItemConstraint(constraint) {
|
|
|
2618
3780
|
return false;
|
|
2619
3781
|
}
|
|
2620
3782
|
}
|
|
2621
|
-
function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
|
|
3783
|
+
function applyPathTargetedConstraints(schema, pathConstraints, ctx, typeNode) {
|
|
2622
3784
|
if (schema.type === "array" && schema.items) {
|
|
2623
|
-
|
|
3785
|
+
const referencedType = typeNode?.kind === "reference" ? resolveReferencedType(typeNode, ctx) : void 0;
|
|
3786
|
+
const nestedType = typeNode?.kind === "array" ? typeNode.items : referencedType?.kind === "array" ? referencedType.items : void 0;
|
|
3787
|
+
schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx, nestedType);
|
|
2624
3788
|
return schema;
|
|
2625
3789
|
}
|
|
2626
3790
|
const byTarget = /* @__PURE__ */ new Map();
|
|
@@ -2635,7 +3799,7 @@ function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
|
|
|
2635
3799
|
for (const [target, constraints] of byTarget) {
|
|
2636
3800
|
const subSchema = {};
|
|
2637
3801
|
applyConstraints(subSchema, constraints, ctx);
|
|
2638
|
-
propertyOverrides[target] = subSchema;
|
|
3802
|
+
propertyOverrides[resolveSerializedPropertyName(target, typeNode, ctx)] = subSchema;
|
|
2639
3803
|
}
|
|
2640
3804
|
if (schema.$ref) {
|
|
2641
3805
|
const { $ref, ...rest } = schema;
|
|
@@ -2683,7 +3847,7 @@ function generateTypeNode(type, ctx) {
|
|
|
2683
3847
|
case "union":
|
|
2684
3848
|
return generateUnionType(type, ctx);
|
|
2685
3849
|
case "reference":
|
|
2686
|
-
return generateReferenceType(type);
|
|
3850
|
+
return generateReferenceType(type, ctx);
|
|
2687
3851
|
case "dynamic":
|
|
2688
3852
|
return generateDynamicType(type);
|
|
2689
3853
|
case "custom":
|
|
@@ -2724,9 +3888,10 @@ function generateObjectType(type, ctx) {
|
|
|
2724
3888
|
const properties = {};
|
|
2725
3889
|
const required = [];
|
|
2726
3890
|
for (const prop of type.properties) {
|
|
2727
|
-
|
|
3891
|
+
const propertyName = getSerializedName(prop.name, prop.metadata);
|
|
3892
|
+
properties[propertyName] = generatePropertySchema(prop, ctx);
|
|
2728
3893
|
if (!prop.optional) {
|
|
2729
|
-
required.push(
|
|
3894
|
+
required.push(propertyName);
|
|
2730
3895
|
}
|
|
2731
3896
|
}
|
|
2732
3897
|
const schema = { type: "object", properties };
|
|
@@ -2747,6 +3912,7 @@ function generateRecordType(type, ctx) {
|
|
|
2747
3912
|
function generatePropertySchema(prop, ctx) {
|
|
2748
3913
|
const schema = generateTypeNode(prop.type, ctx);
|
|
2749
3914
|
applyConstraints(schema, prop.constraints, ctx);
|
|
3915
|
+
applyResolvedMetadata(schema, prop.metadata);
|
|
2750
3916
|
applyAnnotations(schema, prop.annotations, ctx);
|
|
2751
3917
|
return schema;
|
|
2752
3918
|
}
|
|
@@ -2775,8 +3941,28 @@ function isNullableUnion(type) {
|
|
|
2775
3941
|
).length;
|
|
2776
3942
|
return nullCount === 1;
|
|
2777
3943
|
}
|
|
2778
|
-
function generateReferenceType(type) {
|
|
2779
|
-
return { $ref: `#/$defs/${type.name}` };
|
|
3944
|
+
function generateReferenceType(type, ctx) {
|
|
3945
|
+
return { $ref: `#/$defs/${ctx.typeNameMap[type.name] ?? type.name}` };
|
|
3946
|
+
}
|
|
3947
|
+
function applyResolvedMetadata(schema, metadata) {
|
|
3948
|
+
const displayName = getDisplayName(metadata);
|
|
3949
|
+
if (displayName !== void 0) {
|
|
3950
|
+
schema.title = displayName;
|
|
3951
|
+
}
|
|
3952
|
+
}
|
|
3953
|
+
function resolveReferencedType(type, ctx) {
|
|
3954
|
+
return ctx.typeRegistry[type.name]?.type;
|
|
3955
|
+
}
|
|
3956
|
+
function resolveSerializedPropertyName(logicalName, typeNode, ctx) {
|
|
3957
|
+
if (typeNode?.kind === "object") {
|
|
3958
|
+
const property = typeNode.properties.find((candidate) => candidate.name === logicalName);
|
|
3959
|
+
return property === void 0 ? logicalName : getSerializedName(property.name, property.metadata);
|
|
3960
|
+
}
|
|
3961
|
+
if (typeNode?.kind === "reference") {
|
|
3962
|
+
const referencedType = resolveReferencedType(typeNode, ctx);
|
|
3963
|
+
return referencedType === void 0 ? logicalName : resolveSerializedPropertyName(logicalName, referencedType, ctx);
|
|
3964
|
+
}
|
|
3965
|
+
return logicalName;
|
|
2780
3966
|
}
|
|
2781
3967
|
function generateDynamicType(type) {
|
|
2782
3968
|
if (type.dynamicKind === "enum") {
|
|
@@ -2856,7 +4042,7 @@ function applyAnnotations(schema, annotations, ctx) {
|
|
|
2856
4042
|
for (const annotation of annotations) {
|
|
2857
4043
|
switch (annotation.annotationKind) {
|
|
2858
4044
|
case "displayName":
|
|
2859
|
-
schema.title
|
|
4045
|
+
schema.title ??= annotation.value;
|
|
2860
4046
|
break;
|
|
2861
4047
|
case "description":
|
|
2862
4048
|
schema.description = annotation.value;
|
|
@@ -3106,13 +4292,21 @@ function combineRules(parentRule, childRule) {
|
|
|
3106
4292
|
}
|
|
3107
4293
|
};
|
|
3108
4294
|
}
|
|
3109
|
-
function
|
|
3110
|
-
const
|
|
4295
|
+
function getFieldDisplayName(field) {
|
|
4296
|
+
const resolvedDisplayName = getDisplayName(field.metadata);
|
|
4297
|
+
if (resolvedDisplayName !== void 0) {
|
|
4298
|
+
return resolvedDisplayName;
|
|
4299
|
+
}
|
|
4300
|
+
return field.annotations.find((annotation) => annotation.annotationKind === "displayName")?.value;
|
|
4301
|
+
}
|
|
4302
|
+
function fieldNodeToControl(field, fieldNameMap, parentRule) {
|
|
3111
4303
|
const placeholderAnnotation = field.annotations.find((a) => a.annotationKind === "placeholder");
|
|
4304
|
+
const serializedName = fieldNameMap.get(field.name) ?? getSerializedName(field.name, field.metadata);
|
|
4305
|
+
const displayName = getFieldDisplayName(field);
|
|
3112
4306
|
const control = {
|
|
3113
4307
|
type: "Control",
|
|
3114
|
-
scope: fieldToScope(
|
|
3115
|
-
...
|
|
4308
|
+
scope: fieldToScope(serializedName),
|
|
4309
|
+
...displayName !== void 0 && { label: displayName },
|
|
3116
4310
|
...placeholderAnnotation !== void 0 && {
|
|
3117
4311
|
options: { placeholder: placeholderAnnotation.value }
|
|
3118
4312
|
},
|
|
@@ -3120,30 +4314,30 @@ function fieldNodeToControl(field, parentRule) {
|
|
|
3120
4314
|
};
|
|
3121
4315
|
return control;
|
|
3122
4316
|
}
|
|
3123
|
-
function groupNodeToLayout(group, parentRule) {
|
|
4317
|
+
function groupNodeToLayout(group, fieldNameMap, parentRule) {
|
|
3124
4318
|
return {
|
|
3125
4319
|
type: "Group",
|
|
3126
4320
|
label: group.label,
|
|
3127
|
-
elements: irElementsToUiSchema(group.elements, parentRule),
|
|
4321
|
+
elements: irElementsToUiSchema(group.elements, fieldNameMap, parentRule),
|
|
3128
4322
|
...parentRule !== void 0 && { rule: parentRule }
|
|
3129
4323
|
};
|
|
3130
4324
|
}
|
|
3131
|
-
function irElementsToUiSchema(elements, parentRule) {
|
|
4325
|
+
function irElementsToUiSchema(elements, fieldNameMap, parentRule) {
|
|
3132
4326
|
const result = [];
|
|
3133
4327
|
for (const element of elements) {
|
|
3134
4328
|
switch (element.kind) {
|
|
3135
4329
|
case "field": {
|
|
3136
|
-
result.push(fieldNodeToControl(element, parentRule));
|
|
4330
|
+
result.push(fieldNodeToControl(element, fieldNameMap, parentRule));
|
|
3137
4331
|
break;
|
|
3138
4332
|
}
|
|
3139
4333
|
case "group": {
|
|
3140
|
-
result.push(groupNodeToLayout(element, parentRule));
|
|
4334
|
+
result.push(groupNodeToLayout(element, fieldNameMap, parentRule));
|
|
3141
4335
|
break;
|
|
3142
4336
|
}
|
|
3143
4337
|
case "conditional": {
|
|
3144
|
-
const newRule = createShowRule(element.fieldName, element.value);
|
|
4338
|
+
const newRule = createShowRule(fieldNameMap.get(element.fieldName) ?? element.fieldName, element.value);
|
|
3145
4339
|
const combinedRule = parentRule !== void 0 ? combineRules(parentRule, newRule) : newRule;
|
|
3146
|
-
const childElements = irElementsToUiSchema(element.elements, combinedRule);
|
|
4340
|
+
const childElements = irElementsToUiSchema(element.elements, fieldNameMap, combinedRule);
|
|
3147
4341
|
result.push(...childElements);
|
|
3148
4342
|
break;
|
|
3149
4343
|
}
|
|
@@ -3157,17 +4351,40 @@ function irElementsToUiSchema(elements, parentRule) {
|
|
|
3157
4351
|
return result;
|
|
3158
4352
|
}
|
|
3159
4353
|
function generateUiSchemaFromIR(ir) {
|
|
4354
|
+
assertNoSerializedNameCollisions(ir);
|
|
4355
|
+
const fieldNameMap = collectFieldNameMap(ir.elements);
|
|
3160
4356
|
const result = {
|
|
3161
4357
|
type: "VerticalLayout",
|
|
3162
|
-
elements: irElementsToUiSchema(ir.elements)
|
|
4358
|
+
elements: irElementsToUiSchema(ir.elements, fieldNameMap)
|
|
3163
4359
|
};
|
|
3164
4360
|
return parseOrThrow(uiSchema, result, "UI Schema");
|
|
3165
4361
|
}
|
|
4362
|
+
function collectFieldNameMap(elements) {
|
|
4363
|
+
const map = /* @__PURE__ */ new Map();
|
|
4364
|
+
for (const element of elements) {
|
|
4365
|
+
switch (element.kind) {
|
|
4366
|
+
case "field":
|
|
4367
|
+
map.set(element.name, getSerializedName(element.name, element.metadata));
|
|
4368
|
+
break;
|
|
4369
|
+
case "group":
|
|
4370
|
+
case "conditional":
|
|
4371
|
+
for (const [key, value] of collectFieldNameMap(element.elements)) {
|
|
4372
|
+
map.set(key, value);
|
|
4373
|
+
}
|
|
4374
|
+
break;
|
|
4375
|
+
default: {
|
|
4376
|
+
const _exhaustive = element;
|
|
4377
|
+
void _exhaustive;
|
|
4378
|
+
}
|
|
4379
|
+
}
|
|
4380
|
+
}
|
|
4381
|
+
return map;
|
|
4382
|
+
}
|
|
3166
4383
|
|
|
3167
4384
|
// src/validate/constraint-validator.ts
|
|
3168
|
-
var
|
|
4385
|
+
var import_internal3 = require("@formspec/analysis/internal");
|
|
3169
4386
|
function validateFieldNode(ctx, field) {
|
|
3170
|
-
const analysis = (0,
|
|
4387
|
+
const analysis = (0, import_internal3.analyzeConstraintTargets)(
|
|
3171
4388
|
field.name,
|
|
3172
4389
|
field.type,
|
|
3173
4390
|
field.constraints,
|
|
@@ -3185,7 +4402,7 @@ function validateFieldNode(ctx, field) {
|
|
|
3185
4402
|
}
|
|
3186
4403
|
function validateObjectProperty(ctx, parentName, property) {
|
|
3187
4404
|
const qualifiedName = `${parentName}.${property.name}`;
|
|
3188
|
-
const analysis = (0,
|
|
4405
|
+
const analysis = (0, import_internal3.analyzeConstraintTargets)(
|
|
3189
4406
|
qualifiedName,
|
|
3190
4407
|
property.type,
|
|
3191
4408
|
property.constraints,
|
|
@@ -3245,7 +4462,11 @@ function generateClassSchemas(analysis, source, options) {
|
|
|
3245
4462
|
if (errorDiagnostics !== void 0 && errorDiagnostics.length > 0) {
|
|
3246
4463
|
throw new Error(formatValidationError(errorDiagnostics));
|
|
3247
4464
|
}
|
|
3248
|
-
const ir = canonicalizeTSDoc(
|
|
4465
|
+
const ir = canonicalizeTSDoc(
|
|
4466
|
+
analysis,
|
|
4467
|
+
source,
|
|
4468
|
+
options?.metadata !== void 0 ? { metadata: options.metadata } : void 0
|
|
4469
|
+
);
|
|
3249
4470
|
const validationResult = validateIR(ir, {
|
|
3250
4471
|
...options?.extensionRegistry !== void 0 && {
|
|
3251
4472
|
extensionRegistry: options.extensionRegistry
|
|
@@ -3358,7 +4579,24 @@ var import_internals5 = require("@formspec/core/internals");
|
|
|
3358
4579
|
function typeToJsonSchema(type, checker) {
|
|
3359
4580
|
const typeRegistry = {};
|
|
3360
4581
|
const visiting = /* @__PURE__ */ new Set();
|
|
3361
|
-
const
|
|
4582
|
+
const diagnostics = [];
|
|
4583
|
+
const typeNode = resolveTypeNode(
|
|
4584
|
+
type,
|
|
4585
|
+
checker,
|
|
4586
|
+
"",
|
|
4587
|
+
typeRegistry,
|
|
4588
|
+
visiting,
|
|
4589
|
+
void 0,
|
|
4590
|
+
void 0,
|
|
4591
|
+
void 0,
|
|
4592
|
+
diagnostics
|
|
4593
|
+
);
|
|
4594
|
+
if (diagnostics.length > 0) {
|
|
4595
|
+
const diagnosticDetails = diagnostics.map((diagnostic) => `${diagnostic.code}: ${diagnostic.message}`).join("; ");
|
|
4596
|
+
throw new Error(
|
|
4597
|
+
`FormSpec validation failed while resolving method schema types. ${diagnosticDetails}`
|
|
4598
|
+
);
|
|
4599
|
+
}
|
|
3362
4600
|
const fieldProvenance = { surface: "tsdoc", file: "", line: 0, column: 0 };
|
|
3363
4601
|
const ir = {
|
|
3364
4602
|
kind: "form-ir",
|