@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.js
CHANGED
|
@@ -1,5 +1,281 @@
|
|
|
1
1
|
// src/canonicalize/chain-dsl-canonicalizer.ts
|
|
2
|
-
import { IR_VERSION } from "@formspec/core/internals";
|
|
2
|
+
import { IR_VERSION, _getFormSpecMetadataPolicy } from "@formspec/core/internals";
|
|
3
|
+
|
|
4
|
+
// src/metadata/policy.ts
|
|
5
|
+
var NOOP_INFLECT = () => "";
|
|
6
|
+
function normalizePluralization(input) {
|
|
7
|
+
if (input?.mode === "infer-if-missing") {
|
|
8
|
+
return {
|
|
9
|
+
mode: "infer-if-missing",
|
|
10
|
+
infer: () => "",
|
|
11
|
+
inflect: input.inflect
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
if (input?.mode === "require-explicit") {
|
|
15
|
+
return {
|
|
16
|
+
mode: "require-explicit",
|
|
17
|
+
infer: () => "",
|
|
18
|
+
inflect: NOOP_INFLECT
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
mode: "disabled",
|
|
23
|
+
infer: () => "",
|
|
24
|
+
inflect: NOOP_INFLECT
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function normalizeScalarPolicy(input) {
|
|
28
|
+
if (input?.mode === "infer-if-missing") {
|
|
29
|
+
return {
|
|
30
|
+
mode: "infer-if-missing",
|
|
31
|
+
infer: input.infer,
|
|
32
|
+
pluralization: normalizePluralization(input.pluralization)
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
if (input?.mode === "require-explicit") {
|
|
36
|
+
return {
|
|
37
|
+
mode: "require-explicit",
|
|
38
|
+
infer: () => "",
|
|
39
|
+
pluralization: normalizePluralization(input.pluralization)
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
mode: "disabled",
|
|
44
|
+
infer: () => "",
|
|
45
|
+
pluralization: normalizePluralization(input?.pluralization)
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function normalizeDeclarationPolicy(input) {
|
|
49
|
+
return {
|
|
50
|
+
apiName: normalizeScalarPolicy(input?.apiName),
|
|
51
|
+
displayName: normalizeScalarPolicy(input?.displayName)
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function normalizeMetadataPolicy(input) {
|
|
55
|
+
return {
|
|
56
|
+
type: normalizeDeclarationPolicy(input?.type),
|
|
57
|
+
field: normalizeDeclarationPolicy(input?.field),
|
|
58
|
+
method: normalizeDeclarationPolicy(input?.method)
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function getDeclarationMetadataPolicy(policy, declarationKind) {
|
|
62
|
+
return policy[declarationKind];
|
|
63
|
+
}
|
|
64
|
+
function makeMetadataContext(surface, declarationKind, logicalName, buildContext) {
|
|
65
|
+
return {
|
|
66
|
+
surface,
|
|
67
|
+
declarationKind,
|
|
68
|
+
logicalName,
|
|
69
|
+
...buildContext !== void 0 && { buildContext }
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// src/metadata/resolve.ts
|
|
74
|
+
function toExplicitScalar(value) {
|
|
75
|
+
return value !== void 0 && value.trim() !== "" ? { value, source: "explicit" } : void 0;
|
|
76
|
+
}
|
|
77
|
+
function toExplicitResolvedMetadata(explicit) {
|
|
78
|
+
if (explicit === void 0) {
|
|
79
|
+
return void 0;
|
|
80
|
+
}
|
|
81
|
+
const apiName = toExplicitScalar(explicit.apiName);
|
|
82
|
+
const displayName = toExplicitScalar(explicit.displayName);
|
|
83
|
+
const apiNamePlural = toExplicitScalar(explicit.apiNamePlural);
|
|
84
|
+
const displayNamePlural = toExplicitScalar(explicit.displayNamePlural);
|
|
85
|
+
const metadata = {
|
|
86
|
+
...apiName !== void 0 && { apiName },
|
|
87
|
+
...displayName !== void 0 && { displayName },
|
|
88
|
+
...apiNamePlural !== void 0 && { apiNamePlural },
|
|
89
|
+
...displayNamePlural !== void 0 && { displayNamePlural }
|
|
90
|
+
};
|
|
91
|
+
return Object.keys(metadata).length > 0 ? metadata : void 0;
|
|
92
|
+
}
|
|
93
|
+
function resolveScalar(current, policy, context, metadataLabel) {
|
|
94
|
+
if (current !== void 0) {
|
|
95
|
+
return current;
|
|
96
|
+
}
|
|
97
|
+
if (policy.mode === "require-explicit") {
|
|
98
|
+
throw new Error(
|
|
99
|
+
`Metadata policy requires explicit ${metadataLabel} for ${context.declarationKind} "${context.logicalName}" on the ${context.surface} surface.`
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
if (policy.mode !== "infer-if-missing") {
|
|
103
|
+
return void 0;
|
|
104
|
+
}
|
|
105
|
+
const inferredValue = policy.infer(context);
|
|
106
|
+
return inferredValue.trim() !== "" ? { value: inferredValue, source: "inferred" } : void 0;
|
|
107
|
+
}
|
|
108
|
+
function resolvePlural(current, singular, policy, context, metadataLabel) {
|
|
109
|
+
if (current !== void 0) {
|
|
110
|
+
return current;
|
|
111
|
+
}
|
|
112
|
+
if (policy.mode === "require-explicit") {
|
|
113
|
+
throw new Error(
|
|
114
|
+
`Metadata policy requires explicit ${metadataLabel} for ${context.declarationKind} "${context.logicalName}" on the ${context.surface} surface.`
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
if (singular === void 0 || policy.mode !== "infer-if-missing") {
|
|
118
|
+
return void 0;
|
|
119
|
+
}
|
|
120
|
+
const pluralValue = policy.inflect({ ...context, singular: singular.value });
|
|
121
|
+
return pluralValue.trim() !== "" ? { value: pluralValue, source: "inferred" } : void 0;
|
|
122
|
+
}
|
|
123
|
+
function resolveResolvedMetadata(current, policy, context) {
|
|
124
|
+
const apiName = resolveScalar(current?.apiName, policy.apiName, context, "apiName");
|
|
125
|
+
const displayName = resolveScalar(
|
|
126
|
+
current?.displayName,
|
|
127
|
+
policy.displayName,
|
|
128
|
+
context,
|
|
129
|
+
"displayName"
|
|
130
|
+
);
|
|
131
|
+
const apiNamePlural = resolvePlural(
|
|
132
|
+
current?.apiNamePlural,
|
|
133
|
+
apiName,
|
|
134
|
+
policy.apiName.pluralization,
|
|
135
|
+
context,
|
|
136
|
+
"apiNamePlural"
|
|
137
|
+
);
|
|
138
|
+
const displayNamePlural = resolvePlural(
|
|
139
|
+
current?.displayNamePlural,
|
|
140
|
+
displayName,
|
|
141
|
+
policy.displayName.pluralization,
|
|
142
|
+
context,
|
|
143
|
+
"displayNamePlural"
|
|
144
|
+
);
|
|
145
|
+
if (apiName === void 0 && displayName === void 0 && apiNamePlural === void 0 && displayNamePlural === void 0) {
|
|
146
|
+
return void 0;
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
...apiName !== void 0 && { apiName },
|
|
150
|
+
...displayName !== void 0 && { displayName },
|
|
151
|
+
...apiNamePlural !== void 0 && { apiNamePlural },
|
|
152
|
+
...displayNamePlural !== void 0 && { displayNamePlural }
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function resolveTypeNodeMetadata(type, options) {
|
|
156
|
+
switch (type.kind) {
|
|
157
|
+
case "array":
|
|
158
|
+
return {
|
|
159
|
+
...type,
|
|
160
|
+
items: resolveTypeNodeMetadata(type.items, options)
|
|
161
|
+
};
|
|
162
|
+
case "object":
|
|
163
|
+
return {
|
|
164
|
+
...type,
|
|
165
|
+
properties: type.properties.map((property) => resolveObjectPropertyMetadata(property, options))
|
|
166
|
+
};
|
|
167
|
+
case "record":
|
|
168
|
+
return {
|
|
169
|
+
...type,
|
|
170
|
+
valueType: resolveTypeNodeMetadata(type.valueType, options)
|
|
171
|
+
};
|
|
172
|
+
case "union":
|
|
173
|
+
return {
|
|
174
|
+
...type,
|
|
175
|
+
members: type.members.map((member) => resolveTypeNodeMetadata(member, options))
|
|
176
|
+
};
|
|
177
|
+
case "reference":
|
|
178
|
+
case "primitive":
|
|
179
|
+
case "enum":
|
|
180
|
+
case "dynamic":
|
|
181
|
+
case "custom":
|
|
182
|
+
return type;
|
|
183
|
+
default: {
|
|
184
|
+
const _exhaustive = type;
|
|
185
|
+
return _exhaustive;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function resolveObjectPropertyMetadata(property, options) {
|
|
190
|
+
const metadata = resolveResolvedMetadata(property.metadata, options.policy.field, {
|
|
191
|
+
surface: options.surface,
|
|
192
|
+
declarationKind: "field",
|
|
193
|
+
logicalName: property.name,
|
|
194
|
+
...options.buildContext !== void 0 && { buildContext: options.buildContext }
|
|
195
|
+
});
|
|
196
|
+
return {
|
|
197
|
+
...property,
|
|
198
|
+
...metadata !== void 0 && { metadata },
|
|
199
|
+
type: resolveTypeNodeMetadata(property.type, options)
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function resolveFieldMetadataNode(field, options) {
|
|
203
|
+
const metadata = resolveResolvedMetadata(field.metadata, options.policy.field, {
|
|
204
|
+
surface: options.surface,
|
|
205
|
+
declarationKind: "field",
|
|
206
|
+
logicalName: field.name,
|
|
207
|
+
...options.buildContext !== void 0 && { buildContext: options.buildContext }
|
|
208
|
+
});
|
|
209
|
+
return {
|
|
210
|
+
...field,
|
|
211
|
+
...metadata !== void 0 && { metadata },
|
|
212
|
+
type: resolveTypeNodeMetadata(field.type, options)
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
function resolveFormElementMetadata(element, options) {
|
|
216
|
+
switch (element.kind) {
|
|
217
|
+
case "field":
|
|
218
|
+
return resolveFieldMetadataNode(element, options);
|
|
219
|
+
case "group":
|
|
220
|
+
return {
|
|
221
|
+
...element,
|
|
222
|
+
elements: element.elements.map((child) => resolveFormElementMetadata(child, options))
|
|
223
|
+
};
|
|
224
|
+
case "conditional":
|
|
225
|
+
return {
|
|
226
|
+
...element,
|
|
227
|
+
elements: element.elements.map((child) => resolveFormElementMetadata(child, options))
|
|
228
|
+
};
|
|
229
|
+
default: {
|
|
230
|
+
const _exhaustive = element;
|
|
231
|
+
return _exhaustive;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
function resolveTypeDefinitionMetadata(typeDefinition, options) {
|
|
236
|
+
const metadata = resolveResolvedMetadata(typeDefinition.metadata, options.policy.type, {
|
|
237
|
+
surface: options.surface,
|
|
238
|
+
declarationKind: "type",
|
|
239
|
+
logicalName: typeDefinition.name,
|
|
240
|
+
...options.buildContext !== void 0 && { buildContext: options.buildContext }
|
|
241
|
+
});
|
|
242
|
+
return {
|
|
243
|
+
...typeDefinition,
|
|
244
|
+
...metadata !== void 0 && { metadata },
|
|
245
|
+
type: resolveTypeNodeMetadata(typeDefinition.type, options)
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
function resolveMetadata(explicit, policy, context) {
|
|
249
|
+
return resolveResolvedMetadata(toExplicitResolvedMetadata(explicit), policy, context);
|
|
250
|
+
}
|
|
251
|
+
function getSerializedName(logicalName, metadata) {
|
|
252
|
+
return metadata?.apiName?.value ?? logicalName;
|
|
253
|
+
}
|
|
254
|
+
function getDisplayName(metadata) {
|
|
255
|
+
return metadata?.displayName?.value;
|
|
256
|
+
}
|
|
257
|
+
function resolveFormIRMetadata(ir, options) {
|
|
258
|
+
const rootLogicalName = options.rootLogicalName ?? ir.name ?? "FormSpec";
|
|
259
|
+
const metadata = resolveResolvedMetadata(ir.metadata, options.policy.type, {
|
|
260
|
+
surface: options.surface,
|
|
261
|
+
declarationKind: "type",
|
|
262
|
+
logicalName: rootLogicalName,
|
|
263
|
+
...options.buildContext !== void 0 && { buildContext: options.buildContext }
|
|
264
|
+
});
|
|
265
|
+
return {
|
|
266
|
+
...ir,
|
|
267
|
+
...metadata !== void 0 && { metadata },
|
|
268
|
+
elements: ir.elements.map((element) => resolveFormElementMetadata(element, options)),
|
|
269
|
+
typeRegistry: Object.fromEntries(
|
|
270
|
+
Object.entries(ir.typeRegistry).map(([name, definition]) => [
|
|
271
|
+
name,
|
|
272
|
+
resolveTypeDefinitionMetadata(definition, options)
|
|
273
|
+
])
|
|
274
|
+
)
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// src/canonicalize/chain-dsl-canonicalizer.ts
|
|
3
279
|
var CHAIN_DSL_PROVENANCE = {
|
|
4
280
|
surface: "chain-dsl",
|
|
5
281
|
file: "",
|
|
@@ -15,57 +291,60 @@ function isConditional(el) {
|
|
|
15
291
|
function isField(el) {
|
|
16
292
|
return el._type === "field";
|
|
17
293
|
}
|
|
18
|
-
function canonicalizeChainDSL(form) {
|
|
294
|
+
function canonicalizeChainDSL(form, options) {
|
|
295
|
+
const metadataPolicy = normalizeMetadataPolicy(
|
|
296
|
+
options?.metadata ?? _getFormSpecMetadataPolicy(form)
|
|
297
|
+
);
|
|
19
298
|
return {
|
|
20
299
|
kind: "form-ir",
|
|
21
300
|
irVersion: IR_VERSION,
|
|
22
|
-
elements: canonicalizeElements(form.elements),
|
|
301
|
+
elements: canonicalizeElements(form.elements, metadataPolicy),
|
|
23
302
|
rootAnnotations: [],
|
|
24
303
|
typeRegistry: {},
|
|
25
304
|
provenance: CHAIN_DSL_PROVENANCE
|
|
26
305
|
};
|
|
27
306
|
}
|
|
28
|
-
function canonicalizeElements(elements) {
|
|
29
|
-
return elements.map(canonicalizeElement);
|
|
307
|
+
function canonicalizeElements(elements, metadataPolicy) {
|
|
308
|
+
return elements.map((element) => canonicalizeElement(element, metadataPolicy));
|
|
30
309
|
}
|
|
31
|
-
function canonicalizeElement(element) {
|
|
310
|
+
function canonicalizeElement(element, metadataPolicy) {
|
|
32
311
|
if (isField(element)) {
|
|
33
|
-
return canonicalizeField(element);
|
|
312
|
+
return canonicalizeField(element, metadataPolicy);
|
|
34
313
|
}
|
|
35
314
|
if (isGroup(element)) {
|
|
36
|
-
return canonicalizeGroup(element);
|
|
315
|
+
return canonicalizeGroup(element, metadataPolicy);
|
|
37
316
|
}
|
|
38
317
|
if (isConditional(element)) {
|
|
39
|
-
return canonicalizeConditional(element);
|
|
318
|
+
return canonicalizeConditional(element, metadataPolicy);
|
|
40
319
|
}
|
|
41
320
|
const _exhaustive = element;
|
|
42
321
|
throw new Error(`Unknown element type: ${JSON.stringify(_exhaustive)}`);
|
|
43
322
|
}
|
|
44
|
-
function canonicalizeField(field) {
|
|
323
|
+
function canonicalizeField(field, metadataPolicy) {
|
|
45
324
|
switch (field._field) {
|
|
46
325
|
case "text":
|
|
47
|
-
return canonicalizeTextField(field);
|
|
326
|
+
return canonicalizeTextField(field, metadataPolicy);
|
|
48
327
|
case "number":
|
|
49
|
-
return canonicalizeNumberField(field);
|
|
328
|
+
return canonicalizeNumberField(field, metadataPolicy);
|
|
50
329
|
case "boolean":
|
|
51
|
-
return canonicalizeBooleanField(field);
|
|
330
|
+
return canonicalizeBooleanField(field, metadataPolicy);
|
|
52
331
|
case "enum":
|
|
53
|
-
return canonicalizeStaticEnumField(field);
|
|
332
|
+
return canonicalizeStaticEnumField(field, metadataPolicy);
|
|
54
333
|
case "dynamic_enum":
|
|
55
|
-
return canonicalizeDynamicEnumField(field);
|
|
334
|
+
return canonicalizeDynamicEnumField(field, metadataPolicy);
|
|
56
335
|
case "dynamic_schema":
|
|
57
|
-
return canonicalizeDynamicSchemaField(field);
|
|
336
|
+
return canonicalizeDynamicSchemaField(field, metadataPolicy);
|
|
58
337
|
case "array":
|
|
59
|
-
return canonicalizeArrayField(field);
|
|
338
|
+
return canonicalizeArrayField(field, metadataPolicy);
|
|
60
339
|
case "object":
|
|
61
|
-
return canonicalizeObjectField(field);
|
|
340
|
+
return canonicalizeObjectField(field, metadataPolicy);
|
|
62
341
|
default: {
|
|
63
342
|
const _exhaustive = field;
|
|
64
343
|
throw new Error(`Unknown field type: ${JSON.stringify(_exhaustive)}`);
|
|
65
344
|
}
|
|
66
345
|
}
|
|
67
346
|
}
|
|
68
|
-
function canonicalizeTextField(field) {
|
|
347
|
+
function canonicalizeTextField(field, metadataPolicy) {
|
|
69
348
|
const type = { kind: "primitive", primitiveKind: "string" };
|
|
70
349
|
const constraints = [];
|
|
71
350
|
if (field.minLength !== void 0) {
|
|
@@ -97,13 +376,14 @@ function canonicalizeTextField(field) {
|
|
|
97
376
|
}
|
|
98
377
|
return buildFieldNode(
|
|
99
378
|
field.name,
|
|
379
|
+
resolveFieldMetadata(field.name, field, metadataPolicy),
|
|
100
380
|
type,
|
|
101
381
|
field.required,
|
|
102
|
-
buildAnnotations(field
|
|
382
|
+
buildAnnotations(getExplicitDisplayName(field), field.placeholder),
|
|
103
383
|
constraints
|
|
104
384
|
);
|
|
105
385
|
}
|
|
106
|
-
function canonicalizeNumberField(field) {
|
|
386
|
+
function canonicalizeNumberField(field, metadataPolicy) {
|
|
107
387
|
const type = { kind: "primitive", primitiveKind: "number" };
|
|
108
388
|
const constraints = [];
|
|
109
389
|
if (field.min !== void 0) {
|
|
@@ -135,17 +415,24 @@ function canonicalizeNumberField(field) {
|
|
|
135
415
|
}
|
|
136
416
|
return buildFieldNode(
|
|
137
417
|
field.name,
|
|
418
|
+
resolveFieldMetadata(field.name, field, metadataPolicy),
|
|
138
419
|
type,
|
|
139
420
|
field.required,
|
|
140
|
-
buildAnnotations(field
|
|
421
|
+
buildAnnotations(getExplicitDisplayName(field)),
|
|
141
422
|
constraints
|
|
142
423
|
);
|
|
143
424
|
}
|
|
144
|
-
function canonicalizeBooleanField(field) {
|
|
425
|
+
function canonicalizeBooleanField(field, metadataPolicy) {
|
|
145
426
|
const type = { kind: "primitive", primitiveKind: "boolean" };
|
|
146
|
-
return buildFieldNode(
|
|
427
|
+
return buildFieldNode(
|
|
428
|
+
field.name,
|
|
429
|
+
resolveFieldMetadata(field.name, field, metadataPolicy),
|
|
430
|
+
type,
|
|
431
|
+
field.required,
|
|
432
|
+
buildAnnotations(getExplicitDisplayName(field))
|
|
433
|
+
);
|
|
147
434
|
}
|
|
148
|
-
function canonicalizeStaticEnumField(field) {
|
|
435
|
+
function canonicalizeStaticEnumField(field, metadataPolicy) {
|
|
149
436
|
const members = field.options.map((opt) => {
|
|
150
437
|
if (typeof opt === "string") {
|
|
151
438
|
return { value: opt };
|
|
@@ -153,28 +440,46 @@ function canonicalizeStaticEnumField(field) {
|
|
|
153
440
|
return { value: opt.id, displayName: opt.label };
|
|
154
441
|
});
|
|
155
442
|
const type = { kind: "enum", members };
|
|
156
|
-
return buildFieldNode(
|
|
443
|
+
return buildFieldNode(
|
|
444
|
+
field.name,
|
|
445
|
+
resolveFieldMetadata(field.name, field, metadataPolicy),
|
|
446
|
+
type,
|
|
447
|
+
field.required,
|
|
448
|
+
buildAnnotations(getExplicitDisplayName(field))
|
|
449
|
+
);
|
|
157
450
|
}
|
|
158
|
-
function canonicalizeDynamicEnumField(field) {
|
|
451
|
+
function canonicalizeDynamicEnumField(field, metadataPolicy) {
|
|
159
452
|
const type = {
|
|
160
453
|
kind: "dynamic",
|
|
161
454
|
dynamicKind: "enum",
|
|
162
455
|
sourceKey: field.source,
|
|
163
456
|
parameterFields: field.params ? [...field.params] : []
|
|
164
457
|
};
|
|
165
|
-
return buildFieldNode(
|
|
458
|
+
return buildFieldNode(
|
|
459
|
+
field.name,
|
|
460
|
+
resolveFieldMetadata(field.name, field, metadataPolicy),
|
|
461
|
+
type,
|
|
462
|
+
field.required,
|
|
463
|
+
buildAnnotations(getExplicitDisplayName(field))
|
|
464
|
+
);
|
|
166
465
|
}
|
|
167
|
-
function canonicalizeDynamicSchemaField(field) {
|
|
466
|
+
function canonicalizeDynamicSchemaField(field, metadataPolicy) {
|
|
168
467
|
const type = {
|
|
169
468
|
kind: "dynamic",
|
|
170
469
|
dynamicKind: "schema",
|
|
171
470
|
sourceKey: field.schemaSource,
|
|
172
471
|
parameterFields: []
|
|
173
472
|
};
|
|
174
|
-
return buildFieldNode(
|
|
473
|
+
return buildFieldNode(
|
|
474
|
+
field.name,
|
|
475
|
+
resolveFieldMetadata(field.name, field, metadataPolicy),
|
|
476
|
+
type,
|
|
477
|
+
field.required,
|
|
478
|
+
buildAnnotations(getExplicitDisplayName(field))
|
|
479
|
+
);
|
|
175
480
|
}
|
|
176
|
-
function canonicalizeArrayField(field) {
|
|
177
|
-
const itemProperties = buildObjectProperties(field.items);
|
|
481
|
+
function canonicalizeArrayField(field, metadataPolicy) {
|
|
482
|
+
const itemProperties = buildObjectProperties(field.items, metadataPolicy);
|
|
178
483
|
const itemsType = {
|
|
179
484
|
kind: "object",
|
|
180
485
|
properties: itemProperties,
|
|
@@ -202,37 +507,44 @@ function canonicalizeArrayField(field) {
|
|
|
202
507
|
}
|
|
203
508
|
return buildFieldNode(
|
|
204
509
|
field.name,
|
|
510
|
+
resolveFieldMetadata(field.name, field, metadataPolicy),
|
|
205
511
|
type,
|
|
206
512
|
field.required,
|
|
207
|
-
buildAnnotations(field
|
|
513
|
+
buildAnnotations(getExplicitDisplayName(field)),
|
|
208
514
|
constraints
|
|
209
515
|
);
|
|
210
516
|
}
|
|
211
|
-
function canonicalizeObjectField(field) {
|
|
212
|
-
const properties = buildObjectProperties(field.properties);
|
|
517
|
+
function canonicalizeObjectField(field, metadataPolicy) {
|
|
518
|
+
const properties = buildObjectProperties(field.properties, metadataPolicy);
|
|
213
519
|
const type = {
|
|
214
520
|
kind: "object",
|
|
215
521
|
properties,
|
|
216
522
|
additionalProperties: true
|
|
217
523
|
};
|
|
218
|
-
return buildFieldNode(
|
|
524
|
+
return buildFieldNode(
|
|
525
|
+
field.name,
|
|
526
|
+
resolveFieldMetadata(field.name, field, metadataPolicy),
|
|
527
|
+
type,
|
|
528
|
+
field.required,
|
|
529
|
+
buildAnnotations(getExplicitDisplayName(field))
|
|
530
|
+
);
|
|
219
531
|
}
|
|
220
|
-
function canonicalizeGroup(g) {
|
|
532
|
+
function canonicalizeGroup(g, metadataPolicy) {
|
|
221
533
|
return {
|
|
222
534
|
kind: "group",
|
|
223
535
|
label: g.label,
|
|
224
|
-
elements: canonicalizeElements(g.elements),
|
|
536
|
+
elements: canonicalizeElements(g.elements, metadataPolicy),
|
|
225
537
|
provenance: CHAIN_DSL_PROVENANCE
|
|
226
538
|
};
|
|
227
539
|
}
|
|
228
|
-
function canonicalizeConditional(c) {
|
|
540
|
+
function canonicalizeConditional(c, metadataPolicy) {
|
|
229
541
|
return {
|
|
230
542
|
kind: "conditional",
|
|
231
543
|
fieldName: c.field,
|
|
232
544
|
// Conditional values from the chain DSL are JSON-serializable primitives
|
|
233
545
|
// (strings, numbers, booleans) produced by the `is()` predicate helper.
|
|
234
546
|
value: assertJsonValue(c.value),
|
|
235
|
-
elements: canonicalizeElements(c.elements),
|
|
547
|
+
elements: canonicalizeElements(c.elements, metadataPolicy),
|
|
236
548
|
provenance: CHAIN_DSL_PROVENANCE
|
|
237
549
|
};
|
|
238
550
|
}
|
|
@@ -252,10 +564,11 @@ function assertJsonValue(v) {
|
|
|
252
564
|
}
|
|
253
565
|
throw new TypeError(`Conditional value is not a valid JsonValue: ${typeof v}`);
|
|
254
566
|
}
|
|
255
|
-
function buildFieldNode(name, type, required, annotations, constraints = []) {
|
|
567
|
+
function buildFieldNode(name, metadata, type, required, annotations, constraints = []) {
|
|
256
568
|
return {
|
|
257
569
|
kind: "field",
|
|
258
570
|
name,
|
|
571
|
+
...metadata !== void 0 && { metadata },
|
|
259
572
|
type,
|
|
260
573
|
required: required === true,
|
|
261
574
|
constraints,
|
|
@@ -285,13 +598,14 @@ function buildAnnotations(label, placeholder) {
|
|
|
285
598
|
}
|
|
286
599
|
return annotations;
|
|
287
600
|
}
|
|
288
|
-
function buildObjectProperties(elements, insideConditional = false) {
|
|
601
|
+
function buildObjectProperties(elements, metadataPolicy, insideConditional = false) {
|
|
289
602
|
const properties = [];
|
|
290
603
|
for (const el of elements) {
|
|
291
604
|
if (isField(el)) {
|
|
292
|
-
const fieldNode = canonicalizeField(el);
|
|
605
|
+
const fieldNode = canonicalizeField(el, metadataPolicy);
|
|
293
606
|
properties.push({
|
|
294
607
|
name: fieldNode.name,
|
|
608
|
+
...fieldNode.metadata !== void 0 && { metadata: fieldNode.metadata },
|
|
295
609
|
type: fieldNode.type,
|
|
296
610
|
// Fields inside a conditional branch are always optional in the
|
|
297
611
|
// data schema, regardless of their `required` flag — the condition
|
|
@@ -302,17 +616,34 @@ function buildObjectProperties(elements, insideConditional = false) {
|
|
|
302
616
|
provenance: CHAIN_DSL_PROVENANCE
|
|
303
617
|
});
|
|
304
618
|
} else if (isGroup(el)) {
|
|
305
|
-
properties.push(...buildObjectProperties(el.elements, insideConditional));
|
|
619
|
+
properties.push(...buildObjectProperties(el.elements, metadataPolicy, insideConditional));
|
|
306
620
|
} else if (isConditional(el)) {
|
|
307
|
-
properties.push(...buildObjectProperties(el.elements, true));
|
|
621
|
+
properties.push(...buildObjectProperties(el.elements, metadataPolicy, true));
|
|
308
622
|
}
|
|
309
623
|
}
|
|
310
624
|
return properties;
|
|
311
625
|
}
|
|
626
|
+
function getExplicitDisplayName(field) {
|
|
627
|
+
if (field.label !== void 0 && field.displayName !== void 0) {
|
|
628
|
+
throw new Error('Chain DSL fields cannot specify both "label" and "displayName".');
|
|
629
|
+
}
|
|
630
|
+
return field.displayName ?? field.label;
|
|
631
|
+
}
|
|
632
|
+
function resolveFieldMetadata(logicalName, field, metadataPolicy) {
|
|
633
|
+
const displayName = getExplicitDisplayName(field);
|
|
634
|
+
return resolveMetadata(
|
|
635
|
+
{
|
|
636
|
+
...field.apiName !== void 0 && { apiName: field.apiName },
|
|
637
|
+
...displayName !== void 0 && { displayName }
|
|
638
|
+
},
|
|
639
|
+
getDeclarationMetadataPolicy(metadataPolicy, "field"),
|
|
640
|
+
makeMetadataContext("chain-dsl", "field", logicalName)
|
|
641
|
+
);
|
|
642
|
+
}
|
|
312
643
|
|
|
313
644
|
// src/canonicalize/tsdoc-canonicalizer.ts
|
|
314
645
|
import { IR_VERSION as IR_VERSION2 } from "@formspec/core/internals";
|
|
315
|
-
function canonicalizeTSDoc(analysis, source) {
|
|
646
|
+
function canonicalizeTSDoc(analysis, source, options) {
|
|
316
647
|
const file = source?.file ?? "";
|
|
317
648
|
const provenance = {
|
|
318
649
|
surface: "tsdoc",
|
|
@@ -321,15 +652,21 @@ function canonicalizeTSDoc(analysis, source) {
|
|
|
321
652
|
column: 0
|
|
322
653
|
};
|
|
323
654
|
const elements = assembleElements(analysis.fields, analysis.fieldLayouts, provenance);
|
|
324
|
-
|
|
655
|
+
const ir = {
|
|
325
656
|
kind: "form-ir",
|
|
657
|
+
name: analysis.name,
|
|
326
658
|
irVersion: IR_VERSION2,
|
|
327
659
|
elements,
|
|
660
|
+
...analysis.metadata !== void 0 && { metadata: analysis.metadata },
|
|
328
661
|
typeRegistry: analysis.typeRegistry,
|
|
329
662
|
...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { rootAnnotations: analysis.annotations },
|
|
330
663
|
...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { annotations: analysis.annotations },
|
|
331
664
|
provenance
|
|
332
665
|
};
|
|
666
|
+
return resolveFormIRMetadata(ir, {
|
|
667
|
+
policy: normalizeMetadataPolicy(options?.metadata),
|
|
668
|
+
surface: "tsdoc"
|
|
669
|
+
});
|
|
333
670
|
}
|
|
334
671
|
function assembleElements(fields, layouts, provenance) {
|
|
335
672
|
const elements = [];
|
|
@@ -392,6 +729,9 @@ import * as path from "path";
|
|
|
392
729
|
|
|
393
730
|
// src/analyzer/class-analyzer.ts
|
|
394
731
|
import * as ts3 from "typescript";
|
|
732
|
+
import {
|
|
733
|
+
parseCommentBlock as parseCommentBlock2
|
|
734
|
+
} from "@formspec/analysis/internal";
|
|
395
735
|
|
|
396
736
|
// src/analyzer/jsdoc-constraints.ts
|
|
397
737
|
import * as ts2 from "typescript";
|
|
@@ -1294,7 +1634,76 @@ function makeParseOptions(extensionRegistry, fieldType, checker, subjectType, ho
|
|
|
1294
1634
|
...hostType !== void 0 && { hostType }
|
|
1295
1635
|
};
|
|
1296
1636
|
}
|
|
1297
|
-
function
|
|
1637
|
+
function makeExplicitScalarMetadata(value) {
|
|
1638
|
+
return value === void 0 || value === "" ? void 0 : { value, source: "explicit" };
|
|
1639
|
+
}
|
|
1640
|
+
function extractExplicitMetadata(node) {
|
|
1641
|
+
let apiName;
|
|
1642
|
+
let displayName;
|
|
1643
|
+
let apiNamePlural;
|
|
1644
|
+
let displayNamePlural;
|
|
1645
|
+
for (const tag of getLeadingParsedTags(node)) {
|
|
1646
|
+
const value = tag.argumentText.trim();
|
|
1647
|
+
if (value === "") {
|
|
1648
|
+
continue;
|
|
1649
|
+
}
|
|
1650
|
+
if (tag.normalizedTagName === "apiName") {
|
|
1651
|
+
if (tag.target === null) {
|
|
1652
|
+
apiName ??= value;
|
|
1653
|
+
} else if (tag.target.kind === "variant") {
|
|
1654
|
+
if (tag.target.rawText === "singular") {
|
|
1655
|
+
apiName ??= value;
|
|
1656
|
+
} else if (tag.target.rawText === "plural") {
|
|
1657
|
+
apiNamePlural ??= value;
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
continue;
|
|
1661
|
+
}
|
|
1662
|
+
if (tag.normalizedTagName === "displayName") {
|
|
1663
|
+
if (tag.target === null) {
|
|
1664
|
+
displayName ??= value;
|
|
1665
|
+
} else if (tag.target.kind === "variant") {
|
|
1666
|
+
if (tag.target.rawText === "singular") {
|
|
1667
|
+
displayName ??= value;
|
|
1668
|
+
} else if (tag.target.rawText === "plural") {
|
|
1669
|
+
displayNamePlural ??= value;
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
const resolvedApiName = makeExplicitScalarMetadata(apiName);
|
|
1675
|
+
const resolvedDisplayName = makeExplicitScalarMetadata(displayName);
|
|
1676
|
+
const resolvedApiNamePlural = makeExplicitScalarMetadata(apiNamePlural);
|
|
1677
|
+
const resolvedDisplayNamePlural = makeExplicitScalarMetadata(displayNamePlural);
|
|
1678
|
+
const metadata = {
|
|
1679
|
+
...resolvedApiName !== void 0 && { apiName: resolvedApiName },
|
|
1680
|
+
...resolvedDisplayName !== void 0 && { displayName: resolvedDisplayName },
|
|
1681
|
+
...resolvedApiNamePlural !== void 0 && { apiNamePlural: resolvedApiNamePlural },
|
|
1682
|
+
...resolvedDisplayNamePlural !== void 0 && {
|
|
1683
|
+
displayNamePlural: resolvedDisplayNamePlural
|
|
1684
|
+
}
|
|
1685
|
+
};
|
|
1686
|
+
return Object.keys(metadata).length === 0 ? void 0 : metadata;
|
|
1687
|
+
}
|
|
1688
|
+
function resolveNodeMetadata(metadataPolicy, declarationKind, logicalName, node, buildContext) {
|
|
1689
|
+
const explicit = extractExplicitMetadata(node);
|
|
1690
|
+
return resolveMetadata(
|
|
1691
|
+
{
|
|
1692
|
+
...explicit?.apiName !== void 0 && { apiName: explicit.apiName.value },
|
|
1693
|
+
...explicit?.displayName !== void 0 && { displayName: explicit.displayName.value },
|
|
1694
|
+
...explicit?.apiNamePlural !== void 0 && {
|
|
1695
|
+
apiNamePlural: explicit.apiNamePlural.value
|
|
1696
|
+
},
|
|
1697
|
+
...explicit?.displayNamePlural !== void 0 && {
|
|
1698
|
+
displayNamePlural: explicit.displayNamePlural.value
|
|
1699
|
+
}
|
|
1700
|
+
},
|
|
1701
|
+
getDeclarationMetadataPolicy(metadataPolicy, declarationKind),
|
|
1702
|
+
makeMetadataContext("tsdoc", declarationKind, logicalName, buildContext)
|
|
1703
|
+
);
|
|
1704
|
+
}
|
|
1705
|
+
function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry, metadataPolicy) {
|
|
1706
|
+
const normalizedMetadataPolicy = normalizeMetadataPolicy(metadataPolicy);
|
|
1298
1707
|
const name = classDecl.name?.text ?? "AnonymousClass";
|
|
1299
1708
|
const fields = [];
|
|
1300
1709
|
const fieldLayouts = [];
|
|
@@ -1321,6 +1730,7 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
|
|
|
1321
1730
|
visiting,
|
|
1322
1731
|
diagnostics,
|
|
1323
1732
|
classType,
|
|
1733
|
+
normalizedMetadataPolicy,
|
|
1324
1734
|
extensionRegistry
|
|
1325
1735
|
);
|
|
1326
1736
|
if (fieldNode) {
|
|
@@ -1339,9 +1749,25 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
|
|
|
1339
1749
|
}
|
|
1340
1750
|
}
|
|
1341
1751
|
}
|
|
1752
|
+
const specializedFields = applyDeclarationDiscriminatorToFields(
|
|
1753
|
+
fields,
|
|
1754
|
+
classDecl,
|
|
1755
|
+
classType,
|
|
1756
|
+
checker,
|
|
1757
|
+
file,
|
|
1758
|
+
diagnostics,
|
|
1759
|
+
normalizedMetadataPolicy
|
|
1760
|
+
);
|
|
1761
|
+
const metadata = resolveNodeMetadata(normalizedMetadataPolicy, "type", name, classDecl, {
|
|
1762
|
+
checker,
|
|
1763
|
+
declaration: classDecl,
|
|
1764
|
+
subjectType: classType,
|
|
1765
|
+
hostType: classType
|
|
1766
|
+
});
|
|
1342
1767
|
return {
|
|
1343
1768
|
name,
|
|
1344
|
-
|
|
1769
|
+
...metadata !== void 0 && { metadata },
|
|
1770
|
+
fields: specializedFields,
|
|
1345
1771
|
fieldLayouts,
|
|
1346
1772
|
typeRegistry,
|
|
1347
1773
|
...annotations.length > 0 && { annotations },
|
|
@@ -1350,7 +1776,8 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
|
|
|
1350
1776
|
staticMethods
|
|
1351
1777
|
};
|
|
1352
1778
|
}
|
|
1353
|
-
function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegistry) {
|
|
1779
|
+
function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegistry, metadataPolicy) {
|
|
1780
|
+
const normalizedMetadataPolicy = normalizeMetadataPolicy(metadataPolicy);
|
|
1354
1781
|
const name = interfaceDecl.name.text;
|
|
1355
1782
|
const fields = [];
|
|
1356
1783
|
const typeRegistry = {};
|
|
@@ -1374,6 +1801,78 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
|
|
|
1374
1801
|
visiting,
|
|
1375
1802
|
diagnostics,
|
|
1376
1803
|
interfaceType,
|
|
1804
|
+
normalizedMetadataPolicy,
|
|
1805
|
+
extensionRegistry
|
|
1806
|
+
);
|
|
1807
|
+
if (fieldNode) {
|
|
1808
|
+
fields.push(fieldNode);
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
const specializedFields = applyDeclarationDiscriminatorToFields(
|
|
1813
|
+
fields,
|
|
1814
|
+
interfaceDecl,
|
|
1815
|
+
interfaceType,
|
|
1816
|
+
checker,
|
|
1817
|
+
file,
|
|
1818
|
+
diagnostics,
|
|
1819
|
+
normalizedMetadataPolicy
|
|
1820
|
+
);
|
|
1821
|
+
const fieldLayouts = specializedFields.map(() => ({}));
|
|
1822
|
+
const metadata = resolveNodeMetadata(normalizedMetadataPolicy, "type", name, interfaceDecl, {
|
|
1823
|
+
checker,
|
|
1824
|
+
declaration: interfaceDecl,
|
|
1825
|
+
subjectType: interfaceType,
|
|
1826
|
+
hostType: interfaceType
|
|
1827
|
+
});
|
|
1828
|
+
return {
|
|
1829
|
+
name,
|
|
1830
|
+
...metadata !== void 0 && { metadata },
|
|
1831
|
+
fields: specializedFields,
|
|
1832
|
+
fieldLayouts,
|
|
1833
|
+
typeRegistry,
|
|
1834
|
+
...annotations.length > 0 && { annotations },
|
|
1835
|
+
...diagnostics.length > 0 && { diagnostics },
|
|
1836
|
+
instanceMethods: [],
|
|
1837
|
+
staticMethods: []
|
|
1838
|
+
};
|
|
1839
|
+
}
|
|
1840
|
+
function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry, metadataPolicy) {
|
|
1841
|
+
if (!ts3.isTypeLiteralNode(typeAlias.type)) {
|
|
1842
|
+
const sourceFile = typeAlias.getSourceFile();
|
|
1843
|
+
const { line } = sourceFile.getLineAndCharacterOfPosition(typeAlias.getStart());
|
|
1844
|
+
const kindDesc = ts3.SyntaxKind[typeAlias.type.kind] ?? "unknown";
|
|
1845
|
+
return {
|
|
1846
|
+
ok: false,
|
|
1847
|
+
error: `Type alias "${typeAlias.name.text}" at line ${String(line + 1)} is not an object type literal (found ${kindDesc})`
|
|
1848
|
+
};
|
|
1849
|
+
}
|
|
1850
|
+
const typeLiteral = typeAlias.type;
|
|
1851
|
+
const normalizedMetadataPolicy = normalizeMetadataPolicy(metadataPolicy);
|
|
1852
|
+
const name = typeAlias.name.text;
|
|
1853
|
+
const fields = [];
|
|
1854
|
+
const typeRegistry = {};
|
|
1855
|
+
const diagnostics = [];
|
|
1856
|
+
const aliasType = checker.getTypeAtLocation(typeAlias);
|
|
1857
|
+
const typeAliasDoc = extractJSDocParseResult(
|
|
1858
|
+
typeAlias,
|
|
1859
|
+
file,
|
|
1860
|
+
makeParseOptions(extensionRegistry, void 0, checker, aliasType, aliasType)
|
|
1861
|
+
);
|
|
1862
|
+
const annotations = [...typeAliasDoc.annotations];
|
|
1863
|
+
diagnostics.push(...typeAliasDoc.diagnostics);
|
|
1864
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
1865
|
+
for (const member of typeLiteral.members) {
|
|
1866
|
+
if (ts3.isPropertySignature(member)) {
|
|
1867
|
+
const fieldNode = analyzeInterfacePropertyToIR(
|
|
1868
|
+
member,
|
|
1869
|
+
checker,
|
|
1870
|
+
file,
|
|
1871
|
+
typeRegistry,
|
|
1872
|
+
visiting,
|
|
1873
|
+
diagnostics,
|
|
1874
|
+
aliasType,
|
|
1875
|
+
normalizedMetadataPolicy,
|
|
1377
1876
|
extensionRegistry
|
|
1378
1877
|
);
|
|
1379
1878
|
if (fieldNode) {
|
|
@@ -1381,73 +1880,474 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
|
|
|
1381
1880
|
}
|
|
1382
1881
|
}
|
|
1383
1882
|
}
|
|
1384
|
-
const
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1883
|
+
const specializedFields = applyDeclarationDiscriminatorToFields(
|
|
1884
|
+
fields,
|
|
1885
|
+
typeAlias,
|
|
1886
|
+
aliasType,
|
|
1887
|
+
checker,
|
|
1888
|
+
file,
|
|
1889
|
+
diagnostics,
|
|
1890
|
+
normalizedMetadataPolicy
|
|
1891
|
+
);
|
|
1892
|
+
const metadata = resolveNodeMetadata(normalizedMetadataPolicy, "type", name, typeAlias, {
|
|
1893
|
+
checker,
|
|
1894
|
+
declaration: typeAlias,
|
|
1895
|
+
subjectType: aliasType,
|
|
1896
|
+
hostType: aliasType
|
|
1897
|
+
});
|
|
1898
|
+
return {
|
|
1899
|
+
ok: true,
|
|
1900
|
+
analysis: {
|
|
1901
|
+
name,
|
|
1902
|
+
...metadata !== void 0 && { metadata },
|
|
1903
|
+
fields: specializedFields,
|
|
1904
|
+
fieldLayouts: specializedFields.map(() => ({})),
|
|
1905
|
+
typeRegistry,
|
|
1906
|
+
...annotations.length > 0 && { annotations },
|
|
1907
|
+
...diagnostics.length > 0 && { diagnostics },
|
|
1908
|
+
instanceMethods: [],
|
|
1909
|
+
staticMethods: []
|
|
1910
|
+
}
|
|
1911
|
+
};
|
|
1912
|
+
}
|
|
1913
|
+
function makeAnalysisDiagnostic(code, message, primaryLocation, relatedLocations = []) {
|
|
1914
|
+
return {
|
|
1915
|
+
code,
|
|
1916
|
+
message,
|
|
1917
|
+
severity: "error",
|
|
1918
|
+
primaryLocation,
|
|
1919
|
+
relatedLocations
|
|
1920
|
+
};
|
|
1921
|
+
}
|
|
1922
|
+
function getLeadingParsedTags(node) {
|
|
1923
|
+
const sourceFile = node.getSourceFile();
|
|
1924
|
+
const sourceText = sourceFile.getFullText();
|
|
1925
|
+
const commentRanges = ts3.getLeadingCommentRanges(sourceText, node.getFullStart());
|
|
1926
|
+
if (commentRanges === void 0) {
|
|
1927
|
+
return [];
|
|
1928
|
+
}
|
|
1929
|
+
const parsedTags = [];
|
|
1930
|
+
for (const range of commentRanges) {
|
|
1931
|
+
if (range.kind !== ts3.SyntaxKind.MultiLineCommentTrivia) {
|
|
1932
|
+
continue;
|
|
1933
|
+
}
|
|
1934
|
+
const commentText = sourceText.slice(range.pos, range.end);
|
|
1935
|
+
if (!commentText.startsWith("/**")) {
|
|
1936
|
+
continue;
|
|
1937
|
+
}
|
|
1938
|
+
parsedTags.push(...parseCommentBlock2(commentText, { offset: range.pos }).tags);
|
|
1939
|
+
}
|
|
1940
|
+
return parsedTags;
|
|
1941
|
+
}
|
|
1942
|
+
function resolveDiscriminatorProperty(node, checker, fieldName) {
|
|
1943
|
+
const subjectType = checker.getTypeAtLocation(node);
|
|
1944
|
+
const propertySymbol = subjectType.getProperty(fieldName);
|
|
1945
|
+
if (propertySymbol === void 0) {
|
|
1946
|
+
return null;
|
|
1947
|
+
}
|
|
1948
|
+
const declaration = propertySymbol.valueDeclaration ?? propertySymbol.declarations?.find(
|
|
1949
|
+
(candidate) => ts3.isPropertyDeclaration(candidate) || ts3.isPropertySignature(candidate)
|
|
1950
|
+
) ?? propertySymbol.declarations?.[0];
|
|
1951
|
+
return {
|
|
1952
|
+
declaration,
|
|
1953
|
+
type: checker.getTypeOfSymbolAtLocation(propertySymbol, declaration ?? node),
|
|
1954
|
+
optional: !!(propertySymbol.flags & ts3.SymbolFlags.Optional) || declaration !== void 0 && "questionToken" in declaration && declaration.questionToken !== void 0
|
|
1955
|
+
};
|
|
1956
|
+
}
|
|
1957
|
+
function isLocalTypeParameterName(node, typeParameterName) {
|
|
1958
|
+
return node.typeParameters?.some((typeParameter) => typeParameter.name.text === typeParameterName) ?? false;
|
|
1959
|
+
}
|
|
1960
|
+
function isNullishSemanticType(type) {
|
|
1961
|
+
if (type.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined | ts3.TypeFlags.Void | ts3.TypeFlags.Unknown | ts3.TypeFlags.Any)) {
|
|
1962
|
+
return true;
|
|
1963
|
+
}
|
|
1964
|
+
return type.isUnion() && type.types.some((member) => isNullishSemanticType(member));
|
|
1965
|
+
}
|
|
1966
|
+
function isStringLikeSemanticType(type) {
|
|
1967
|
+
if (type.flags & ts3.TypeFlags.StringLike) {
|
|
1968
|
+
return true;
|
|
1969
|
+
}
|
|
1970
|
+
if (type.isUnion()) {
|
|
1971
|
+
return type.types.length > 0 && type.types.every((member) => isStringLikeSemanticType(member));
|
|
1972
|
+
}
|
|
1973
|
+
return false;
|
|
1974
|
+
}
|
|
1975
|
+
function extractDiscriminatorDirective(node, file, diagnostics) {
|
|
1976
|
+
const discriminatorTags = getLeadingParsedTags(node).filter(
|
|
1977
|
+
(tag) => tag.normalizedTagName === "discriminator"
|
|
1978
|
+
);
|
|
1979
|
+
if (discriminatorTags.length === 0) {
|
|
1980
|
+
return null;
|
|
1981
|
+
}
|
|
1982
|
+
const [firstTag, ...duplicateTags] = discriminatorTags;
|
|
1983
|
+
for (const _duplicateTag of duplicateTags) {
|
|
1984
|
+
diagnostics.push(
|
|
1985
|
+
makeAnalysisDiagnostic(
|
|
1986
|
+
"DUPLICATE_TAG",
|
|
1987
|
+
'Duplicate "@discriminator" tag. Only one discriminator declaration is allowed per declaration.',
|
|
1988
|
+
provenanceForNode(node, file)
|
|
1989
|
+
)
|
|
1990
|
+
);
|
|
1991
|
+
}
|
|
1992
|
+
if (firstTag === void 0) {
|
|
1993
|
+
return null;
|
|
1994
|
+
}
|
|
1995
|
+
const firstTarget = firstTag.target;
|
|
1996
|
+
if (firstTarget?.path === null || firstTarget?.valid !== true) {
|
|
1997
|
+
diagnostics.push(
|
|
1998
|
+
makeAnalysisDiagnostic(
|
|
1999
|
+
"INVALID_TAG_ARGUMENT",
|
|
2000
|
+
'Tag "@discriminator" requires a direct path target like ":kind".',
|
|
2001
|
+
provenanceForNode(node, file)
|
|
2002
|
+
)
|
|
2003
|
+
);
|
|
2004
|
+
return null;
|
|
2005
|
+
}
|
|
2006
|
+
if (firstTarget.path.segments.length !== 1) {
|
|
2007
|
+
diagnostics.push(
|
|
2008
|
+
makeAnalysisDiagnostic(
|
|
2009
|
+
"INVALID_TAG_ARGUMENT",
|
|
2010
|
+
'Tag "@discriminator" only supports direct property targets in v1; nested paths are out of scope.',
|
|
2011
|
+
provenanceForNode(node, file)
|
|
2012
|
+
)
|
|
2013
|
+
);
|
|
2014
|
+
return null;
|
|
2015
|
+
}
|
|
2016
|
+
const typeParameterName = firstTag.argumentText.trim();
|
|
2017
|
+
if (!/^[A-Za-z_$][\w$]*$/u.test(typeParameterName)) {
|
|
2018
|
+
diagnostics.push(
|
|
2019
|
+
makeAnalysisDiagnostic(
|
|
2020
|
+
"INVALID_TAG_ARGUMENT",
|
|
2021
|
+
'Tag "@discriminator" requires a local type parameter name as its source operand.',
|
|
2022
|
+
provenanceForNode(node, file)
|
|
2023
|
+
)
|
|
2024
|
+
);
|
|
2025
|
+
return null;
|
|
2026
|
+
}
|
|
2027
|
+
return {
|
|
2028
|
+
fieldName: firstTarget.path.segments[0] ?? firstTarget.rawText,
|
|
2029
|
+
typeParameterName,
|
|
2030
|
+
provenance: provenanceForNode(node, file)
|
|
2031
|
+
};
|
|
2032
|
+
}
|
|
2033
|
+
function validateDiscriminatorDirective(node, checker, file, diagnostics) {
|
|
2034
|
+
const directive = extractDiscriminatorDirective(node, file, diagnostics);
|
|
2035
|
+
if (directive === null) {
|
|
2036
|
+
return null;
|
|
2037
|
+
}
|
|
2038
|
+
if (!isLocalTypeParameterName(node, directive.typeParameterName)) {
|
|
2039
|
+
diagnostics.push(
|
|
2040
|
+
makeAnalysisDiagnostic(
|
|
2041
|
+
"INVALID_TAG_ARGUMENT",
|
|
2042
|
+
`Tag "@discriminator" references "${directive.typeParameterName}", but the source operand must be a type parameter declared on the same declaration.`,
|
|
2043
|
+
directive.provenance
|
|
2044
|
+
)
|
|
2045
|
+
);
|
|
2046
|
+
return null;
|
|
2047
|
+
}
|
|
2048
|
+
const property = resolveDiscriminatorProperty(node, checker, directive.fieldName);
|
|
2049
|
+
if (property === null) {
|
|
2050
|
+
diagnostics.push(
|
|
2051
|
+
makeAnalysisDiagnostic(
|
|
2052
|
+
"UNKNOWN_PATH_TARGET",
|
|
2053
|
+
`Tag "@discriminator" targets "${directive.fieldName}", but no direct property with that name exists on this declaration.`,
|
|
2054
|
+
directive.provenance
|
|
2055
|
+
)
|
|
2056
|
+
);
|
|
2057
|
+
return null;
|
|
2058
|
+
}
|
|
2059
|
+
if (property.optional) {
|
|
2060
|
+
diagnostics.push(
|
|
2061
|
+
makeAnalysisDiagnostic(
|
|
2062
|
+
"TYPE_MISMATCH",
|
|
2063
|
+
`Discriminator field "${directive.fieldName}" must be required; optional discriminator fields are not supported.`,
|
|
2064
|
+
directive.provenance,
|
|
2065
|
+
property.declaration !== void 0 ? [provenanceForNode(property.declaration, file)] : []
|
|
2066
|
+
)
|
|
2067
|
+
);
|
|
2068
|
+
return null;
|
|
2069
|
+
}
|
|
2070
|
+
if (isNullishSemanticType(property.type)) {
|
|
2071
|
+
diagnostics.push(
|
|
2072
|
+
makeAnalysisDiagnostic(
|
|
2073
|
+
"TYPE_MISMATCH",
|
|
2074
|
+
`Discriminator field "${directive.fieldName}" must not be nullable.`,
|
|
2075
|
+
directive.provenance,
|
|
2076
|
+
property.declaration !== void 0 ? [provenanceForNode(property.declaration, file)] : []
|
|
2077
|
+
)
|
|
2078
|
+
);
|
|
2079
|
+
return null;
|
|
2080
|
+
}
|
|
2081
|
+
if (!isStringLikeSemanticType(property.type)) {
|
|
2082
|
+
diagnostics.push(
|
|
2083
|
+
makeAnalysisDiagnostic(
|
|
2084
|
+
"TYPE_MISMATCH",
|
|
2085
|
+
`Discriminator field "${directive.fieldName}" must be string-like.`,
|
|
2086
|
+
directive.provenance,
|
|
2087
|
+
property.declaration !== void 0 ? [provenanceForNode(property.declaration, file)] : []
|
|
2088
|
+
)
|
|
2089
|
+
);
|
|
2090
|
+
return null;
|
|
2091
|
+
}
|
|
2092
|
+
return directive;
|
|
2093
|
+
}
|
|
2094
|
+
function getConcreteTypeArgumentForDiscriminator(node, subjectType, checker, typeParameterName) {
|
|
2095
|
+
const typeParameterIndex = node.typeParameters?.findIndex(
|
|
2096
|
+
(typeParameter) => typeParameter.name.text === typeParameterName
|
|
2097
|
+
) ?? -1;
|
|
2098
|
+
if (typeParameterIndex < 0) {
|
|
2099
|
+
return null;
|
|
2100
|
+
}
|
|
2101
|
+
const referenceTypeArguments = (isTypeReference(subjectType) ? subjectType.typeArguments : void 0) ?? subjectType.aliasTypeArguments;
|
|
2102
|
+
if (referenceTypeArguments?.[typeParameterIndex] !== void 0) {
|
|
2103
|
+
return referenceTypeArguments[typeParameterIndex] ?? null;
|
|
2104
|
+
}
|
|
2105
|
+
const localTypeParameter = node.typeParameters?.[typeParameterIndex];
|
|
2106
|
+
return localTypeParameter === void 0 ? null : checker.getTypeAtLocation(localTypeParameter);
|
|
2107
|
+
}
|
|
2108
|
+
function resolveLiteralDiscriminatorPropertyValue(boundType, fieldName, checker, provenance, diagnostics) {
|
|
2109
|
+
const propertySymbol = boundType.getProperty(fieldName);
|
|
2110
|
+
if (propertySymbol === void 0) {
|
|
2111
|
+
return void 0;
|
|
2112
|
+
}
|
|
2113
|
+
const declaration = propertySymbol.valueDeclaration ?? propertySymbol.declarations?.[0];
|
|
2114
|
+
const anchorNode = declaration ?? boundType.symbol.declarations?.[0] ?? null;
|
|
2115
|
+
const resolvedAnchorNode = anchorNode ?? resolveNamedDiscriminatorDeclaration(boundType, checker);
|
|
2116
|
+
if (resolvedAnchorNode === null) {
|
|
2117
|
+
return void 0;
|
|
2118
|
+
}
|
|
2119
|
+
const propertyType = checker.getTypeOfSymbolAtLocation(
|
|
2120
|
+
propertySymbol,
|
|
2121
|
+
resolvedAnchorNode
|
|
2122
|
+
);
|
|
2123
|
+
if (propertyType.isStringLiteral()) {
|
|
2124
|
+
return propertyType.value;
|
|
2125
|
+
}
|
|
2126
|
+
if (propertyType.isUnion()) {
|
|
2127
|
+
const nonNullMembers = propertyType.types.filter(
|
|
2128
|
+
(member) => !(member.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined))
|
|
2129
|
+
);
|
|
2130
|
+
if (nonNullMembers.length > 0 && nonNullMembers.every((member) => member.isStringLiteral())) {
|
|
2131
|
+
diagnostics.push(
|
|
2132
|
+
makeAnalysisDiagnostic(
|
|
2133
|
+
"INVALID_TAG_ARGUMENT",
|
|
2134
|
+
"Discriminator resolution for union-valued identity properties is out of scope for v1.",
|
|
2135
|
+
provenance
|
|
2136
|
+
)
|
|
2137
|
+
);
|
|
2138
|
+
return null;
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
return void 0;
|
|
2142
|
+
}
|
|
2143
|
+
function resolveDiscriminatorApiName(boundType, checker, metadataPolicy) {
|
|
2144
|
+
const declaration = resolveNamedDiscriminatorDeclaration(boundType, checker);
|
|
2145
|
+
if (declaration === null) {
|
|
2146
|
+
return void 0;
|
|
2147
|
+
}
|
|
2148
|
+
const metadata = resolveNodeMetadata(
|
|
2149
|
+
metadataPolicy,
|
|
2150
|
+
"type",
|
|
2151
|
+
getDiscriminatorLogicalName(boundType, declaration, checker),
|
|
2152
|
+
declaration,
|
|
2153
|
+
{
|
|
2154
|
+
checker,
|
|
2155
|
+
declaration,
|
|
2156
|
+
subjectType: boundType
|
|
2157
|
+
}
|
|
2158
|
+
);
|
|
2159
|
+
return metadata?.apiName;
|
|
2160
|
+
}
|
|
2161
|
+
function resolveNamedDiscriminatorDeclaration(type, checker, seen = /* @__PURE__ */ new Set()) {
|
|
2162
|
+
if (seen.has(type)) {
|
|
2163
|
+
return null;
|
|
2164
|
+
}
|
|
2165
|
+
seen.add(type);
|
|
2166
|
+
const symbol = type.aliasSymbol ?? type.getSymbol();
|
|
2167
|
+
if (symbol !== void 0) {
|
|
2168
|
+
const aliased = symbol.flags & ts3.SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : void 0;
|
|
2169
|
+
const targetSymbol = aliased ?? symbol;
|
|
2170
|
+
const declaration = targetSymbol.declarations?.find(
|
|
2171
|
+
(candidate) => ts3.isClassDeclaration(candidate) || ts3.isInterfaceDeclaration(candidate) || ts3.isTypeAliasDeclaration(candidate) || ts3.isEnumDeclaration(candidate)
|
|
2172
|
+
);
|
|
2173
|
+
if (declaration !== void 0) {
|
|
2174
|
+
if (ts3.isTypeAliasDeclaration(declaration) && ts3.isTypeReferenceNode(declaration.type) && checker.getTypeFromTypeNode(declaration.type) !== type) {
|
|
2175
|
+
return resolveNamedDiscriminatorDeclaration(
|
|
2176
|
+
checker.getTypeFromTypeNode(declaration.type),
|
|
2177
|
+
checker,
|
|
2178
|
+
seen
|
|
2179
|
+
);
|
|
2180
|
+
}
|
|
2181
|
+
return declaration;
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
return null;
|
|
1395
2185
|
}
|
|
1396
|
-
function
|
|
1397
|
-
if (
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
2186
|
+
function resolveDiscriminatorValue(boundType, fieldName, checker, provenance, diagnostics, metadataPolicy) {
|
|
2187
|
+
if (boundType === null) {
|
|
2188
|
+
diagnostics.push(
|
|
2189
|
+
makeAnalysisDiagnostic(
|
|
2190
|
+
"INVALID_TAG_ARGUMENT",
|
|
2191
|
+
"Discriminator resolution failed because no concrete type argument is available for the referenced type parameter.",
|
|
2192
|
+
provenance
|
|
2193
|
+
)
|
|
2194
|
+
);
|
|
2195
|
+
return null;
|
|
1405
2196
|
}
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
2197
|
+
if (boundType.isStringLiteral()) {
|
|
2198
|
+
return boundType.value;
|
|
2199
|
+
}
|
|
2200
|
+
if (boundType.isUnion()) {
|
|
2201
|
+
const nonNullMembers = boundType.types.filter(
|
|
2202
|
+
(member) => !(member.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined))
|
|
2203
|
+
);
|
|
2204
|
+
if (nonNullMembers.every((member) => member.isStringLiteral())) {
|
|
2205
|
+
diagnostics.push(
|
|
2206
|
+
makeAnalysisDiagnostic(
|
|
2207
|
+
"INVALID_TAG_ARGUMENT",
|
|
2208
|
+
"Discriminator resolution for unions of string literals is out of scope for v1.",
|
|
2209
|
+
provenance
|
|
2210
|
+
)
|
|
2211
|
+
);
|
|
2212
|
+
return null;
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
const literalIdentityValue = resolveLiteralDiscriminatorPropertyValue(
|
|
2216
|
+
boundType,
|
|
2217
|
+
fieldName,
|
|
2218
|
+
checker,
|
|
2219
|
+
provenance,
|
|
2220
|
+
diagnostics
|
|
1415
2221
|
);
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
2222
|
+
if (literalIdentityValue !== void 0) {
|
|
2223
|
+
return literalIdentityValue;
|
|
2224
|
+
}
|
|
2225
|
+
const apiName = resolveDiscriminatorApiName(boundType, checker, metadataPolicy);
|
|
2226
|
+
if (apiName?.source === "explicit") {
|
|
2227
|
+
return apiName.value;
|
|
2228
|
+
}
|
|
2229
|
+
if (apiName?.source === "inferred") {
|
|
2230
|
+
return apiName.value;
|
|
2231
|
+
}
|
|
2232
|
+
diagnostics.push(
|
|
2233
|
+
makeAnalysisDiagnostic(
|
|
2234
|
+
"INVALID_TAG_ARGUMENT",
|
|
2235
|
+
"Discriminator resolution could not derive a JSON-facing discriminator value from the referenced type argument.",
|
|
2236
|
+
provenance
|
|
2237
|
+
)
|
|
2238
|
+
);
|
|
2239
|
+
return null;
|
|
2240
|
+
}
|
|
2241
|
+
function getDeclarationName(node) {
|
|
2242
|
+
if (ts3.isClassDeclaration(node) || ts3.isInterfaceDeclaration(node) || ts3.isTypeAliasDeclaration(node) || ts3.isEnumDeclaration(node)) {
|
|
2243
|
+
return node.name?.text ?? "anonymous";
|
|
2244
|
+
}
|
|
2245
|
+
return "anonymous";
|
|
2246
|
+
}
|
|
2247
|
+
function getResolvedTypeArguments(type) {
|
|
2248
|
+
return (isTypeReference(type) ? type.typeArguments : void 0) ?? type.aliasTypeArguments ?? [];
|
|
2249
|
+
}
|
|
2250
|
+
function getDiscriminatorLogicalName(type, declaration, checker) {
|
|
2251
|
+
const baseName = getDeclarationName(declaration);
|
|
2252
|
+
const typeArguments = getResolvedTypeArguments(type);
|
|
2253
|
+
return typeArguments.length === 0 ? baseName : buildInstantiatedReferenceName(baseName, typeArguments, checker);
|
|
2254
|
+
}
|
|
2255
|
+
function applyDeclarationDiscriminatorToFields(fields, node, subjectType, checker, file, diagnostics, metadataPolicy) {
|
|
2256
|
+
const directive = validateDiscriminatorDirective(node, checker, file, diagnostics);
|
|
2257
|
+
if (directive === null) {
|
|
2258
|
+
return [...fields];
|
|
2259
|
+
}
|
|
2260
|
+
const discriminatorValue = resolveDiscriminatorValue(
|
|
2261
|
+
getConcreteTypeArgumentForDiscriminator(
|
|
2262
|
+
node,
|
|
2263
|
+
subjectType,
|
|
2264
|
+
checker,
|
|
2265
|
+
directive.typeParameterName
|
|
2266
|
+
),
|
|
2267
|
+
directive.fieldName,
|
|
2268
|
+
checker,
|
|
2269
|
+
directive.provenance,
|
|
2270
|
+
diagnostics,
|
|
2271
|
+
metadataPolicy
|
|
2272
|
+
);
|
|
2273
|
+
if (discriminatorValue === null) {
|
|
2274
|
+
return [...fields];
|
|
2275
|
+
}
|
|
2276
|
+
return fields.map(
|
|
2277
|
+
(field) => field.name === directive.fieldName ? {
|
|
2278
|
+
...field,
|
|
2279
|
+
type: {
|
|
2280
|
+
kind: "enum",
|
|
2281
|
+
members: [{ value: discriminatorValue }]
|
|
2282
|
+
}
|
|
2283
|
+
} : field
|
|
2284
|
+
);
|
|
2285
|
+
}
|
|
2286
|
+
function buildInstantiatedReferenceName(baseName, typeArguments, checker) {
|
|
2287
|
+
const renderedArguments = typeArguments.map(
|
|
2288
|
+
(typeArgument) => checker.typeToString(typeArgument).replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "")
|
|
2289
|
+
).filter((value) => value !== "");
|
|
2290
|
+
return renderedArguments.length === 0 ? baseName : `${baseName}__${renderedArguments.join("__")}`;
|
|
2291
|
+
}
|
|
2292
|
+
function extractReferenceTypeArguments(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy, extensionRegistry, diagnostics) {
|
|
2293
|
+
const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
|
|
2294
|
+
if (typeNode === void 0) {
|
|
2295
|
+
return [];
|
|
2296
|
+
}
|
|
2297
|
+
const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
|
|
2298
|
+
if (!ts3.isTypeReferenceNode(resolvedTypeNode) || resolvedTypeNode.typeArguments === void 0) {
|
|
2299
|
+
return [];
|
|
2300
|
+
}
|
|
2301
|
+
return resolvedTypeNode.typeArguments.map((argumentNode) => {
|
|
2302
|
+
const argumentType = checker.getTypeFromTypeNode(argumentNode);
|
|
2303
|
+
return {
|
|
2304
|
+
tsType: argumentType,
|
|
2305
|
+
typeNode: resolveTypeNode(
|
|
2306
|
+
argumentType,
|
|
1423
2307
|
checker,
|
|
1424
2308
|
file,
|
|
1425
2309
|
typeRegistry,
|
|
1426
2310
|
visiting,
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
extensionRegistry
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
2311
|
+
argumentNode,
|
|
2312
|
+
metadataPolicy,
|
|
2313
|
+
extensionRegistry,
|
|
2314
|
+
diagnostics
|
|
2315
|
+
)
|
|
2316
|
+
};
|
|
2317
|
+
});
|
|
2318
|
+
}
|
|
2319
|
+
function applyDiscriminatorToObjectProperties(properties, node, subjectType, checker, file, diagnostics, metadataPolicy) {
|
|
2320
|
+
const directive = validateDiscriminatorDirective(node, checker, file, diagnostics);
|
|
2321
|
+
if (directive === null) {
|
|
2322
|
+
return properties;
|
|
1435
2323
|
}
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
2324
|
+
const discriminatorValue = resolveDiscriminatorValue(
|
|
2325
|
+
getConcreteTypeArgumentForDiscriminator(
|
|
2326
|
+
node,
|
|
2327
|
+
subjectType,
|
|
2328
|
+
checker,
|
|
2329
|
+
directive.typeParameterName
|
|
2330
|
+
),
|
|
2331
|
+
directive.fieldName,
|
|
2332
|
+
checker,
|
|
2333
|
+
directive.provenance,
|
|
2334
|
+
diagnostics,
|
|
2335
|
+
metadataPolicy
|
|
2336
|
+
);
|
|
2337
|
+
if (discriminatorValue === null) {
|
|
2338
|
+
return properties;
|
|
2339
|
+
}
|
|
2340
|
+
return properties.map(
|
|
2341
|
+
(property) => property.name === directive.fieldName ? {
|
|
2342
|
+
...property,
|
|
2343
|
+
type: {
|
|
2344
|
+
kind: "enum",
|
|
2345
|
+
members: [{ value: discriminatorValue }]
|
|
2346
|
+
}
|
|
2347
|
+
} : property
|
|
2348
|
+
);
|
|
1449
2349
|
}
|
|
1450
|
-
function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, extensionRegistry) {
|
|
2350
|
+
function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, metadataPolicy, extensionRegistry) {
|
|
1451
2351
|
if (!ts3.isIdentifier(prop.name)) {
|
|
1452
2352
|
return null;
|
|
1453
2353
|
}
|
|
@@ -1462,6 +2362,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnosti
|
|
|
1462
2362
|
typeRegistry,
|
|
1463
2363
|
visiting,
|
|
1464
2364
|
prop,
|
|
2365
|
+
metadataPolicy,
|
|
1465
2366
|
extensionRegistry,
|
|
1466
2367
|
diagnostics
|
|
1467
2368
|
);
|
|
@@ -1485,9 +2386,16 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnosti
|
|
|
1485
2386
|
annotations.push(defaultAnnotation);
|
|
1486
2387
|
}
|
|
1487
2388
|
({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
|
|
2389
|
+
const metadata = resolveNodeMetadata(metadataPolicy, "field", name, prop, {
|
|
2390
|
+
checker,
|
|
2391
|
+
declaration: prop,
|
|
2392
|
+
subjectType: tsType,
|
|
2393
|
+
hostType
|
|
2394
|
+
});
|
|
1488
2395
|
return {
|
|
1489
2396
|
kind: "field",
|
|
1490
2397
|
name,
|
|
2398
|
+
...metadata !== void 0 && { metadata },
|
|
1491
2399
|
type,
|
|
1492
2400
|
required: !optional,
|
|
1493
2401
|
constraints,
|
|
@@ -1495,7 +2403,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnosti
|
|
|
1495
2403
|
provenance
|
|
1496
2404
|
};
|
|
1497
2405
|
}
|
|
1498
|
-
function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, extensionRegistry) {
|
|
2406
|
+
function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, metadataPolicy, extensionRegistry) {
|
|
1499
2407
|
if (!ts3.isIdentifier(prop.name)) {
|
|
1500
2408
|
return null;
|
|
1501
2409
|
}
|
|
@@ -1510,6 +2418,7 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
|
|
|
1510
2418
|
typeRegistry,
|
|
1511
2419
|
visiting,
|
|
1512
2420
|
prop,
|
|
2421
|
+
metadataPolicy,
|
|
1513
2422
|
extensionRegistry,
|
|
1514
2423
|
diagnostics
|
|
1515
2424
|
);
|
|
@@ -1529,9 +2438,16 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
|
|
|
1529
2438
|
let annotations = [];
|
|
1530
2439
|
annotations.push(...docResult.annotations);
|
|
1531
2440
|
({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
|
|
2441
|
+
const metadata = resolveNodeMetadata(metadataPolicy, "field", name, prop, {
|
|
2442
|
+
checker,
|
|
2443
|
+
declaration: prop,
|
|
2444
|
+
subjectType: tsType,
|
|
2445
|
+
hostType
|
|
2446
|
+
});
|
|
1532
2447
|
return {
|
|
1533
2448
|
kind: "field",
|
|
1534
2449
|
name,
|
|
2450
|
+
...metadata !== void 0 && { metadata },
|
|
1535
2451
|
type,
|
|
1536
2452
|
required: !optional,
|
|
1537
2453
|
constraints,
|
|
@@ -1656,7 +2572,7 @@ function getTypeNodeRegistrationName(typeNode) {
|
|
|
1656
2572
|
}
|
|
1657
2573
|
return null;
|
|
1658
2574
|
}
|
|
1659
|
-
function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
|
|
2575
|
+
function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
|
|
1660
2576
|
const customType = resolveRegisteredCustomType(sourceNode, extensionRegistry, checker);
|
|
1661
2577
|
if (customType) {
|
|
1662
2578
|
return customType;
|
|
@@ -1668,6 +2584,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
|
|
|
1668
2584
|
typeRegistry,
|
|
1669
2585
|
visiting,
|
|
1670
2586
|
sourceNode,
|
|
2587
|
+
metadataPolicy,
|
|
1671
2588
|
extensionRegistry,
|
|
1672
2589
|
diagnostics
|
|
1673
2590
|
);
|
|
@@ -1712,6 +2629,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
|
|
|
1712
2629
|
typeRegistry,
|
|
1713
2630
|
visiting,
|
|
1714
2631
|
sourceNode,
|
|
2632
|
+
metadataPolicy,
|
|
1715
2633
|
extensionRegistry,
|
|
1716
2634
|
diagnostics
|
|
1717
2635
|
);
|
|
@@ -1724,6 +2642,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
|
|
|
1724
2642
|
typeRegistry,
|
|
1725
2643
|
visiting,
|
|
1726
2644
|
sourceNode,
|
|
2645
|
+
metadataPolicy,
|
|
1727
2646
|
extensionRegistry,
|
|
1728
2647
|
diagnostics
|
|
1729
2648
|
);
|
|
@@ -1735,13 +2654,15 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
|
|
|
1735
2654
|
file,
|
|
1736
2655
|
typeRegistry,
|
|
1737
2656
|
visiting,
|
|
2657
|
+
sourceNode,
|
|
2658
|
+
metadataPolicy,
|
|
1738
2659
|
extensionRegistry,
|
|
1739
2660
|
diagnostics
|
|
1740
2661
|
);
|
|
1741
2662
|
}
|
|
1742
2663
|
return { kind: "primitive", primitiveKind: "string" };
|
|
1743
2664
|
}
|
|
1744
|
-
function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
|
|
2665
|
+
function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
|
|
1745
2666
|
if (!(type.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null))) {
|
|
1746
2667
|
return null;
|
|
1747
2668
|
}
|
|
@@ -1761,14 +2682,21 @@ function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiti
|
|
|
1761
2682
|
file,
|
|
1762
2683
|
makeParseOptions(extensionRegistry)
|
|
1763
2684
|
);
|
|
2685
|
+
const metadata = resolveNodeMetadata(metadataPolicy, "type", aliasName, aliasDecl, {
|
|
2686
|
+
checker,
|
|
2687
|
+
declaration: aliasDecl,
|
|
2688
|
+
subjectType: aliasType
|
|
2689
|
+
});
|
|
1764
2690
|
typeRegistry[aliasName] = {
|
|
1765
2691
|
name: aliasName,
|
|
2692
|
+
...metadata !== void 0 && { metadata },
|
|
1766
2693
|
type: resolveAliasedPrimitiveTarget(
|
|
1767
2694
|
aliasType,
|
|
1768
2695
|
checker,
|
|
1769
2696
|
file,
|
|
1770
2697
|
typeRegistry,
|
|
1771
2698
|
visiting,
|
|
2699
|
+
metadataPolicy,
|
|
1772
2700
|
extensionRegistry,
|
|
1773
2701
|
diagnostics
|
|
1774
2702
|
),
|
|
@@ -1797,7 +2725,7 @@ function shouldEmitPrimitiveAliasDefinition(typeNode, checker) {
|
|
|
1797
2725
|
const resolved = checker.getTypeFromTypeNode(aliasDecl.type);
|
|
1798
2726
|
return !!(resolved.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null));
|
|
1799
2727
|
}
|
|
1800
|
-
function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
|
|
2728
|
+
function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
|
|
1801
2729
|
const nestedAliasDecl = type.aliasSymbol?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
1802
2730
|
if (nestedAliasDecl !== void 0) {
|
|
1803
2731
|
return resolveAliasedPrimitiveTarget(
|
|
@@ -1806,6 +2734,7 @@ function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiti
|
|
|
1806
2734
|
file,
|
|
1807
2735
|
typeRegistry,
|
|
1808
2736
|
visiting,
|
|
2737
|
+
metadataPolicy,
|
|
1809
2738
|
extensionRegistry,
|
|
1810
2739
|
diagnostics
|
|
1811
2740
|
);
|
|
@@ -1817,11 +2746,12 @@ function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiti
|
|
|
1817
2746
|
typeRegistry,
|
|
1818
2747
|
visiting,
|
|
1819
2748
|
void 0,
|
|
2749
|
+
metadataPolicy,
|
|
1820
2750
|
extensionRegistry,
|
|
1821
2751
|
diagnostics
|
|
1822
2752
|
);
|
|
1823
2753
|
}
|
|
1824
|
-
function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
|
|
2754
|
+
function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
|
|
1825
2755
|
const typeName = getNamedTypeName(type);
|
|
1826
2756
|
const namedDecl = getNamedTypeDeclaration(type);
|
|
1827
2757
|
if (typeName && typeName in typeRegistry) {
|
|
@@ -1856,8 +2786,14 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
1856
2786
|
return result;
|
|
1857
2787
|
}
|
|
1858
2788
|
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
2789
|
+
const metadata = namedDecl !== void 0 ? resolveNodeMetadata(metadataPolicy, "type", typeName, namedDecl, {
|
|
2790
|
+
checker,
|
|
2791
|
+
declaration: namedDecl,
|
|
2792
|
+
subjectType: type
|
|
2793
|
+
}) : void 0;
|
|
1859
2794
|
typeRegistry[typeName] = {
|
|
1860
2795
|
name: typeName,
|
|
2796
|
+
...metadata !== void 0 && { metadata },
|
|
1861
2797
|
type: result,
|
|
1862
2798
|
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
1863
2799
|
provenance: provenanceForDeclaration(namedDecl ?? sourceNode, file)
|
|
@@ -1911,6 +2847,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
1911
2847
|
typeRegistry,
|
|
1912
2848
|
visiting,
|
|
1913
2849
|
nonNullMembers[0].sourceNode ?? sourceNode,
|
|
2850
|
+
metadataPolicy,
|
|
1914
2851
|
extensionRegistry,
|
|
1915
2852
|
diagnostics
|
|
1916
2853
|
);
|
|
@@ -1928,6 +2865,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
1928
2865
|
typeRegistry,
|
|
1929
2866
|
visiting,
|
|
1930
2867
|
memberSourceNode ?? sourceNode,
|
|
2868
|
+
metadataPolicy,
|
|
1931
2869
|
extensionRegistry,
|
|
1932
2870
|
diagnostics
|
|
1933
2871
|
)
|
|
@@ -1937,7 +2875,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
1937
2875
|
}
|
|
1938
2876
|
return registerNamed({ kind: "union", members });
|
|
1939
2877
|
}
|
|
1940
|
-
function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
|
|
2878
|
+
function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
|
|
1941
2879
|
const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
|
|
1942
2880
|
const elementType = typeArgs?.[0];
|
|
1943
2881
|
const elementSourceNode = extractArrayElementTypeNode(sourceNode, checker);
|
|
@@ -1948,12 +2886,13 @@ function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
1948
2886
|
typeRegistry,
|
|
1949
2887
|
visiting,
|
|
1950
2888
|
elementSourceNode,
|
|
2889
|
+
metadataPolicy,
|
|
1951
2890
|
extensionRegistry,
|
|
1952
2891
|
diagnostics
|
|
1953
2892
|
) : { kind: "primitive", primitiveKind: "string" };
|
|
1954
2893
|
return { kind: "array", items };
|
|
1955
2894
|
}
|
|
1956
|
-
function tryResolveRecordType(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
|
|
2895
|
+
function tryResolveRecordType(type, checker, file, typeRegistry, visiting, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
|
|
1957
2896
|
if (type.getProperties().length > 0) {
|
|
1958
2897
|
return null;
|
|
1959
2898
|
}
|
|
@@ -1968,6 +2907,7 @@ function tryResolveRecordType(type, checker, file, typeRegistry, visiting, exten
|
|
|
1968
2907
|
typeRegistry,
|
|
1969
2908
|
visiting,
|
|
1970
2909
|
void 0,
|
|
2910
|
+
metadataPolicy,
|
|
1971
2911
|
extensionRegistry,
|
|
1972
2912
|
diagnostics
|
|
1973
2913
|
);
|
|
@@ -1998,35 +2938,76 @@ function typeNodeContainsReference(type, targetName) {
|
|
|
1998
2938
|
}
|
|
1999
2939
|
}
|
|
2000
2940
|
}
|
|
2001
|
-
function
|
|
2941
|
+
function shouldEmitResolvedObjectProperty(property, declaration) {
|
|
2942
|
+
if (property.name.startsWith("__@")) {
|
|
2943
|
+
return false;
|
|
2944
|
+
}
|
|
2945
|
+
if (declaration !== void 0 && "name" in declaration && declaration.name !== void 0) {
|
|
2946
|
+
const name = declaration.name;
|
|
2947
|
+
if (ts3.isComputedPropertyName(name) || ts3.isPrivateIdentifier(name)) {
|
|
2948
|
+
return false;
|
|
2949
|
+
}
|
|
2950
|
+
if (!ts3.isIdentifier(name) && !ts3.isStringLiteral(name) && !ts3.isNumericLiteral(name)) {
|
|
2951
|
+
return false;
|
|
2952
|
+
}
|
|
2953
|
+
}
|
|
2954
|
+
return true;
|
|
2955
|
+
}
|
|
2956
|
+
function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
|
|
2957
|
+
const collectedDiagnostics = diagnostics ?? [];
|
|
2002
2958
|
const typeName = getNamedTypeName(type);
|
|
2003
2959
|
const namedTypeName = typeName ?? void 0;
|
|
2004
2960
|
const namedDecl = getNamedTypeDeclaration(type);
|
|
2005
|
-
const
|
|
2961
|
+
const referenceTypeArguments = extractReferenceTypeArguments(
|
|
2962
|
+
type,
|
|
2963
|
+
checker,
|
|
2964
|
+
file,
|
|
2965
|
+
typeRegistry,
|
|
2966
|
+
visiting,
|
|
2967
|
+
sourceNode,
|
|
2968
|
+
metadataPolicy,
|
|
2969
|
+
extensionRegistry,
|
|
2970
|
+
collectedDiagnostics
|
|
2971
|
+
);
|
|
2972
|
+
const instantiatedTypeName = namedTypeName !== void 0 && referenceTypeArguments.length > 0 ? buildInstantiatedReferenceName(
|
|
2973
|
+
namedTypeName,
|
|
2974
|
+
referenceTypeArguments.map((argument) => argument.tsType),
|
|
2975
|
+
checker
|
|
2976
|
+
) : void 0;
|
|
2977
|
+
const registryTypeName = instantiatedTypeName ?? namedTypeName;
|
|
2978
|
+
const shouldRegisterNamedType = registryTypeName !== void 0 && !(registryTypeName === "Record" && namedDecl?.getSourceFile().fileName !== file);
|
|
2006
2979
|
const clearNamedTypeRegistration = () => {
|
|
2007
|
-
if (
|
|
2980
|
+
if (registryTypeName === void 0 || !shouldRegisterNamedType) {
|
|
2008
2981
|
return;
|
|
2009
2982
|
}
|
|
2010
|
-
Reflect.deleteProperty(typeRegistry,
|
|
2983
|
+
Reflect.deleteProperty(typeRegistry, registryTypeName);
|
|
2011
2984
|
};
|
|
2012
2985
|
if (visiting.has(type)) {
|
|
2013
|
-
if (
|
|
2014
|
-
return {
|
|
2986
|
+
if (registryTypeName !== void 0 && shouldRegisterNamedType) {
|
|
2987
|
+
return {
|
|
2988
|
+
kind: "reference",
|
|
2989
|
+
name: registryTypeName,
|
|
2990
|
+
typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
|
|
2991
|
+
};
|
|
2015
2992
|
}
|
|
2016
2993
|
return { kind: "object", properties: [], additionalProperties: false };
|
|
2017
2994
|
}
|
|
2018
|
-
if (
|
|
2019
|
-
typeRegistry[
|
|
2020
|
-
name:
|
|
2995
|
+
if (registryTypeName !== void 0 && shouldRegisterNamedType && !typeRegistry[registryTypeName]) {
|
|
2996
|
+
typeRegistry[registryTypeName] = {
|
|
2997
|
+
name: registryTypeName,
|
|
2021
2998
|
type: RESOLVING_TYPE_PLACEHOLDER,
|
|
2022
2999
|
provenance: provenanceForDeclaration(namedDecl, file)
|
|
2023
3000
|
};
|
|
2024
3001
|
}
|
|
2025
3002
|
visiting.add(type);
|
|
2026
|
-
if (
|
|
2027
|
-
if (typeRegistry[
|
|
3003
|
+
if (registryTypeName !== void 0 && shouldRegisterNamedType && typeRegistry[registryTypeName]?.type !== void 0) {
|
|
3004
|
+
if (typeRegistry[registryTypeName].type !== RESOLVING_TYPE_PLACEHOLDER) {
|
|
2028
3005
|
visiting.delete(type);
|
|
2029
|
-
return {
|
|
3006
|
+
return {
|
|
3007
|
+
kind: "reference",
|
|
3008
|
+
name: registryTypeName,
|
|
3009
|
+
typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
|
|
3010
|
+
};
|
|
2030
3011
|
}
|
|
2031
3012
|
}
|
|
2032
3013
|
const recordNode = tryResolveRecordType(
|
|
@@ -2035,25 +3016,36 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
2035
3016
|
file,
|
|
2036
3017
|
typeRegistry,
|
|
2037
3018
|
visiting,
|
|
3019
|
+
metadataPolicy,
|
|
2038
3020
|
extensionRegistry,
|
|
2039
|
-
|
|
3021
|
+
collectedDiagnostics
|
|
2040
3022
|
);
|
|
2041
3023
|
if (recordNode) {
|
|
2042
3024
|
visiting.delete(type);
|
|
2043
|
-
if (
|
|
2044
|
-
const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType,
|
|
3025
|
+
if (registryTypeName !== void 0 && shouldRegisterNamedType) {
|
|
3026
|
+
const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType, registryTypeName);
|
|
2045
3027
|
if (!isRecursiveRecord) {
|
|
2046
3028
|
clearNamedTypeRegistration();
|
|
2047
3029
|
return recordNode;
|
|
2048
3030
|
}
|
|
2049
3031
|
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
2050
|
-
|
|
2051
|
-
|
|
3032
|
+
const metadata = namedDecl !== void 0 ? resolveNodeMetadata(metadataPolicy, "type", registryTypeName, namedDecl, {
|
|
3033
|
+
checker,
|
|
3034
|
+
declaration: namedDecl,
|
|
3035
|
+
subjectType: type
|
|
3036
|
+
}) : void 0;
|
|
3037
|
+
typeRegistry[registryTypeName] = {
|
|
3038
|
+
name: registryTypeName,
|
|
3039
|
+
...metadata !== void 0 && { metadata },
|
|
2052
3040
|
type: recordNode,
|
|
2053
3041
|
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
2054
3042
|
provenance: provenanceForDeclaration(namedDecl, file)
|
|
2055
3043
|
};
|
|
2056
|
-
return {
|
|
3044
|
+
return {
|
|
3045
|
+
kind: "reference",
|
|
3046
|
+
name: registryTypeName,
|
|
3047
|
+
typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
|
|
3048
|
+
};
|
|
2057
3049
|
}
|
|
2058
3050
|
return recordNode;
|
|
2059
3051
|
}
|
|
@@ -2064,12 +3056,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
2064
3056
|
file,
|
|
2065
3057
|
typeRegistry,
|
|
2066
3058
|
visiting,
|
|
2067
|
-
|
|
3059
|
+
metadataPolicy,
|
|
3060
|
+
collectedDiagnostics,
|
|
2068
3061
|
extensionRegistry
|
|
2069
3062
|
);
|
|
2070
3063
|
for (const prop of type.getProperties()) {
|
|
2071
3064
|
const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
|
|
2072
3065
|
if (!declaration) continue;
|
|
3066
|
+
if (!shouldEmitResolvedObjectProperty(prop, declaration)) continue;
|
|
2073
3067
|
const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
|
|
2074
3068
|
const optional = !!(prop.flags & ts3.SymbolFlags.Optional);
|
|
2075
3069
|
const propTypeNode = resolveTypeNode(
|
|
@@ -2079,12 +3073,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
2079
3073
|
typeRegistry,
|
|
2080
3074
|
visiting,
|
|
2081
3075
|
declaration,
|
|
3076
|
+
metadataPolicy,
|
|
2082
3077
|
extensionRegistry,
|
|
2083
|
-
|
|
3078
|
+
collectedDiagnostics
|
|
2084
3079
|
);
|
|
2085
3080
|
const fieldNodeInfo = fieldInfoMap?.get(prop.name);
|
|
2086
3081
|
properties.push({
|
|
2087
3082
|
name: prop.name,
|
|
3083
|
+
...fieldNodeInfo?.metadata !== void 0 && { metadata: fieldNodeInfo.metadata },
|
|
2088
3084
|
type: propTypeNode,
|
|
2089
3085
|
optional,
|
|
2090
3086
|
constraints: fieldNodeInfo?.constraints ?? [],
|
|
@@ -2095,22 +3091,40 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
2095
3091
|
visiting.delete(type);
|
|
2096
3092
|
const objectNode = {
|
|
2097
3093
|
kind: "object",
|
|
2098
|
-
properties
|
|
3094
|
+
properties: namedDecl !== void 0 && (ts3.isClassDeclaration(namedDecl) || ts3.isInterfaceDeclaration(namedDecl) || ts3.isTypeAliasDeclaration(namedDecl)) ? applyDiscriminatorToObjectProperties(
|
|
3095
|
+
properties,
|
|
3096
|
+
namedDecl,
|
|
3097
|
+
type,
|
|
3098
|
+
checker,
|
|
3099
|
+
file,
|
|
3100
|
+
collectedDiagnostics,
|
|
3101
|
+
metadataPolicy
|
|
3102
|
+
) : properties,
|
|
2099
3103
|
additionalProperties: true
|
|
2100
3104
|
};
|
|
2101
|
-
if (
|
|
3105
|
+
if (registryTypeName !== void 0 && shouldRegisterNamedType) {
|
|
2102
3106
|
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
2103
|
-
|
|
2104
|
-
|
|
3107
|
+
const metadata = namedDecl !== void 0 ? resolveNodeMetadata(metadataPolicy, "type", registryTypeName, namedDecl, {
|
|
3108
|
+
checker,
|
|
3109
|
+
declaration: namedDecl,
|
|
3110
|
+
subjectType: type
|
|
3111
|
+
}) : void 0;
|
|
3112
|
+
typeRegistry[registryTypeName] = {
|
|
3113
|
+
name: registryTypeName,
|
|
3114
|
+
...metadata !== void 0 && { metadata },
|
|
2105
3115
|
type: objectNode,
|
|
2106
3116
|
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
2107
3117
|
provenance: provenanceForDeclaration(namedDecl, file)
|
|
2108
3118
|
};
|
|
2109
|
-
return {
|
|
3119
|
+
return {
|
|
3120
|
+
kind: "reference",
|
|
3121
|
+
name: registryTypeName,
|
|
3122
|
+
typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
|
|
3123
|
+
};
|
|
2110
3124
|
}
|
|
2111
3125
|
return objectNode;
|
|
2112
3126
|
}
|
|
2113
|
-
function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, diagnostics, extensionRegistry) {
|
|
3127
|
+
function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, metadataPolicy, diagnostics, extensionRegistry) {
|
|
2114
3128
|
const symbols = [type.getSymbol(), type.aliasSymbol].filter(
|
|
2115
3129
|
(s) => s?.declarations != null && s.declarations.length > 0
|
|
2116
3130
|
);
|
|
@@ -2131,10 +3145,12 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
2131
3145
|
visiting,
|
|
2132
3146
|
diagnostics,
|
|
2133
3147
|
hostType,
|
|
3148
|
+
metadataPolicy,
|
|
2134
3149
|
extensionRegistry
|
|
2135
3150
|
);
|
|
2136
3151
|
if (fieldNode) {
|
|
2137
3152
|
map.set(fieldNode.name, {
|
|
3153
|
+
...fieldNode.metadata !== void 0 && { metadata: fieldNode.metadata },
|
|
2138
3154
|
constraints: [...fieldNode.constraints],
|
|
2139
3155
|
annotations: [...fieldNode.annotations],
|
|
2140
3156
|
provenance: fieldNode.provenance
|
|
@@ -2152,6 +3168,7 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
2152
3168
|
file,
|
|
2153
3169
|
typeRegistry,
|
|
2154
3170
|
visiting,
|
|
3171
|
+
metadataPolicy,
|
|
2155
3172
|
checker.getTypeAtLocation(interfaceDecl),
|
|
2156
3173
|
diagnostics,
|
|
2157
3174
|
extensionRegistry
|
|
@@ -2165,6 +3182,7 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
2165
3182
|
file,
|
|
2166
3183
|
typeRegistry,
|
|
2167
3184
|
visiting,
|
|
3185
|
+
metadataPolicy,
|
|
2168
3186
|
checker.getTypeAtLocation(typeAliasDecl),
|
|
2169
3187
|
diagnostics,
|
|
2170
3188
|
extensionRegistry
|
|
@@ -2216,7 +3234,7 @@ function isNullishTypeNode(typeNode) {
|
|
|
2216
3234
|
}
|
|
2217
3235
|
return ts3.isLiteralTypeNode(typeNode) && (typeNode.literal.kind === ts3.SyntaxKind.NullKeyword || typeNode.literal.kind === ts3.SyntaxKind.UndefinedKeyword);
|
|
2218
3236
|
}
|
|
2219
|
-
function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, hostType, diagnostics, extensionRegistry) {
|
|
3237
|
+
function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, metadataPolicy, hostType, diagnostics, extensionRegistry) {
|
|
2220
3238
|
const map = /* @__PURE__ */ new Map();
|
|
2221
3239
|
for (const member of members) {
|
|
2222
3240
|
if (ts3.isPropertySignature(member)) {
|
|
@@ -2228,10 +3246,12 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, h
|
|
|
2228
3246
|
visiting,
|
|
2229
3247
|
diagnostics,
|
|
2230
3248
|
hostType,
|
|
3249
|
+
metadataPolicy,
|
|
2231
3250
|
extensionRegistry
|
|
2232
3251
|
);
|
|
2233
3252
|
if (fieldNode) {
|
|
2234
3253
|
map.set(fieldNode.name, {
|
|
3254
|
+
...fieldNode.metadata !== void 0 && { metadata: fieldNode.metadata },
|
|
2235
3255
|
constraints: [...fieldNode.constraints],
|
|
2236
3256
|
annotations: [...fieldNode.annotations],
|
|
2237
3257
|
provenance: fieldNode.provenance
|
|
@@ -2262,6 +3282,7 @@ function extractTypeAliasConstraintNodes(typeNode, checker, file, extensionRegis
|
|
|
2262
3282
|
{},
|
|
2263
3283
|
/* @__PURE__ */ new Set(),
|
|
2264
3284
|
aliasDecl.type,
|
|
3285
|
+
void 0,
|
|
2265
3286
|
extensionRegistry
|
|
2266
3287
|
);
|
|
2267
3288
|
const constraints = extractJSDocConstraintNodes(
|
|
@@ -2447,15 +3468,27 @@ function findInterfaceByName(sourceFile, interfaceName) {
|
|
|
2447
3468
|
function findTypeAliasByName(sourceFile, aliasName) {
|
|
2448
3469
|
return findNodeByName(sourceFile, aliasName, ts4.isTypeAliasDeclaration, (n) => n.name.text);
|
|
2449
3470
|
}
|
|
2450
|
-
function analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensionRegistry) {
|
|
3471
|
+
function analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensionRegistry, metadataPolicy) {
|
|
2451
3472
|
const analysisFilePath = path.resolve(filePath);
|
|
2452
3473
|
const classDecl = findClassByName(ctx.sourceFile, typeName);
|
|
2453
3474
|
if (classDecl !== null) {
|
|
2454
|
-
return analyzeClassToIR(
|
|
3475
|
+
return analyzeClassToIR(
|
|
3476
|
+
classDecl,
|
|
3477
|
+
ctx.checker,
|
|
3478
|
+
analysisFilePath,
|
|
3479
|
+
extensionRegistry,
|
|
3480
|
+
metadataPolicy
|
|
3481
|
+
);
|
|
2455
3482
|
}
|
|
2456
3483
|
const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
|
|
2457
3484
|
if (interfaceDecl !== null) {
|
|
2458
|
-
return analyzeInterfaceToIR(
|
|
3485
|
+
return analyzeInterfaceToIR(
|
|
3486
|
+
interfaceDecl,
|
|
3487
|
+
ctx.checker,
|
|
3488
|
+
analysisFilePath,
|
|
3489
|
+
extensionRegistry,
|
|
3490
|
+
metadataPolicy
|
|
3491
|
+
);
|
|
2459
3492
|
}
|
|
2460
3493
|
const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
|
|
2461
3494
|
if (typeAlias !== null) {
|
|
@@ -2463,7 +3496,8 @@ function analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensi
|
|
|
2463
3496
|
typeAlias,
|
|
2464
3497
|
ctx.checker,
|
|
2465
3498
|
analysisFilePath,
|
|
2466
|
-
extensionRegistry
|
|
3499
|
+
extensionRegistry,
|
|
3500
|
+
metadataPolicy
|
|
2467
3501
|
);
|
|
2468
3502
|
if (result.ok) {
|
|
2469
3503
|
return result.analysis;
|
|
@@ -2478,6 +3512,120 @@ function analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensi
|
|
|
2478
3512
|
// src/generators/class-schema.ts
|
|
2479
3513
|
import "typescript";
|
|
2480
3514
|
|
|
3515
|
+
// src/metadata/collision-guards.ts
|
|
3516
|
+
function assertUniqueSerializedNames(entries, scope) {
|
|
3517
|
+
const seen = /* @__PURE__ */ new Map();
|
|
3518
|
+
for (const entry of entries) {
|
|
3519
|
+
const previous = seen.get(entry.serializedName);
|
|
3520
|
+
if (previous !== void 0) {
|
|
3521
|
+
if (previous.logicalName === entry.logicalName && previous.category === entry.category) {
|
|
3522
|
+
continue;
|
|
3523
|
+
}
|
|
3524
|
+
throw new Error(
|
|
3525
|
+
`Serialized name collision in ${scope}: ${previous.category} "${previous.logicalName}" and ${entry.category} "${entry.logicalName}" both resolve to "${entry.serializedName}".`
|
|
3526
|
+
);
|
|
3527
|
+
}
|
|
3528
|
+
seen.set(entry.serializedName, entry);
|
|
3529
|
+
}
|
|
3530
|
+
}
|
|
3531
|
+
function collectFlattenedFields(elements) {
|
|
3532
|
+
const fields = [];
|
|
3533
|
+
for (const element of elements) {
|
|
3534
|
+
switch (element.kind) {
|
|
3535
|
+
case "field":
|
|
3536
|
+
fields.push(element);
|
|
3537
|
+
break;
|
|
3538
|
+
case "group":
|
|
3539
|
+
case "conditional":
|
|
3540
|
+
fields.push(...collectFlattenedFields(element.elements));
|
|
3541
|
+
break;
|
|
3542
|
+
default: {
|
|
3543
|
+
const exhaustive = element;
|
|
3544
|
+
void exhaustive;
|
|
3545
|
+
}
|
|
3546
|
+
}
|
|
3547
|
+
}
|
|
3548
|
+
return fields;
|
|
3549
|
+
}
|
|
3550
|
+
function validateObjectProperties(properties, scope) {
|
|
3551
|
+
assertUniqueSerializedNames(
|
|
3552
|
+
properties.map((property) => ({
|
|
3553
|
+
logicalName: property.name,
|
|
3554
|
+
serializedName: getSerializedName(property.name, property.metadata),
|
|
3555
|
+
category: "object property"
|
|
3556
|
+
})),
|
|
3557
|
+
scope
|
|
3558
|
+
);
|
|
3559
|
+
for (const property of properties) {
|
|
3560
|
+
validateTypeNode(
|
|
3561
|
+
property.type,
|
|
3562
|
+
`${scope}.${getSerializedName(property.name, property.metadata)}`
|
|
3563
|
+
);
|
|
3564
|
+
}
|
|
3565
|
+
}
|
|
3566
|
+
function validateTypeNode(type, scope) {
|
|
3567
|
+
switch (type.kind) {
|
|
3568
|
+
case "array":
|
|
3569
|
+
validateTypeNode(type.items, `${scope}[]`);
|
|
3570
|
+
break;
|
|
3571
|
+
case "object":
|
|
3572
|
+
validateObjectProperties(type.properties, scope);
|
|
3573
|
+
break;
|
|
3574
|
+
case "record":
|
|
3575
|
+
validateTypeNode(type.valueType, `${scope}.*`);
|
|
3576
|
+
break;
|
|
3577
|
+
case "union":
|
|
3578
|
+
type.members.forEach((member, index) => {
|
|
3579
|
+
validateTypeNode(member, `${scope}|${String(index)}`);
|
|
3580
|
+
});
|
|
3581
|
+
break;
|
|
3582
|
+
case "reference":
|
|
3583
|
+
case "primitive":
|
|
3584
|
+
case "enum":
|
|
3585
|
+
case "dynamic":
|
|
3586
|
+
case "custom":
|
|
3587
|
+
break;
|
|
3588
|
+
default: {
|
|
3589
|
+
const exhaustive = type;
|
|
3590
|
+
void exhaustive;
|
|
3591
|
+
}
|
|
3592
|
+
}
|
|
3593
|
+
}
|
|
3594
|
+
function validateTypeDefinitions(typeRegistry) {
|
|
3595
|
+
const definitions = Object.values(typeRegistry);
|
|
3596
|
+
assertUniqueSerializedNames(
|
|
3597
|
+
definitions.map((definition) => ({
|
|
3598
|
+
logicalName: definition.name,
|
|
3599
|
+
serializedName: getSerializedName(definition.name, definition.metadata),
|
|
3600
|
+
category: "type definition"
|
|
3601
|
+
})),
|
|
3602
|
+
"$defs"
|
|
3603
|
+
);
|
|
3604
|
+
for (const definition of definitions) {
|
|
3605
|
+
validateTypeDefinition(definition);
|
|
3606
|
+
}
|
|
3607
|
+
}
|
|
3608
|
+
function validateTypeDefinition(definition) {
|
|
3609
|
+
validateTypeNode(
|
|
3610
|
+
definition.type,
|
|
3611
|
+
`type "${getSerializedName(definition.name, definition.metadata)}"`
|
|
3612
|
+
);
|
|
3613
|
+
}
|
|
3614
|
+
function assertNoSerializedNameCollisions(ir) {
|
|
3615
|
+
assertUniqueSerializedNames(
|
|
3616
|
+
collectFlattenedFields(ir.elements).map((field) => ({
|
|
3617
|
+
logicalName: field.name,
|
|
3618
|
+
serializedName: getSerializedName(field.name, field.metadata),
|
|
3619
|
+
category: "field"
|
|
3620
|
+
})),
|
|
3621
|
+
"form root"
|
|
3622
|
+
);
|
|
3623
|
+
for (const field of collectFlattenedFields(ir.elements)) {
|
|
3624
|
+
validateTypeNode(field.type, `field "${getSerializedName(field.name, field.metadata)}"`);
|
|
3625
|
+
}
|
|
3626
|
+
validateTypeDefinitions(ir.typeRegistry);
|
|
3627
|
+
}
|
|
3628
|
+
|
|
2481
3629
|
// src/json-schema/ir-generator.ts
|
|
2482
3630
|
function makeContext(options) {
|
|
2483
3631
|
const vendorPrefix = options?.vendorPrefix ?? "x-formspec";
|
|
@@ -2488,19 +3636,33 @@ function makeContext(options) {
|
|
|
2488
3636
|
}
|
|
2489
3637
|
return {
|
|
2490
3638
|
defs: {},
|
|
3639
|
+
typeNameMap: {},
|
|
3640
|
+
typeRegistry: {},
|
|
2491
3641
|
extensionRegistry: options?.extensionRegistry,
|
|
2492
3642
|
vendorPrefix
|
|
2493
3643
|
};
|
|
2494
3644
|
}
|
|
2495
3645
|
function generateJsonSchemaFromIR(ir, options) {
|
|
2496
|
-
|
|
3646
|
+
assertNoSerializedNameCollisions(ir);
|
|
3647
|
+
const ctx = {
|
|
3648
|
+
...makeContext(options),
|
|
3649
|
+
typeRegistry: ir.typeRegistry,
|
|
3650
|
+
typeNameMap: Object.fromEntries(
|
|
3651
|
+
Object.entries(ir.typeRegistry).map(([name, typeDef]) => [
|
|
3652
|
+
name,
|
|
3653
|
+
getSerializedName(name, typeDef.metadata)
|
|
3654
|
+
])
|
|
3655
|
+
)
|
|
3656
|
+
};
|
|
2497
3657
|
for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
|
|
2498
|
-
ctx.
|
|
3658
|
+
const schemaName = ctx.typeNameMap[name] ?? name;
|
|
3659
|
+
ctx.defs[schemaName] = generateTypeNode(typeDef.type, ctx);
|
|
3660
|
+
applyResolvedMetadata(ctx.defs[schemaName], typeDef.metadata);
|
|
2499
3661
|
if (typeDef.constraints && typeDef.constraints.length > 0) {
|
|
2500
|
-
applyConstraints(ctx.defs[
|
|
3662
|
+
applyConstraints(ctx.defs[schemaName], typeDef.constraints, ctx);
|
|
2501
3663
|
}
|
|
2502
3664
|
if (typeDef.annotations && typeDef.annotations.length > 0) {
|
|
2503
|
-
applyAnnotations(ctx.defs[
|
|
3665
|
+
applyAnnotations(ctx.defs[schemaName], typeDef.annotations, ctx);
|
|
2504
3666
|
}
|
|
2505
3667
|
}
|
|
2506
3668
|
const properties = {};
|
|
@@ -2513,6 +3675,7 @@ function generateJsonSchemaFromIR(ir, options) {
|
|
|
2513
3675
|
properties,
|
|
2514
3676
|
...uniqueRequired.length > 0 && { required: uniqueRequired }
|
|
2515
3677
|
};
|
|
3678
|
+
applyResolvedMetadata(result, ir.metadata);
|
|
2516
3679
|
if (ir.annotations && ir.annotations.length > 0) {
|
|
2517
3680
|
applyAnnotations(result, ir.annotations, ctx);
|
|
2518
3681
|
}
|
|
@@ -2525,9 +3688,9 @@ function collectFields(elements, properties, required, ctx) {
|
|
|
2525
3688
|
for (const element of elements) {
|
|
2526
3689
|
switch (element.kind) {
|
|
2527
3690
|
case "field":
|
|
2528
|
-
properties[element.name] = generateFieldSchema(element, ctx);
|
|
3691
|
+
properties[getSerializedName(element.name, element.metadata)] = generateFieldSchema(element, ctx);
|
|
2529
3692
|
if (element.required) {
|
|
2530
|
-
required.push(element.name);
|
|
3693
|
+
required.push(getSerializedName(element.name, element.metadata));
|
|
2531
3694
|
}
|
|
2532
3695
|
break;
|
|
2533
3696
|
case "group":
|
|
@@ -2571,6 +3734,7 @@ function generateFieldSchema(field, ctx) {
|
|
|
2571
3734
|
rootAnnotations.push(annotation);
|
|
2572
3735
|
}
|
|
2573
3736
|
}
|
|
3737
|
+
applyResolvedMetadata(schema, field.metadata);
|
|
2574
3738
|
applyAnnotations(schema, rootAnnotations, ctx);
|
|
2575
3739
|
if (itemStringSchema !== void 0) {
|
|
2576
3740
|
applyAnnotations(itemStringSchema, itemAnnotations, ctx);
|
|
@@ -2578,7 +3742,7 @@ function generateFieldSchema(field, ctx) {
|
|
|
2578
3742
|
if (pathConstraints.length === 0) {
|
|
2579
3743
|
return schema;
|
|
2580
3744
|
}
|
|
2581
|
-
return applyPathTargetedConstraints(schema, pathConstraints, ctx);
|
|
3745
|
+
return applyPathTargetedConstraints(schema, pathConstraints, ctx, field.type);
|
|
2582
3746
|
}
|
|
2583
3747
|
function isStringItemConstraint(constraint) {
|
|
2584
3748
|
switch (constraint.constraintKind) {
|
|
@@ -2590,9 +3754,11 @@ function isStringItemConstraint(constraint) {
|
|
|
2590
3754
|
return false;
|
|
2591
3755
|
}
|
|
2592
3756
|
}
|
|
2593
|
-
function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
|
|
3757
|
+
function applyPathTargetedConstraints(schema, pathConstraints, ctx, typeNode) {
|
|
2594
3758
|
if (schema.type === "array" && schema.items) {
|
|
2595
|
-
|
|
3759
|
+
const referencedType = typeNode?.kind === "reference" ? resolveReferencedType(typeNode, ctx) : void 0;
|
|
3760
|
+
const nestedType = typeNode?.kind === "array" ? typeNode.items : referencedType?.kind === "array" ? referencedType.items : void 0;
|
|
3761
|
+
schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx, nestedType);
|
|
2596
3762
|
return schema;
|
|
2597
3763
|
}
|
|
2598
3764
|
const byTarget = /* @__PURE__ */ new Map();
|
|
@@ -2607,7 +3773,7 @@ function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
|
|
|
2607
3773
|
for (const [target, constraints] of byTarget) {
|
|
2608
3774
|
const subSchema = {};
|
|
2609
3775
|
applyConstraints(subSchema, constraints, ctx);
|
|
2610
|
-
propertyOverrides[target] = subSchema;
|
|
3776
|
+
propertyOverrides[resolveSerializedPropertyName(target, typeNode, ctx)] = subSchema;
|
|
2611
3777
|
}
|
|
2612
3778
|
if (schema.$ref) {
|
|
2613
3779
|
const { $ref, ...rest } = schema;
|
|
@@ -2655,7 +3821,7 @@ function generateTypeNode(type, ctx) {
|
|
|
2655
3821
|
case "union":
|
|
2656
3822
|
return generateUnionType(type, ctx);
|
|
2657
3823
|
case "reference":
|
|
2658
|
-
return generateReferenceType(type);
|
|
3824
|
+
return generateReferenceType(type, ctx);
|
|
2659
3825
|
case "dynamic":
|
|
2660
3826
|
return generateDynamicType(type);
|
|
2661
3827
|
case "custom":
|
|
@@ -2696,9 +3862,10 @@ function generateObjectType(type, ctx) {
|
|
|
2696
3862
|
const properties = {};
|
|
2697
3863
|
const required = [];
|
|
2698
3864
|
for (const prop of type.properties) {
|
|
2699
|
-
|
|
3865
|
+
const propertyName = getSerializedName(prop.name, prop.metadata);
|
|
3866
|
+
properties[propertyName] = generatePropertySchema(prop, ctx);
|
|
2700
3867
|
if (!prop.optional) {
|
|
2701
|
-
required.push(
|
|
3868
|
+
required.push(propertyName);
|
|
2702
3869
|
}
|
|
2703
3870
|
}
|
|
2704
3871
|
const schema = { type: "object", properties };
|
|
@@ -2719,6 +3886,7 @@ function generateRecordType(type, ctx) {
|
|
|
2719
3886
|
function generatePropertySchema(prop, ctx) {
|
|
2720
3887
|
const schema = generateTypeNode(prop.type, ctx);
|
|
2721
3888
|
applyConstraints(schema, prop.constraints, ctx);
|
|
3889
|
+
applyResolvedMetadata(schema, prop.metadata);
|
|
2722
3890
|
applyAnnotations(schema, prop.annotations, ctx);
|
|
2723
3891
|
return schema;
|
|
2724
3892
|
}
|
|
@@ -2747,8 +3915,28 @@ function isNullableUnion(type) {
|
|
|
2747
3915
|
).length;
|
|
2748
3916
|
return nullCount === 1;
|
|
2749
3917
|
}
|
|
2750
|
-
function generateReferenceType(type) {
|
|
2751
|
-
return { $ref: `#/$defs/${type.name}` };
|
|
3918
|
+
function generateReferenceType(type, ctx) {
|
|
3919
|
+
return { $ref: `#/$defs/${ctx.typeNameMap[type.name] ?? type.name}` };
|
|
3920
|
+
}
|
|
3921
|
+
function applyResolvedMetadata(schema, metadata) {
|
|
3922
|
+
const displayName = getDisplayName(metadata);
|
|
3923
|
+
if (displayName !== void 0) {
|
|
3924
|
+
schema.title = displayName;
|
|
3925
|
+
}
|
|
3926
|
+
}
|
|
3927
|
+
function resolveReferencedType(type, ctx) {
|
|
3928
|
+
return ctx.typeRegistry[type.name]?.type;
|
|
3929
|
+
}
|
|
3930
|
+
function resolveSerializedPropertyName(logicalName, typeNode, ctx) {
|
|
3931
|
+
if (typeNode?.kind === "object") {
|
|
3932
|
+
const property = typeNode.properties.find((candidate) => candidate.name === logicalName);
|
|
3933
|
+
return property === void 0 ? logicalName : getSerializedName(property.name, property.metadata);
|
|
3934
|
+
}
|
|
3935
|
+
if (typeNode?.kind === "reference") {
|
|
3936
|
+
const referencedType = resolveReferencedType(typeNode, ctx);
|
|
3937
|
+
return referencedType === void 0 ? logicalName : resolveSerializedPropertyName(logicalName, referencedType, ctx);
|
|
3938
|
+
}
|
|
3939
|
+
return logicalName;
|
|
2752
3940
|
}
|
|
2753
3941
|
function generateDynamicType(type) {
|
|
2754
3942
|
if (type.dynamicKind === "enum") {
|
|
@@ -2828,7 +4016,7 @@ function applyAnnotations(schema, annotations, ctx) {
|
|
|
2828
4016
|
for (const annotation of annotations) {
|
|
2829
4017
|
switch (annotation.annotationKind) {
|
|
2830
4018
|
case "displayName":
|
|
2831
|
-
schema.title
|
|
4019
|
+
schema.title ??= annotation.value;
|
|
2832
4020
|
break;
|
|
2833
4021
|
case "description":
|
|
2834
4022
|
schema.description = annotation.value;
|
|
@@ -3078,13 +4266,21 @@ function combineRules(parentRule, childRule) {
|
|
|
3078
4266
|
}
|
|
3079
4267
|
};
|
|
3080
4268
|
}
|
|
3081
|
-
function
|
|
3082
|
-
const
|
|
4269
|
+
function getFieldDisplayName(field) {
|
|
4270
|
+
const resolvedDisplayName = getDisplayName(field.metadata);
|
|
4271
|
+
if (resolvedDisplayName !== void 0) {
|
|
4272
|
+
return resolvedDisplayName;
|
|
4273
|
+
}
|
|
4274
|
+
return field.annotations.find((annotation) => annotation.annotationKind === "displayName")?.value;
|
|
4275
|
+
}
|
|
4276
|
+
function fieldNodeToControl(field, fieldNameMap, parentRule) {
|
|
3083
4277
|
const placeholderAnnotation = field.annotations.find((a) => a.annotationKind === "placeholder");
|
|
4278
|
+
const serializedName = fieldNameMap.get(field.name) ?? getSerializedName(field.name, field.metadata);
|
|
4279
|
+
const displayName = getFieldDisplayName(field);
|
|
3084
4280
|
const control = {
|
|
3085
4281
|
type: "Control",
|
|
3086
|
-
scope: fieldToScope(
|
|
3087
|
-
...
|
|
4282
|
+
scope: fieldToScope(serializedName),
|
|
4283
|
+
...displayName !== void 0 && { label: displayName },
|
|
3088
4284
|
...placeholderAnnotation !== void 0 && {
|
|
3089
4285
|
options: { placeholder: placeholderAnnotation.value }
|
|
3090
4286
|
},
|
|
@@ -3092,30 +4288,30 @@ function fieldNodeToControl(field, parentRule) {
|
|
|
3092
4288
|
};
|
|
3093
4289
|
return control;
|
|
3094
4290
|
}
|
|
3095
|
-
function groupNodeToLayout(group, parentRule) {
|
|
4291
|
+
function groupNodeToLayout(group, fieldNameMap, parentRule) {
|
|
3096
4292
|
return {
|
|
3097
4293
|
type: "Group",
|
|
3098
4294
|
label: group.label,
|
|
3099
|
-
elements: irElementsToUiSchema(group.elements, parentRule),
|
|
4295
|
+
elements: irElementsToUiSchema(group.elements, fieldNameMap, parentRule),
|
|
3100
4296
|
...parentRule !== void 0 && { rule: parentRule }
|
|
3101
4297
|
};
|
|
3102
4298
|
}
|
|
3103
|
-
function irElementsToUiSchema(elements, parentRule) {
|
|
4299
|
+
function irElementsToUiSchema(elements, fieldNameMap, parentRule) {
|
|
3104
4300
|
const result = [];
|
|
3105
4301
|
for (const element of elements) {
|
|
3106
4302
|
switch (element.kind) {
|
|
3107
4303
|
case "field": {
|
|
3108
|
-
result.push(fieldNodeToControl(element, parentRule));
|
|
4304
|
+
result.push(fieldNodeToControl(element, fieldNameMap, parentRule));
|
|
3109
4305
|
break;
|
|
3110
4306
|
}
|
|
3111
4307
|
case "group": {
|
|
3112
|
-
result.push(groupNodeToLayout(element, parentRule));
|
|
4308
|
+
result.push(groupNodeToLayout(element, fieldNameMap, parentRule));
|
|
3113
4309
|
break;
|
|
3114
4310
|
}
|
|
3115
4311
|
case "conditional": {
|
|
3116
|
-
const newRule = createShowRule(element.fieldName, element.value);
|
|
4312
|
+
const newRule = createShowRule(fieldNameMap.get(element.fieldName) ?? element.fieldName, element.value);
|
|
3117
4313
|
const combinedRule = parentRule !== void 0 ? combineRules(parentRule, newRule) : newRule;
|
|
3118
|
-
const childElements = irElementsToUiSchema(element.elements, combinedRule);
|
|
4314
|
+
const childElements = irElementsToUiSchema(element.elements, fieldNameMap, combinedRule);
|
|
3119
4315
|
result.push(...childElements);
|
|
3120
4316
|
break;
|
|
3121
4317
|
}
|
|
@@ -3129,12 +4325,35 @@ function irElementsToUiSchema(elements, parentRule) {
|
|
|
3129
4325
|
return result;
|
|
3130
4326
|
}
|
|
3131
4327
|
function generateUiSchemaFromIR(ir) {
|
|
4328
|
+
assertNoSerializedNameCollisions(ir);
|
|
4329
|
+
const fieldNameMap = collectFieldNameMap(ir.elements);
|
|
3132
4330
|
const result = {
|
|
3133
4331
|
type: "VerticalLayout",
|
|
3134
|
-
elements: irElementsToUiSchema(ir.elements)
|
|
4332
|
+
elements: irElementsToUiSchema(ir.elements, fieldNameMap)
|
|
3135
4333
|
};
|
|
3136
4334
|
return parseOrThrow(uiSchema, result, "UI Schema");
|
|
3137
4335
|
}
|
|
4336
|
+
function collectFieldNameMap(elements) {
|
|
4337
|
+
const map = /* @__PURE__ */ new Map();
|
|
4338
|
+
for (const element of elements) {
|
|
4339
|
+
switch (element.kind) {
|
|
4340
|
+
case "field":
|
|
4341
|
+
map.set(element.name, getSerializedName(element.name, element.metadata));
|
|
4342
|
+
break;
|
|
4343
|
+
case "group":
|
|
4344
|
+
case "conditional":
|
|
4345
|
+
for (const [key, value] of collectFieldNameMap(element.elements)) {
|
|
4346
|
+
map.set(key, value);
|
|
4347
|
+
}
|
|
4348
|
+
break;
|
|
4349
|
+
default: {
|
|
4350
|
+
const _exhaustive = element;
|
|
4351
|
+
void _exhaustive;
|
|
4352
|
+
}
|
|
4353
|
+
}
|
|
4354
|
+
}
|
|
4355
|
+
return map;
|
|
4356
|
+
}
|
|
3138
4357
|
|
|
3139
4358
|
// src/validate/constraint-validator.ts
|
|
3140
4359
|
import {
|
|
@@ -3219,7 +4438,11 @@ function generateClassSchemas(analysis, source, options) {
|
|
|
3219
4438
|
if (errorDiagnostics !== void 0 && errorDiagnostics.length > 0) {
|
|
3220
4439
|
throw new Error(formatValidationError(errorDiagnostics));
|
|
3221
4440
|
}
|
|
3222
|
-
const ir = canonicalizeTSDoc(
|
|
4441
|
+
const ir = canonicalizeTSDoc(
|
|
4442
|
+
analysis,
|
|
4443
|
+
source,
|
|
4444
|
+
options?.metadata !== void 0 ? { metadata: options.metadata } : void 0
|
|
4445
|
+
);
|
|
3223
4446
|
const validationResult = validateIR(ir, {
|
|
3224
4447
|
...options?.extensionRegistry !== void 0 && {
|
|
3225
4448
|
extensionRegistry: options.extensionRegistry
|
|
@@ -3332,7 +4555,24 @@ import { IR_VERSION as IR_VERSION3 } from "@formspec/core/internals";
|
|
|
3332
4555
|
function typeToJsonSchema(type, checker) {
|
|
3333
4556
|
const typeRegistry = {};
|
|
3334
4557
|
const visiting = /* @__PURE__ */ new Set();
|
|
3335
|
-
const
|
|
4558
|
+
const diagnostics = [];
|
|
4559
|
+
const typeNode = resolveTypeNode(
|
|
4560
|
+
type,
|
|
4561
|
+
checker,
|
|
4562
|
+
"",
|
|
4563
|
+
typeRegistry,
|
|
4564
|
+
visiting,
|
|
4565
|
+
void 0,
|
|
4566
|
+
void 0,
|
|
4567
|
+
void 0,
|
|
4568
|
+
diagnostics
|
|
4569
|
+
);
|
|
4570
|
+
if (diagnostics.length > 0) {
|
|
4571
|
+
const diagnosticDetails = diagnostics.map((diagnostic) => `${diagnostic.code}: ${diagnostic.message}`).join("; ");
|
|
4572
|
+
throw new Error(
|
|
4573
|
+
`FormSpec validation failed while resolving method schema types. ${diagnosticDetails}`
|
|
4574
|
+
);
|
|
4575
|
+
}
|
|
3336
4576
|
const fieldProvenance = { surface: "tsdoc", file: "", line: 0, column: 0 };
|
|
3337
4577
|
const ir = {
|
|
3338
4578
|
kind: "form-ir",
|