@formspec/build 0.1.0-alpha.15 → 0.1.0-alpha.17
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/dist/__tests__/fixtures/edge-cases.d.ts +11 -0
- package/dist/__tests__/fixtures/edge-cases.d.ts.map +1 -1
- package/dist/__tests__/fixtures/example-numeric-extension.d.ts +20 -0
- package/dist/__tests__/fixtures/example-numeric-extension.d.ts.map +1 -0
- package/dist/__tests__/fixtures/mixed-authoring-shipping-address.d.ts +31 -0
- package/dist/__tests__/fixtures/mixed-authoring-shipping-address.d.ts.map +1 -0
- package/dist/__tests__/mixed-authoring.test.d.ts +2 -0
- package/dist/__tests__/mixed-authoring.test.d.ts.map +1 -0
- package/dist/__tests__/numeric-extension.integration.test.d.ts +2 -0
- package/dist/__tests__/numeric-extension.integration.test.d.ts.map +1 -0
- package/dist/__tests__/parity/utils.d.ts +5 -3
- package/dist/__tests__/parity/utils.d.ts.map +1 -1
- package/dist/analyzer/class-analyzer.d.ts +8 -5
- package/dist/analyzer/class-analyzer.d.ts.map +1 -1
- package/dist/analyzer/jsdoc-constraints.d.ts +3 -2
- package/dist/analyzer/jsdoc-constraints.d.ts.map +1 -1
- package/dist/analyzer/tsdoc-parser.d.ts +38 -4
- package/dist/analyzer/tsdoc-parser.d.ts.map +1 -1
- package/dist/browser.cjs +371 -21
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +371 -21
- package/dist/browser.js.map +1 -1
- package/dist/build.d.ts +67 -3
- package/dist/canonicalize/tsdoc-canonicalizer.d.ts.map +1 -1
- package/dist/cli.cjs +1159 -150
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1159 -150
- package/dist/cli.js.map +1 -1
- package/dist/extensions/registry.d.ts +25 -1
- package/dist/extensions/registry.d.ts.map +1 -1
- package/dist/generators/class-schema.d.ts +4 -4
- package/dist/generators/class-schema.d.ts.map +1 -1
- package/dist/generators/mixed-authoring.d.ts +45 -0
- package/dist/generators/mixed-authoring.d.ts.map +1 -0
- package/dist/index.cjs +1146 -149
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1145 -149
- package/dist/index.js.map +1 -1
- package/dist/internals.cjs +1156 -149
- package/dist/internals.cjs.map +1 -1
- package/dist/internals.js +1154 -147
- package/dist/internals.js.map +1 -1
- package/dist/json-schema/ir-generator.d.ts +3 -2
- package/dist/json-schema/ir-generator.d.ts.map +1 -1
- package/dist/ui-schema/ir-generator.d.ts.map +1 -1
- package/dist/validate/constraint-validator.d.ts.map +1 -1
- package/package.json +3 -3
- package/dist/__tests__/jsdoc-constraints.test.d.ts +0 -9
- package/dist/__tests__/jsdoc-constraints.test.d.ts.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -342,6 +342,7 @@ function canonicalizeTSDoc(analysis, source) {
|
|
|
342
342
|
irVersion: IR_VERSION2,
|
|
343
343
|
elements,
|
|
344
344
|
typeRegistry: analysis.typeRegistry,
|
|
345
|
+
...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { annotations: analysis.annotations },
|
|
345
346
|
provenance
|
|
346
347
|
};
|
|
347
348
|
}
|
|
@@ -432,6 +433,9 @@ function generateJsonSchemaFromIR(ir, options) {
|
|
|
432
433
|
const ctx = makeContext(options);
|
|
433
434
|
for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
|
|
434
435
|
ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
|
|
436
|
+
if (typeDef.annotations && typeDef.annotations.length > 0) {
|
|
437
|
+
applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
|
|
438
|
+
}
|
|
435
439
|
}
|
|
436
440
|
const properties = {};
|
|
437
441
|
const required = [];
|
|
@@ -443,6 +447,9 @@ function generateJsonSchemaFromIR(ir, options) {
|
|
|
443
447
|
properties,
|
|
444
448
|
...uniqueRequired.length > 0 && { required: uniqueRequired }
|
|
445
449
|
};
|
|
450
|
+
if (ir.annotations && ir.annotations.length > 0) {
|
|
451
|
+
applyAnnotations(result, ir.annotations, ctx);
|
|
452
|
+
}
|
|
446
453
|
if (Object.keys(ctx.defs).length > 0) {
|
|
447
454
|
result.$defs = ctx.defs;
|
|
448
455
|
}
|
|
@@ -472,22 +479,51 @@ function collectFields(elements, properties, required, ctx) {
|
|
|
472
479
|
}
|
|
473
480
|
function generateFieldSchema(field, ctx) {
|
|
474
481
|
const schema = generateTypeNode(field.type, ctx);
|
|
482
|
+
const itemStringSchema = schema.type === "array" && schema.items?.type === "string" ? schema.items : void 0;
|
|
475
483
|
const directConstraints = [];
|
|
484
|
+
const itemConstraints = [];
|
|
476
485
|
const pathConstraints = [];
|
|
477
486
|
for (const c of field.constraints) {
|
|
478
487
|
if (c.path) {
|
|
479
488
|
pathConstraints.push(c);
|
|
489
|
+
} else if (itemStringSchema !== void 0 && isStringItemConstraint(c)) {
|
|
490
|
+
itemConstraints.push(c);
|
|
480
491
|
} else {
|
|
481
492
|
directConstraints.push(c);
|
|
482
493
|
}
|
|
483
494
|
}
|
|
484
495
|
applyConstraints(schema, directConstraints, ctx);
|
|
485
|
-
|
|
496
|
+
if (itemStringSchema !== void 0) {
|
|
497
|
+
applyConstraints(itemStringSchema, itemConstraints, ctx);
|
|
498
|
+
}
|
|
499
|
+
const rootAnnotations = [];
|
|
500
|
+
const itemAnnotations = [];
|
|
501
|
+
for (const annotation of field.annotations) {
|
|
502
|
+
if (itemStringSchema !== void 0 && annotation.annotationKind === "format") {
|
|
503
|
+
itemAnnotations.push(annotation);
|
|
504
|
+
} else {
|
|
505
|
+
rootAnnotations.push(annotation);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
applyAnnotations(schema, rootAnnotations, ctx);
|
|
509
|
+
if (itemStringSchema !== void 0) {
|
|
510
|
+
applyAnnotations(itemStringSchema, itemAnnotations, ctx);
|
|
511
|
+
}
|
|
486
512
|
if (pathConstraints.length === 0) {
|
|
487
513
|
return schema;
|
|
488
514
|
}
|
|
489
515
|
return applyPathTargetedConstraints(schema, pathConstraints, ctx);
|
|
490
516
|
}
|
|
517
|
+
function isStringItemConstraint(constraint) {
|
|
518
|
+
switch (constraint.constraintKind) {
|
|
519
|
+
case "minLength":
|
|
520
|
+
case "maxLength":
|
|
521
|
+
case "pattern":
|
|
522
|
+
return true;
|
|
523
|
+
default:
|
|
524
|
+
return false;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
491
527
|
function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
|
|
492
528
|
if (schema.type === "array" && schema.items) {
|
|
493
529
|
schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx);
|
|
@@ -705,6 +741,9 @@ function applyConstraints(schema, constraints, ctx) {
|
|
|
705
741
|
case "uniqueItems":
|
|
706
742
|
schema.uniqueItems = constraint.value;
|
|
707
743
|
break;
|
|
744
|
+
case "const":
|
|
745
|
+
schema.const = constraint.value;
|
|
746
|
+
break;
|
|
708
747
|
case "allowedMembers":
|
|
709
748
|
break;
|
|
710
749
|
case "custom":
|
|
@@ -729,8 +768,14 @@ function applyAnnotations(schema, annotations, ctx) {
|
|
|
729
768
|
case "defaultValue":
|
|
730
769
|
schema.default = annotation.value;
|
|
731
770
|
break;
|
|
771
|
+
case "format":
|
|
772
|
+
schema.format = annotation.value;
|
|
773
|
+
break;
|
|
732
774
|
case "deprecated":
|
|
733
775
|
schema.deprecated = true;
|
|
776
|
+
if (annotation.message !== void 0 && annotation.message !== "") {
|
|
777
|
+
schema["x-formspec-deprecation-description"] = annotation.message;
|
|
778
|
+
}
|
|
734
779
|
break;
|
|
735
780
|
case "placeholder":
|
|
736
781
|
break;
|
|
@@ -762,7 +807,12 @@ function applyCustomConstraint(schema, constraint, ctx) {
|
|
|
762
807
|
`Cannot generate JSON Schema for custom constraint "${constraint.constraintId}" without a matching extension registration`
|
|
763
808
|
);
|
|
764
809
|
}
|
|
765
|
-
|
|
810
|
+
assignVendorPrefixedExtensionKeywords(
|
|
811
|
+
schema,
|
|
812
|
+
registration.toJsonSchema(constraint.payload, ctx.vendorPrefix),
|
|
813
|
+
ctx.vendorPrefix,
|
|
814
|
+
`custom constraint "${constraint.constraintId}"`
|
|
815
|
+
);
|
|
766
816
|
}
|
|
767
817
|
function applyCustomAnnotation(schema, annotation, ctx) {
|
|
768
818
|
const registration = ctx.extensionRegistry?.findAnnotation(annotation.annotationId);
|
|
@@ -774,7 +824,22 @@ function applyCustomAnnotation(schema, annotation, ctx) {
|
|
|
774
824
|
if (registration.toJsonSchema === void 0) {
|
|
775
825
|
return;
|
|
776
826
|
}
|
|
777
|
-
|
|
827
|
+
assignVendorPrefixedExtensionKeywords(
|
|
828
|
+
schema,
|
|
829
|
+
registration.toJsonSchema(annotation.value, ctx.vendorPrefix),
|
|
830
|
+
ctx.vendorPrefix,
|
|
831
|
+
`custom annotation "${annotation.annotationId}"`
|
|
832
|
+
);
|
|
833
|
+
}
|
|
834
|
+
function assignVendorPrefixedExtensionKeywords(schema, extensionSchema, vendorPrefix, source) {
|
|
835
|
+
for (const [key, value] of Object.entries(extensionSchema)) {
|
|
836
|
+
if (!key.startsWith(`${vendorPrefix}-`)) {
|
|
837
|
+
throw new Error(
|
|
838
|
+
`Cannot apply ${source}: extension hooks may only emit "${vendorPrefix}-*" JSON Schema keywords`
|
|
839
|
+
);
|
|
840
|
+
}
|
|
841
|
+
schema[key] = value;
|
|
842
|
+
}
|
|
778
843
|
}
|
|
779
844
|
var init_ir_generator = __esm({
|
|
780
845
|
"src/json-schema/ir-generator.ts"() {
|
|
@@ -936,25 +1001,31 @@ function createShowRule(fieldName, value) {
|
|
|
936
1001
|
}
|
|
937
1002
|
};
|
|
938
1003
|
}
|
|
1004
|
+
function flattenConditionSchema(scope, schema) {
|
|
1005
|
+
if (schema.allOf === void 0) {
|
|
1006
|
+
if (scope === "#") {
|
|
1007
|
+
return [schema];
|
|
1008
|
+
}
|
|
1009
|
+
const fieldName = scope.replace("#/properties/", "");
|
|
1010
|
+
return [
|
|
1011
|
+
{
|
|
1012
|
+
properties: {
|
|
1013
|
+
[fieldName]: schema
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
];
|
|
1017
|
+
}
|
|
1018
|
+
return schema.allOf.flatMap((member) => flattenConditionSchema(scope, member));
|
|
1019
|
+
}
|
|
939
1020
|
function combineRules(parentRule, childRule) {
|
|
940
|
-
const parentCondition = parentRule.condition;
|
|
941
|
-
const childCondition = childRule.condition;
|
|
942
1021
|
return {
|
|
943
1022
|
effect: "SHOW",
|
|
944
1023
|
condition: {
|
|
945
1024
|
scope: "#",
|
|
946
1025
|
schema: {
|
|
947
1026
|
allOf: [
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
[parentCondition.scope.replace("#/properties/", "")]: parentCondition.schema
|
|
951
|
-
}
|
|
952
|
-
},
|
|
953
|
-
{
|
|
954
|
-
properties: {
|
|
955
|
-
[childCondition.scope.replace("#/properties/", "")]: childCondition.schema
|
|
956
|
-
}
|
|
957
|
-
}
|
|
1027
|
+
...flattenConditionSchema(parentRule.condition.scope, parentRule.condition.schema),
|
|
1028
|
+
...flattenConditionSchema(childRule.condition.scope, childRule.condition.schema)
|
|
958
1029
|
]
|
|
959
1030
|
}
|
|
960
1031
|
}
|
|
@@ -962,10 +1033,14 @@ function combineRules(parentRule, childRule) {
|
|
|
962
1033
|
}
|
|
963
1034
|
function fieldNodeToControl(field, parentRule) {
|
|
964
1035
|
const displayNameAnnotation = field.annotations.find((a) => a.annotationKind === "displayName");
|
|
1036
|
+
const placeholderAnnotation = field.annotations.find((a) => a.annotationKind === "placeholder");
|
|
965
1037
|
const control = {
|
|
966
1038
|
type: "Control",
|
|
967
1039
|
scope: fieldToScope(field.name),
|
|
968
1040
|
...displayNameAnnotation !== void 0 && { label: displayNameAnnotation.value },
|
|
1041
|
+
...placeholderAnnotation !== void 0 && {
|
|
1042
|
+
options: { placeholder: placeholderAnnotation.value }
|
|
1043
|
+
},
|
|
969
1044
|
...parentRule !== void 0 && { rule: parentRule }
|
|
970
1045
|
};
|
|
971
1046
|
return control;
|
|
@@ -1049,7 +1124,10 @@ var init_types = __esm({
|
|
|
1049
1124
|
// src/extensions/registry.ts
|
|
1050
1125
|
function createExtensionRegistry(extensions) {
|
|
1051
1126
|
const typeMap = /* @__PURE__ */ new Map();
|
|
1127
|
+
const typeNameMap = /* @__PURE__ */ new Map();
|
|
1052
1128
|
const constraintMap = /* @__PURE__ */ new Map();
|
|
1129
|
+
const constraintTagMap = /* @__PURE__ */ new Map();
|
|
1130
|
+
const builtinBroadeningMap = /* @__PURE__ */ new Map();
|
|
1053
1131
|
const annotationMap = /* @__PURE__ */ new Map();
|
|
1054
1132
|
for (const ext of extensions) {
|
|
1055
1133
|
if (ext.types !== void 0) {
|
|
@@ -1059,6 +1137,27 @@ function createExtensionRegistry(extensions) {
|
|
|
1059
1137
|
throw new Error(`Duplicate custom type ID: "${qualifiedId}"`);
|
|
1060
1138
|
}
|
|
1061
1139
|
typeMap.set(qualifiedId, type);
|
|
1140
|
+
for (const sourceTypeName of type.tsTypeNames ?? [type.typeName]) {
|
|
1141
|
+
if (typeNameMap.has(sourceTypeName)) {
|
|
1142
|
+
throw new Error(`Duplicate custom type source name: "${sourceTypeName}"`);
|
|
1143
|
+
}
|
|
1144
|
+
typeNameMap.set(sourceTypeName, {
|
|
1145
|
+
extensionId: ext.extensionId,
|
|
1146
|
+
registration: type
|
|
1147
|
+
});
|
|
1148
|
+
}
|
|
1149
|
+
if (type.builtinConstraintBroadenings !== void 0) {
|
|
1150
|
+
for (const broadening of type.builtinConstraintBroadenings) {
|
|
1151
|
+
const key = `${qualifiedId}:${broadening.tagName}`;
|
|
1152
|
+
if (builtinBroadeningMap.has(key)) {
|
|
1153
|
+
throw new Error(`Duplicate built-in constraint broadening: "${key}"`);
|
|
1154
|
+
}
|
|
1155
|
+
builtinBroadeningMap.set(key, {
|
|
1156
|
+
extensionId: ext.extensionId,
|
|
1157
|
+
registration: broadening
|
|
1158
|
+
});
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1062
1161
|
}
|
|
1063
1162
|
}
|
|
1064
1163
|
if (ext.constraints !== void 0) {
|
|
@@ -1070,6 +1169,17 @@ function createExtensionRegistry(extensions) {
|
|
|
1070
1169
|
constraintMap.set(qualifiedId, constraint);
|
|
1071
1170
|
}
|
|
1072
1171
|
}
|
|
1172
|
+
if (ext.constraintTags !== void 0) {
|
|
1173
|
+
for (const tag of ext.constraintTags) {
|
|
1174
|
+
if (constraintTagMap.has(tag.tagName)) {
|
|
1175
|
+
throw new Error(`Duplicate custom constraint tag: "@${tag.tagName}"`);
|
|
1176
|
+
}
|
|
1177
|
+
constraintTagMap.set(tag.tagName, {
|
|
1178
|
+
extensionId: ext.extensionId,
|
|
1179
|
+
registration: tag
|
|
1180
|
+
});
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1073
1183
|
if (ext.annotations !== void 0) {
|
|
1074
1184
|
for (const annotation of ext.annotations) {
|
|
1075
1185
|
const qualifiedId = `${ext.extensionId}/${annotation.annotationName}`;
|
|
@@ -1083,7 +1193,10 @@ function createExtensionRegistry(extensions) {
|
|
|
1083
1193
|
return {
|
|
1084
1194
|
extensions,
|
|
1085
1195
|
findType: (typeId) => typeMap.get(typeId),
|
|
1196
|
+
findTypeByName: (typeName) => typeNameMap.get(typeName),
|
|
1086
1197
|
findConstraint: (constraintId) => constraintMap.get(constraintId),
|
|
1198
|
+
findConstraintTag: (tagName) => constraintTagMap.get(tagName),
|
|
1199
|
+
findBuiltinConstraintBroadening: (typeId, tagName) => builtinBroadeningMap.get(`${typeId}:${tagName}`),
|
|
1087
1200
|
findAnnotation: (annotationId) => annotationMap.get(annotationId)
|
|
1088
1201
|
};
|
|
1089
1202
|
}
|
|
@@ -1276,7 +1389,7 @@ import {
|
|
|
1276
1389
|
normalizeConstraintTagName,
|
|
1277
1390
|
isBuiltinConstraintName
|
|
1278
1391
|
} from "@formspec/core";
|
|
1279
|
-
function createFormSpecTSDocConfig() {
|
|
1392
|
+
function createFormSpecTSDocConfig(extensionTagNames = []) {
|
|
1280
1393
|
const config = new TSDocConfiguration();
|
|
1281
1394
|
for (const tagName of Object.keys(BUILTIN_CONSTRAINT_DEFINITIONS)) {
|
|
1282
1395
|
config.addTagDefinition(
|
|
@@ -1287,7 +1400,16 @@ function createFormSpecTSDocConfig() {
|
|
|
1287
1400
|
})
|
|
1288
1401
|
);
|
|
1289
1402
|
}
|
|
1290
|
-
for (const tagName of ["displayName", "description"]) {
|
|
1403
|
+
for (const tagName of ["displayName", "description", "format", "placeholder"]) {
|
|
1404
|
+
config.addTagDefinition(
|
|
1405
|
+
new TSDocTagDefinition({
|
|
1406
|
+
tagName: "@" + tagName,
|
|
1407
|
+
syntaxKind: TSDocTagSyntaxKind.BlockTag,
|
|
1408
|
+
allowMultiple: true
|
|
1409
|
+
})
|
|
1410
|
+
);
|
|
1411
|
+
}
|
|
1412
|
+
for (const tagName of extensionTagNames) {
|
|
1291
1413
|
config.addTagDefinition(
|
|
1292
1414
|
new TSDocTagDefinition({
|
|
1293
1415
|
tagName: "@" + tagName,
|
|
@@ -1298,13 +1420,30 @@ function createFormSpecTSDocConfig() {
|
|
|
1298
1420
|
}
|
|
1299
1421
|
return config;
|
|
1300
1422
|
}
|
|
1301
|
-
function getParser() {
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1423
|
+
function getParser(options) {
|
|
1424
|
+
const extensionTagNames = [
|
|
1425
|
+
...options?.extensionRegistry?.extensions.flatMap(
|
|
1426
|
+
(extension) => (extension.constraintTags ?? []).map((tag) => tag.tagName)
|
|
1427
|
+
) ?? []
|
|
1428
|
+
].sort();
|
|
1429
|
+
const cacheKey = extensionTagNames.join("|");
|
|
1430
|
+
const existing = parserCache.get(cacheKey);
|
|
1431
|
+
if (existing) {
|
|
1432
|
+
return existing;
|
|
1433
|
+
}
|
|
1434
|
+
const parser = new TSDocParser(createFormSpecTSDocConfig(extensionTagNames));
|
|
1435
|
+
parserCache.set(cacheKey, parser);
|
|
1436
|
+
return parser;
|
|
1437
|
+
}
|
|
1438
|
+
function parseTSDocTags(node, file = "", options) {
|
|
1306
1439
|
const constraints = [];
|
|
1307
1440
|
const annotations = [];
|
|
1441
|
+
let displayName;
|
|
1442
|
+
let description;
|
|
1443
|
+
let placeholder;
|
|
1444
|
+
let displayNameProvenance;
|
|
1445
|
+
let descriptionProvenance;
|
|
1446
|
+
let placeholderProvenance;
|
|
1308
1447
|
const sourceFile = node.getSourceFile();
|
|
1309
1448
|
const sourceText = sourceFile.getFullText();
|
|
1310
1449
|
const commentRanges = ts2.getLeadingCommentRanges(sourceText, node.getFullStart());
|
|
@@ -1317,52 +1456,92 @@ function parseTSDocTags(node, file = "") {
|
|
|
1317
1456
|
if (!commentText.startsWith("/**")) {
|
|
1318
1457
|
continue;
|
|
1319
1458
|
}
|
|
1320
|
-
const parser = getParser();
|
|
1459
|
+
const parser = getParser(options);
|
|
1321
1460
|
const parserContext = parser.parseRange(
|
|
1322
1461
|
TextRange.fromStringRange(sourceText, range.pos, range.end)
|
|
1323
1462
|
);
|
|
1324
1463
|
const docComment = parserContext.docComment;
|
|
1325
1464
|
for (const block of docComment.customBlocks) {
|
|
1326
1465
|
const tagName = normalizeConstraintTagName(block.blockTag.tagName.substring(1));
|
|
1327
|
-
if (tagName === "displayName" || tagName === "description") {
|
|
1466
|
+
if (tagName === "displayName" || tagName === "description" || tagName === "format" || tagName === "placeholder") {
|
|
1328
1467
|
const text2 = extractBlockText(block).trim();
|
|
1329
1468
|
if (text2 === "") continue;
|
|
1330
1469
|
const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
|
|
1331
1470
|
if (tagName === "displayName") {
|
|
1471
|
+
if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
|
|
1472
|
+
displayName = text2;
|
|
1473
|
+
displayNameProvenance = provenance2;
|
|
1474
|
+
}
|
|
1475
|
+
} else if (tagName === "format") {
|
|
1332
1476
|
annotations.push({
|
|
1333
1477
|
kind: "annotation",
|
|
1334
|
-
annotationKind: "
|
|
1478
|
+
annotationKind: "format",
|
|
1335
1479
|
value: text2,
|
|
1336
1480
|
provenance: provenance2
|
|
1337
1481
|
});
|
|
1338
1482
|
} else {
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1483
|
+
if (tagName === "description" && description === void 0) {
|
|
1484
|
+
description = text2;
|
|
1485
|
+
descriptionProvenance = provenance2;
|
|
1486
|
+
} else if (tagName === "placeholder" && placeholder === void 0) {
|
|
1487
|
+
placeholder = text2;
|
|
1488
|
+
placeholderProvenance = provenance2;
|
|
1489
|
+
}
|
|
1345
1490
|
}
|
|
1346
1491
|
continue;
|
|
1347
1492
|
}
|
|
1348
1493
|
if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
1349
1494
|
const text = extractBlockText(block).trim();
|
|
1350
|
-
|
|
1495
|
+
const expectedType = isBuiltinConstraintName(tagName) ? BUILTIN_CONSTRAINT_DEFINITIONS[tagName] : void 0;
|
|
1496
|
+
if (text === "" && expectedType !== "boolean") continue;
|
|
1351
1497
|
const provenance = provenanceForComment(range, sourceFile, file, tagName);
|
|
1352
|
-
const constraintNode = parseConstraintValue(tagName, text, provenance);
|
|
1498
|
+
const constraintNode = parseConstraintValue(tagName, text, provenance, options);
|
|
1353
1499
|
if (constraintNode) {
|
|
1354
1500
|
constraints.push(constraintNode);
|
|
1355
1501
|
}
|
|
1356
1502
|
}
|
|
1357
1503
|
if (docComment.deprecatedBlock !== void 0) {
|
|
1504
|
+
const message = extractBlockText(docComment.deprecatedBlock).trim();
|
|
1358
1505
|
annotations.push({
|
|
1359
1506
|
kind: "annotation",
|
|
1360
1507
|
annotationKind: "deprecated",
|
|
1508
|
+
...message !== "" && { message },
|
|
1361
1509
|
provenance: provenanceForComment(range, sourceFile, file, "deprecated")
|
|
1362
1510
|
});
|
|
1363
1511
|
}
|
|
1512
|
+
if (description === void 0 && docComment.remarksBlock !== void 0) {
|
|
1513
|
+
const remarks = extractBlockText(docComment.remarksBlock).trim();
|
|
1514
|
+
if (remarks !== "") {
|
|
1515
|
+
description = remarks;
|
|
1516
|
+
descriptionProvenance = provenanceForComment(range, sourceFile, file, "remarks");
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1364
1519
|
}
|
|
1365
1520
|
}
|
|
1521
|
+
if (displayName !== void 0 && displayNameProvenance !== void 0) {
|
|
1522
|
+
annotations.push({
|
|
1523
|
+
kind: "annotation",
|
|
1524
|
+
annotationKind: "displayName",
|
|
1525
|
+
value: displayName,
|
|
1526
|
+
provenance: displayNameProvenance
|
|
1527
|
+
});
|
|
1528
|
+
}
|
|
1529
|
+
if (description !== void 0 && descriptionProvenance !== void 0) {
|
|
1530
|
+
annotations.push({
|
|
1531
|
+
kind: "annotation",
|
|
1532
|
+
annotationKind: "description",
|
|
1533
|
+
value: description,
|
|
1534
|
+
provenance: descriptionProvenance
|
|
1535
|
+
});
|
|
1536
|
+
}
|
|
1537
|
+
if (placeholder !== void 0 && placeholderProvenance !== void 0) {
|
|
1538
|
+
annotations.push({
|
|
1539
|
+
kind: "annotation",
|
|
1540
|
+
annotationKind: "placeholder",
|
|
1541
|
+
value: placeholder,
|
|
1542
|
+
provenance: placeholderProvenance
|
|
1543
|
+
});
|
|
1544
|
+
}
|
|
1366
1545
|
const jsDocTagsAll = ts2.getJSDocTags(node);
|
|
1367
1546
|
for (const tag of jsDocTagsAll) {
|
|
1368
1547
|
const tagName = normalizeConstraintTagName(tag.tagName.text);
|
|
@@ -1371,13 +1550,40 @@ function parseTSDocTags(node, file = "") {
|
|
|
1371
1550
|
if (commentText === void 0 || commentText.trim() === "") continue;
|
|
1372
1551
|
const text = commentText.trim();
|
|
1373
1552
|
const provenance = provenanceForJSDocTag(tag, file);
|
|
1374
|
-
|
|
1553
|
+
if (tagName === "defaultValue") {
|
|
1554
|
+
const defaultValueNode = parseDefaultValueValue(text, provenance);
|
|
1555
|
+
annotations.push(defaultValueNode);
|
|
1556
|
+
continue;
|
|
1557
|
+
}
|
|
1558
|
+
const constraintNode = parseConstraintValue(tagName, text, provenance, options);
|
|
1375
1559
|
if (constraintNode) {
|
|
1376
1560
|
constraints.push(constraintNode);
|
|
1377
1561
|
}
|
|
1378
1562
|
}
|
|
1379
1563
|
return { constraints, annotations };
|
|
1380
1564
|
}
|
|
1565
|
+
function extractDisplayNameMetadata(node) {
|
|
1566
|
+
let displayName;
|
|
1567
|
+
const memberDisplayNames = /* @__PURE__ */ new Map();
|
|
1568
|
+
for (const tag of ts2.getJSDocTags(node)) {
|
|
1569
|
+
const tagName = normalizeConstraintTagName(tag.tagName.text);
|
|
1570
|
+
if (tagName !== "displayName") continue;
|
|
1571
|
+
const commentText = getTagCommentText(tag);
|
|
1572
|
+
if (commentText === void 0) continue;
|
|
1573
|
+
const text = commentText.trim();
|
|
1574
|
+
if (text === "") continue;
|
|
1575
|
+
const memberTarget = parseMemberTargetDisplayName(text);
|
|
1576
|
+
if (memberTarget) {
|
|
1577
|
+
memberDisplayNames.set(memberTarget.target, memberTarget.label);
|
|
1578
|
+
continue;
|
|
1579
|
+
}
|
|
1580
|
+
displayName ??= text;
|
|
1581
|
+
}
|
|
1582
|
+
return {
|
|
1583
|
+
...displayName !== void 0 && { displayName },
|
|
1584
|
+
memberDisplayNames
|
|
1585
|
+
};
|
|
1586
|
+
}
|
|
1381
1587
|
function extractPathTarget(text) {
|
|
1382
1588
|
const trimmed = text.trimStart();
|
|
1383
1589
|
const match = /^:([a-zA-Z_]\w*)\s+([\s\S]*)$/.exec(trimmed);
|
|
@@ -1405,7 +1611,11 @@ function extractPlainText(node) {
|
|
|
1405
1611
|
}
|
|
1406
1612
|
return result;
|
|
1407
1613
|
}
|
|
1408
|
-
function parseConstraintValue(tagName, text, provenance) {
|
|
1614
|
+
function parseConstraintValue(tagName, text, provenance, options) {
|
|
1615
|
+
const customConstraint = parseExtensionConstraintValue(tagName, text, provenance, options);
|
|
1616
|
+
if (customConstraint) {
|
|
1617
|
+
return customConstraint;
|
|
1618
|
+
}
|
|
1409
1619
|
if (!isBuiltinConstraintName(tagName)) {
|
|
1410
1620
|
return null;
|
|
1411
1621
|
}
|
|
@@ -1440,7 +1650,45 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
1440
1650
|
}
|
|
1441
1651
|
return null;
|
|
1442
1652
|
}
|
|
1653
|
+
if (expectedType === "boolean") {
|
|
1654
|
+
const trimmed = effectiveText.trim();
|
|
1655
|
+
if (trimmed !== "" && trimmed !== "true") {
|
|
1656
|
+
return null;
|
|
1657
|
+
}
|
|
1658
|
+
if (tagName === "uniqueItems") {
|
|
1659
|
+
return {
|
|
1660
|
+
kind: "constraint",
|
|
1661
|
+
constraintKind: "uniqueItems",
|
|
1662
|
+
value: true,
|
|
1663
|
+
...path4 && { path: path4 },
|
|
1664
|
+
provenance
|
|
1665
|
+
};
|
|
1666
|
+
}
|
|
1667
|
+
return null;
|
|
1668
|
+
}
|
|
1443
1669
|
if (expectedType === "json") {
|
|
1670
|
+
if (tagName === "const") {
|
|
1671
|
+
const trimmedText = effectiveText.trim();
|
|
1672
|
+
if (trimmedText === "") return null;
|
|
1673
|
+
try {
|
|
1674
|
+
const parsed2 = JSON.parse(trimmedText);
|
|
1675
|
+
return {
|
|
1676
|
+
kind: "constraint",
|
|
1677
|
+
constraintKind: "const",
|
|
1678
|
+
value: parsed2,
|
|
1679
|
+
...path4 && { path: path4 },
|
|
1680
|
+
provenance
|
|
1681
|
+
};
|
|
1682
|
+
} catch {
|
|
1683
|
+
return {
|
|
1684
|
+
kind: "constraint",
|
|
1685
|
+
constraintKind: "const",
|
|
1686
|
+
value: trimmedText,
|
|
1687
|
+
...path4 && { path: path4 },
|
|
1688
|
+
provenance
|
|
1689
|
+
};
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1444
1692
|
const parsed = tryParseJson(effectiveText);
|
|
1445
1693
|
if (!Array.isArray(parsed)) {
|
|
1446
1694
|
return null;
|
|
@@ -1472,6 +1720,111 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
1472
1720
|
provenance
|
|
1473
1721
|
};
|
|
1474
1722
|
}
|
|
1723
|
+
function parseExtensionConstraintValue(tagName, text, provenance, options) {
|
|
1724
|
+
const pathResult = extractPathTarget(text);
|
|
1725
|
+
const effectiveText = pathResult ? pathResult.remainingText : text;
|
|
1726
|
+
const path4 = pathResult?.path;
|
|
1727
|
+
const registry = options?.extensionRegistry;
|
|
1728
|
+
if (registry === void 0) {
|
|
1729
|
+
return null;
|
|
1730
|
+
}
|
|
1731
|
+
const directTag = registry.findConstraintTag(tagName);
|
|
1732
|
+
if (directTag !== void 0) {
|
|
1733
|
+
return makeCustomConstraintNode(
|
|
1734
|
+
directTag.extensionId,
|
|
1735
|
+
directTag.registration.constraintName,
|
|
1736
|
+
directTag.registration.parseValue(effectiveText),
|
|
1737
|
+
provenance,
|
|
1738
|
+
path4,
|
|
1739
|
+
registry
|
|
1740
|
+
);
|
|
1741
|
+
}
|
|
1742
|
+
if (!isBuiltinConstraintName(tagName)) {
|
|
1743
|
+
return null;
|
|
1744
|
+
}
|
|
1745
|
+
const broadenedTypeId = getBroadenedCustomTypeId(options?.fieldType);
|
|
1746
|
+
if (broadenedTypeId === void 0) {
|
|
1747
|
+
return null;
|
|
1748
|
+
}
|
|
1749
|
+
const broadened = registry.findBuiltinConstraintBroadening(broadenedTypeId, tagName);
|
|
1750
|
+
if (broadened === void 0) {
|
|
1751
|
+
return null;
|
|
1752
|
+
}
|
|
1753
|
+
return makeCustomConstraintNode(
|
|
1754
|
+
broadened.extensionId,
|
|
1755
|
+
broadened.registration.constraintName,
|
|
1756
|
+
broadened.registration.parseValue(effectiveText),
|
|
1757
|
+
provenance,
|
|
1758
|
+
path4,
|
|
1759
|
+
registry
|
|
1760
|
+
);
|
|
1761
|
+
}
|
|
1762
|
+
function getBroadenedCustomTypeId(fieldType) {
|
|
1763
|
+
if (fieldType?.kind === "custom") {
|
|
1764
|
+
return fieldType.typeId;
|
|
1765
|
+
}
|
|
1766
|
+
if (fieldType?.kind !== "union") {
|
|
1767
|
+
return void 0;
|
|
1768
|
+
}
|
|
1769
|
+
const customMembers = fieldType.members.filter(
|
|
1770
|
+
(member) => member.kind === "custom"
|
|
1771
|
+
);
|
|
1772
|
+
if (customMembers.length !== 1) {
|
|
1773
|
+
return void 0;
|
|
1774
|
+
}
|
|
1775
|
+
const nonCustomMembers = fieldType.members.filter((member) => member.kind !== "custom");
|
|
1776
|
+
const allOtherMembersAreNull = nonCustomMembers.every(
|
|
1777
|
+
(member) => member.kind === "primitive" && member.primitiveKind === "null"
|
|
1778
|
+
);
|
|
1779
|
+
const customMember = customMembers[0];
|
|
1780
|
+
return allOtherMembersAreNull && customMember !== void 0 ? customMember.typeId : void 0;
|
|
1781
|
+
}
|
|
1782
|
+
function makeCustomConstraintNode(extensionId, constraintName, payload, provenance, path4, registry) {
|
|
1783
|
+
const constraintId = `${extensionId}/${constraintName}`;
|
|
1784
|
+
const registration = registry.findConstraint(constraintId);
|
|
1785
|
+
if (registration === void 0) {
|
|
1786
|
+
throw new Error(
|
|
1787
|
+
`Custom TSDoc tag resolved to unregistered constraint "${constraintId}". Register the constraint before using its tag.`
|
|
1788
|
+
);
|
|
1789
|
+
}
|
|
1790
|
+
return {
|
|
1791
|
+
kind: "constraint",
|
|
1792
|
+
constraintKind: "custom",
|
|
1793
|
+
constraintId,
|
|
1794
|
+
payload,
|
|
1795
|
+
compositionRule: registration.compositionRule,
|
|
1796
|
+
...path4 && { path: path4 },
|
|
1797
|
+
provenance
|
|
1798
|
+
};
|
|
1799
|
+
}
|
|
1800
|
+
function parseDefaultValueValue(text, provenance) {
|
|
1801
|
+
const trimmed = text.trim();
|
|
1802
|
+
let value;
|
|
1803
|
+
if (trimmed === "null") {
|
|
1804
|
+
value = null;
|
|
1805
|
+
} else if (trimmed === "true") {
|
|
1806
|
+
value = true;
|
|
1807
|
+
} else if (trimmed === "false") {
|
|
1808
|
+
value = false;
|
|
1809
|
+
} else {
|
|
1810
|
+
const parsed = tryParseJson(trimmed);
|
|
1811
|
+
value = parsed !== null ? parsed : trimmed;
|
|
1812
|
+
}
|
|
1813
|
+
return {
|
|
1814
|
+
kind: "annotation",
|
|
1815
|
+
annotationKind: "defaultValue",
|
|
1816
|
+
value,
|
|
1817
|
+
provenance
|
|
1818
|
+
};
|
|
1819
|
+
}
|
|
1820
|
+
function isMemberTargetDisplayName(text) {
|
|
1821
|
+
return parseMemberTargetDisplayName(text) !== null;
|
|
1822
|
+
}
|
|
1823
|
+
function parseMemberTargetDisplayName(text) {
|
|
1824
|
+
const match = /^:([^\s]+)\s+([\s\S]+)$/.exec(text);
|
|
1825
|
+
if (!match?.[1] || !match[2]) return null;
|
|
1826
|
+
return { target: match[1], label: match[2].trim() };
|
|
1827
|
+
}
|
|
1475
1828
|
function provenanceForComment(range, sourceFile, file, tagName) {
|
|
1476
1829
|
const { line, character } = sourceFile.getLineAndCharacterOfPosition(range.pos);
|
|
1477
1830
|
return {
|
|
@@ -1502,7 +1855,7 @@ function getTagCommentText(tag) {
|
|
|
1502
1855
|
}
|
|
1503
1856
|
return ts2.getTextOfJSDocComment(tag.comment);
|
|
1504
1857
|
}
|
|
1505
|
-
var NUMERIC_CONSTRAINT_MAP, LENGTH_CONSTRAINT_MAP, TAGS_REQUIRING_RAW_TEXT,
|
|
1858
|
+
var NUMERIC_CONSTRAINT_MAP, LENGTH_CONSTRAINT_MAP, TAGS_REQUIRING_RAW_TEXT, parserCache;
|
|
1506
1859
|
var init_tsdoc_parser = __esm({
|
|
1507
1860
|
"src/analyzer/tsdoc-parser.ts"() {
|
|
1508
1861
|
"use strict";
|
|
@@ -1520,18 +1873,19 @@ var init_tsdoc_parser = __esm({
|
|
|
1520
1873
|
minItems: "minItems",
|
|
1521
1874
|
maxItems: "maxItems"
|
|
1522
1875
|
};
|
|
1523
|
-
TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions"]);
|
|
1876
|
+
TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions", "defaultValue"]);
|
|
1877
|
+
parserCache = /* @__PURE__ */ new Map();
|
|
1524
1878
|
}
|
|
1525
1879
|
});
|
|
1526
1880
|
|
|
1527
1881
|
// src/analyzer/jsdoc-constraints.ts
|
|
1528
1882
|
import * as ts3 from "typescript";
|
|
1529
|
-
function extractJSDocConstraintNodes(node, file = "") {
|
|
1530
|
-
const result = parseTSDocTags(node, file);
|
|
1883
|
+
function extractJSDocConstraintNodes(node, file = "", options) {
|
|
1884
|
+
const result = parseTSDocTags(node, file, options);
|
|
1531
1885
|
return [...result.constraints];
|
|
1532
1886
|
}
|
|
1533
|
-
function extractJSDocAnnotationNodes(node, file = "") {
|
|
1534
|
-
const result = parseTSDocTags(node, file);
|
|
1887
|
+
function extractJSDocAnnotationNodes(node, file = "", options) {
|
|
1888
|
+
const result = parseTSDocTags(node, file, options);
|
|
1535
1889
|
return [...result.annotations];
|
|
1536
1890
|
}
|
|
1537
1891
|
function extractDefaultValueAnnotation(initializer, file = "") {
|
|
@@ -1582,17 +1936,38 @@ function isObjectType(type) {
|
|
|
1582
1936
|
function isTypeReference(type) {
|
|
1583
1937
|
return !!(type.flags & ts4.TypeFlags.Object) && !!(type.objectFlags & ts4.ObjectFlags.Reference);
|
|
1584
1938
|
}
|
|
1585
|
-
function
|
|
1939
|
+
function makeParseOptions(extensionRegistry, fieldType) {
|
|
1940
|
+
if (extensionRegistry === void 0 && fieldType === void 0) {
|
|
1941
|
+
return void 0;
|
|
1942
|
+
}
|
|
1943
|
+
return {
|
|
1944
|
+
...extensionRegistry !== void 0 && { extensionRegistry },
|
|
1945
|
+
...fieldType !== void 0 && { fieldType }
|
|
1946
|
+
};
|
|
1947
|
+
}
|
|
1948
|
+
function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
|
|
1586
1949
|
const name = classDecl.name?.text ?? "AnonymousClass";
|
|
1587
1950
|
const fields = [];
|
|
1588
1951
|
const fieldLayouts = [];
|
|
1589
1952
|
const typeRegistry = {};
|
|
1953
|
+
const annotations = extractJSDocAnnotationNodes(
|
|
1954
|
+
classDecl,
|
|
1955
|
+
file,
|
|
1956
|
+
makeParseOptions(extensionRegistry)
|
|
1957
|
+
);
|
|
1590
1958
|
const visiting = /* @__PURE__ */ new Set();
|
|
1591
1959
|
const instanceMethods = [];
|
|
1592
1960
|
const staticMethods = [];
|
|
1593
1961
|
for (const member of classDecl.members) {
|
|
1594
1962
|
if (ts4.isPropertyDeclaration(member)) {
|
|
1595
|
-
const fieldNode = analyzeFieldToIR(
|
|
1963
|
+
const fieldNode = analyzeFieldToIR(
|
|
1964
|
+
member,
|
|
1965
|
+
checker,
|
|
1966
|
+
file,
|
|
1967
|
+
typeRegistry,
|
|
1968
|
+
visiting,
|
|
1969
|
+
extensionRegistry
|
|
1970
|
+
);
|
|
1596
1971
|
if (fieldNode) {
|
|
1597
1972
|
fields.push(fieldNode);
|
|
1598
1973
|
fieldLayouts.push({});
|
|
@@ -1609,25 +1984,53 @@ function analyzeClassToIR(classDecl, checker, file = "") {
|
|
|
1609
1984
|
}
|
|
1610
1985
|
}
|
|
1611
1986
|
}
|
|
1612
|
-
return {
|
|
1987
|
+
return {
|
|
1988
|
+
name,
|
|
1989
|
+
fields,
|
|
1990
|
+
fieldLayouts,
|
|
1991
|
+
typeRegistry,
|
|
1992
|
+
...annotations.length > 0 && { annotations },
|
|
1993
|
+
instanceMethods,
|
|
1994
|
+
staticMethods
|
|
1995
|
+
};
|
|
1613
1996
|
}
|
|
1614
|
-
function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
|
|
1997
|
+
function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegistry) {
|
|
1615
1998
|
const name = interfaceDecl.name.text;
|
|
1616
1999
|
const fields = [];
|
|
1617
2000
|
const typeRegistry = {};
|
|
2001
|
+
const annotations = extractJSDocAnnotationNodes(
|
|
2002
|
+
interfaceDecl,
|
|
2003
|
+
file,
|
|
2004
|
+
makeParseOptions(extensionRegistry)
|
|
2005
|
+
);
|
|
1618
2006
|
const visiting = /* @__PURE__ */ new Set();
|
|
1619
2007
|
for (const member of interfaceDecl.members) {
|
|
1620
2008
|
if (ts4.isPropertySignature(member)) {
|
|
1621
|
-
const fieldNode = analyzeInterfacePropertyToIR(
|
|
2009
|
+
const fieldNode = analyzeInterfacePropertyToIR(
|
|
2010
|
+
member,
|
|
2011
|
+
checker,
|
|
2012
|
+
file,
|
|
2013
|
+
typeRegistry,
|
|
2014
|
+
visiting,
|
|
2015
|
+
extensionRegistry
|
|
2016
|
+
);
|
|
1622
2017
|
if (fieldNode) {
|
|
1623
2018
|
fields.push(fieldNode);
|
|
1624
2019
|
}
|
|
1625
2020
|
}
|
|
1626
2021
|
}
|
|
1627
2022
|
const fieldLayouts = fields.map(() => ({}));
|
|
1628
|
-
return {
|
|
2023
|
+
return {
|
|
2024
|
+
name,
|
|
2025
|
+
fields,
|
|
2026
|
+
fieldLayouts,
|
|
2027
|
+
typeRegistry,
|
|
2028
|
+
...annotations.length > 0 && { annotations },
|
|
2029
|
+
instanceMethods: [],
|
|
2030
|
+
staticMethods: []
|
|
2031
|
+
};
|
|
1629
2032
|
}
|
|
1630
|
-
function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
2033
|
+
function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry) {
|
|
1631
2034
|
if (!ts4.isTypeLiteralNode(typeAlias.type)) {
|
|
1632
2035
|
const sourceFile = typeAlias.getSourceFile();
|
|
1633
2036
|
const { line } = sourceFile.getLineAndCharacterOfPosition(typeAlias.getStart());
|
|
@@ -1640,10 +2043,22 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
|
1640
2043
|
const name = typeAlias.name.text;
|
|
1641
2044
|
const fields = [];
|
|
1642
2045
|
const typeRegistry = {};
|
|
2046
|
+
const annotations = extractJSDocAnnotationNodes(
|
|
2047
|
+
typeAlias,
|
|
2048
|
+
file,
|
|
2049
|
+
makeParseOptions(extensionRegistry)
|
|
2050
|
+
);
|
|
1643
2051
|
const visiting = /* @__PURE__ */ new Set();
|
|
1644
2052
|
for (const member of typeAlias.type.members) {
|
|
1645
2053
|
if (ts4.isPropertySignature(member)) {
|
|
1646
|
-
const fieldNode = analyzeInterfacePropertyToIR(
|
|
2054
|
+
const fieldNode = analyzeInterfacePropertyToIR(
|
|
2055
|
+
member,
|
|
2056
|
+
checker,
|
|
2057
|
+
file,
|
|
2058
|
+
typeRegistry,
|
|
2059
|
+
visiting,
|
|
2060
|
+
extensionRegistry
|
|
2061
|
+
);
|
|
1647
2062
|
if (fieldNode) {
|
|
1648
2063
|
fields.push(fieldNode);
|
|
1649
2064
|
}
|
|
@@ -1656,12 +2071,13 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
|
1656
2071
|
fields,
|
|
1657
2072
|
fieldLayouts: fields.map(() => ({})),
|
|
1658
2073
|
typeRegistry,
|
|
2074
|
+
...annotations.length > 0 && { annotations },
|
|
1659
2075
|
instanceMethods: [],
|
|
1660
2076
|
staticMethods: []
|
|
1661
2077
|
}
|
|
1662
2078
|
};
|
|
1663
2079
|
}
|
|
1664
|
-
function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
|
|
2080
|
+
function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1665
2081
|
if (!ts4.isIdentifier(prop.name)) {
|
|
1666
2082
|
return null;
|
|
1667
2083
|
}
|
|
@@ -1669,16 +2085,28 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
|
|
|
1669
2085
|
const tsType = checker.getTypeAtLocation(prop);
|
|
1670
2086
|
const optional = prop.questionToken !== void 0;
|
|
1671
2087
|
const provenance = provenanceForNode(prop, file);
|
|
1672
|
-
let type = resolveTypeNode(
|
|
2088
|
+
let type = resolveTypeNode(
|
|
2089
|
+
tsType,
|
|
2090
|
+
checker,
|
|
2091
|
+
file,
|
|
2092
|
+
typeRegistry,
|
|
2093
|
+
visiting,
|
|
2094
|
+
prop,
|
|
2095
|
+
extensionRegistry
|
|
2096
|
+
);
|
|
1673
2097
|
const constraints = [];
|
|
1674
2098
|
if (prop.type) {
|
|
1675
|
-
constraints.push(
|
|
2099
|
+
constraints.push(
|
|
2100
|
+
...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
|
|
2101
|
+
);
|
|
1676
2102
|
}
|
|
1677
|
-
constraints.push(...extractJSDocConstraintNodes(prop, file));
|
|
2103
|
+
constraints.push(...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type)));
|
|
1678
2104
|
let annotations = [];
|
|
1679
|
-
annotations.push(
|
|
2105
|
+
annotations.push(
|
|
2106
|
+
...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
2107
|
+
);
|
|
1680
2108
|
const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
|
|
1681
|
-
if (defaultAnnotation) {
|
|
2109
|
+
if (defaultAnnotation && !annotations.some((a) => a.annotationKind === "defaultValue")) {
|
|
1682
2110
|
annotations.push(defaultAnnotation);
|
|
1683
2111
|
}
|
|
1684
2112
|
({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
|
|
@@ -1692,7 +2120,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
|
|
|
1692
2120
|
provenance
|
|
1693
2121
|
};
|
|
1694
2122
|
}
|
|
1695
|
-
function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting) {
|
|
2123
|
+
function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1696
2124
|
if (!ts4.isIdentifier(prop.name)) {
|
|
1697
2125
|
return null;
|
|
1698
2126
|
}
|
|
@@ -1700,14 +2128,26 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
|
|
|
1700
2128
|
const tsType = checker.getTypeAtLocation(prop);
|
|
1701
2129
|
const optional = prop.questionToken !== void 0;
|
|
1702
2130
|
const provenance = provenanceForNode(prop, file);
|
|
1703
|
-
let type = resolveTypeNode(
|
|
2131
|
+
let type = resolveTypeNode(
|
|
2132
|
+
tsType,
|
|
2133
|
+
checker,
|
|
2134
|
+
file,
|
|
2135
|
+
typeRegistry,
|
|
2136
|
+
visiting,
|
|
2137
|
+
prop,
|
|
2138
|
+
extensionRegistry
|
|
2139
|
+
);
|
|
1704
2140
|
const constraints = [];
|
|
1705
2141
|
if (prop.type) {
|
|
1706
|
-
constraints.push(
|
|
2142
|
+
constraints.push(
|
|
2143
|
+
...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
|
|
2144
|
+
);
|
|
1707
2145
|
}
|
|
1708
|
-
constraints.push(...extractJSDocConstraintNodes(prop, file));
|
|
2146
|
+
constraints.push(...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type)));
|
|
1709
2147
|
let annotations = [];
|
|
1710
|
-
annotations.push(
|
|
2148
|
+
annotations.push(
|
|
2149
|
+
...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
2150
|
+
);
|
|
1711
2151
|
({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
|
|
1712
2152
|
return {
|
|
1713
2153
|
kind: "field",
|
|
@@ -1781,7 +2221,66 @@ function parseEnumMemberDisplayName(value) {
|
|
|
1781
2221
|
if (label === "") return null;
|
|
1782
2222
|
return { value: match[1], label };
|
|
1783
2223
|
}
|
|
1784
|
-
function
|
|
2224
|
+
function resolveRegisteredCustomType(sourceNode, extensionRegistry, checker) {
|
|
2225
|
+
if (sourceNode === void 0 || extensionRegistry === void 0) {
|
|
2226
|
+
return null;
|
|
2227
|
+
}
|
|
2228
|
+
const typeNode = extractTypeNodeFromSource(sourceNode);
|
|
2229
|
+
if (typeNode === void 0) {
|
|
2230
|
+
return null;
|
|
2231
|
+
}
|
|
2232
|
+
return resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker);
|
|
2233
|
+
}
|
|
2234
|
+
function resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker) {
|
|
2235
|
+
if (ts4.isParenthesizedTypeNode(typeNode)) {
|
|
2236
|
+
return resolveRegisteredCustomTypeFromTypeNode(typeNode.type, extensionRegistry, checker);
|
|
2237
|
+
}
|
|
2238
|
+
const typeName = getTypeNodeRegistrationName(typeNode);
|
|
2239
|
+
if (typeName === null) {
|
|
2240
|
+
return null;
|
|
2241
|
+
}
|
|
2242
|
+
const registration = extensionRegistry.findTypeByName(typeName);
|
|
2243
|
+
if (registration !== void 0) {
|
|
2244
|
+
return {
|
|
2245
|
+
kind: "custom",
|
|
2246
|
+
typeId: `${registration.extensionId}/${registration.registration.typeName}`,
|
|
2247
|
+
payload: null
|
|
2248
|
+
};
|
|
2249
|
+
}
|
|
2250
|
+
if (ts4.isTypeReferenceNode(typeNode) && ts4.isIdentifier(typeNode.typeName)) {
|
|
2251
|
+
const aliasDecl = checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts4.isTypeAliasDeclaration);
|
|
2252
|
+
if (aliasDecl !== void 0) {
|
|
2253
|
+
return resolveRegisteredCustomTypeFromTypeNode(aliasDecl.type, extensionRegistry, checker);
|
|
2254
|
+
}
|
|
2255
|
+
}
|
|
2256
|
+
return null;
|
|
2257
|
+
}
|
|
2258
|
+
function extractTypeNodeFromSource(sourceNode) {
|
|
2259
|
+
if (ts4.isPropertyDeclaration(sourceNode) || ts4.isPropertySignature(sourceNode) || ts4.isParameter(sourceNode) || ts4.isTypeAliasDeclaration(sourceNode)) {
|
|
2260
|
+
return sourceNode.type;
|
|
2261
|
+
}
|
|
2262
|
+
if (ts4.isTypeNode(sourceNode)) {
|
|
2263
|
+
return sourceNode;
|
|
2264
|
+
}
|
|
2265
|
+
return void 0;
|
|
2266
|
+
}
|
|
2267
|
+
function getTypeNodeRegistrationName(typeNode) {
|
|
2268
|
+
if (ts4.isTypeReferenceNode(typeNode)) {
|
|
2269
|
+
return ts4.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : typeNode.typeName.right.text;
|
|
2270
|
+
}
|
|
2271
|
+
if (ts4.isParenthesizedTypeNode(typeNode)) {
|
|
2272
|
+
return getTypeNodeRegistrationName(typeNode.type);
|
|
2273
|
+
}
|
|
2274
|
+
if (typeNode.kind === ts4.SyntaxKind.BigIntKeyword || typeNode.kind === ts4.SyntaxKind.StringKeyword || typeNode.kind === ts4.SyntaxKind.NumberKeyword || typeNode.kind === ts4.SyntaxKind.BooleanKeyword) {
|
|
2275
|
+
return typeNode.getText();
|
|
2276
|
+
}
|
|
2277
|
+
return null;
|
|
2278
|
+
}
|
|
2279
|
+
function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
2280
|
+
const customType = resolveRegisteredCustomType(sourceNode, extensionRegistry, checker);
|
|
2281
|
+
if (customType) {
|
|
2282
|
+
return customType;
|
|
2283
|
+
}
|
|
1785
2284
|
if (type.flags & ts4.TypeFlags.String) {
|
|
1786
2285
|
return { kind: "primitive", primitiveKind: "string" };
|
|
1787
2286
|
}
|
|
@@ -1810,88 +2309,162 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
|
|
|
1810
2309
|
};
|
|
1811
2310
|
}
|
|
1812
2311
|
if (type.isUnion()) {
|
|
1813
|
-
return resolveUnionType(
|
|
2312
|
+
return resolveUnionType(
|
|
2313
|
+
type,
|
|
2314
|
+
checker,
|
|
2315
|
+
file,
|
|
2316
|
+
typeRegistry,
|
|
2317
|
+
visiting,
|
|
2318
|
+
sourceNode,
|
|
2319
|
+
extensionRegistry
|
|
2320
|
+
);
|
|
1814
2321
|
}
|
|
1815
2322
|
if (checker.isArrayType(type)) {
|
|
1816
|
-
return resolveArrayType(
|
|
2323
|
+
return resolveArrayType(
|
|
2324
|
+
type,
|
|
2325
|
+
checker,
|
|
2326
|
+
file,
|
|
2327
|
+
typeRegistry,
|
|
2328
|
+
visiting,
|
|
2329
|
+
sourceNode,
|
|
2330
|
+
extensionRegistry
|
|
2331
|
+
);
|
|
1817
2332
|
}
|
|
1818
2333
|
if (isObjectType(type)) {
|
|
1819
|
-
return resolveObjectType(type, checker, file, typeRegistry, visiting);
|
|
2334
|
+
return resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry);
|
|
1820
2335
|
}
|
|
1821
2336
|
return { kind: "primitive", primitiveKind: "string" };
|
|
1822
2337
|
}
|
|
1823
|
-
function resolveUnionType(type, checker, file, typeRegistry, visiting) {
|
|
2338
|
+
function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
2339
|
+
const typeName = getNamedTypeName(type);
|
|
2340
|
+
const namedDecl = getNamedTypeDeclaration(type);
|
|
2341
|
+
if (typeName && typeName in typeRegistry) {
|
|
2342
|
+
return { kind: "reference", name: typeName, typeArguments: [] };
|
|
2343
|
+
}
|
|
1824
2344
|
const allTypes = type.types;
|
|
2345
|
+
const unionMemberTypeNodes = extractUnionMemberTypeNodes(sourceNode, checker);
|
|
2346
|
+
const nonNullSourceNodes = unionMemberTypeNodes.filter(
|
|
2347
|
+
(memberTypeNode) => !isNullishTypeNode(resolveAliasedTypeNode(memberTypeNode, checker))
|
|
2348
|
+
);
|
|
1825
2349
|
const nonNullTypes = allTypes.filter(
|
|
1826
|
-
(
|
|
2350
|
+
(memberType) => !(memberType.flags & (ts4.TypeFlags.Null | ts4.TypeFlags.Undefined))
|
|
1827
2351
|
);
|
|
2352
|
+
const nonNullMembers = nonNullTypes.map((memberType, index) => ({
|
|
2353
|
+
memberType,
|
|
2354
|
+
sourceNode: nonNullSourceNodes.length === nonNullTypes.length ? nonNullSourceNodes[index] : void 0
|
|
2355
|
+
}));
|
|
1828
2356
|
const hasNull = allTypes.some((t) => t.flags & ts4.TypeFlags.Null);
|
|
2357
|
+
const memberDisplayNames = /* @__PURE__ */ new Map();
|
|
2358
|
+
if (namedDecl) {
|
|
2359
|
+
for (const [value, label] of extractDisplayNameMetadata(namedDecl).memberDisplayNames) {
|
|
2360
|
+
memberDisplayNames.set(value, label);
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
if (sourceNode) {
|
|
2364
|
+
for (const [value, label] of extractDisplayNameMetadata(sourceNode).memberDisplayNames) {
|
|
2365
|
+
memberDisplayNames.set(value, label);
|
|
2366
|
+
}
|
|
2367
|
+
}
|
|
2368
|
+
const registerNamed = (result) => {
|
|
2369
|
+
if (!typeName) {
|
|
2370
|
+
return result;
|
|
2371
|
+
}
|
|
2372
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
2373
|
+
typeRegistry[typeName] = {
|
|
2374
|
+
name: typeName,
|
|
2375
|
+
type: result,
|
|
2376
|
+
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
2377
|
+
provenance: provenanceForDeclaration(namedDecl ?? sourceNode, file)
|
|
2378
|
+
};
|
|
2379
|
+
return { kind: "reference", name: typeName, typeArguments: [] };
|
|
2380
|
+
};
|
|
2381
|
+
const applyMemberLabels = (members2) => members2.map((value) => {
|
|
2382
|
+
const displayName = memberDisplayNames.get(String(value));
|
|
2383
|
+
return displayName !== void 0 ? { value, displayName } : { value };
|
|
2384
|
+
});
|
|
1829
2385
|
const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts4.TypeFlags.BooleanLiteral);
|
|
1830
2386
|
if (isBooleanUnion2) {
|
|
1831
2387
|
const boolNode = { kind: "primitive", primitiveKind: "boolean" };
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
}
|
|
1838
|
-
return boolNode;
|
|
2388
|
+
const result = hasNull ? {
|
|
2389
|
+
kind: "union",
|
|
2390
|
+
members: [boolNode, { kind: "primitive", primitiveKind: "null" }]
|
|
2391
|
+
} : boolNode;
|
|
2392
|
+
return registerNamed(result);
|
|
1839
2393
|
}
|
|
1840
2394
|
const allStringLiterals = nonNullTypes.every((t) => t.isStringLiteral());
|
|
1841
2395
|
if (allStringLiterals && nonNullTypes.length > 0) {
|
|
1842
2396
|
const stringTypes = nonNullTypes.filter((t) => t.isStringLiteral());
|
|
1843
2397
|
const enumNode = {
|
|
1844
2398
|
kind: "enum",
|
|
1845
|
-
members: stringTypes.map((t) =>
|
|
2399
|
+
members: applyMemberLabels(stringTypes.map((t) => t.value))
|
|
1846
2400
|
};
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
}
|
|
1853
|
-
return enumNode;
|
|
2401
|
+
const result = hasNull ? {
|
|
2402
|
+
kind: "union",
|
|
2403
|
+
members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
|
|
2404
|
+
} : enumNode;
|
|
2405
|
+
return registerNamed(result);
|
|
1854
2406
|
}
|
|
1855
2407
|
const allNumberLiterals = nonNullTypes.every((t) => t.isNumberLiteral());
|
|
1856
2408
|
if (allNumberLiterals && nonNullTypes.length > 0) {
|
|
1857
2409
|
const numberTypes = nonNullTypes.filter((t) => t.isNumberLiteral());
|
|
1858
2410
|
const enumNode = {
|
|
1859
2411
|
kind: "enum",
|
|
1860
|
-
members: numberTypes.map((t) =>
|
|
2412
|
+
members: applyMemberLabels(numberTypes.map((t) => t.value))
|
|
1861
2413
|
};
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
2414
|
+
const result = hasNull ? {
|
|
2415
|
+
kind: "union",
|
|
2416
|
+
members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
|
|
2417
|
+
} : enumNode;
|
|
2418
|
+
return registerNamed(result);
|
|
2419
|
+
}
|
|
2420
|
+
if (nonNullMembers.length === 1 && nonNullMembers[0]) {
|
|
2421
|
+
const inner = resolveTypeNode(
|
|
2422
|
+
nonNullMembers[0].memberType,
|
|
2423
|
+
checker,
|
|
2424
|
+
file,
|
|
2425
|
+
typeRegistry,
|
|
2426
|
+
visiting,
|
|
2427
|
+
nonNullMembers[0].sourceNode ?? sourceNode,
|
|
2428
|
+
extensionRegistry
|
|
2429
|
+
);
|
|
2430
|
+
const result = hasNull ? {
|
|
2431
|
+
kind: "union",
|
|
2432
|
+
members: [inner, { kind: "primitive", primitiveKind: "null" }]
|
|
2433
|
+
} : inner;
|
|
2434
|
+
return registerNamed(result);
|
|
2435
|
+
}
|
|
2436
|
+
const members = nonNullMembers.map(
|
|
2437
|
+
({ memberType, sourceNode: memberSourceNode }) => resolveTypeNode(
|
|
2438
|
+
memberType,
|
|
2439
|
+
checker,
|
|
2440
|
+
file,
|
|
2441
|
+
typeRegistry,
|
|
2442
|
+
visiting,
|
|
2443
|
+
memberSourceNode ?? sourceNode,
|
|
2444
|
+
extensionRegistry
|
|
2445
|
+
)
|
|
1882
2446
|
);
|
|
1883
2447
|
if (hasNull) {
|
|
1884
2448
|
members.push({ kind: "primitive", primitiveKind: "null" });
|
|
1885
2449
|
}
|
|
1886
|
-
return { kind: "union", members };
|
|
2450
|
+
return registerNamed({ kind: "union", members });
|
|
1887
2451
|
}
|
|
1888
|
-
function resolveArrayType(type, checker, file, typeRegistry, visiting) {
|
|
2452
|
+
function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
1889
2453
|
const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
|
|
1890
2454
|
const elementType = typeArgs?.[0];
|
|
1891
|
-
const
|
|
2455
|
+
const elementSourceNode = extractArrayElementTypeNode(sourceNode, checker);
|
|
2456
|
+
const items = elementType ? resolveTypeNode(
|
|
2457
|
+
elementType,
|
|
2458
|
+
checker,
|
|
2459
|
+
file,
|
|
2460
|
+
typeRegistry,
|
|
2461
|
+
visiting,
|
|
2462
|
+
elementSourceNode,
|
|
2463
|
+
extensionRegistry
|
|
2464
|
+
) : { kind: "primitive", primitiveKind: "string" };
|
|
1892
2465
|
return { kind: "array", items };
|
|
1893
2466
|
}
|
|
1894
|
-
function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
|
|
2467
|
+
function tryResolveRecordType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1895
2468
|
if (type.getProperties().length > 0) {
|
|
1896
2469
|
return null;
|
|
1897
2470
|
}
|
|
@@ -1899,39 +2472,123 @@ function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
|
|
|
1899
2472
|
if (!indexInfo) {
|
|
1900
2473
|
return null;
|
|
1901
2474
|
}
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
}
|
|
2475
|
+
const valueType = resolveTypeNode(
|
|
2476
|
+
indexInfo.type,
|
|
2477
|
+
checker,
|
|
2478
|
+
file,
|
|
2479
|
+
typeRegistry,
|
|
2480
|
+
visiting,
|
|
2481
|
+
void 0,
|
|
2482
|
+
extensionRegistry
|
|
2483
|
+
);
|
|
2484
|
+
return { kind: "record", valueType };
|
|
1912
2485
|
}
|
|
1913
|
-
function
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
2486
|
+
function typeNodeContainsReference(type, targetName) {
|
|
2487
|
+
switch (type.kind) {
|
|
2488
|
+
case "reference":
|
|
2489
|
+
return type.name === targetName;
|
|
2490
|
+
case "array":
|
|
2491
|
+
return typeNodeContainsReference(type.items, targetName);
|
|
2492
|
+
case "record":
|
|
2493
|
+
return typeNodeContainsReference(type.valueType, targetName);
|
|
2494
|
+
case "union":
|
|
2495
|
+
return type.members.some((member) => typeNodeContainsReference(member, targetName));
|
|
2496
|
+
case "object":
|
|
2497
|
+
return type.properties.some(
|
|
2498
|
+
(property) => typeNodeContainsReference(property.type, targetName)
|
|
2499
|
+
);
|
|
2500
|
+
case "primitive":
|
|
2501
|
+
case "enum":
|
|
2502
|
+
case "dynamic":
|
|
2503
|
+
case "custom":
|
|
2504
|
+
return false;
|
|
2505
|
+
default: {
|
|
2506
|
+
const _exhaustive = type;
|
|
2507
|
+
return _exhaustive;
|
|
2508
|
+
}
|
|
1917
2509
|
}
|
|
2510
|
+
}
|
|
2511
|
+
function resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2512
|
+
const typeName = getNamedTypeName(type);
|
|
2513
|
+
const namedTypeName = typeName ?? void 0;
|
|
2514
|
+
const namedDecl = getNamedTypeDeclaration(type);
|
|
2515
|
+
const shouldRegisterNamedType = namedTypeName !== void 0 && !(namedTypeName === "Record" && namedDecl?.getSourceFile().fileName !== file);
|
|
2516
|
+
const clearNamedTypeRegistration = () => {
|
|
2517
|
+
if (namedTypeName === void 0 || !shouldRegisterNamedType) {
|
|
2518
|
+
return;
|
|
2519
|
+
}
|
|
2520
|
+
Reflect.deleteProperty(typeRegistry, namedTypeName);
|
|
2521
|
+
};
|
|
1918
2522
|
if (visiting.has(type)) {
|
|
2523
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
2524
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
2525
|
+
}
|
|
1919
2526
|
return { kind: "object", properties: [], additionalProperties: false };
|
|
1920
2527
|
}
|
|
2528
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType && !typeRegistry[namedTypeName]) {
|
|
2529
|
+
typeRegistry[namedTypeName] = {
|
|
2530
|
+
name: namedTypeName,
|
|
2531
|
+
type: RESOLVING_TYPE_PLACEHOLDER,
|
|
2532
|
+
provenance: provenanceForDeclaration(namedDecl, file)
|
|
2533
|
+
};
|
|
2534
|
+
}
|
|
1921
2535
|
visiting.add(type);
|
|
1922
|
-
|
|
1923
|
-
|
|
2536
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType && typeRegistry[namedTypeName]?.type !== void 0) {
|
|
2537
|
+
if (typeRegistry[namedTypeName].type !== RESOLVING_TYPE_PLACEHOLDER) {
|
|
2538
|
+
visiting.delete(type);
|
|
2539
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2542
|
+
const recordNode = tryResolveRecordType(
|
|
2543
|
+
type,
|
|
2544
|
+
checker,
|
|
2545
|
+
file,
|
|
2546
|
+
typeRegistry,
|
|
2547
|
+
visiting,
|
|
2548
|
+
extensionRegistry
|
|
2549
|
+
);
|
|
2550
|
+
if (recordNode) {
|
|
1924
2551
|
visiting.delete(type);
|
|
1925
|
-
|
|
2552
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
2553
|
+
const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType, namedTypeName);
|
|
2554
|
+
if (!isRecursiveRecord) {
|
|
2555
|
+
clearNamedTypeRegistration();
|
|
2556
|
+
return recordNode;
|
|
2557
|
+
}
|
|
2558
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
2559
|
+
typeRegistry[namedTypeName] = {
|
|
2560
|
+
name: namedTypeName,
|
|
2561
|
+
type: recordNode,
|
|
2562
|
+
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
2563
|
+
provenance: provenanceForDeclaration(namedDecl, file)
|
|
2564
|
+
};
|
|
2565
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
2566
|
+
}
|
|
2567
|
+
return recordNode;
|
|
1926
2568
|
}
|
|
1927
2569
|
const properties = [];
|
|
1928
|
-
const fieldInfoMap = getNamedTypeFieldNodeInfoMap(
|
|
2570
|
+
const fieldInfoMap = getNamedTypeFieldNodeInfoMap(
|
|
2571
|
+
type,
|
|
2572
|
+
checker,
|
|
2573
|
+
file,
|
|
2574
|
+
typeRegistry,
|
|
2575
|
+
visiting,
|
|
2576
|
+
extensionRegistry
|
|
2577
|
+
);
|
|
1929
2578
|
for (const prop of type.getProperties()) {
|
|
1930
2579
|
const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
|
|
1931
2580
|
if (!declaration) continue;
|
|
1932
2581
|
const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
|
|
1933
2582
|
const optional = !!(prop.flags & ts4.SymbolFlags.Optional);
|
|
1934
|
-
const propTypeNode = resolveTypeNode(
|
|
2583
|
+
const propTypeNode = resolveTypeNode(
|
|
2584
|
+
propType,
|
|
2585
|
+
checker,
|
|
2586
|
+
file,
|
|
2587
|
+
typeRegistry,
|
|
2588
|
+
visiting,
|
|
2589
|
+
declaration,
|
|
2590
|
+
extensionRegistry
|
|
2591
|
+
);
|
|
1935
2592
|
const fieldNodeInfo = fieldInfoMap?.get(prop.name);
|
|
1936
2593
|
properties.push({
|
|
1937
2594
|
name: prop.name,
|
|
@@ -1948,17 +2605,19 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
|
1948
2605
|
properties,
|
|
1949
2606
|
additionalProperties: true
|
|
1950
2607
|
};
|
|
1951
|
-
if (
|
|
1952
|
-
|
|
1953
|
-
|
|
2608
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
2609
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
2610
|
+
typeRegistry[namedTypeName] = {
|
|
2611
|
+
name: namedTypeName,
|
|
1954
2612
|
type: objectNode,
|
|
1955
|
-
|
|
2613
|
+
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
2614
|
+
provenance: provenanceForDeclaration(namedDecl, file)
|
|
1956
2615
|
};
|
|
1957
|
-
return { kind: "reference", name:
|
|
2616
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
1958
2617
|
}
|
|
1959
2618
|
return objectNode;
|
|
1960
2619
|
}
|
|
1961
|
-
function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting) {
|
|
2620
|
+
function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1962
2621
|
const symbols = [type.getSymbol(), type.aliasSymbol].filter(
|
|
1963
2622
|
(s) => s?.declarations != null && s.declarations.length > 0
|
|
1964
2623
|
);
|
|
@@ -1970,7 +2629,14 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
1970
2629
|
const map = /* @__PURE__ */ new Map();
|
|
1971
2630
|
for (const member of classDecl.members) {
|
|
1972
2631
|
if (ts4.isPropertyDeclaration(member) && ts4.isIdentifier(member.name)) {
|
|
1973
|
-
const fieldNode = analyzeFieldToIR(
|
|
2632
|
+
const fieldNode = analyzeFieldToIR(
|
|
2633
|
+
member,
|
|
2634
|
+
checker,
|
|
2635
|
+
file,
|
|
2636
|
+
typeRegistry,
|
|
2637
|
+
visiting,
|
|
2638
|
+
extensionRegistry
|
|
2639
|
+
);
|
|
1974
2640
|
if (fieldNode) {
|
|
1975
2641
|
map.set(fieldNode.name, {
|
|
1976
2642
|
constraints: [...fieldNode.constraints],
|
|
@@ -1984,7 +2650,14 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
1984
2650
|
}
|
|
1985
2651
|
const interfaceDecl = declarations.find(ts4.isInterfaceDeclaration);
|
|
1986
2652
|
if (interfaceDecl) {
|
|
1987
|
-
return buildFieldNodeInfoMap(
|
|
2653
|
+
return buildFieldNodeInfoMap(
|
|
2654
|
+
interfaceDecl.members,
|
|
2655
|
+
checker,
|
|
2656
|
+
file,
|
|
2657
|
+
typeRegistry,
|
|
2658
|
+
visiting,
|
|
2659
|
+
extensionRegistry
|
|
2660
|
+
);
|
|
1988
2661
|
}
|
|
1989
2662
|
const typeAliasDecl = declarations.find(ts4.isTypeAliasDeclaration);
|
|
1990
2663
|
if (typeAliasDecl && ts4.isTypeLiteralNode(typeAliasDecl.type)) {
|
|
@@ -1993,17 +2666,68 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
1993
2666
|
checker,
|
|
1994
2667
|
file,
|
|
1995
2668
|
typeRegistry,
|
|
1996
|
-
visiting
|
|
2669
|
+
visiting,
|
|
2670
|
+
extensionRegistry
|
|
1997
2671
|
);
|
|
1998
2672
|
}
|
|
1999
2673
|
}
|
|
2000
2674
|
return null;
|
|
2001
2675
|
}
|
|
2002
|
-
function
|
|
2676
|
+
function extractArrayElementTypeNode(sourceNode, checker) {
|
|
2677
|
+
const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
|
|
2678
|
+
if (typeNode === void 0) {
|
|
2679
|
+
return void 0;
|
|
2680
|
+
}
|
|
2681
|
+
const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
|
|
2682
|
+
if (ts4.isArrayTypeNode(resolvedTypeNode)) {
|
|
2683
|
+
return resolvedTypeNode.elementType;
|
|
2684
|
+
}
|
|
2685
|
+
if (ts4.isTypeReferenceNode(resolvedTypeNode) && ts4.isIdentifier(resolvedTypeNode.typeName) && resolvedTypeNode.typeName.text === "Array" && resolvedTypeNode.typeArguments?.[0]) {
|
|
2686
|
+
return resolvedTypeNode.typeArguments[0];
|
|
2687
|
+
}
|
|
2688
|
+
return void 0;
|
|
2689
|
+
}
|
|
2690
|
+
function extractUnionMemberTypeNodes(sourceNode, checker) {
|
|
2691
|
+
const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
|
|
2692
|
+
if (!typeNode) {
|
|
2693
|
+
return [];
|
|
2694
|
+
}
|
|
2695
|
+
const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
|
|
2696
|
+
return ts4.isUnionTypeNode(resolvedTypeNode) ? [...resolvedTypeNode.types] : [];
|
|
2697
|
+
}
|
|
2698
|
+
function resolveAliasedTypeNode(typeNode, checker, visited = /* @__PURE__ */ new Set()) {
|
|
2699
|
+
if (ts4.isParenthesizedTypeNode(typeNode)) {
|
|
2700
|
+
return resolveAliasedTypeNode(typeNode.type, checker, visited);
|
|
2701
|
+
}
|
|
2702
|
+
if (!ts4.isTypeReferenceNode(typeNode) || !ts4.isIdentifier(typeNode.typeName)) {
|
|
2703
|
+
return typeNode;
|
|
2704
|
+
}
|
|
2705
|
+
const symbol = checker.getSymbolAtLocation(typeNode.typeName);
|
|
2706
|
+
const aliasDecl = symbol?.declarations?.find(ts4.isTypeAliasDeclaration);
|
|
2707
|
+
if (aliasDecl === void 0 || visited.has(aliasDecl)) {
|
|
2708
|
+
return typeNode;
|
|
2709
|
+
}
|
|
2710
|
+
visited.add(aliasDecl);
|
|
2711
|
+
return resolveAliasedTypeNode(aliasDecl.type, checker, visited);
|
|
2712
|
+
}
|
|
2713
|
+
function isNullishTypeNode(typeNode) {
|
|
2714
|
+
if (typeNode.kind === ts4.SyntaxKind.NullKeyword || typeNode.kind === ts4.SyntaxKind.UndefinedKeyword) {
|
|
2715
|
+
return true;
|
|
2716
|
+
}
|
|
2717
|
+
return ts4.isLiteralTypeNode(typeNode) && (typeNode.literal.kind === ts4.SyntaxKind.NullKeyword || typeNode.literal.kind === ts4.SyntaxKind.UndefinedKeyword);
|
|
2718
|
+
}
|
|
2719
|
+
function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2003
2720
|
const map = /* @__PURE__ */ new Map();
|
|
2004
2721
|
for (const member of members) {
|
|
2005
2722
|
if (ts4.isPropertySignature(member)) {
|
|
2006
|
-
const fieldNode = analyzeInterfacePropertyToIR(
|
|
2723
|
+
const fieldNode = analyzeInterfacePropertyToIR(
|
|
2724
|
+
member,
|
|
2725
|
+
checker,
|
|
2726
|
+
file,
|
|
2727
|
+
typeRegistry,
|
|
2728
|
+
visiting,
|
|
2729
|
+
extensionRegistry
|
|
2730
|
+
);
|
|
2007
2731
|
if (fieldNode) {
|
|
2008
2732
|
map.set(fieldNode.name, {
|
|
2009
2733
|
constraints: [...fieldNode.constraints],
|
|
@@ -2015,7 +2739,7 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting) {
|
|
|
2015
2739
|
}
|
|
2016
2740
|
return map;
|
|
2017
2741
|
}
|
|
2018
|
-
function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
|
|
2742
|
+
function extractTypeAliasConstraintNodes(typeNode, checker, file, extensionRegistry, depth = 0) {
|
|
2019
2743
|
if (!ts4.isTypeReferenceNode(typeNode)) return [];
|
|
2020
2744
|
if (depth >= MAX_ALIAS_CHAIN_DEPTH) {
|
|
2021
2745
|
const aliasName = typeNode.typeName.getText();
|
|
@@ -2028,8 +2752,29 @@ function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
|
|
|
2028
2752
|
const aliasDecl = symbol.declarations.find(ts4.isTypeAliasDeclaration);
|
|
2029
2753
|
if (!aliasDecl) return [];
|
|
2030
2754
|
if (ts4.isTypeLiteralNode(aliasDecl.type)) return [];
|
|
2031
|
-
const
|
|
2032
|
-
|
|
2755
|
+
const aliasFieldType = resolveTypeNode(
|
|
2756
|
+
checker.getTypeAtLocation(aliasDecl.type),
|
|
2757
|
+
checker,
|
|
2758
|
+
file,
|
|
2759
|
+
{},
|
|
2760
|
+
/* @__PURE__ */ new Set(),
|
|
2761
|
+
aliasDecl.type,
|
|
2762
|
+
extensionRegistry
|
|
2763
|
+
);
|
|
2764
|
+
const constraints = extractJSDocConstraintNodes(
|
|
2765
|
+
aliasDecl,
|
|
2766
|
+
file,
|
|
2767
|
+
makeParseOptions(extensionRegistry, aliasFieldType)
|
|
2768
|
+
);
|
|
2769
|
+
constraints.push(
|
|
2770
|
+
...extractTypeAliasConstraintNodes(
|
|
2771
|
+
aliasDecl.type,
|
|
2772
|
+
checker,
|
|
2773
|
+
file,
|
|
2774
|
+
extensionRegistry,
|
|
2775
|
+
depth + 1
|
|
2776
|
+
)
|
|
2777
|
+
);
|
|
2033
2778
|
return constraints;
|
|
2034
2779
|
}
|
|
2035
2780
|
function provenanceForNode(node, file) {
|
|
@@ -2045,6 +2790,12 @@ function provenanceForNode(node, file) {
|
|
|
2045
2790
|
function provenanceForFile(file) {
|
|
2046
2791
|
return { surface: "tsdoc", file, line: 0, column: 0 };
|
|
2047
2792
|
}
|
|
2793
|
+
function provenanceForDeclaration(node, file) {
|
|
2794
|
+
if (!node) {
|
|
2795
|
+
return provenanceForFile(file);
|
|
2796
|
+
}
|
|
2797
|
+
return provenanceForNode(node, file);
|
|
2798
|
+
}
|
|
2048
2799
|
function getNamedTypeName(type) {
|
|
2049
2800
|
const symbol = type.getSymbol();
|
|
2050
2801
|
if (symbol?.declarations) {
|
|
@@ -2063,6 +2814,20 @@ function getNamedTypeName(type) {
|
|
|
2063
2814
|
}
|
|
2064
2815
|
return null;
|
|
2065
2816
|
}
|
|
2817
|
+
function getNamedTypeDeclaration(type) {
|
|
2818
|
+
const symbol = type.getSymbol();
|
|
2819
|
+
if (symbol?.declarations) {
|
|
2820
|
+
const decl = symbol.declarations[0];
|
|
2821
|
+
if (decl && (ts4.isClassDeclaration(decl) || ts4.isInterfaceDeclaration(decl) || ts4.isTypeAliasDeclaration(decl))) {
|
|
2822
|
+
return decl;
|
|
2823
|
+
}
|
|
2824
|
+
}
|
|
2825
|
+
const aliasSymbol = type.aliasSymbol;
|
|
2826
|
+
if (aliasSymbol?.declarations) {
|
|
2827
|
+
return aliasSymbol.declarations.find(ts4.isTypeAliasDeclaration);
|
|
2828
|
+
}
|
|
2829
|
+
return void 0;
|
|
2830
|
+
}
|
|
2066
2831
|
function analyzeMethod(method, checker) {
|
|
2067
2832
|
if (!ts4.isIdentifier(method.name)) {
|
|
2068
2833
|
return null;
|
|
@@ -2103,20 +2868,26 @@ function detectFormSpecReference(typeNode) {
|
|
|
2103
2868
|
}
|
|
2104
2869
|
return null;
|
|
2105
2870
|
}
|
|
2106
|
-
var MAX_ALIAS_CHAIN_DEPTH;
|
|
2871
|
+
var RESOLVING_TYPE_PLACEHOLDER, MAX_ALIAS_CHAIN_DEPTH;
|
|
2107
2872
|
var init_class_analyzer = __esm({
|
|
2108
2873
|
"src/analyzer/class-analyzer.ts"() {
|
|
2109
2874
|
"use strict";
|
|
2110
2875
|
init_jsdoc_constraints();
|
|
2876
|
+
init_tsdoc_parser();
|
|
2877
|
+
RESOLVING_TYPE_PLACEHOLDER = {
|
|
2878
|
+
kind: "object",
|
|
2879
|
+
properties: [],
|
|
2880
|
+
additionalProperties: true
|
|
2881
|
+
};
|
|
2111
2882
|
MAX_ALIAS_CHAIN_DEPTH = 8;
|
|
2112
2883
|
}
|
|
2113
2884
|
});
|
|
2114
2885
|
|
|
2115
2886
|
// src/generators/class-schema.ts
|
|
2116
|
-
function generateClassSchemas(analysis, source) {
|
|
2887
|
+
function generateClassSchemas(analysis, source, options) {
|
|
2117
2888
|
const ir = canonicalizeTSDoc(analysis, source);
|
|
2118
2889
|
return {
|
|
2119
|
-
jsonSchema: generateJsonSchemaFromIR(ir),
|
|
2890
|
+
jsonSchema: generateJsonSchemaFromIR(ir, options),
|
|
2120
2891
|
uiSchema: generateUiSchemaFromIR(ir)
|
|
2121
2892
|
};
|
|
2122
2893
|
}
|
|
@@ -2126,27 +2897,54 @@ function generateSchemasFromClass(options) {
|
|
|
2126
2897
|
if (!classDecl) {
|
|
2127
2898
|
throw new Error(`Class "${options.className}" not found in ${options.filePath}`);
|
|
2128
2899
|
}
|
|
2129
|
-
const analysis = analyzeClassToIR(
|
|
2130
|
-
|
|
2900
|
+
const analysis = analyzeClassToIR(
|
|
2901
|
+
classDecl,
|
|
2902
|
+
ctx.checker,
|
|
2903
|
+
options.filePath,
|
|
2904
|
+
options.extensionRegistry
|
|
2905
|
+
);
|
|
2906
|
+
return generateClassSchemas(
|
|
2907
|
+
analysis,
|
|
2908
|
+
{ file: options.filePath },
|
|
2909
|
+
{
|
|
2910
|
+
extensionRegistry: options.extensionRegistry,
|
|
2911
|
+
vendorPrefix: options.vendorPrefix
|
|
2912
|
+
}
|
|
2913
|
+
);
|
|
2131
2914
|
}
|
|
2132
2915
|
function generateSchemas(options) {
|
|
2133
2916
|
const ctx = createProgramContext(options.filePath);
|
|
2134
2917
|
const source = { file: options.filePath };
|
|
2135
2918
|
const classDecl = findClassByName(ctx.sourceFile, options.typeName);
|
|
2136
2919
|
if (classDecl) {
|
|
2137
|
-
const analysis = analyzeClassToIR(
|
|
2138
|
-
|
|
2920
|
+
const analysis = analyzeClassToIR(
|
|
2921
|
+
classDecl,
|
|
2922
|
+
ctx.checker,
|
|
2923
|
+
options.filePath,
|
|
2924
|
+
options.extensionRegistry
|
|
2925
|
+
);
|
|
2926
|
+
return generateClassSchemas(analysis, source, options);
|
|
2139
2927
|
}
|
|
2140
2928
|
const interfaceDecl = findInterfaceByName(ctx.sourceFile, options.typeName);
|
|
2141
2929
|
if (interfaceDecl) {
|
|
2142
|
-
const analysis = analyzeInterfaceToIR(
|
|
2143
|
-
|
|
2930
|
+
const analysis = analyzeInterfaceToIR(
|
|
2931
|
+
interfaceDecl,
|
|
2932
|
+
ctx.checker,
|
|
2933
|
+
options.filePath,
|
|
2934
|
+
options.extensionRegistry
|
|
2935
|
+
);
|
|
2936
|
+
return generateClassSchemas(analysis, source, options);
|
|
2144
2937
|
}
|
|
2145
2938
|
const typeAlias = findTypeAliasByName(ctx.sourceFile, options.typeName);
|
|
2146
2939
|
if (typeAlias) {
|
|
2147
|
-
const result = analyzeTypeAliasToIR(
|
|
2940
|
+
const result = analyzeTypeAliasToIR(
|
|
2941
|
+
typeAlias,
|
|
2942
|
+
ctx.checker,
|
|
2943
|
+
options.filePath,
|
|
2944
|
+
options.extensionRegistry
|
|
2945
|
+
);
|
|
2148
2946
|
if (result.ok) {
|
|
2149
|
-
return generateClassSchemas(result.analysis, source);
|
|
2947
|
+
return generateClassSchemas(result.analysis, source, options);
|
|
2150
2948
|
}
|
|
2151
2949
|
throw new Error(result.error);
|
|
2152
2950
|
}
|
|
@@ -2165,10 +2963,220 @@ var init_class_schema = __esm({
|
|
|
2165
2963
|
}
|
|
2166
2964
|
});
|
|
2167
2965
|
|
|
2966
|
+
// src/generators/mixed-authoring.ts
|
|
2967
|
+
function buildMixedAuthoringSchemas(options) {
|
|
2968
|
+
const { filePath, typeName, overlays, ...schemaOptions } = options;
|
|
2969
|
+
const analysis = analyzeNamedType(filePath, typeName, schemaOptions.extensionRegistry);
|
|
2970
|
+
const composedAnalysis = composeAnalysisWithOverlays(analysis, overlays);
|
|
2971
|
+
const ir = canonicalizeTSDoc(composedAnalysis, { file: filePath });
|
|
2972
|
+
return {
|
|
2973
|
+
jsonSchema: generateJsonSchemaFromIR(ir, schemaOptions),
|
|
2974
|
+
uiSchema: generateUiSchemaFromIR(ir)
|
|
2975
|
+
};
|
|
2976
|
+
}
|
|
2977
|
+
function analyzeNamedType(filePath, typeName, extensionRegistry) {
|
|
2978
|
+
const ctx = createProgramContext(filePath);
|
|
2979
|
+
const source = { file: filePath };
|
|
2980
|
+
const classDecl = findClassByName(ctx.sourceFile, typeName);
|
|
2981
|
+
if (classDecl !== null) {
|
|
2982
|
+
return analyzeClassToIR(classDecl, ctx.checker, source.file, extensionRegistry);
|
|
2983
|
+
}
|
|
2984
|
+
const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
|
|
2985
|
+
if (interfaceDecl !== null) {
|
|
2986
|
+
return analyzeInterfaceToIR(interfaceDecl, ctx.checker, source.file, extensionRegistry);
|
|
2987
|
+
}
|
|
2988
|
+
const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
|
|
2989
|
+
if (typeAlias !== null) {
|
|
2990
|
+
const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, source.file, extensionRegistry);
|
|
2991
|
+
if (result.ok) {
|
|
2992
|
+
return result.analysis;
|
|
2993
|
+
}
|
|
2994
|
+
throw new Error(result.error);
|
|
2995
|
+
}
|
|
2996
|
+
throw new Error(
|
|
2997
|
+
`Type "${typeName}" not found as a class, interface, or type alias in ${filePath}`
|
|
2998
|
+
);
|
|
2999
|
+
}
|
|
3000
|
+
function composeAnalysisWithOverlays(analysis, overlays) {
|
|
3001
|
+
const overlayIR = canonicalizeChainDSL(overlays);
|
|
3002
|
+
const overlayFields = collectOverlayFields(overlayIR.elements);
|
|
3003
|
+
if (overlayFields.length === 0) {
|
|
3004
|
+
return analysis;
|
|
3005
|
+
}
|
|
3006
|
+
const overlayByName = /* @__PURE__ */ new Map();
|
|
3007
|
+
for (const field of overlayFields) {
|
|
3008
|
+
if (overlayByName.has(field.name)) {
|
|
3009
|
+
throw new Error(`Mixed-authoring overlays define "${field.name}" more than once`);
|
|
3010
|
+
}
|
|
3011
|
+
overlayByName.set(field.name, field);
|
|
3012
|
+
}
|
|
3013
|
+
const mergedFields = [];
|
|
3014
|
+
for (const baseField of analysis.fields) {
|
|
3015
|
+
const overlayField = overlayByName.get(baseField.name);
|
|
3016
|
+
if (overlayField === void 0) {
|
|
3017
|
+
mergedFields.push(baseField);
|
|
3018
|
+
continue;
|
|
3019
|
+
}
|
|
3020
|
+
mergedFields.push(mergeFieldOverlay(baseField, overlayField, analysis.typeRegistry));
|
|
3021
|
+
overlayByName.delete(baseField.name);
|
|
3022
|
+
}
|
|
3023
|
+
if (overlayByName.size > 0) {
|
|
3024
|
+
const unknownFields = [...overlayByName.keys()].sort().join(", ");
|
|
3025
|
+
throw new Error(
|
|
3026
|
+
`Mixed-authoring overlays reference fields that are not present in the static model: ${unknownFields}`
|
|
3027
|
+
);
|
|
3028
|
+
}
|
|
3029
|
+
return {
|
|
3030
|
+
...analysis,
|
|
3031
|
+
fields: mergedFields
|
|
3032
|
+
};
|
|
3033
|
+
}
|
|
3034
|
+
function collectOverlayFields(elements) {
|
|
3035
|
+
const fields = [];
|
|
3036
|
+
for (const element of elements) {
|
|
3037
|
+
switch (element.kind) {
|
|
3038
|
+
case "field":
|
|
3039
|
+
fields.push(element);
|
|
3040
|
+
break;
|
|
3041
|
+
case "group":
|
|
3042
|
+
fields.push(...collectOverlayFields(element.elements));
|
|
3043
|
+
break;
|
|
3044
|
+
case "conditional":
|
|
3045
|
+
fields.push(...collectOverlayFields(element.elements));
|
|
3046
|
+
break;
|
|
3047
|
+
default: {
|
|
3048
|
+
const _exhaustive = element;
|
|
3049
|
+
void _exhaustive;
|
|
3050
|
+
}
|
|
3051
|
+
}
|
|
3052
|
+
}
|
|
3053
|
+
return fields;
|
|
3054
|
+
}
|
|
3055
|
+
function mergeFieldOverlay(baseField, overlayField, typeRegistry) {
|
|
3056
|
+
assertSupportedOverlayField(baseField, overlayField);
|
|
3057
|
+
return {
|
|
3058
|
+
...baseField,
|
|
3059
|
+
type: mergeFieldType(baseField, overlayField, typeRegistry),
|
|
3060
|
+
annotations: mergeAnnotations(baseField.annotations, overlayField.annotations)
|
|
3061
|
+
};
|
|
3062
|
+
}
|
|
3063
|
+
function assertSupportedOverlayField(baseField, overlayField) {
|
|
3064
|
+
if (overlayField.constraints.length > 0) {
|
|
3065
|
+
throw new Error(
|
|
3066
|
+
`Mixed-authoring overlay for "${baseField.name}" cannot define constraints; keep constraints on the static model`
|
|
3067
|
+
);
|
|
3068
|
+
}
|
|
3069
|
+
if (overlayField.required && !baseField.required) {
|
|
3070
|
+
throw new Error(
|
|
3071
|
+
`Mixed-authoring overlay for "${baseField.name}" cannot change requiredness; keep requiredness on the static model`
|
|
3072
|
+
);
|
|
3073
|
+
}
|
|
3074
|
+
}
|
|
3075
|
+
function mergeFieldType(baseField, overlayField, typeRegistry) {
|
|
3076
|
+
const { type: baseType } = baseField;
|
|
3077
|
+
const { type: overlayType } = overlayField;
|
|
3078
|
+
if (overlayType.kind === "object" || overlayType.kind === "array") {
|
|
3079
|
+
throw new Error(
|
|
3080
|
+
`Mixed-authoring overlays do not support nested object or array overlays for "${baseField.name}"`
|
|
3081
|
+
);
|
|
3082
|
+
}
|
|
3083
|
+
if (overlayType.kind === "dynamic") {
|
|
3084
|
+
if (!isCompatibleDynamicOverlay(baseField, overlayField, typeRegistry)) {
|
|
3085
|
+
throw new Error(
|
|
3086
|
+
`Mixed-authoring overlay for "${baseField.name}" is incompatible with the static field type`
|
|
3087
|
+
);
|
|
3088
|
+
}
|
|
3089
|
+
return overlayType;
|
|
3090
|
+
}
|
|
3091
|
+
if (!isSameStaticTypeShape(baseType, overlayType)) {
|
|
3092
|
+
throw new Error(
|
|
3093
|
+
`Mixed-authoring overlay for "${baseField.name}" must preserve the static field type`
|
|
3094
|
+
);
|
|
3095
|
+
}
|
|
3096
|
+
return baseType;
|
|
3097
|
+
}
|
|
3098
|
+
function isCompatibleDynamicOverlay(baseField, overlayField, typeRegistry) {
|
|
3099
|
+
const overlayType = overlayField.type;
|
|
3100
|
+
if (overlayType.kind !== "dynamic") {
|
|
3101
|
+
return false;
|
|
3102
|
+
}
|
|
3103
|
+
const resolvedBaseType = resolveReferenceType(baseField.type, typeRegistry);
|
|
3104
|
+
if (resolvedBaseType === null) {
|
|
3105
|
+
return false;
|
|
3106
|
+
}
|
|
3107
|
+
if (overlayType.dynamicKind === "enum") {
|
|
3108
|
+
return resolvedBaseType.kind === "primitive" ? resolvedBaseType.primitiveKind === "string" : resolvedBaseType.kind === "enum";
|
|
3109
|
+
}
|
|
3110
|
+
return resolvedBaseType.kind === "object" || resolvedBaseType.kind === "record";
|
|
3111
|
+
}
|
|
3112
|
+
function resolveReferenceType(type, typeRegistry, seen = /* @__PURE__ */ new Set()) {
|
|
3113
|
+
if (type.kind !== "reference") {
|
|
3114
|
+
return type;
|
|
3115
|
+
}
|
|
3116
|
+
if (seen.has(type.name)) {
|
|
3117
|
+
return null;
|
|
3118
|
+
}
|
|
3119
|
+
const definition = typeRegistry[type.name];
|
|
3120
|
+
if (definition === void 0) {
|
|
3121
|
+
return null;
|
|
3122
|
+
}
|
|
3123
|
+
seen.add(type.name);
|
|
3124
|
+
return resolveReferenceType(definition.type, typeRegistry, seen);
|
|
3125
|
+
}
|
|
3126
|
+
function isSameStaticTypeShape(baseType, overlayType) {
|
|
3127
|
+
if (baseType.kind !== overlayType.kind) {
|
|
3128
|
+
return false;
|
|
3129
|
+
}
|
|
3130
|
+
switch (baseType.kind) {
|
|
3131
|
+
case "primitive":
|
|
3132
|
+
return overlayType.kind === "primitive" && baseType.primitiveKind === overlayType.primitiveKind;
|
|
3133
|
+
case "enum":
|
|
3134
|
+
return overlayType.kind === "enum";
|
|
3135
|
+
case "dynamic":
|
|
3136
|
+
return overlayType.kind === "dynamic" && baseType.dynamicKind === overlayType.dynamicKind && baseType.sourceKey === overlayType.sourceKey;
|
|
3137
|
+
case "record":
|
|
3138
|
+
return overlayType.kind === "record";
|
|
3139
|
+
case "reference":
|
|
3140
|
+
return overlayType.kind === "reference" && baseType.name === overlayType.name;
|
|
3141
|
+
case "union":
|
|
3142
|
+
return overlayType.kind === "union";
|
|
3143
|
+
case "custom":
|
|
3144
|
+
return overlayType.kind === "custom" && baseType.typeId === overlayType.typeId;
|
|
3145
|
+
case "object":
|
|
3146
|
+
case "array":
|
|
3147
|
+
return true;
|
|
3148
|
+
default: {
|
|
3149
|
+
const _exhaustive = baseType;
|
|
3150
|
+
return _exhaustive;
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
}
|
|
3154
|
+
function mergeAnnotations(baseAnnotations, overlayAnnotations) {
|
|
3155
|
+
const baseKeys = new Set(baseAnnotations.map(annotationKey));
|
|
3156
|
+
const overlayOnly = overlayAnnotations.filter(
|
|
3157
|
+
(annotation) => !baseKeys.has(annotationKey(annotation))
|
|
3158
|
+
);
|
|
3159
|
+
return [...baseAnnotations, ...overlayOnly];
|
|
3160
|
+
}
|
|
3161
|
+
function annotationKey(annotation) {
|
|
3162
|
+
return annotation.annotationKind === "custom" ? `${annotation.annotationKind}:${annotation.annotationId}` : annotation.annotationKind;
|
|
3163
|
+
}
|
|
3164
|
+
var init_mixed_authoring = __esm({
|
|
3165
|
+
"src/generators/mixed-authoring.ts"() {
|
|
3166
|
+
"use strict";
|
|
3167
|
+
init_ir_generator();
|
|
3168
|
+
init_ir_generator2();
|
|
3169
|
+
init_canonicalize();
|
|
3170
|
+
init_program();
|
|
3171
|
+
init_class_analyzer();
|
|
3172
|
+
}
|
|
3173
|
+
});
|
|
3174
|
+
|
|
2168
3175
|
// src/index.ts
|
|
2169
3176
|
var index_exports = {};
|
|
2170
3177
|
__export(index_exports, {
|
|
2171
3178
|
buildFormSchemas: () => buildFormSchemas,
|
|
3179
|
+
buildMixedAuthoringSchemas: () => buildMixedAuthoringSchemas,
|
|
2172
3180
|
categorizationSchema: () => categorizationSchema,
|
|
2173
3181
|
categorySchema: () => categorySchema,
|
|
2174
3182
|
controlSchema: () => controlSchema,
|
|
@@ -2233,6 +3241,7 @@ var init_index = __esm({
|
|
|
2233
3241
|
init_ir_generator();
|
|
2234
3242
|
init_generator2();
|
|
2235
3243
|
init_class_schema();
|
|
3244
|
+
init_mixed_authoring();
|
|
2236
3245
|
}
|
|
2237
3246
|
});
|
|
2238
3247
|
|