@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.cjs
CHANGED
|
@@ -362,6 +362,7 @@ function canonicalizeTSDoc(analysis, source) {
|
|
|
362
362
|
irVersion: import_core2.IR_VERSION,
|
|
363
363
|
elements,
|
|
364
364
|
typeRegistry: analysis.typeRegistry,
|
|
365
|
+
...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { annotations: analysis.annotations },
|
|
365
366
|
provenance
|
|
366
367
|
};
|
|
367
368
|
}
|
|
@@ -454,6 +455,9 @@ function generateJsonSchemaFromIR(ir, options) {
|
|
|
454
455
|
const ctx = makeContext(options);
|
|
455
456
|
for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
|
|
456
457
|
ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
|
|
458
|
+
if (typeDef.annotations && typeDef.annotations.length > 0) {
|
|
459
|
+
applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
|
|
460
|
+
}
|
|
457
461
|
}
|
|
458
462
|
const properties = {};
|
|
459
463
|
const required = [];
|
|
@@ -465,6 +469,9 @@ function generateJsonSchemaFromIR(ir, options) {
|
|
|
465
469
|
properties,
|
|
466
470
|
...uniqueRequired.length > 0 && { required: uniqueRequired }
|
|
467
471
|
};
|
|
472
|
+
if (ir.annotations && ir.annotations.length > 0) {
|
|
473
|
+
applyAnnotations(result, ir.annotations, ctx);
|
|
474
|
+
}
|
|
468
475
|
if (Object.keys(ctx.defs).length > 0) {
|
|
469
476
|
result.$defs = ctx.defs;
|
|
470
477
|
}
|
|
@@ -494,22 +501,51 @@ function collectFields(elements, properties, required, ctx) {
|
|
|
494
501
|
}
|
|
495
502
|
function generateFieldSchema(field, ctx) {
|
|
496
503
|
const schema = generateTypeNode(field.type, ctx);
|
|
504
|
+
const itemStringSchema = schema.type === "array" && schema.items?.type === "string" ? schema.items : void 0;
|
|
497
505
|
const directConstraints = [];
|
|
506
|
+
const itemConstraints = [];
|
|
498
507
|
const pathConstraints = [];
|
|
499
508
|
for (const c of field.constraints) {
|
|
500
509
|
if (c.path) {
|
|
501
510
|
pathConstraints.push(c);
|
|
511
|
+
} else if (itemStringSchema !== void 0 && isStringItemConstraint(c)) {
|
|
512
|
+
itemConstraints.push(c);
|
|
502
513
|
} else {
|
|
503
514
|
directConstraints.push(c);
|
|
504
515
|
}
|
|
505
516
|
}
|
|
506
517
|
applyConstraints(schema, directConstraints, ctx);
|
|
507
|
-
|
|
518
|
+
if (itemStringSchema !== void 0) {
|
|
519
|
+
applyConstraints(itemStringSchema, itemConstraints, ctx);
|
|
520
|
+
}
|
|
521
|
+
const rootAnnotations = [];
|
|
522
|
+
const itemAnnotations = [];
|
|
523
|
+
for (const annotation of field.annotations) {
|
|
524
|
+
if (itemStringSchema !== void 0 && annotation.annotationKind === "format") {
|
|
525
|
+
itemAnnotations.push(annotation);
|
|
526
|
+
} else {
|
|
527
|
+
rootAnnotations.push(annotation);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
applyAnnotations(schema, rootAnnotations, ctx);
|
|
531
|
+
if (itemStringSchema !== void 0) {
|
|
532
|
+
applyAnnotations(itemStringSchema, itemAnnotations, ctx);
|
|
533
|
+
}
|
|
508
534
|
if (pathConstraints.length === 0) {
|
|
509
535
|
return schema;
|
|
510
536
|
}
|
|
511
537
|
return applyPathTargetedConstraints(schema, pathConstraints, ctx);
|
|
512
538
|
}
|
|
539
|
+
function isStringItemConstraint(constraint) {
|
|
540
|
+
switch (constraint.constraintKind) {
|
|
541
|
+
case "minLength":
|
|
542
|
+
case "maxLength":
|
|
543
|
+
case "pattern":
|
|
544
|
+
return true;
|
|
545
|
+
default:
|
|
546
|
+
return false;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
513
549
|
function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
|
|
514
550
|
if (schema.type === "array" && schema.items) {
|
|
515
551
|
schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx);
|
|
@@ -727,6 +763,9 @@ function applyConstraints(schema, constraints, ctx) {
|
|
|
727
763
|
case "uniqueItems":
|
|
728
764
|
schema.uniqueItems = constraint.value;
|
|
729
765
|
break;
|
|
766
|
+
case "const":
|
|
767
|
+
schema.const = constraint.value;
|
|
768
|
+
break;
|
|
730
769
|
case "allowedMembers":
|
|
731
770
|
break;
|
|
732
771
|
case "custom":
|
|
@@ -751,8 +790,14 @@ function applyAnnotations(schema, annotations, ctx) {
|
|
|
751
790
|
case "defaultValue":
|
|
752
791
|
schema.default = annotation.value;
|
|
753
792
|
break;
|
|
793
|
+
case "format":
|
|
794
|
+
schema.format = annotation.value;
|
|
795
|
+
break;
|
|
754
796
|
case "deprecated":
|
|
755
797
|
schema.deprecated = true;
|
|
798
|
+
if (annotation.message !== void 0 && annotation.message !== "") {
|
|
799
|
+
schema["x-formspec-deprecation-description"] = annotation.message;
|
|
800
|
+
}
|
|
756
801
|
break;
|
|
757
802
|
case "placeholder":
|
|
758
803
|
break;
|
|
@@ -784,7 +829,12 @@ function applyCustomConstraint(schema, constraint, ctx) {
|
|
|
784
829
|
`Cannot generate JSON Schema for custom constraint "${constraint.constraintId}" without a matching extension registration`
|
|
785
830
|
);
|
|
786
831
|
}
|
|
787
|
-
|
|
832
|
+
assignVendorPrefixedExtensionKeywords(
|
|
833
|
+
schema,
|
|
834
|
+
registration.toJsonSchema(constraint.payload, ctx.vendorPrefix),
|
|
835
|
+
ctx.vendorPrefix,
|
|
836
|
+
`custom constraint "${constraint.constraintId}"`
|
|
837
|
+
);
|
|
788
838
|
}
|
|
789
839
|
function applyCustomAnnotation(schema, annotation, ctx) {
|
|
790
840
|
const registration = ctx.extensionRegistry?.findAnnotation(annotation.annotationId);
|
|
@@ -796,7 +846,22 @@ function applyCustomAnnotation(schema, annotation, ctx) {
|
|
|
796
846
|
if (registration.toJsonSchema === void 0) {
|
|
797
847
|
return;
|
|
798
848
|
}
|
|
799
|
-
|
|
849
|
+
assignVendorPrefixedExtensionKeywords(
|
|
850
|
+
schema,
|
|
851
|
+
registration.toJsonSchema(annotation.value, ctx.vendorPrefix),
|
|
852
|
+
ctx.vendorPrefix,
|
|
853
|
+
`custom annotation "${annotation.annotationId}"`
|
|
854
|
+
);
|
|
855
|
+
}
|
|
856
|
+
function assignVendorPrefixedExtensionKeywords(schema, extensionSchema, vendorPrefix, source) {
|
|
857
|
+
for (const [key, value] of Object.entries(extensionSchema)) {
|
|
858
|
+
if (!key.startsWith(`${vendorPrefix}-`)) {
|
|
859
|
+
throw new Error(
|
|
860
|
+
`Cannot apply ${source}: extension hooks may only emit "${vendorPrefix}-*" JSON Schema keywords`
|
|
861
|
+
);
|
|
862
|
+
}
|
|
863
|
+
schema[key] = value;
|
|
864
|
+
}
|
|
800
865
|
}
|
|
801
866
|
var init_ir_generator = __esm({
|
|
802
867
|
"src/json-schema/ir-generator.ts"() {
|
|
@@ -957,25 +1022,31 @@ function createShowRule(fieldName, value) {
|
|
|
957
1022
|
}
|
|
958
1023
|
};
|
|
959
1024
|
}
|
|
1025
|
+
function flattenConditionSchema(scope, schema) {
|
|
1026
|
+
if (schema.allOf === void 0) {
|
|
1027
|
+
if (scope === "#") {
|
|
1028
|
+
return [schema];
|
|
1029
|
+
}
|
|
1030
|
+
const fieldName = scope.replace("#/properties/", "");
|
|
1031
|
+
return [
|
|
1032
|
+
{
|
|
1033
|
+
properties: {
|
|
1034
|
+
[fieldName]: schema
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
];
|
|
1038
|
+
}
|
|
1039
|
+
return schema.allOf.flatMap((member) => flattenConditionSchema(scope, member));
|
|
1040
|
+
}
|
|
960
1041
|
function combineRules(parentRule, childRule) {
|
|
961
|
-
const parentCondition = parentRule.condition;
|
|
962
|
-
const childCondition = childRule.condition;
|
|
963
1042
|
return {
|
|
964
1043
|
effect: "SHOW",
|
|
965
1044
|
condition: {
|
|
966
1045
|
scope: "#",
|
|
967
1046
|
schema: {
|
|
968
1047
|
allOf: [
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
[parentCondition.scope.replace("#/properties/", "")]: parentCondition.schema
|
|
972
|
-
}
|
|
973
|
-
},
|
|
974
|
-
{
|
|
975
|
-
properties: {
|
|
976
|
-
[childCondition.scope.replace("#/properties/", "")]: childCondition.schema
|
|
977
|
-
}
|
|
978
|
-
}
|
|
1048
|
+
...flattenConditionSchema(parentRule.condition.scope, parentRule.condition.schema),
|
|
1049
|
+
...flattenConditionSchema(childRule.condition.scope, childRule.condition.schema)
|
|
979
1050
|
]
|
|
980
1051
|
}
|
|
981
1052
|
}
|
|
@@ -983,10 +1054,14 @@ function combineRules(parentRule, childRule) {
|
|
|
983
1054
|
}
|
|
984
1055
|
function fieldNodeToControl(field, parentRule) {
|
|
985
1056
|
const displayNameAnnotation = field.annotations.find((a) => a.annotationKind === "displayName");
|
|
1057
|
+
const placeholderAnnotation = field.annotations.find((a) => a.annotationKind === "placeholder");
|
|
986
1058
|
const control = {
|
|
987
1059
|
type: "Control",
|
|
988
1060
|
scope: fieldToScope(field.name),
|
|
989
1061
|
...displayNameAnnotation !== void 0 && { label: displayNameAnnotation.value },
|
|
1062
|
+
...placeholderAnnotation !== void 0 && {
|
|
1063
|
+
options: { placeholder: placeholderAnnotation.value }
|
|
1064
|
+
},
|
|
990
1065
|
...parentRule !== void 0 && { rule: parentRule }
|
|
991
1066
|
};
|
|
992
1067
|
return control;
|
|
@@ -1072,7 +1147,10 @@ var init_types = __esm({
|
|
|
1072
1147
|
// src/extensions/registry.ts
|
|
1073
1148
|
function createExtensionRegistry(extensions) {
|
|
1074
1149
|
const typeMap = /* @__PURE__ */ new Map();
|
|
1150
|
+
const typeNameMap = /* @__PURE__ */ new Map();
|
|
1075
1151
|
const constraintMap = /* @__PURE__ */ new Map();
|
|
1152
|
+
const constraintTagMap = /* @__PURE__ */ new Map();
|
|
1153
|
+
const builtinBroadeningMap = /* @__PURE__ */ new Map();
|
|
1076
1154
|
const annotationMap = /* @__PURE__ */ new Map();
|
|
1077
1155
|
for (const ext of extensions) {
|
|
1078
1156
|
if (ext.types !== void 0) {
|
|
@@ -1082,6 +1160,27 @@ function createExtensionRegistry(extensions) {
|
|
|
1082
1160
|
throw new Error(`Duplicate custom type ID: "${qualifiedId}"`);
|
|
1083
1161
|
}
|
|
1084
1162
|
typeMap.set(qualifiedId, type);
|
|
1163
|
+
for (const sourceTypeName of type.tsTypeNames ?? [type.typeName]) {
|
|
1164
|
+
if (typeNameMap.has(sourceTypeName)) {
|
|
1165
|
+
throw new Error(`Duplicate custom type source name: "${sourceTypeName}"`);
|
|
1166
|
+
}
|
|
1167
|
+
typeNameMap.set(sourceTypeName, {
|
|
1168
|
+
extensionId: ext.extensionId,
|
|
1169
|
+
registration: type
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1172
|
+
if (type.builtinConstraintBroadenings !== void 0) {
|
|
1173
|
+
for (const broadening of type.builtinConstraintBroadenings) {
|
|
1174
|
+
const key = `${qualifiedId}:${broadening.tagName}`;
|
|
1175
|
+
if (builtinBroadeningMap.has(key)) {
|
|
1176
|
+
throw new Error(`Duplicate built-in constraint broadening: "${key}"`);
|
|
1177
|
+
}
|
|
1178
|
+
builtinBroadeningMap.set(key, {
|
|
1179
|
+
extensionId: ext.extensionId,
|
|
1180
|
+
registration: broadening
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1085
1184
|
}
|
|
1086
1185
|
}
|
|
1087
1186
|
if (ext.constraints !== void 0) {
|
|
@@ -1093,6 +1192,17 @@ function createExtensionRegistry(extensions) {
|
|
|
1093
1192
|
constraintMap.set(qualifiedId, constraint);
|
|
1094
1193
|
}
|
|
1095
1194
|
}
|
|
1195
|
+
if (ext.constraintTags !== void 0) {
|
|
1196
|
+
for (const tag of ext.constraintTags) {
|
|
1197
|
+
if (constraintTagMap.has(tag.tagName)) {
|
|
1198
|
+
throw new Error(`Duplicate custom constraint tag: "@${tag.tagName}"`);
|
|
1199
|
+
}
|
|
1200
|
+
constraintTagMap.set(tag.tagName, {
|
|
1201
|
+
extensionId: ext.extensionId,
|
|
1202
|
+
registration: tag
|
|
1203
|
+
});
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1096
1206
|
if (ext.annotations !== void 0) {
|
|
1097
1207
|
for (const annotation of ext.annotations) {
|
|
1098
1208
|
const qualifiedId = `${ext.extensionId}/${annotation.annotationName}`;
|
|
@@ -1106,7 +1216,10 @@ function createExtensionRegistry(extensions) {
|
|
|
1106
1216
|
return {
|
|
1107
1217
|
extensions,
|
|
1108
1218
|
findType: (typeId) => typeMap.get(typeId),
|
|
1219
|
+
findTypeByName: (typeName) => typeNameMap.get(typeName),
|
|
1109
1220
|
findConstraint: (constraintId) => constraintMap.get(constraintId),
|
|
1221
|
+
findConstraintTag: (tagName) => constraintTagMap.get(tagName),
|
|
1222
|
+
findBuiltinConstraintBroadening: (typeId, tagName) => builtinBroadeningMap.get(`${typeId}:${tagName}`),
|
|
1110
1223
|
findAnnotation: (annotationId) => annotationMap.get(annotationId)
|
|
1111
1224
|
};
|
|
1112
1225
|
}
|
|
@@ -1285,7 +1398,7 @@ var init_json_utils = __esm({
|
|
|
1285
1398
|
});
|
|
1286
1399
|
|
|
1287
1400
|
// src/analyzer/tsdoc-parser.ts
|
|
1288
|
-
function createFormSpecTSDocConfig() {
|
|
1401
|
+
function createFormSpecTSDocConfig(extensionTagNames = []) {
|
|
1289
1402
|
const config = new import_tsdoc.TSDocConfiguration();
|
|
1290
1403
|
for (const tagName of Object.keys(import_core3.BUILTIN_CONSTRAINT_DEFINITIONS)) {
|
|
1291
1404
|
config.addTagDefinition(
|
|
@@ -1296,7 +1409,16 @@ function createFormSpecTSDocConfig() {
|
|
|
1296
1409
|
})
|
|
1297
1410
|
);
|
|
1298
1411
|
}
|
|
1299
|
-
for (const tagName of ["displayName", "description"]) {
|
|
1412
|
+
for (const tagName of ["displayName", "description", "format", "placeholder"]) {
|
|
1413
|
+
config.addTagDefinition(
|
|
1414
|
+
new import_tsdoc.TSDocTagDefinition({
|
|
1415
|
+
tagName: "@" + tagName,
|
|
1416
|
+
syntaxKind: import_tsdoc.TSDocTagSyntaxKind.BlockTag,
|
|
1417
|
+
allowMultiple: true
|
|
1418
|
+
})
|
|
1419
|
+
);
|
|
1420
|
+
}
|
|
1421
|
+
for (const tagName of extensionTagNames) {
|
|
1300
1422
|
config.addTagDefinition(
|
|
1301
1423
|
new import_tsdoc.TSDocTagDefinition({
|
|
1302
1424
|
tagName: "@" + tagName,
|
|
@@ -1307,13 +1429,30 @@ function createFormSpecTSDocConfig() {
|
|
|
1307
1429
|
}
|
|
1308
1430
|
return config;
|
|
1309
1431
|
}
|
|
1310
|
-
function getParser() {
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1432
|
+
function getParser(options) {
|
|
1433
|
+
const extensionTagNames = [
|
|
1434
|
+
...options?.extensionRegistry?.extensions.flatMap(
|
|
1435
|
+
(extension) => (extension.constraintTags ?? []).map((tag) => tag.tagName)
|
|
1436
|
+
) ?? []
|
|
1437
|
+
].sort();
|
|
1438
|
+
const cacheKey = extensionTagNames.join("|");
|
|
1439
|
+
const existing = parserCache.get(cacheKey);
|
|
1440
|
+
if (existing) {
|
|
1441
|
+
return existing;
|
|
1442
|
+
}
|
|
1443
|
+
const parser = new import_tsdoc.TSDocParser(createFormSpecTSDocConfig(extensionTagNames));
|
|
1444
|
+
parserCache.set(cacheKey, parser);
|
|
1445
|
+
return parser;
|
|
1446
|
+
}
|
|
1447
|
+
function parseTSDocTags(node, file = "", options) {
|
|
1315
1448
|
const constraints = [];
|
|
1316
1449
|
const annotations = [];
|
|
1450
|
+
let displayName;
|
|
1451
|
+
let description;
|
|
1452
|
+
let placeholder;
|
|
1453
|
+
let displayNameProvenance;
|
|
1454
|
+
let descriptionProvenance;
|
|
1455
|
+
let placeholderProvenance;
|
|
1317
1456
|
const sourceFile = node.getSourceFile();
|
|
1318
1457
|
const sourceText = sourceFile.getFullText();
|
|
1319
1458
|
const commentRanges = ts2.getLeadingCommentRanges(sourceText, node.getFullStart());
|
|
@@ -1326,52 +1465,92 @@ function parseTSDocTags(node, file = "") {
|
|
|
1326
1465
|
if (!commentText.startsWith("/**")) {
|
|
1327
1466
|
continue;
|
|
1328
1467
|
}
|
|
1329
|
-
const parser = getParser();
|
|
1468
|
+
const parser = getParser(options);
|
|
1330
1469
|
const parserContext = parser.parseRange(
|
|
1331
1470
|
import_tsdoc.TextRange.fromStringRange(sourceText, range.pos, range.end)
|
|
1332
1471
|
);
|
|
1333
1472
|
const docComment = parserContext.docComment;
|
|
1334
1473
|
for (const block of docComment.customBlocks) {
|
|
1335
1474
|
const tagName = (0, import_core3.normalizeConstraintTagName)(block.blockTag.tagName.substring(1));
|
|
1336
|
-
if (tagName === "displayName" || tagName === "description") {
|
|
1475
|
+
if (tagName === "displayName" || tagName === "description" || tagName === "format" || tagName === "placeholder") {
|
|
1337
1476
|
const text2 = extractBlockText(block).trim();
|
|
1338
1477
|
if (text2 === "") continue;
|
|
1339
1478
|
const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
|
|
1340
1479
|
if (tagName === "displayName") {
|
|
1480
|
+
if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
|
|
1481
|
+
displayName = text2;
|
|
1482
|
+
displayNameProvenance = provenance2;
|
|
1483
|
+
}
|
|
1484
|
+
} else if (tagName === "format") {
|
|
1341
1485
|
annotations.push({
|
|
1342
1486
|
kind: "annotation",
|
|
1343
|
-
annotationKind: "
|
|
1487
|
+
annotationKind: "format",
|
|
1344
1488
|
value: text2,
|
|
1345
1489
|
provenance: provenance2
|
|
1346
1490
|
});
|
|
1347
1491
|
} else {
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1492
|
+
if (tagName === "description" && description === void 0) {
|
|
1493
|
+
description = text2;
|
|
1494
|
+
descriptionProvenance = provenance2;
|
|
1495
|
+
} else if (tagName === "placeholder" && placeholder === void 0) {
|
|
1496
|
+
placeholder = text2;
|
|
1497
|
+
placeholderProvenance = provenance2;
|
|
1498
|
+
}
|
|
1354
1499
|
}
|
|
1355
1500
|
continue;
|
|
1356
1501
|
}
|
|
1357
1502
|
if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
1358
1503
|
const text = extractBlockText(block).trim();
|
|
1359
|
-
|
|
1504
|
+
const expectedType = (0, import_core3.isBuiltinConstraintName)(tagName) ? import_core3.BUILTIN_CONSTRAINT_DEFINITIONS[tagName] : void 0;
|
|
1505
|
+
if (text === "" && expectedType !== "boolean") continue;
|
|
1360
1506
|
const provenance = provenanceForComment(range, sourceFile, file, tagName);
|
|
1361
|
-
const constraintNode = parseConstraintValue(tagName, text, provenance);
|
|
1507
|
+
const constraintNode = parseConstraintValue(tagName, text, provenance, options);
|
|
1362
1508
|
if (constraintNode) {
|
|
1363
1509
|
constraints.push(constraintNode);
|
|
1364
1510
|
}
|
|
1365
1511
|
}
|
|
1366
1512
|
if (docComment.deprecatedBlock !== void 0) {
|
|
1513
|
+
const message = extractBlockText(docComment.deprecatedBlock).trim();
|
|
1367
1514
|
annotations.push({
|
|
1368
1515
|
kind: "annotation",
|
|
1369
1516
|
annotationKind: "deprecated",
|
|
1517
|
+
...message !== "" && { message },
|
|
1370
1518
|
provenance: provenanceForComment(range, sourceFile, file, "deprecated")
|
|
1371
1519
|
});
|
|
1372
1520
|
}
|
|
1521
|
+
if (description === void 0 && docComment.remarksBlock !== void 0) {
|
|
1522
|
+
const remarks = extractBlockText(docComment.remarksBlock).trim();
|
|
1523
|
+
if (remarks !== "") {
|
|
1524
|
+
description = remarks;
|
|
1525
|
+
descriptionProvenance = provenanceForComment(range, sourceFile, file, "remarks");
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1373
1528
|
}
|
|
1374
1529
|
}
|
|
1530
|
+
if (displayName !== void 0 && displayNameProvenance !== void 0) {
|
|
1531
|
+
annotations.push({
|
|
1532
|
+
kind: "annotation",
|
|
1533
|
+
annotationKind: "displayName",
|
|
1534
|
+
value: displayName,
|
|
1535
|
+
provenance: displayNameProvenance
|
|
1536
|
+
});
|
|
1537
|
+
}
|
|
1538
|
+
if (description !== void 0 && descriptionProvenance !== void 0) {
|
|
1539
|
+
annotations.push({
|
|
1540
|
+
kind: "annotation",
|
|
1541
|
+
annotationKind: "description",
|
|
1542
|
+
value: description,
|
|
1543
|
+
provenance: descriptionProvenance
|
|
1544
|
+
});
|
|
1545
|
+
}
|
|
1546
|
+
if (placeholder !== void 0 && placeholderProvenance !== void 0) {
|
|
1547
|
+
annotations.push({
|
|
1548
|
+
kind: "annotation",
|
|
1549
|
+
annotationKind: "placeholder",
|
|
1550
|
+
value: placeholder,
|
|
1551
|
+
provenance: placeholderProvenance
|
|
1552
|
+
});
|
|
1553
|
+
}
|
|
1375
1554
|
const jsDocTagsAll = ts2.getJSDocTags(node);
|
|
1376
1555
|
for (const tag of jsDocTagsAll) {
|
|
1377
1556
|
const tagName = (0, import_core3.normalizeConstraintTagName)(tag.tagName.text);
|
|
@@ -1380,13 +1559,40 @@ function parseTSDocTags(node, file = "") {
|
|
|
1380
1559
|
if (commentText === void 0 || commentText.trim() === "") continue;
|
|
1381
1560
|
const text = commentText.trim();
|
|
1382
1561
|
const provenance = provenanceForJSDocTag(tag, file);
|
|
1383
|
-
|
|
1562
|
+
if (tagName === "defaultValue") {
|
|
1563
|
+
const defaultValueNode = parseDefaultValueValue(text, provenance);
|
|
1564
|
+
annotations.push(defaultValueNode);
|
|
1565
|
+
continue;
|
|
1566
|
+
}
|
|
1567
|
+
const constraintNode = parseConstraintValue(tagName, text, provenance, options);
|
|
1384
1568
|
if (constraintNode) {
|
|
1385
1569
|
constraints.push(constraintNode);
|
|
1386
1570
|
}
|
|
1387
1571
|
}
|
|
1388
1572
|
return { constraints, annotations };
|
|
1389
1573
|
}
|
|
1574
|
+
function extractDisplayNameMetadata(node) {
|
|
1575
|
+
let displayName;
|
|
1576
|
+
const memberDisplayNames = /* @__PURE__ */ new Map();
|
|
1577
|
+
for (const tag of ts2.getJSDocTags(node)) {
|
|
1578
|
+
const tagName = (0, import_core3.normalizeConstraintTagName)(tag.tagName.text);
|
|
1579
|
+
if (tagName !== "displayName") continue;
|
|
1580
|
+
const commentText = getTagCommentText(tag);
|
|
1581
|
+
if (commentText === void 0) continue;
|
|
1582
|
+
const text = commentText.trim();
|
|
1583
|
+
if (text === "") continue;
|
|
1584
|
+
const memberTarget = parseMemberTargetDisplayName(text);
|
|
1585
|
+
if (memberTarget) {
|
|
1586
|
+
memberDisplayNames.set(memberTarget.target, memberTarget.label);
|
|
1587
|
+
continue;
|
|
1588
|
+
}
|
|
1589
|
+
displayName ??= text;
|
|
1590
|
+
}
|
|
1591
|
+
return {
|
|
1592
|
+
...displayName !== void 0 && { displayName },
|
|
1593
|
+
memberDisplayNames
|
|
1594
|
+
};
|
|
1595
|
+
}
|
|
1390
1596
|
function extractPathTarget(text) {
|
|
1391
1597
|
const trimmed = text.trimStart();
|
|
1392
1598
|
const match = /^:([a-zA-Z_]\w*)\s+([\s\S]*)$/.exec(trimmed);
|
|
@@ -1414,7 +1620,11 @@ function extractPlainText(node) {
|
|
|
1414
1620
|
}
|
|
1415
1621
|
return result;
|
|
1416
1622
|
}
|
|
1417
|
-
function parseConstraintValue(tagName, text, provenance) {
|
|
1623
|
+
function parseConstraintValue(tagName, text, provenance, options) {
|
|
1624
|
+
const customConstraint = parseExtensionConstraintValue(tagName, text, provenance, options);
|
|
1625
|
+
if (customConstraint) {
|
|
1626
|
+
return customConstraint;
|
|
1627
|
+
}
|
|
1418
1628
|
if (!(0, import_core3.isBuiltinConstraintName)(tagName)) {
|
|
1419
1629
|
return null;
|
|
1420
1630
|
}
|
|
@@ -1449,7 +1659,45 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
1449
1659
|
}
|
|
1450
1660
|
return null;
|
|
1451
1661
|
}
|
|
1662
|
+
if (expectedType === "boolean") {
|
|
1663
|
+
const trimmed = effectiveText.trim();
|
|
1664
|
+
if (trimmed !== "" && trimmed !== "true") {
|
|
1665
|
+
return null;
|
|
1666
|
+
}
|
|
1667
|
+
if (tagName === "uniqueItems") {
|
|
1668
|
+
return {
|
|
1669
|
+
kind: "constraint",
|
|
1670
|
+
constraintKind: "uniqueItems",
|
|
1671
|
+
value: true,
|
|
1672
|
+
...path4 && { path: path4 },
|
|
1673
|
+
provenance
|
|
1674
|
+
};
|
|
1675
|
+
}
|
|
1676
|
+
return null;
|
|
1677
|
+
}
|
|
1452
1678
|
if (expectedType === "json") {
|
|
1679
|
+
if (tagName === "const") {
|
|
1680
|
+
const trimmedText = effectiveText.trim();
|
|
1681
|
+
if (trimmedText === "") return null;
|
|
1682
|
+
try {
|
|
1683
|
+
const parsed2 = JSON.parse(trimmedText);
|
|
1684
|
+
return {
|
|
1685
|
+
kind: "constraint",
|
|
1686
|
+
constraintKind: "const",
|
|
1687
|
+
value: parsed2,
|
|
1688
|
+
...path4 && { path: path4 },
|
|
1689
|
+
provenance
|
|
1690
|
+
};
|
|
1691
|
+
} catch {
|
|
1692
|
+
return {
|
|
1693
|
+
kind: "constraint",
|
|
1694
|
+
constraintKind: "const",
|
|
1695
|
+
value: trimmedText,
|
|
1696
|
+
...path4 && { path: path4 },
|
|
1697
|
+
provenance
|
|
1698
|
+
};
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1453
1701
|
const parsed = tryParseJson(effectiveText);
|
|
1454
1702
|
if (!Array.isArray(parsed)) {
|
|
1455
1703
|
return null;
|
|
@@ -1481,6 +1729,111 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
1481
1729
|
provenance
|
|
1482
1730
|
};
|
|
1483
1731
|
}
|
|
1732
|
+
function parseExtensionConstraintValue(tagName, text, provenance, options) {
|
|
1733
|
+
const pathResult = extractPathTarget(text);
|
|
1734
|
+
const effectiveText = pathResult ? pathResult.remainingText : text;
|
|
1735
|
+
const path4 = pathResult?.path;
|
|
1736
|
+
const registry = options?.extensionRegistry;
|
|
1737
|
+
if (registry === void 0) {
|
|
1738
|
+
return null;
|
|
1739
|
+
}
|
|
1740
|
+
const directTag = registry.findConstraintTag(tagName);
|
|
1741
|
+
if (directTag !== void 0) {
|
|
1742
|
+
return makeCustomConstraintNode(
|
|
1743
|
+
directTag.extensionId,
|
|
1744
|
+
directTag.registration.constraintName,
|
|
1745
|
+
directTag.registration.parseValue(effectiveText),
|
|
1746
|
+
provenance,
|
|
1747
|
+
path4,
|
|
1748
|
+
registry
|
|
1749
|
+
);
|
|
1750
|
+
}
|
|
1751
|
+
if (!(0, import_core3.isBuiltinConstraintName)(tagName)) {
|
|
1752
|
+
return null;
|
|
1753
|
+
}
|
|
1754
|
+
const broadenedTypeId = getBroadenedCustomTypeId(options?.fieldType);
|
|
1755
|
+
if (broadenedTypeId === void 0) {
|
|
1756
|
+
return null;
|
|
1757
|
+
}
|
|
1758
|
+
const broadened = registry.findBuiltinConstraintBroadening(broadenedTypeId, tagName);
|
|
1759
|
+
if (broadened === void 0) {
|
|
1760
|
+
return null;
|
|
1761
|
+
}
|
|
1762
|
+
return makeCustomConstraintNode(
|
|
1763
|
+
broadened.extensionId,
|
|
1764
|
+
broadened.registration.constraintName,
|
|
1765
|
+
broadened.registration.parseValue(effectiveText),
|
|
1766
|
+
provenance,
|
|
1767
|
+
path4,
|
|
1768
|
+
registry
|
|
1769
|
+
);
|
|
1770
|
+
}
|
|
1771
|
+
function getBroadenedCustomTypeId(fieldType) {
|
|
1772
|
+
if (fieldType?.kind === "custom") {
|
|
1773
|
+
return fieldType.typeId;
|
|
1774
|
+
}
|
|
1775
|
+
if (fieldType?.kind !== "union") {
|
|
1776
|
+
return void 0;
|
|
1777
|
+
}
|
|
1778
|
+
const customMembers = fieldType.members.filter(
|
|
1779
|
+
(member) => member.kind === "custom"
|
|
1780
|
+
);
|
|
1781
|
+
if (customMembers.length !== 1) {
|
|
1782
|
+
return void 0;
|
|
1783
|
+
}
|
|
1784
|
+
const nonCustomMembers = fieldType.members.filter((member) => member.kind !== "custom");
|
|
1785
|
+
const allOtherMembersAreNull = nonCustomMembers.every(
|
|
1786
|
+
(member) => member.kind === "primitive" && member.primitiveKind === "null"
|
|
1787
|
+
);
|
|
1788
|
+
const customMember = customMembers[0];
|
|
1789
|
+
return allOtherMembersAreNull && customMember !== void 0 ? customMember.typeId : void 0;
|
|
1790
|
+
}
|
|
1791
|
+
function makeCustomConstraintNode(extensionId, constraintName, payload, provenance, path4, registry) {
|
|
1792
|
+
const constraintId = `${extensionId}/${constraintName}`;
|
|
1793
|
+
const registration = registry.findConstraint(constraintId);
|
|
1794
|
+
if (registration === void 0) {
|
|
1795
|
+
throw new Error(
|
|
1796
|
+
`Custom TSDoc tag resolved to unregistered constraint "${constraintId}". Register the constraint before using its tag.`
|
|
1797
|
+
);
|
|
1798
|
+
}
|
|
1799
|
+
return {
|
|
1800
|
+
kind: "constraint",
|
|
1801
|
+
constraintKind: "custom",
|
|
1802
|
+
constraintId,
|
|
1803
|
+
payload,
|
|
1804
|
+
compositionRule: registration.compositionRule,
|
|
1805
|
+
...path4 && { path: path4 },
|
|
1806
|
+
provenance
|
|
1807
|
+
};
|
|
1808
|
+
}
|
|
1809
|
+
function parseDefaultValueValue(text, provenance) {
|
|
1810
|
+
const trimmed = text.trim();
|
|
1811
|
+
let value;
|
|
1812
|
+
if (trimmed === "null") {
|
|
1813
|
+
value = null;
|
|
1814
|
+
} else if (trimmed === "true") {
|
|
1815
|
+
value = true;
|
|
1816
|
+
} else if (trimmed === "false") {
|
|
1817
|
+
value = false;
|
|
1818
|
+
} else {
|
|
1819
|
+
const parsed = tryParseJson(trimmed);
|
|
1820
|
+
value = parsed !== null ? parsed : trimmed;
|
|
1821
|
+
}
|
|
1822
|
+
return {
|
|
1823
|
+
kind: "annotation",
|
|
1824
|
+
annotationKind: "defaultValue",
|
|
1825
|
+
value,
|
|
1826
|
+
provenance
|
|
1827
|
+
};
|
|
1828
|
+
}
|
|
1829
|
+
function isMemberTargetDisplayName(text) {
|
|
1830
|
+
return parseMemberTargetDisplayName(text) !== null;
|
|
1831
|
+
}
|
|
1832
|
+
function parseMemberTargetDisplayName(text) {
|
|
1833
|
+
const match = /^:([^\s]+)\s+([\s\S]+)$/.exec(text);
|
|
1834
|
+
if (!match?.[1] || !match[2]) return null;
|
|
1835
|
+
return { target: match[1], label: match[2].trim() };
|
|
1836
|
+
}
|
|
1484
1837
|
function provenanceForComment(range, sourceFile, file, tagName) {
|
|
1485
1838
|
const { line, character } = sourceFile.getLineAndCharacterOfPosition(range.pos);
|
|
1486
1839
|
return {
|
|
@@ -1511,7 +1864,7 @@ function getTagCommentText(tag) {
|
|
|
1511
1864
|
}
|
|
1512
1865
|
return ts2.getTextOfJSDocComment(tag.comment);
|
|
1513
1866
|
}
|
|
1514
|
-
var ts2, import_tsdoc, import_core3, NUMERIC_CONSTRAINT_MAP, LENGTH_CONSTRAINT_MAP, TAGS_REQUIRING_RAW_TEXT,
|
|
1867
|
+
var ts2, import_tsdoc, import_core3, NUMERIC_CONSTRAINT_MAP, LENGTH_CONSTRAINT_MAP, TAGS_REQUIRING_RAW_TEXT, parserCache;
|
|
1515
1868
|
var init_tsdoc_parser = __esm({
|
|
1516
1869
|
"src/analyzer/tsdoc-parser.ts"() {
|
|
1517
1870
|
"use strict";
|
|
@@ -1532,17 +1885,18 @@ var init_tsdoc_parser = __esm({
|
|
|
1532
1885
|
minItems: "minItems",
|
|
1533
1886
|
maxItems: "maxItems"
|
|
1534
1887
|
};
|
|
1535
|
-
TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions"]);
|
|
1888
|
+
TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions", "defaultValue"]);
|
|
1889
|
+
parserCache = /* @__PURE__ */ new Map();
|
|
1536
1890
|
}
|
|
1537
1891
|
});
|
|
1538
1892
|
|
|
1539
1893
|
// src/analyzer/jsdoc-constraints.ts
|
|
1540
|
-
function extractJSDocConstraintNodes(node, file = "") {
|
|
1541
|
-
const result = parseTSDocTags(node, file);
|
|
1894
|
+
function extractJSDocConstraintNodes(node, file = "", options) {
|
|
1895
|
+
const result = parseTSDocTags(node, file, options);
|
|
1542
1896
|
return [...result.constraints];
|
|
1543
1897
|
}
|
|
1544
|
-
function extractJSDocAnnotationNodes(node, file = "") {
|
|
1545
|
-
const result = parseTSDocTags(node, file);
|
|
1898
|
+
function extractJSDocAnnotationNodes(node, file = "", options) {
|
|
1899
|
+
const result = parseTSDocTags(node, file, options);
|
|
1546
1900
|
return [...result.annotations];
|
|
1547
1901
|
}
|
|
1548
1902
|
function extractDefaultValueAnnotation(initializer, file = "") {
|
|
@@ -1594,17 +1948,38 @@ function isObjectType(type) {
|
|
|
1594
1948
|
function isTypeReference(type) {
|
|
1595
1949
|
return !!(type.flags & ts4.TypeFlags.Object) && !!(type.objectFlags & ts4.ObjectFlags.Reference);
|
|
1596
1950
|
}
|
|
1597
|
-
function
|
|
1951
|
+
function makeParseOptions(extensionRegistry, fieldType) {
|
|
1952
|
+
if (extensionRegistry === void 0 && fieldType === void 0) {
|
|
1953
|
+
return void 0;
|
|
1954
|
+
}
|
|
1955
|
+
return {
|
|
1956
|
+
...extensionRegistry !== void 0 && { extensionRegistry },
|
|
1957
|
+
...fieldType !== void 0 && { fieldType }
|
|
1958
|
+
};
|
|
1959
|
+
}
|
|
1960
|
+
function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
|
|
1598
1961
|
const name = classDecl.name?.text ?? "AnonymousClass";
|
|
1599
1962
|
const fields = [];
|
|
1600
1963
|
const fieldLayouts = [];
|
|
1601
1964
|
const typeRegistry = {};
|
|
1965
|
+
const annotations = extractJSDocAnnotationNodes(
|
|
1966
|
+
classDecl,
|
|
1967
|
+
file,
|
|
1968
|
+
makeParseOptions(extensionRegistry)
|
|
1969
|
+
);
|
|
1602
1970
|
const visiting = /* @__PURE__ */ new Set();
|
|
1603
1971
|
const instanceMethods = [];
|
|
1604
1972
|
const staticMethods = [];
|
|
1605
1973
|
for (const member of classDecl.members) {
|
|
1606
1974
|
if (ts4.isPropertyDeclaration(member)) {
|
|
1607
|
-
const fieldNode = analyzeFieldToIR(
|
|
1975
|
+
const fieldNode = analyzeFieldToIR(
|
|
1976
|
+
member,
|
|
1977
|
+
checker,
|
|
1978
|
+
file,
|
|
1979
|
+
typeRegistry,
|
|
1980
|
+
visiting,
|
|
1981
|
+
extensionRegistry
|
|
1982
|
+
);
|
|
1608
1983
|
if (fieldNode) {
|
|
1609
1984
|
fields.push(fieldNode);
|
|
1610
1985
|
fieldLayouts.push({});
|
|
@@ -1621,25 +1996,53 @@ function analyzeClassToIR(classDecl, checker, file = "") {
|
|
|
1621
1996
|
}
|
|
1622
1997
|
}
|
|
1623
1998
|
}
|
|
1624
|
-
return {
|
|
1999
|
+
return {
|
|
2000
|
+
name,
|
|
2001
|
+
fields,
|
|
2002
|
+
fieldLayouts,
|
|
2003
|
+
typeRegistry,
|
|
2004
|
+
...annotations.length > 0 && { annotations },
|
|
2005
|
+
instanceMethods,
|
|
2006
|
+
staticMethods
|
|
2007
|
+
};
|
|
1625
2008
|
}
|
|
1626
|
-
function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
|
|
2009
|
+
function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegistry) {
|
|
1627
2010
|
const name = interfaceDecl.name.text;
|
|
1628
2011
|
const fields = [];
|
|
1629
2012
|
const typeRegistry = {};
|
|
2013
|
+
const annotations = extractJSDocAnnotationNodes(
|
|
2014
|
+
interfaceDecl,
|
|
2015
|
+
file,
|
|
2016
|
+
makeParseOptions(extensionRegistry)
|
|
2017
|
+
);
|
|
1630
2018
|
const visiting = /* @__PURE__ */ new Set();
|
|
1631
2019
|
for (const member of interfaceDecl.members) {
|
|
1632
2020
|
if (ts4.isPropertySignature(member)) {
|
|
1633
|
-
const fieldNode = analyzeInterfacePropertyToIR(
|
|
2021
|
+
const fieldNode = analyzeInterfacePropertyToIR(
|
|
2022
|
+
member,
|
|
2023
|
+
checker,
|
|
2024
|
+
file,
|
|
2025
|
+
typeRegistry,
|
|
2026
|
+
visiting,
|
|
2027
|
+
extensionRegistry
|
|
2028
|
+
);
|
|
1634
2029
|
if (fieldNode) {
|
|
1635
2030
|
fields.push(fieldNode);
|
|
1636
2031
|
}
|
|
1637
2032
|
}
|
|
1638
2033
|
}
|
|
1639
2034
|
const fieldLayouts = fields.map(() => ({}));
|
|
1640
|
-
return {
|
|
2035
|
+
return {
|
|
2036
|
+
name,
|
|
2037
|
+
fields,
|
|
2038
|
+
fieldLayouts,
|
|
2039
|
+
typeRegistry,
|
|
2040
|
+
...annotations.length > 0 && { annotations },
|
|
2041
|
+
instanceMethods: [],
|
|
2042
|
+
staticMethods: []
|
|
2043
|
+
};
|
|
1641
2044
|
}
|
|
1642
|
-
function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
2045
|
+
function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry) {
|
|
1643
2046
|
if (!ts4.isTypeLiteralNode(typeAlias.type)) {
|
|
1644
2047
|
const sourceFile = typeAlias.getSourceFile();
|
|
1645
2048
|
const { line } = sourceFile.getLineAndCharacterOfPosition(typeAlias.getStart());
|
|
@@ -1652,10 +2055,22 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
|
1652
2055
|
const name = typeAlias.name.text;
|
|
1653
2056
|
const fields = [];
|
|
1654
2057
|
const typeRegistry = {};
|
|
2058
|
+
const annotations = extractJSDocAnnotationNodes(
|
|
2059
|
+
typeAlias,
|
|
2060
|
+
file,
|
|
2061
|
+
makeParseOptions(extensionRegistry)
|
|
2062
|
+
);
|
|
1655
2063
|
const visiting = /* @__PURE__ */ new Set();
|
|
1656
2064
|
for (const member of typeAlias.type.members) {
|
|
1657
2065
|
if (ts4.isPropertySignature(member)) {
|
|
1658
|
-
const fieldNode = analyzeInterfacePropertyToIR(
|
|
2066
|
+
const fieldNode = analyzeInterfacePropertyToIR(
|
|
2067
|
+
member,
|
|
2068
|
+
checker,
|
|
2069
|
+
file,
|
|
2070
|
+
typeRegistry,
|
|
2071
|
+
visiting,
|
|
2072
|
+
extensionRegistry
|
|
2073
|
+
);
|
|
1659
2074
|
if (fieldNode) {
|
|
1660
2075
|
fields.push(fieldNode);
|
|
1661
2076
|
}
|
|
@@ -1668,12 +2083,13 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
|
1668
2083
|
fields,
|
|
1669
2084
|
fieldLayouts: fields.map(() => ({})),
|
|
1670
2085
|
typeRegistry,
|
|
2086
|
+
...annotations.length > 0 && { annotations },
|
|
1671
2087
|
instanceMethods: [],
|
|
1672
2088
|
staticMethods: []
|
|
1673
2089
|
}
|
|
1674
2090
|
};
|
|
1675
2091
|
}
|
|
1676
|
-
function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
|
|
2092
|
+
function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1677
2093
|
if (!ts4.isIdentifier(prop.name)) {
|
|
1678
2094
|
return null;
|
|
1679
2095
|
}
|
|
@@ -1681,16 +2097,28 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
|
|
|
1681
2097
|
const tsType = checker.getTypeAtLocation(prop);
|
|
1682
2098
|
const optional = prop.questionToken !== void 0;
|
|
1683
2099
|
const provenance = provenanceForNode(prop, file);
|
|
1684
|
-
let type = resolveTypeNode(
|
|
2100
|
+
let type = resolveTypeNode(
|
|
2101
|
+
tsType,
|
|
2102
|
+
checker,
|
|
2103
|
+
file,
|
|
2104
|
+
typeRegistry,
|
|
2105
|
+
visiting,
|
|
2106
|
+
prop,
|
|
2107
|
+
extensionRegistry
|
|
2108
|
+
);
|
|
1685
2109
|
const constraints = [];
|
|
1686
2110
|
if (prop.type) {
|
|
1687
|
-
constraints.push(
|
|
2111
|
+
constraints.push(
|
|
2112
|
+
...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
|
|
2113
|
+
);
|
|
1688
2114
|
}
|
|
1689
|
-
constraints.push(...extractJSDocConstraintNodes(prop, file));
|
|
2115
|
+
constraints.push(...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type)));
|
|
1690
2116
|
let annotations = [];
|
|
1691
|
-
annotations.push(
|
|
2117
|
+
annotations.push(
|
|
2118
|
+
...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
2119
|
+
);
|
|
1692
2120
|
const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
|
|
1693
|
-
if (defaultAnnotation) {
|
|
2121
|
+
if (defaultAnnotation && !annotations.some((a) => a.annotationKind === "defaultValue")) {
|
|
1694
2122
|
annotations.push(defaultAnnotation);
|
|
1695
2123
|
}
|
|
1696
2124
|
({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
|
|
@@ -1704,7 +2132,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
|
|
|
1704
2132
|
provenance
|
|
1705
2133
|
};
|
|
1706
2134
|
}
|
|
1707
|
-
function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting) {
|
|
2135
|
+
function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1708
2136
|
if (!ts4.isIdentifier(prop.name)) {
|
|
1709
2137
|
return null;
|
|
1710
2138
|
}
|
|
@@ -1712,14 +2140,26 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
|
|
|
1712
2140
|
const tsType = checker.getTypeAtLocation(prop);
|
|
1713
2141
|
const optional = prop.questionToken !== void 0;
|
|
1714
2142
|
const provenance = provenanceForNode(prop, file);
|
|
1715
|
-
let type = resolveTypeNode(
|
|
2143
|
+
let type = resolveTypeNode(
|
|
2144
|
+
tsType,
|
|
2145
|
+
checker,
|
|
2146
|
+
file,
|
|
2147
|
+
typeRegistry,
|
|
2148
|
+
visiting,
|
|
2149
|
+
prop,
|
|
2150
|
+
extensionRegistry
|
|
2151
|
+
);
|
|
1716
2152
|
const constraints = [];
|
|
1717
2153
|
if (prop.type) {
|
|
1718
|
-
constraints.push(
|
|
2154
|
+
constraints.push(
|
|
2155
|
+
...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
|
|
2156
|
+
);
|
|
1719
2157
|
}
|
|
1720
|
-
constraints.push(...extractJSDocConstraintNodes(prop, file));
|
|
2158
|
+
constraints.push(...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type)));
|
|
1721
2159
|
let annotations = [];
|
|
1722
|
-
annotations.push(
|
|
2160
|
+
annotations.push(
|
|
2161
|
+
...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
2162
|
+
);
|
|
1723
2163
|
({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
|
|
1724
2164
|
return {
|
|
1725
2165
|
kind: "field",
|
|
@@ -1793,7 +2233,66 @@ function parseEnumMemberDisplayName(value) {
|
|
|
1793
2233
|
if (label === "") return null;
|
|
1794
2234
|
return { value: match[1], label };
|
|
1795
2235
|
}
|
|
1796
|
-
function
|
|
2236
|
+
function resolveRegisteredCustomType(sourceNode, extensionRegistry, checker) {
|
|
2237
|
+
if (sourceNode === void 0 || extensionRegistry === void 0) {
|
|
2238
|
+
return null;
|
|
2239
|
+
}
|
|
2240
|
+
const typeNode = extractTypeNodeFromSource(sourceNode);
|
|
2241
|
+
if (typeNode === void 0) {
|
|
2242
|
+
return null;
|
|
2243
|
+
}
|
|
2244
|
+
return resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker);
|
|
2245
|
+
}
|
|
2246
|
+
function resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker) {
|
|
2247
|
+
if (ts4.isParenthesizedTypeNode(typeNode)) {
|
|
2248
|
+
return resolveRegisteredCustomTypeFromTypeNode(typeNode.type, extensionRegistry, checker);
|
|
2249
|
+
}
|
|
2250
|
+
const typeName = getTypeNodeRegistrationName(typeNode);
|
|
2251
|
+
if (typeName === null) {
|
|
2252
|
+
return null;
|
|
2253
|
+
}
|
|
2254
|
+
const registration = extensionRegistry.findTypeByName(typeName);
|
|
2255
|
+
if (registration !== void 0) {
|
|
2256
|
+
return {
|
|
2257
|
+
kind: "custom",
|
|
2258
|
+
typeId: `${registration.extensionId}/${registration.registration.typeName}`,
|
|
2259
|
+
payload: null
|
|
2260
|
+
};
|
|
2261
|
+
}
|
|
2262
|
+
if (ts4.isTypeReferenceNode(typeNode) && ts4.isIdentifier(typeNode.typeName)) {
|
|
2263
|
+
const aliasDecl = checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts4.isTypeAliasDeclaration);
|
|
2264
|
+
if (aliasDecl !== void 0) {
|
|
2265
|
+
return resolveRegisteredCustomTypeFromTypeNode(aliasDecl.type, extensionRegistry, checker);
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
return null;
|
|
2269
|
+
}
|
|
2270
|
+
function extractTypeNodeFromSource(sourceNode) {
|
|
2271
|
+
if (ts4.isPropertyDeclaration(sourceNode) || ts4.isPropertySignature(sourceNode) || ts4.isParameter(sourceNode) || ts4.isTypeAliasDeclaration(sourceNode)) {
|
|
2272
|
+
return sourceNode.type;
|
|
2273
|
+
}
|
|
2274
|
+
if (ts4.isTypeNode(sourceNode)) {
|
|
2275
|
+
return sourceNode;
|
|
2276
|
+
}
|
|
2277
|
+
return void 0;
|
|
2278
|
+
}
|
|
2279
|
+
function getTypeNodeRegistrationName(typeNode) {
|
|
2280
|
+
if (ts4.isTypeReferenceNode(typeNode)) {
|
|
2281
|
+
return ts4.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : typeNode.typeName.right.text;
|
|
2282
|
+
}
|
|
2283
|
+
if (ts4.isParenthesizedTypeNode(typeNode)) {
|
|
2284
|
+
return getTypeNodeRegistrationName(typeNode.type);
|
|
2285
|
+
}
|
|
2286
|
+
if (typeNode.kind === ts4.SyntaxKind.BigIntKeyword || typeNode.kind === ts4.SyntaxKind.StringKeyword || typeNode.kind === ts4.SyntaxKind.NumberKeyword || typeNode.kind === ts4.SyntaxKind.BooleanKeyword) {
|
|
2287
|
+
return typeNode.getText();
|
|
2288
|
+
}
|
|
2289
|
+
return null;
|
|
2290
|
+
}
|
|
2291
|
+
function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
2292
|
+
const customType = resolveRegisteredCustomType(sourceNode, extensionRegistry, checker);
|
|
2293
|
+
if (customType) {
|
|
2294
|
+
return customType;
|
|
2295
|
+
}
|
|
1797
2296
|
if (type.flags & ts4.TypeFlags.String) {
|
|
1798
2297
|
return { kind: "primitive", primitiveKind: "string" };
|
|
1799
2298
|
}
|
|
@@ -1822,88 +2321,162 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
|
|
|
1822
2321
|
};
|
|
1823
2322
|
}
|
|
1824
2323
|
if (type.isUnion()) {
|
|
1825
|
-
return resolveUnionType(
|
|
2324
|
+
return resolveUnionType(
|
|
2325
|
+
type,
|
|
2326
|
+
checker,
|
|
2327
|
+
file,
|
|
2328
|
+
typeRegistry,
|
|
2329
|
+
visiting,
|
|
2330
|
+
sourceNode,
|
|
2331
|
+
extensionRegistry
|
|
2332
|
+
);
|
|
1826
2333
|
}
|
|
1827
2334
|
if (checker.isArrayType(type)) {
|
|
1828
|
-
return resolveArrayType(
|
|
2335
|
+
return resolveArrayType(
|
|
2336
|
+
type,
|
|
2337
|
+
checker,
|
|
2338
|
+
file,
|
|
2339
|
+
typeRegistry,
|
|
2340
|
+
visiting,
|
|
2341
|
+
sourceNode,
|
|
2342
|
+
extensionRegistry
|
|
2343
|
+
);
|
|
1829
2344
|
}
|
|
1830
2345
|
if (isObjectType(type)) {
|
|
1831
|
-
return resolveObjectType(type, checker, file, typeRegistry, visiting);
|
|
2346
|
+
return resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry);
|
|
1832
2347
|
}
|
|
1833
2348
|
return { kind: "primitive", primitiveKind: "string" };
|
|
1834
2349
|
}
|
|
1835
|
-
function resolveUnionType(type, checker, file, typeRegistry, visiting) {
|
|
2350
|
+
function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
2351
|
+
const typeName = getNamedTypeName(type);
|
|
2352
|
+
const namedDecl = getNamedTypeDeclaration(type);
|
|
2353
|
+
if (typeName && typeName in typeRegistry) {
|
|
2354
|
+
return { kind: "reference", name: typeName, typeArguments: [] };
|
|
2355
|
+
}
|
|
1836
2356
|
const allTypes = type.types;
|
|
2357
|
+
const unionMemberTypeNodes = extractUnionMemberTypeNodes(sourceNode, checker);
|
|
2358
|
+
const nonNullSourceNodes = unionMemberTypeNodes.filter(
|
|
2359
|
+
(memberTypeNode) => !isNullishTypeNode(resolveAliasedTypeNode(memberTypeNode, checker))
|
|
2360
|
+
);
|
|
1837
2361
|
const nonNullTypes = allTypes.filter(
|
|
1838
|
-
(
|
|
2362
|
+
(memberType) => !(memberType.flags & (ts4.TypeFlags.Null | ts4.TypeFlags.Undefined))
|
|
1839
2363
|
);
|
|
2364
|
+
const nonNullMembers = nonNullTypes.map((memberType, index) => ({
|
|
2365
|
+
memberType,
|
|
2366
|
+
sourceNode: nonNullSourceNodes.length === nonNullTypes.length ? nonNullSourceNodes[index] : void 0
|
|
2367
|
+
}));
|
|
1840
2368
|
const hasNull = allTypes.some((t) => t.flags & ts4.TypeFlags.Null);
|
|
2369
|
+
const memberDisplayNames = /* @__PURE__ */ new Map();
|
|
2370
|
+
if (namedDecl) {
|
|
2371
|
+
for (const [value, label] of extractDisplayNameMetadata(namedDecl).memberDisplayNames) {
|
|
2372
|
+
memberDisplayNames.set(value, label);
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
if (sourceNode) {
|
|
2376
|
+
for (const [value, label] of extractDisplayNameMetadata(sourceNode).memberDisplayNames) {
|
|
2377
|
+
memberDisplayNames.set(value, label);
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
const registerNamed = (result) => {
|
|
2381
|
+
if (!typeName) {
|
|
2382
|
+
return result;
|
|
2383
|
+
}
|
|
2384
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
2385
|
+
typeRegistry[typeName] = {
|
|
2386
|
+
name: typeName,
|
|
2387
|
+
type: result,
|
|
2388
|
+
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
2389
|
+
provenance: provenanceForDeclaration(namedDecl ?? sourceNode, file)
|
|
2390
|
+
};
|
|
2391
|
+
return { kind: "reference", name: typeName, typeArguments: [] };
|
|
2392
|
+
};
|
|
2393
|
+
const applyMemberLabels = (members2) => members2.map((value) => {
|
|
2394
|
+
const displayName = memberDisplayNames.get(String(value));
|
|
2395
|
+
return displayName !== void 0 ? { value, displayName } : { value };
|
|
2396
|
+
});
|
|
1841
2397
|
const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts4.TypeFlags.BooleanLiteral);
|
|
1842
2398
|
if (isBooleanUnion2) {
|
|
1843
2399
|
const boolNode = { kind: "primitive", primitiveKind: "boolean" };
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
}
|
|
1850
|
-
return boolNode;
|
|
2400
|
+
const result = hasNull ? {
|
|
2401
|
+
kind: "union",
|
|
2402
|
+
members: [boolNode, { kind: "primitive", primitiveKind: "null" }]
|
|
2403
|
+
} : boolNode;
|
|
2404
|
+
return registerNamed(result);
|
|
1851
2405
|
}
|
|
1852
2406
|
const allStringLiterals = nonNullTypes.every((t) => t.isStringLiteral());
|
|
1853
2407
|
if (allStringLiterals && nonNullTypes.length > 0) {
|
|
1854
2408
|
const stringTypes = nonNullTypes.filter((t) => t.isStringLiteral());
|
|
1855
2409
|
const enumNode = {
|
|
1856
2410
|
kind: "enum",
|
|
1857
|
-
members: stringTypes.map((t) =>
|
|
2411
|
+
members: applyMemberLabels(stringTypes.map((t) => t.value))
|
|
1858
2412
|
};
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
}
|
|
1865
|
-
return enumNode;
|
|
2413
|
+
const result = hasNull ? {
|
|
2414
|
+
kind: "union",
|
|
2415
|
+
members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
|
|
2416
|
+
} : enumNode;
|
|
2417
|
+
return registerNamed(result);
|
|
1866
2418
|
}
|
|
1867
2419
|
const allNumberLiterals = nonNullTypes.every((t) => t.isNumberLiteral());
|
|
1868
2420
|
if (allNumberLiterals && nonNullTypes.length > 0) {
|
|
1869
2421
|
const numberTypes = nonNullTypes.filter((t) => t.isNumberLiteral());
|
|
1870
2422
|
const enumNode = {
|
|
1871
2423
|
kind: "enum",
|
|
1872
|
-
members: numberTypes.map((t) =>
|
|
2424
|
+
members: applyMemberLabels(numberTypes.map((t) => t.value))
|
|
1873
2425
|
};
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
2426
|
+
const result = hasNull ? {
|
|
2427
|
+
kind: "union",
|
|
2428
|
+
members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
|
|
2429
|
+
} : enumNode;
|
|
2430
|
+
return registerNamed(result);
|
|
2431
|
+
}
|
|
2432
|
+
if (nonNullMembers.length === 1 && nonNullMembers[0]) {
|
|
2433
|
+
const inner = resolveTypeNode(
|
|
2434
|
+
nonNullMembers[0].memberType,
|
|
2435
|
+
checker,
|
|
2436
|
+
file,
|
|
2437
|
+
typeRegistry,
|
|
2438
|
+
visiting,
|
|
2439
|
+
nonNullMembers[0].sourceNode ?? sourceNode,
|
|
2440
|
+
extensionRegistry
|
|
2441
|
+
);
|
|
2442
|
+
const result = hasNull ? {
|
|
2443
|
+
kind: "union",
|
|
2444
|
+
members: [inner, { kind: "primitive", primitiveKind: "null" }]
|
|
2445
|
+
} : inner;
|
|
2446
|
+
return registerNamed(result);
|
|
2447
|
+
}
|
|
2448
|
+
const members = nonNullMembers.map(
|
|
2449
|
+
({ memberType, sourceNode: memberSourceNode }) => resolveTypeNode(
|
|
2450
|
+
memberType,
|
|
2451
|
+
checker,
|
|
2452
|
+
file,
|
|
2453
|
+
typeRegistry,
|
|
2454
|
+
visiting,
|
|
2455
|
+
memberSourceNode ?? sourceNode,
|
|
2456
|
+
extensionRegistry
|
|
2457
|
+
)
|
|
1894
2458
|
);
|
|
1895
2459
|
if (hasNull) {
|
|
1896
2460
|
members.push({ kind: "primitive", primitiveKind: "null" });
|
|
1897
2461
|
}
|
|
1898
|
-
return { kind: "union", members };
|
|
2462
|
+
return registerNamed({ kind: "union", members });
|
|
1899
2463
|
}
|
|
1900
|
-
function resolveArrayType(type, checker, file, typeRegistry, visiting) {
|
|
2464
|
+
function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
1901
2465
|
const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
|
|
1902
2466
|
const elementType = typeArgs?.[0];
|
|
1903
|
-
const
|
|
2467
|
+
const elementSourceNode = extractArrayElementTypeNode(sourceNode, checker);
|
|
2468
|
+
const items = elementType ? resolveTypeNode(
|
|
2469
|
+
elementType,
|
|
2470
|
+
checker,
|
|
2471
|
+
file,
|
|
2472
|
+
typeRegistry,
|
|
2473
|
+
visiting,
|
|
2474
|
+
elementSourceNode,
|
|
2475
|
+
extensionRegistry
|
|
2476
|
+
) : { kind: "primitive", primitiveKind: "string" };
|
|
1904
2477
|
return { kind: "array", items };
|
|
1905
2478
|
}
|
|
1906
|
-
function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
|
|
2479
|
+
function tryResolveRecordType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1907
2480
|
if (type.getProperties().length > 0) {
|
|
1908
2481
|
return null;
|
|
1909
2482
|
}
|
|
@@ -1911,39 +2484,123 @@ function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
|
|
|
1911
2484
|
if (!indexInfo) {
|
|
1912
2485
|
return null;
|
|
1913
2486
|
}
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
}
|
|
2487
|
+
const valueType = resolveTypeNode(
|
|
2488
|
+
indexInfo.type,
|
|
2489
|
+
checker,
|
|
2490
|
+
file,
|
|
2491
|
+
typeRegistry,
|
|
2492
|
+
visiting,
|
|
2493
|
+
void 0,
|
|
2494
|
+
extensionRegistry
|
|
2495
|
+
);
|
|
2496
|
+
return { kind: "record", valueType };
|
|
1924
2497
|
}
|
|
1925
|
-
function
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
2498
|
+
function typeNodeContainsReference(type, targetName) {
|
|
2499
|
+
switch (type.kind) {
|
|
2500
|
+
case "reference":
|
|
2501
|
+
return type.name === targetName;
|
|
2502
|
+
case "array":
|
|
2503
|
+
return typeNodeContainsReference(type.items, targetName);
|
|
2504
|
+
case "record":
|
|
2505
|
+
return typeNodeContainsReference(type.valueType, targetName);
|
|
2506
|
+
case "union":
|
|
2507
|
+
return type.members.some((member) => typeNodeContainsReference(member, targetName));
|
|
2508
|
+
case "object":
|
|
2509
|
+
return type.properties.some(
|
|
2510
|
+
(property) => typeNodeContainsReference(property.type, targetName)
|
|
2511
|
+
);
|
|
2512
|
+
case "primitive":
|
|
2513
|
+
case "enum":
|
|
2514
|
+
case "dynamic":
|
|
2515
|
+
case "custom":
|
|
2516
|
+
return false;
|
|
2517
|
+
default: {
|
|
2518
|
+
const _exhaustive = type;
|
|
2519
|
+
return _exhaustive;
|
|
2520
|
+
}
|
|
1929
2521
|
}
|
|
2522
|
+
}
|
|
2523
|
+
function resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2524
|
+
const typeName = getNamedTypeName(type);
|
|
2525
|
+
const namedTypeName = typeName ?? void 0;
|
|
2526
|
+
const namedDecl = getNamedTypeDeclaration(type);
|
|
2527
|
+
const shouldRegisterNamedType = namedTypeName !== void 0 && !(namedTypeName === "Record" && namedDecl?.getSourceFile().fileName !== file);
|
|
2528
|
+
const clearNamedTypeRegistration = () => {
|
|
2529
|
+
if (namedTypeName === void 0 || !shouldRegisterNamedType) {
|
|
2530
|
+
return;
|
|
2531
|
+
}
|
|
2532
|
+
Reflect.deleteProperty(typeRegistry, namedTypeName);
|
|
2533
|
+
};
|
|
1930
2534
|
if (visiting.has(type)) {
|
|
2535
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
2536
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
2537
|
+
}
|
|
1931
2538
|
return { kind: "object", properties: [], additionalProperties: false };
|
|
1932
2539
|
}
|
|
2540
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType && !typeRegistry[namedTypeName]) {
|
|
2541
|
+
typeRegistry[namedTypeName] = {
|
|
2542
|
+
name: namedTypeName,
|
|
2543
|
+
type: RESOLVING_TYPE_PLACEHOLDER,
|
|
2544
|
+
provenance: provenanceForDeclaration(namedDecl, file)
|
|
2545
|
+
};
|
|
2546
|
+
}
|
|
1933
2547
|
visiting.add(type);
|
|
1934
|
-
|
|
1935
|
-
|
|
2548
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType && typeRegistry[namedTypeName]?.type !== void 0) {
|
|
2549
|
+
if (typeRegistry[namedTypeName].type !== RESOLVING_TYPE_PLACEHOLDER) {
|
|
2550
|
+
visiting.delete(type);
|
|
2551
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
2552
|
+
}
|
|
2553
|
+
}
|
|
2554
|
+
const recordNode = tryResolveRecordType(
|
|
2555
|
+
type,
|
|
2556
|
+
checker,
|
|
2557
|
+
file,
|
|
2558
|
+
typeRegistry,
|
|
2559
|
+
visiting,
|
|
2560
|
+
extensionRegistry
|
|
2561
|
+
);
|
|
2562
|
+
if (recordNode) {
|
|
1936
2563
|
visiting.delete(type);
|
|
1937
|
-
|
|
2564
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
2565
|
+
const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType, namedTypeName);
|
|
2566
|
+
if (!isRecursiveRecord) {
|
|
2567
|
+
clearNamedTypeRegistration();
|
|
2568
|
+
return recordNode;
|
|
2569
|
+
}
|
|
2570
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
2571
|
+
typeRegistry[namedTypeName] = {
|
|
2572
|
+
name: namedTypeName,
|
|
2573
|
+
type: recordNode,
|
|
2574
|
+
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
2575
|
+
provenance: provenanceForDeclaration(namedDecl, file)
|
|
2576
|
+
};
|
|
2577
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
2578
|
+
}
|
|
2579
|
+
return recordNode;
|
|
1938
2580
|
}
|
|
1939
2581
|
const properties = [];
|
|
1940
|
-
const fieldInfoMap = getNamedTypeFieldNodeInfoMap(
|
|
2582
|
+
const fieldInfoMap = getNamedTypeFieldNodeInfoMap(
|
|
2583
|
+
type,
|
|
2584
|
+
checker,
|
|
2585
|
+
file,
|
|
2586
|
+
typeRegistry,
|
|
2587
|
+
visiting,
|
|
2588
|
+
extensionRegistry
|
|
2589
|
+
);
|
|
1941
2590
|
for (const prop of type.getProperties()) {
|
|
1942
2591
|
const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
|
|
1943
2592
|
if (!declaration) continue;
|
|
1944
2593
|
const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
|
|
1945
2594
|
const optional = !!(prop.flags & ts4.SymbolFlags.Optional);
|
|
1946
|
-
const propTypeNode = resolveTypeNode(
|
|
2595
|
+
const propTypeNode = resolveTypeNode(
|
|
2596
|
+
propType,
|
|
2597
|
+
checker,
|
|
2598
|
+
file,
|
|
2599
|
+
typeRegistry,
|
|
2600
|
+
visiting,
|
|
2601
|
+
declaration,
|
|
2602
|
+
extensionRegistry
|
|
2603
|
+
);
|
|
1947
2604
|
const fieldNodeInfo = fieldInfoMap?.get(prop.name);
|
|
1948
2605
|
properties.push({
|
|
1949
2606
|
name: prop.name,
|
|
@@ -1960,17 +2617,19 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
|
1960
2617
|
properties,
|
|
1961
2618
|
additionalProperties: true
|
|
1962
2619
|
};
|
|
1963
|
-
if (
|
|
1964
|
-
|
|
1965
|
-
|
|
2620
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
2621
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
2622
|
+
typeRegistry[namedTypeName] = {
|
|
2623
|
+
name: namedTypeName,
|
|
1966
2624
|
type: objectNode,
|
|
1967
|
-
|
|
2625
|
+
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
2626
|
+
provenance: provenanceForDeclaration(namedDecl, file)
|
|
1968
2627
|
};
|
|
1969
|
-
return { kind: "reference", name:
|
|
2628
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
1970
2629
|
}
|
|
1971
2630
|
return objectNode;
|
|
1972
2631
|
}
|
|
1973
|
-
function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting) {
|
|
2632
|
+
function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1974
2633
|
const symbols = [type.getSymbol(), type.aliasSymbol].filter(
|
|
1975
2634
|
(s) => s?.declarations != null && s.declarations.length > 0
|
|
1976
2635
|
);
|
|
@@ -1982,7 +2641,14 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
1982
2641
|
const map = /* @__PURE__ */ new Map();
|
|
1983
2642
|
for (const member of classDecl.members) {
|
|
1984
2643
|
if (ts4.isPropertyDeclaration(member) && ts4.isIdentifier(member.name)) {
|
|
1985
|
-
const fieldNode = analyzeFieldToIR(
|
|
2644
|
+
const fieldNode = analyzeFieldToIR(
|
|
2645
|
+
member,
|
|
2646
|
+
checker,
|
|
2647
|
+
file,
|
|
2648
|
+
typeRegistry,
|
|
2649
|
+
visiting,
|
|
2650
|
+
extensionRegistry
|
|
2651
|
+
);
|
|
1986
2652
|
if (fieldNode) {
|
|
1987
2653
|
map.set(fieldNode.name, {
|
|
1988
2654
|
constraints: [...fieldNode.constraints],
|
|
@@ -1996,7 +2662,14 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
1996
2662
|
}
|
|
1997
2663
|
const interfaceDecl = declarations.find(ts4.isInterfaceDeclaration);
|
|
1998
2664
|
if (interfaceDecl) {
|
|
1999
|
-
return buildFieldNodeInfoMap(
|
|
2665
|
+
return buildFieldNodeInfoMap(
|
|
2666
|
+
interfaceDecl.members,
|
|
2667
|
+
checker,
|
|
2668
|
+
file,
|
|
2669
|
+
typeRegistry,
|
|
2670
|
+
visiting,
|
|
2671
|
+
extensionRegistry
|
|
2672
|
+
);
|
|
2000
2673
|
}
|
|
2001
2674
|
const typeAliasDecl = declarations.find(ts4.isTypeAliasDeclaration);
|
|
2002
2675
|
if (typeAliasDecl && ts4.isTypeLiteralNode(typeAliasDecl.type)) {
|
|
@@ -2005,17 +2678,68 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
2005
2678
|
checker,
|
|
2006
2679
|
file,
|
|
2007
2680
|
typeRegistry,
|
|
2008
|
-
visiting
|
|
2681
|
+
visiting,
|
|
2682
|
+
extensionRegistry
|
|
2009
2683
|
);
|
|
2010
2684
|
}
|
|
2011
2685
|
}
|
|
2012
2686
|
return null;
|
|
2013
2687
|
}
|
|
2014
|
-
function
|
|
2688
|
+
function extractArrayElementTypeNode(sourceNode, checker) {
|
|
2689
|
+
const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
|
|
2690
|
+
if (typeNode === void 0) {
|
|
2691
|
+
return void 0;
|
|
2692
|
+
}
|
|
2693
|
+
const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
|
|
2694
|
+
if (ts4.isArrayTypeNode(resolvedTypeNode)) {
|
|
2695
|
+
return resolvedTypeNode.elementType;
|
|
2696
|
+
}
|
|
2697
|
+
if (ts4.isTypeReferenceNode(resolvedTypeNode) && ts4.isIdentifier(resolvedTypeNode.typeName) && resolvedTypeNode.typeName.text === "Array" && resolvedTypeNode.typeArguments?.[0]) {
|
|
2698
|
+
return resolvedTypeNode.typeArguments[0];
|
|
2699
|
+
}
|
|
2700
|
+
return void 0;
|
|
2701
|
+
}
|
|
2702
|
+
function extractUnionMemberTypeNodes(sourceNode, checker) {
|
|
2703
|
+
const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
|
|
2704
|
+
if (!typeNode) {
|
|
2705
|
+
return [];
|
|
2706
|
+
}
|
|
2707
|
+
const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
|
|
2708
|
+
return ts4.isUnionTypeNode(resolvedTypeNode) ? [...resolvedTypeNode.types] : [];
|
|
2709
|
+
}
|
|
2710
|
+
function resolveAliasedTypeNode(typeNode, checker, visited = /* @__PURE__ */ new Set()) {
|
|
2711
|
+
if (ts4.isParenthesizedTypeNode(typeNode)) {
|
|
2712
|
+
return resolveAliasedTypeNode(typeNode.type, checker, visited);
|
|
2713
|
+
}
|
|
2714
|
+
if (!ts4.isTypeReferenceNode(typeNode) || !ts4.isIdentifier(typeNode.typeName)) {
|
|
2715
|
+
return typeNode;
|
|
2716
|
+
}
|
|
2717
|
+
const symbol = checker.getSymbolAtLocation(typeNode.typeName);
|
|
2718
|
+
const aliasDecl = symbol?.declarations?.find(ts4.isTypeAliasDeclaration);
|
|
2719
|
+
if (aliasDecl === void 0 || visited.has(aliasDecl)) {
|
|
2720
|
+
return typeNode;
|
|
2721
|
+
}
|
|
2722
|
+
visited.add(aliasDecl);
|
|
2723
|
+
return resolveAliasedTypeNode(aliasDecl.type, checker, visited);
|
|
2724
|
+
}
|
|
2725
|
+
function isNullishTypeNode(typeNode) {
|
|
2726
|
+
if (typeNode.kind === ts4.SyntaxKind.NullKeyword || typeNode.kind === ts4.SyntaxKind.UndefinedKeyword) {
|
|
2727
|
+
return true;
|
|
2728
|
+
}
|
|
2729
|
+
return ts4.isLiteralTypeNode(typeNode) && (typeNode.literal.kind === ts4.SyntaxKind.NullKeyword || typeNode.literal.kind === ts4.SyntaxKind.UndefinedKeyword);
|
|
2730
|
+
}
|
|
2731
|
+
function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2015
2732
|
const map = /* @__PURE__ */ new Map();
|
|
2016
2733
|
for (const member of members) {
|
|
2017
2734
|
if (ts4.isPropertySignature(member)) {
|
|
2018
|
-
const fieldNode = analyzeInterfacePropertyToIR(
|
|
2735
|
+
const fieldNode = analyzeInterfacePropertyToIR(
|
|
2736
|
+
member,
|
|
2737
|
+
checker,
|
|
2738
|
+
file,
|
|
2739
|
+
typeRegistry,
|
|
2740
|
+
visiting,
|
|
2741
|
+
extensionRegistry
|
|
2742
|
+
);
|
|
2019
2743
|
if (fieldNode) {
|
|
2020
2744
|
map.set(fieldNode.name, {
|
|
2021
2745
|
constraints: [...fieldNode.constraints],
|
|
@@ -2027,7 +2751,7 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting) {
|
|
|
2027
2751
|
}
|
|
2028
2752
|
return map;
|
|
2029
2753
|
}
|
|
2030
|
-
function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
|
|
2754
|
+
function extractTypeAliasConstraintNodes(typeNode, checker, file, extensionRegistry, depth = 0) {
|
|
2031
2755
|
if (!ts4.isTypeReferenceNode(typeNode)) return [];
|
|
2032
2756
|
if (depth >= MAX_ALIAS_CHAIN_DEPTH) {
|
|
2033
2757
|
const aliasName = typeNode.typeName.getText();
|
|
@@ -2040,8 +2764,29 @@ function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
|
|
|
2040
2764
|
const aliasDecl = symbol.declarations.find(ts4.isTypeAliasDeclaration);
|
|
2041
2765
|
if (!aliasDecl) return [];
|
|
2042
2766
|
if (ts4.isTypeLiteralNode(aliasDecl.type)) return [];
|
|
2043
|
-
const
|
|
2044
|
-
|
|
2767
|
+
const aliasFieldType = resolveTypeNode(
|
|
2768
|
+
checker.getTypeAtLocation(aliasDecl.type),
|
|
2769
|
+
checker,
|
|
2770
|
+
file,
|
|
2771
|
+
{},
|
|
2772
|
+
/* @__PURE__ */ new Set(),
|
|
2773
|
+
aliasDecl.type,
|
|
2774
|
+
extensionRegistry
|
|
2775
|
+
);
|
|
2776
|
+
const constraints = extractJSDocConstraintNodes(
|
|
2777
|
+
aliasDecl,
|
|
2778
|
+
file,
|
|
2779
|
+
makeParseOptions(extensionRegistry, aliasFieldType)
|
|
2780
|
+
);
|
|
2781
|
+
constraints.push(
|
|
2782
|
+
...extractTypeAliasConstraintNodes(
|
|
2783
|
+
aliasDecl.type,
|
|
2784
|
+
checker,
|
|
2785
|
+
file,
|
|
2786
|
+
extensionRegistry,
|
|
2787
|
+
depth + 1
|
|
2788
|
+
)
|
|
2789
|
+
);
|
|
2045
2790
|
return constraints;
|
|
2046
2791
|
}
|
|
2047
2792
|
function provenanceForNode(node, file) {
|
|
@@ -2057,6 +2802,12 @@ function provenanceForNode(node, file) {
|
|
|
2057
2802
|
function provenanceForFile(file) {
|
|
2058
2803
|
return { surface: "tsdoc", file, line: 0, column: 0 };
|
|
2059
2804
|
}
|
|
2805
|
+
function provenanceForDeclaration(node, file) {
|
|
2806
|
+
if (!node) {
|
|
2807
|
+
return provenanceForFile(file);
|
|
2808
|
+
}
|
|
2809
|
+
return provenanceForNode(node, file);
|
|
2810
|
+
}
|
|
2060
2811
|
function getNamedTypeName(type) {
|
|
2061
2812
|
const symbol = type.getSymbol();
|
|
2062
2813
|
if (symbol?.declarations) {
|
|
@@ -2075,6 +2826,20 @@ function getNamedTypeName(type) {
|
|
|
2075
2826
|
}
|
|
2076
2827
|
return null;
|
|
2077
2828
|
}
|
|
2829
|
+
function getNamedTypeDeclaration(type) {
|
|
2830
|
+
const symbol = type.getSymbol();
|
|
2831
|
+
if (symbol?.declarations) {
|
|
2832
|
+
const decl = symbol.declarations[0];
|
|
2833
|
+
if (decl && (ts4.isClassDeclaration(decl) || ts4.isInterfaceDeclaration(decl) || ts4.isTypeAliasDeclaration(decl))) {
|
|
2834
|
+
return decl;
|
|
2835
|
+
}
|
|
2836
|
+
}
|
|
2837
|
+
const aliasSymbol = type.aliasSymbol;
|
|
2838
|
+
if (aliasSymbol?.declarations) {
|
|
2839
|
+
return aliasSymbol.declarations.find(ts4.isTypeAliasDeclaration);
|
|
2840
|
+
}
|
|
2841
|
+
return void 0;
|
|
2842
|
+
}
|
|
2078
2843
|
function analyzeMethod(method, checker) {
|
|
2079
2844
|
if (!ts4.isIdentifier(method.name)) {
|
|
2080
2845
|
return null;
|
|
@@ -2115,21 +2880,27 @@ function detectFormSpecReference(typeNode) {
|
|
|
2115
2880
|
}
|
|
2116
2881
|
return null;
|
|
2117
2882
|
}
|
|
2118
|
-
var ts4, MAX_ALIAS_CHAIN_DEPTH;
|
|
2883
|
+
var ts4, RESOLVING_TYPE_PLACEHOLDER, MAX_ALIAS_CHAIN_DEPTH;
|
|
2119
2884
|
var init_class_analyzer = __esm({
|
|
2120
2885
|
"src/analyzer/class-analyzer.ts"() {
|
|
2121
2886
|
"use strict";
|
|
2122
2887
|
ts4 = __toESM(require("typescript"), 1);
|
|
2123
2888
|
init_jsdoc_constraints();
|
|
2889
|
+
init_tsdoc_parser();
|
|
2890
|
+
RESOLVING_TYPE_PLACEHOLDER = {
|
|
2891
|
+
kind: "object",
|
|
2892
|
+
properties: [],
|
|
2893
|
+
additionalProperties: true
|
|
2894
|
+
};
|
|
2124
2895
|
MAX_ALIAS_CHAIN_DEPTH = 8;
|
|
2125
2896
|
}
|
|
2126
2897
|
});
|
|
2127
2898
|
|
|
2128
2899
|
// src/generators/class-schema.ts
|
|
2129
|
-
function generateClassSchemas(analysis, source) {
|
|
2900
|
+
function generateClassSchemas(analysis, source, options) {
|
|
2130
2901
|
const ir = canonicalizeTSDoc(analysis, source);
|
|
2131
2902
|
return {
|
|
2132
|
-
jsonSchema: generateJsonSchemaFromIR(ir),
|
|
2903
|
+
jsonSchema: generateJsonSchemaFromIR(ir, options),
|
|
2133
2904
|
uiSchema: generateUiSchemaFromIR(ir)
|
|
2134
2905
|
};
|
|
2135
2906
|
}
|
|
@@ -2139,27 +2910,54 @@ function generateSchemasFromClass(options) {
|
|
|
2139
2910
|
if (!classDecl) {
|
|
2140
2911
|
throw new Error(`Class "${options.className}" not found in ${options.filePath}`);
|
|
2141
2912
|
}
|
|
2142
|
-
const analysis = analyzeClassToIR(
|
|
2143
|
-
|
|
2913
|
+
const analysis = analyzeClassToIR(
|
|
2914
|
+
classDecl,
|
|
2915
|
+
ctx.checker,
|
|
2916
|
+
options.filePath,
|
|
2917
|
+
options.extensionRegistry
|
|
2918
|
+
);
|
|
2919
|
+
return generateClassSchemas(
|
|
2920
|
+
analysis,
|
|
2921
|
+
{ file: options.filePath },
|
|
2922
|
+
{
|
|
2923
|
+
extensionRegistry: options.extensionRegistry,
|
|
2924
|
+
vendorPrefix: options.vendorPrefix
|
|
2925
|
+
}
|
|
2926
|
+
);
|
|
2144
2927
|
}
|
|
2145
2928
|
function generateSchemas(options) {
|
|
2146
2929
|
const ctx = createProgramContext(options.filePath);
|
|
2147
2930
|
const source = { file: options.filePath };
|
|
2148
2931
|
const classDecl = findClassByName(ctx.sourceFile, options.typeName);
|
|
2149
2932
|
if (classDecl) {
|
|
2150
|
-
const analysis = analyzeClassToIR(
|
|
2151
|
-
|
|
2933
|
+
const analysis = analyzeClassToIR(
|
|
2934
|
+
classDecl,
|
|
2935
|
+
ctx.checker,
|
|
2936
|
+
options.filePath,
|
|
2937
|
+
options.extensionRegistry
|
|
2938
|
+
);
|
|
2939
|
+
return generateClassSchemas(analysis, source, options);
|
|
2152
2940
|
}
|
|
2153
2941
|
const interfaceDecl = findInterfaceByName(ctx.sourceFile, options.typeName);
|
|
2154
2942
|
if (interfaceDecl) {
|
|
2155
|
-
const analysis = analyzeInterfaceToIR(
|
|
2156
|
-
|
|
2943
|
+
const analysis = analyzeInterfaceToIR(
|
|
2944
|
+
interfaceDecl,
|
|
2945
|
+
ctx.checker,
|
|
2946
|
+
options.filePath,
|
|
2947
|
+
options.extensionRegistry
|
|
2948
|
+
);
|
|
2949
|
+
return generateClassSchemas(analysis, source, options);
|
|
2157
2950
|
}
|
|
2158
2951
|
const typeAlias = findTypeAliasByName(ctx.sourceFile, options.typeName);
|
|
2159
2952
|
if (typeAlias) {
|
|
2160
|
-
const result = analyzeTypeAliasToIR(
|
|
2953
|
+
const result = analyzeTypeAliasToIR(
|
|
2954
|
+
typeAlias,
|
|
2955
|
+
ctx.checker,
|
|
2956
|
+
options.filePath,
|
|
2957
|
+
options.extensionRegistry
|
|
2958
|
+
);
|
|
2161
2959
|
if (result.ok) {
|
|
2162
|
-
return generateClassSchemas(result.analysis, source);
|
|
2960
|
+
return generateClassSchemas(result.analysis, source, options);
|
|
2163
2961
|
}
|
|
2164
2962
|
throw new Error(result.error);
|
|
2165
2963
|
}
|
|
@@ -2178,10 +2976,220 @@ var init_class_schema = __esm({
|
|
|
2178
2976
|
}
|
|
2179
2977
|
});
|
|
2180
2978
|
|
|
2979
|
+
// src/generators/mixed-authoring.ts
|
|
2980
|
+
function buildMixedAuthoringSchemas(options) {
|
|
2981
|
+
const { filePath, typeName, overlays, ...schemaOptions } = options;
|
|
2982
|
+
const analysis = analyzeNamedType(filePath, typeName, schemaOptions.extensionRegistry);
|
|
2983
|
+
const composedAnalysis = composeAnalysisWithOverlays(analysis, overlays);
|
|
2984
|
+
const ir = canonicalizeTSDoc(composedAnalysis, { file: filePath });
|
|
2985
|
+
return {
|
|
2986
|
+
jsonSchema: generateJsonSchemaFromIR(ir, schemaOptions),
|
|
2987
|
+
uiSchema: generateUiSchemaFromIR(ir)
|
|
2988
|
+
};
|
|
2989
|
+
}
|
|
2990
|
+
function analyzeNamedType(filePath, typeName, extensionRegistry) {
|
|
2991
|
+
const ctx = createProgramContext(filePath);
|
|
2992
|
+
const source = { file: filePath };
|
|
2993
|
+
const classDecl = findClassByName(ctx.sourceFile, typeName);
|
|
2994
|
+
if (classDecl !== null) {
|
|
2995
|
+
return analyzeClassToIR(classDecl, ctx.checker, source.file, extensionRegistry);
|
|
2996
|
+
}
|
|
2997
|
+
const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
|
|
2998
|
+
if (interfaceDecl !== null) {
|
|
2999
|
+
return analyzeInterfaceToIR(interfaceDecl, ctx.checker, source.file, extensionRegistry);
|
|
3000
|
+
}
|
|
3001
|
+
const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
|
|
3002
|
+
if (typeAlias !== null) {
|
|
3003
|
+
const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, source.file, extensionRegistry);
|
|
3004
|
+
if (result.ok) {
|
|
3005
|
+
return result.analysis;
|
|
3006
|
+
}
|
|
3007
|
+
throw new Error(result.error);
|
|
3008
|
+
}
|
|
3009
|
+
throw new Error(
|
|
3010
|
+
`Type "${typeName}" not found as a class, interface, or type alias in ${filePath}`
|
|
3011
|
+
);
|
|
3012
|
+
}
|
|
3013
|
+
function composeAnalysisWithOverlays(analysis, overlays) {
|
|
3014
|
+
const overlayIR = canonicalizeChainDSL(overlays);
|
|
3015
|
+
const overlayFields = collectOverlayFields(overlayIR.elements);
|
|
3016
|
+
if (overlayFields.length === 0) {
|
|
3017
|
+
return analysis;
|
|
3018
|
+
}
|
|
3019
|
+
const overlayByName = /* @__PURE__ */ new Map();
|
|
3020
|
+
for (const field of overlayFields) {
|
|
3021
|
+
if (overlayByName.has(field.name)) {
|
|
3022
|
+
throw new Error(`Mixed-authoring overlays define "${field.name}" more than once`);
|
|
3023
|
+
}
|
|
3024
|
+
overlayByName.set(field.name, field);
|
|
3025
|
+
}
|
|
3026
|
+
const mergedFields = [];
|
|
3027
|
+
for (const baseField of analysis.fields) {
|
|
3028
|
+
const overlayField = overlayByName.get(baseField.name);
|
|
3029
|
+
if (overlayField === void 0) {
|
|
3030
|
+
mergedFields.push(baseField);
|
|
3031
|
+
continue;
|
|
3032
|
+
}
|
|
3033
|
+
mergedFields.push(mergeFieldOverlay(baseField, overlayField, analysis.typeRegistry));
|
|
3034
|
+
overlayByName.delete(baseField.name);
|
|
3035
|
+
}
|
|
3036
|
+
if (overlayByName.size > 0) {
|
|
3037
|
+
const unknownFields = [...overlayByName.keys()].sort().join(", ");
|
|
3038
|
+
throw new Error(
|
|
3039
|
+
`Mixed-authoring overlays reference fields that are not present in the static model: ${unknownFields}`
|
|
3040
|
+
);
|
|
3041
|
+
}
|
|
3042
|
+
return {
|
|
3043
|
+
...analysis,
|
|
3044
|
+
fields: mergedFields
|
|
3045
|
+
};
|
|
3046
|
+
}
|
|
3047
|
+
function collectOverlayFields(elements) {
|
|
3048
|
+
const fields = [];
|
|
3049
|
+
for (const element of elements) {
|
|
3050
|
+
switch (element.kind) {
|
|
3051
|
+
case "field":
|
|
3052
|
+
fields.push(element);
|
|
3053
|
+
break;
|
|
3054
|
+
case "group":
|
|
3055
|
+
fields.push(...collectOverlayFields(element.elements));
|
|
3056
|
+
break;
|
|
3057
|
+
case "conditional":
|
|
3058
|
+
fields.push(...collectOverlayFields(element.elements));
|
|
3059
|
+
break;
|
|
3060
|
+
default: {
|
|
3061
|
+
const _exhaustive = element;
|
|
3062
|
+
void _exhaustive;
|
|
3063
|
+
}
|
|
3064
|
+
}
|
|
3065
|
+
}
|
|
3066
|
+
return fields;
|
|
3067
|
+
}
|
|
3068
|
+
function mergeFieldOverlay(baseField, overlayField, typeRegistry) {
|
|
3069
|
+
assertSupportedOverlayField(baseField, overlayField);
|
|
3070
|
+
return {
|
|
3071
|
+
...baseField,
|
|
3072
|
+
type: mergeFieldType(baseField, overlayField, typeRegistry),
|
|
3073
|
+
annotations: mergeAnnotations(baseField.annotations, overlayField.annotations)
|
|
3074
|
+
};
|
|
3075
|
+
}
|
|
3076
|
+
function assertSupportedOverlayField(baseField, overlayField) {
|
|
3077
|
+
if (overlayField.constraints.length > 0) {
|
|
3078
|
+
throw new Error(
|
|
3079
|
+
`Mixed-authoring overlay for "${baseField.name}" cannot define constraints; keep constraints on the static model`
|
|
3080
|
+
);
|
|
3081
|
+
}
|
|
3082
|
+
if (overlayField.required && !baseField.required) {
|
|
3083
|
+
throw new Error(
|
|
3084
|
+
`Mixed-authoring overlay for "${baseField.name}" cannot change requiredness; keep requiredness on the static model`
|
|
3085
|
+
);
|
|
3086
|
+
}
|
|
3087
|
+
}
|
|
3088
|
+
function mergeFieldType(baseField, overlayField, typeRegistry) {
|
|
3089
|
+
const { type: baseType } = baseField;
|
|
3090
|
+
const { type: overlayType } = overlayField;
|
|
3091
|
+
if (overlayType.kind === "object" || overlayType.kind === "array") {
|
|
3092
|
+
throw new Error(
|
|
3093
|
+
`Mixed-authoring overlays do not support nested object or array overlays for "${baseField.name}"`
|
|
3094
|
+
);
|
|
3095
|
+
}
|
|
3096
|
+
if (overlayType.kind === "dynamic") {
|
|
3097
|
+
if (!isCompatibleDynamicOverlay(baseField, overlayField, typeRegistry)) {
|
|
3098
|
+
throw new Error(
|
|
3099
|
+
`Mixed-authoring overlay for "${baseField.name}" is incompatible with the static field type`
|
|
3100
|
+
);
|
|
3101
|
+
}
|
|
3102
|
+
return overlayType;
|
|
3103
|
+
}
|
|
3104
|
+
if (!isSameStaticTypeShape(baseType, overlayType)) {
|
|
3105
|
+
throw new Error(
|
|
3106
|
+
`Mixed-authoring overlay for "${baseField.name}" must preserve the static field type`
|
|
3107
|
+
);
|
|
3108
|
+
}
|
|
3109
|
+
return baseType;
|
|
3110
|
+
}
|
|
3111
|
+
function isCompatibleDynamicOverlay(baseField, overlayField, typeRegistry) {
|
|
3112
|
+
const overlayType = overlayField.type;
|
|
3113
|
+
if (overlayType.kind !== "dynamic") {
|
|
3114
|
+
return false;
|
|
3115
|
+
}
|
|
3116
|
+
const resolvedBaseType = resolveReferenceType(baseField.type, typeRegistry);
|
|
3117
|
+
if (resolvedBaseType === null) {
|
|
3118
|
+
return false;
|
|
3119
|
+
}
|
|
3120
|
+
if (overlayType.dynamicKind === "enum") {
|
|
3121
|
+
return resolvedBaseType.kind === "primitive" ? resolvedBaseType.primitiveKind === "string" : resolvedBaseType.kind === "enum";
|
|
3122
|
+
}
|
|
3123
|
+
return resolvedBaseType.kind === "object" || resolvedBaseType.kind === "record";
|
|
3124
|
+
}
|
|
3125
|
+
function resolveReferenceType(type, typeRegistry, seen = /* @__PURE__ */ new Set()) {
|
|
3126
|
+
if (type.kind !== "reference") {
|
|
3127
|
+
return type;
|
|
3128
|
+
}
|
|
3129
|
+
if (seen.has(type.name)) {
|
|
3130
|
+
return null;
|
|
3131
|
+
}
|
|
3132
|
+
const definition = typeRegistry[type.name];
|
|
3133
|
+
if (definition === void 0) {
|
|
3134
|
+
return null;
|
|
3135
|
+
}
|
|
3136
|
+
seen.add(type.name);
|
|
3137
|
+
return resolveReferenceType(definition.type, typeRegistry, seen);
|
|
3138
|
+
}
|
|
3139
|
+
function isSameStaticTypeShape(baseType, overlayType) {
|
|
3140
|
+
if (baseType.kind !== overlayType.kind) {
|
|
3141
|
+
return false;
|
|
3142
|
+
}
|
|
3143
|
+
switch (baseType.kind) {
|
|
3144
|
+
case "primitive":
|
|
3145
|
+
return overlayType.kind === "primitive" && baseType.primitiveKind === overlayType.primitiveKind;
|
|
3146
|
+
case "enum":
|
|
3147
|
+
return overlayType.kind === "enum";
|
|
3148
|
+
case "dynamic":
|
|
3149
|
+
return overlayType.kind === "dynamic" && baseType.dynamicKind === overlayType.dynamicKind && baseType.sourceKey === overlayType.sourceKey;
|
|
3150
|
+
case "record":
|
|
3151
|
+
return overlayType.kind === "record";
|
|
3152
|
+
case "reference":
|
|
3153
|
+
return overlayType.kind === "reference" && baseType.name === overlayType.name;
|
|
3154
|
+
case "union":
|
|
3155
|
+
return overlayType.kind === "union";
|
|
3156
|
+
case "custom":
|
|
3157
|
+
return overlayType.kind === "custom" && baseType.typeId === overlayType.typeId;
|
|
3158
|
+
case "object":
|
|
3159
|
+
case "array":
|
|
3160
|
+
return true;
|
|
3161
|
+
default: {
|
|
3162
|
+
const _exhaustive = baseType;
|
|
3163
|
+
return _exhaustive;
|
|
3164
|
+
}
|
|
3165
|
+
}
|
|
3166
|
+
}
|
|
3167
|
+
function mergeAnnotations(baseAnnotations, overlayAnnotations) {
|
|
3168
|
+
const baseKeys = new Set(baseAnnotations.map(annotationKey));
|
|
3169
|
+
const overlayOnly = overlayAnnotations.filter(
|
|
3170
|
+
(annotation) => !baseKeys.has(annotationKey(annotation))
|
|
3171
|
+
);
|
|
3172
|
+
return [...baseAnnotations, ...overlayOnly];
|
|
3173
|
+
}
|
|
3174
|
+
function annotationKey(annotation) {
|
|
3175
|
+
return annotation.annotationKind === "custom" ? `${annotation.annotationKind}:${annotation.annotationId}` : annotation.annotationKind;
|
|
3176
|
+
}
|
|
3177
|
+
var init_mixed_authoring = __esm({
|
|
3178
|
+
"src/generators/mixed-authoring.ts"() {
|
|
3179
|
+
"use strict";
|
|
3180
|
+
init_ir_generator();
|
|
3181
|
+
init_ir_generator2();
|
|
3182
|
+
init_canonicalize();
|
|
3183
|
+
init_program();
|
|
3184
|
+
init_class_analyzer();
|
|
3185
|
+
}
|
|
3186
|
+
});
|
|
3187
|
+
|
|
2181
3188
|
// src/index.ts
|
|
2182
3189
|
var index_exports = {};
|
|
2183
3190
|
__export(index_exports, {
|
|
2184
3191
|
buildFormSchemas: () => buildFormSchemas,
|
|
3192
|
+
buildMixedAuthoringSchemas: () => buildMixedAuthoringSchemas,
|
|
2185
3193
|
categorizationSchema: () => categorizationSchema,
|
|
2186
3194
|
categorySchema: () => categorySchema,
|
|
2187
3195
|
controlSchema: () => controlSchema,
|
|
@@ -2247,6 +3255,7 @@ var init_index = __esm({
|
|
|
2247
3255
|
init_ir_generator();
|
|
2248
3256
|
init_generator2();
|
|
2249
3257
|
init_class_schema();
|
|
3258
|
+
init_mixed_authoring();
|
|
2250
3259
|
}
|
|
2251
3260
|
});
|
|
2252
3261
|
|