@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/index.js
CHANGED
|
@@ -325,6 +325,7 @@ function canonicalizeTSDoc(analysis, source) {
|
|
|
325
325
|
irVersion: IR_VERSION2,
|
|
326
326
|
elements,
|
|
327
327
|
typeRegistry: analysis.typeRegistry,
|
|
328
|
+
...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { annotations: analysis.annotations },
|
|
328
329
|
provenance
|
|
329
330
|
};
|
|
330
331
|
}
|
|
@@ -401,6 +402,9 @@ function generateJsonSchemaFromIR(ir, options) {
|
|
|
401
402
|
const ctx = makeContext(options);
|
|
402
403
|
for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
|
|
403
404
|
ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
|
|
405
|
+
if (typeDef.annotations && typeDef.annotations.length > 0) {
|
|
406
|
+
applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
|
|
407
|
+
}
|
|
404
408
|
}
|
|
405
409
|
const properties = {};
|
|
406
410
|
const required = [];
|
|
@@ -412,6 +416,9 @@ function generateJsonSchemaFromIR(ir, options) {
|
|
|
412
416
|
properties,
|
|
413
417
|
...uniqueRequired.length > 0 && { required: uniqueRequired }
|
|
414
418
|
};
|
|
419
|
+
if (ir.annotations && ir.annotations.length > 0) {
|
|
420
|
+
applyAnnotations(result, ir.annotations, ctx);
|
|
421
|
+
}
|
|
415
422
|
if (Object.keys(ctx.defs).length > 0) {
|
|
416
423
|
result.$defs = ctx.defs;
|
|
417
424
|
}
|
|
@@ -441,22 +448,51 @@ function collectFields(elements, properties, required, ctx) {
|
|
|
441
448
|
}
|
|
442
449
|
function generateFieldSchema(field, ctx) {
|
|
443
450
|
const schema = generateTypeNode(field.type, ctx);
|
|
451
|
+
const itemStringSchema = schema.type === "array" && schema.items?.type === "string" ? schema.items : void 0;
|
|
444
452
|
const directConstraints = [];
|
|
453
|
+
const itemConstraints = [];
|
|
445
454
|
const pathConstraints = [];
|
|
446
455
|
for (const c of field.constraints) {
|
|
447
456
|
if (c.path) {
|
|
448
457
|
pathConstraints.push(c);
|
|
458
|
+
} else if (itemStringSchema !== void 0 && isStringItemConstraint(c)) {
|
|
459
|
+
itemConstraints.push(c);
|
|
449
460
|
} else {
|
|
450
461
|
directConstraints.push(c);
|
|
451
462
|
}
|
|
452
463
|
}
|
|
453
464
|
applyConstraints(schema, directConstraints, ctx);
|
|
454
|
-
|
|
465
|
+
if (itemStringSchema !== void 0) {
|
|
466
|
+
applyConstraints(itemStringSchema, itemConstraints, ctx);
|
|
467
|
+
}
|
|
468
|
+
const rootAnnotations = [];
|
|
469
|
+
const itemAnnotations = [];
|
|
470
|
+
for (const annotation of field.annotations) {
|
|
471
|
+
if (itemStringSchema !== void 0 && annotation.annotationKind === "format") {
|
|
472
|
+
itemAnnotations.push(annotation);
|
|
473
|
+
} else {
|
|
474
|
+
rootAnnotations.push(annotation);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
applyAnnotations(schema, rootAnnotations, ctx);
|
|
478
|
+
if (itemStringSchema !== void 0) {
|
|
479
|
+
applyAnnotations(itemStringSchema, itemAnnotations, ctx);
|
|
480
|
+
}
|
|
455
481
|
if (pathConstraints.length === 0) {
|
|
456
482
|
return schema;
|
|
457
483
|
}
|
|
458
484
|
return applyPathTargetedConstraints(schema, pathConstraints, ctx);
|
|
459
485
|
}
|
|
486
|
+
function isStringItemConstraint(constraint) {
|
|
487
|
+
switch (constraint.constraintKind) {
|
|
488
|
+
case "minLength":
|
|
489
|
+
case "maxLength":
|
|
490
|
+
case "pattern":
|
|
491
|
+
return true;
|
|
492
|
+
default:
|
|
493
|
+
return false;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
460
496
|
function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
|
|
461
497
|
if (schema.type === "array" && schema.items) {
|
|
462
498
|
schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx);
|
|
@@ -674,6 +710,9 @@ function applyConstraints(schema, constraints, ctx) {
|
|
|
674
710
|
case "uniqueItems":
|
|
675
711
|
schema.uniqueItems = constraint.value;
|
|
676
712
|
break;
|
|
713
|
+
case "const":
|
|
714
|
+
schema.const = constraint.value;
|
|
715
|
+
break;
|
|
677
716
|
case "allowedMembers":
|
|
678
717
|
break;
|
|
679
718
|
case "custom":
|
|
@@ -698,8 +737,14 @@ function applyAnnotations(schema, annotations, ctx) {
|
|
|
698
737
|
case "defaultValue":
|
|
699
738
|
schema.default = annotation.value;
|
|
700
739
|
break;
|
|
740
|
+
case "format":
|
|
741
|
+
schema.format = annotation.value;
|
|
742
|
+
break;
|
|
701
743
|
case "deprecated":
|
|
702
744
|
schema.deprecated = true;
|
|
745
|
+
if (annotation.message !== void 0 && annotation.message !== "") {
|
|
746
|
+
schema["x-formspec-deprecation-description"] = annotation.message;
|
|
747
|
+
}
|
|
703
748
|
break;
|
|
704
749
|
case "placeholder":
|
|
705
750
|
break;
|
|
@@ -731,7 +776,12 @@ function applyCustomConstraint(schema, constraint, ctx) {
|
|
|
731
776
|
`Cannot generate JSON Schema for custom constraint "${constraint.constraintId}" without a matching extension registration`
|
|
732
777
|
);
|
|
733
778
|
}
|
|
734
|
-
|
|
779
|
+
assignVendorPrefixedExtensionKeywords(
|
|
780
|
+
schema,
|
|
781
|
+
registration.toJsonSchema(constraint.payload, ctx.vendorPrefix),
|
|
782
|
+
ctx.vendorPrefix,
|
|
783
|
+
`custom constraint "${constraint.constraintId}"`
|
|
784
|
+
);
|
|
735
785
|
}
|
|
736
786
|
function applyCustomAnnotation(schema, annotation, ctx) {
|
|
737
787
|
const registration = ctx.extensionRegistry?.findAnnotation(annotation.annotationId);
|
|
@@ -743,7 +793,22 @@ function applyCustomAnnotation(schema, annotation, ctx) {
|
|
|
743
793
|
if (registration.toJsonSchema === void 0) {
|
|
744
794
|
return;
|
|
745
795
|
}
|
|
746
|
-
|
|
796
|
+
assignVendorPrefixedExtensionKeywords(
|
|
797
|
+
schema,
|
|
798
|
+
registration.toJsonSchema(annotation.value, ctx.vendorPrefix),
|
|
799
|
+
ctx.vendorPrefix,
|
|
800
|
+
`custom annotation "${annotation.annotationId}"`
|
|
801
|
+
);
|
|
802
|
+
}
|
|
803
|
+
function assignVendorPrefixedExtensionKeywords(schema, extensionSchema, vendorPrefix, source) {
|
|
804
|
+
for (const [key, value] of Object.entries(extensionSchema)) {
|
|
805
|
+
if (!key.startsWith(`${vendorPrefix}-`)) {
|
|
806
|
+
throw new Error(
|
|
807
|
+
`Cannot apply ${source}: extension hooks may only emit "${vendorPrefix}-*" JSON Schema keywords`
|
|
808
|
+
);
|
|
809
|
+
}
|
|
810
|
+
schema[key] = value;
|
|
811
|
+
}
|
|
747
812
|
}
|
|
748
813
|
|
|
749
814
|
// src/json-schema/generator.ts
|
|
@@ -887,25 +952,31 @@ function createShowRule(fieldName, value) {
|
|
|
887
952
|
}
|
|
888
953
|
};
|
|
889
954
|
}
|
|
955
|
+
function flattenConditionSchema(scope, schema) {
|
|
956
|
+
if (schema.allOf === void 0) {
|
|
957
|
+
if (scope === "#") {
|
|
958
|
+
return [schema];
|
|
959
|
+
}
|
|
960
|
+
const fieldName = scope.replace("#/properties/", "");
|
|
961
|
+
return [
|
|
962
|
+
{
|
|
963
|
+
properties: {
|
|
964
|
+
[fieldName]: schema
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
];
|
|
968
|
+
}
|
|
969
|
+
return schema.allOf.flatMap((member) => flattenConditionSchema(scope, member));
|
|
970
|
+
}
|
|
890
971
|
function combineRules(parentRule, childRule) {
|
|
891
|
-
const parentCondition = parentRule.condition;
|
|
892
|
-
const childCondition = childRule.condition;
|
|
893
972
|
return {
|
|
894
973
|
effect: "SHOW",
|
|
895
974
|
condition: {
|
|
896
975
|
scope: "#",
|
|
897
976
|
schema: {
|
|
898
977
|
allOf: [
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
[parentCondition.scope.replace("#/properties/", "")]: parentCondition.schema
|
|
902
|
-
}
|
|
903
|
-
},
|
|
904
|
-
{
|
|
905
|
-
properties: {
|
|
906
|
-
[childCondition.scope.replace("#/properties/", "")]: childCondition.schema
|
|
907
|
-
}
|
|
908
|
-
}
|
|
978
|
+
...flattenConditionSchema(parentRule.condition.scope, parentRule.condition.schema),
|
|
979
|
+
...flattenConditionSchema(childRule.condition.scope, childRule.condition.schema)
|
|
909
980
|
]
|
|
910
981
|
}
|
|
911
982
|
}
|
|
@@ -913,10 +984,14 @@ function combineRules(parentRule, childRule) {
|
|
|
913
984
|
}
|
|
914
985
|
function fieldNodeToControl(field, parentRule) {
|
|
915
986
|
const displayNameAnnotation = field.annotations.find((a) => a.annotationKind === "displayName");
|
|
987
|
+
const placeholderAnnotation = field.annotations.find((a) => a.annotationKind === "placeholder");
|
|
916
988
|
const control = {
|
|
917
989
|
type: "Control",
|
|
918
990
|
scope: fieldToScope(field.name),
|
|
919
991
|
...displayNameAnnotation !== void 0 && { label: displayNameAnnotation.value },
|
|
992
|
+
...placeholderAnnotation !== void 0 && {
|
|
993
|
+
options: { placeholder: placeholderAnnotation.value }
|
|
994
|
+
},
|
|
920
995
|
...parentRule !== void 0 && { rule: parentRule }
|
|
921
996
|
};
|
|
922
997
|
return control;
|
|
@@ -986,7 +1061,10 @@ function getSchemaExtension(schema, key) {
|
|
|
986
1061
|
// src/extensions/registry.ts
|
|
987
1062
|
function createExtensionRegistry(extensions) {
|
|
988
1063
|
const typeMap = /* @__PURE__ */ new Map();
|
|
1064
|
+
const typeNameMap = /* @__PURE__ */ new Map();
|
|
989
1065
|
const constraintMap = /* @__PURE__ */ new Map();
|
|
1066
|
+
const constraintTagMap = /* @__PURE__ */ new Map();
|
|
1067
|
+
const builtinBroadeningMap = /* @__PURE__ */ new Map();
|
|
990
1068
|
const annotationMap = /* @__PURE__ */ new Map();
|
|
991
1069
|
for (const ext of extensions) {
|
|
992
1070
|
if (ext.types !== void 0) {
|
|
@@ -996,6 +1074,27 @@ function createExtensionRegistry(extensions) {
|
|
|
996
1074
|
throw new Error(`Duplicate custom type ID: "${qualifiedId}"`);
|
|
997
1075
|
}
|
|
998
1076
|
typeMap.set(qualifiedId, type);
|
|
1077
|
+
for (const sourceTypeName of type.tsTypeNames ?? [type.typeName]) {
|
|
1078
|
+
if (typeNameMap.has(sourceTypeName)) {
|
|
1079
|
+
throw new Error(`Duplicate custom type source name: "${sourceTypeName}"`);
|
|
1080
|
+
}
|
|
1081
|
+
typeNameMap.set(sourceTypeName, {
|
|
1082
|
+
extensionId: ext.extensionId,
|
|
1083
|
+
registration: type
|
|
1084
|
+
});
|
|
1085
|
+
}
|
|
1086
|
+
if (type.builtinConstraintBroadenings !== void 0) {
|
|
1087
|
+
for (const broadening of type.builtinConstraintBroadenings) {
|
|
1088
|
+
const key = `${qualifiedId}:${broadening.tagName}`;
|
|
1089
|
+
if (builtinBroadeningMap.has(key)) {
|
|
1090
|
+
throw new Error(`Duplicate built-in constraint broadening: "${key}"`);
|
|
1091
|
+
}
|
|
1092
|
+
builtinBroadeningMap.set(key, {
|
|
1093
|
+
extensionId: ext.extensionId,
|
|
1094
|
+
registration: broadening
|
|
1095
|
+
});
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
999
1098
|
}
|
|
1000
1099
|
}
|
|
1001
1100
|
if (ext.constraints !== void 0) {
|
|
@@ -1007,6 +1106,17 @@ function createExtensionRegistry(extensions) {
|
|
|
1007
1106
|
constraintMap.set(qualifiedId, constraint);
|
|
1008
1107
|
}
|
|
1009
1108
|
}
|
|
1109
|
+
if (ext.constraintTags !== void 0) {
|
|
1110
|
+
for (const tag of ext.constraintTags) {
|
|
1111
|
+
if (constraintTagMap.has(tag.tagName)) {
|
|
1112
|
+
throw new Error(`Duplicate custom constraint tag: "@${tag.tagName}"`);
|
|
1113
|
+
}
|
|
1114
|
+
constraintTagMap.set(tag.tagName, {
|
|
1115
|
+
extensionId: ext.extensionId,
|
|
1116
|
+
registration: tag
|
|
1117
|
+
});
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1010
1120
|
if (ext.annotations !== void 0) {
|
|
1011
1121
|
for (const annotation of ext.annotations) {
|
|
1012
1122
|
const qualifiedId = `${ext.extensionId}/${annotation.annotationName}`;
|
|
@@ -1020,7 +1130,10 @@ function createExtensionRegistry(extensions) {
|
|
|
1020
1130
|
return {
|
|
1021
1131
|
extensions,
|
|
1022
1132
|
findType: (typeId) => typeMap.get(typeId),
|
|
1133
|
+
findTypeByName: (typeName) => typeNameMap.get(typeName),
|
|
1023
1134
|
findConstraint: (constraintId) => constraintMap.get(constraintId),
|
|
1135
|
+
findConstraintTag: (tagName) => constraintTagMap.get(tagName),
|
|
1136
|
+
findBuiltinConstraintBroadening: (typeId, tagName) => builtinBroadeningMap.get(`${typeId}:${tagName}`),
|
|
1024
1137
|
findAnnotation: (annotationId) => annotationMap.get(annotationId)
|
|
1025
1138
|
};
|
|
1026
1139
|
}
|
|
@@ -1205,8 +1318,8 @@ var LENGTH_CONSTRAINT_MAP = {
|
|
|
1205
1318
|
minItems: "minItems",
|
|
1206
1319
|
maxItems: "maxItems"
|
|
1207
1320
|
};
|
|
1208
|
-
var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions"]);
|
|
1209
|
-
function createFormSpecTSDocConfig() {
|
|
1321
|
+
var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions", "defaultValue"]);
|
|
1322
|
+
function createFormSpecTSDocConfig(extensionTagNames = []) {
|
|
1210
1323
|
const config = new TSDocConfiguration();
|
|
1211
1324
|
for (const tagName of Object.keys(BUILTIN_CONSTRAINT_DEFINITIONS)) {
|
|
1212
1325
|
config.addTagDefinition(
|
|
@@ -1217,7 +1330,16 @@ function createFormSpecTSDocConfig() {
|
|
|
1217
1330
|
})
|
|
1218
1331
|
);
|
|
1219
1332
|
}
|
|
1220
|
-
for (const tagName of ["displayName", "description"]) {
|
|
1333
|
+
for (const tagName of ["displayName", "description", "format", "placeholder"]) {
|
|
1334
|
+
config.addTagDefinition(
|
|
1335
|
+
new TSDocTagDefinition({
|
|
1336
|
+
tagName: "@" + tagName,
|
|
1337
|
+
syntaxKind: TSDocTagSyntaxKind.BlockTag,
|
|
1338
|
+
allowMultiple: true
|
|
1339
|
+
})
|
|
1340
|
+
);
|
|
1341
|
+
}
|
|
1342
|
+
for (const tagName of extensionTagNames) {
|
|
1221
1343
|
config.addTagDefinition(
|
|
1222
1344
|
new TSDocTagDefinition({
|
|
1223
1345
|
tagName: "@" + tagName,
|
|
@@ -1228,14 +1350,31 @@ function createFormSpecTSDocConfig() {
|
|
|
1228
1350
|
}
|
|
1229
1351
|
return config;
|
|
1230
1352
|
}
|
|
1231
|
-
var
|
|
1232
|
-
function getParser() {
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1353
|
+
var parserCache = /* @__PURE__ */ new Map();
|
|
1354
|
+
function getParser(options) {
|
|
1355
|
+
const extensionTagNames = [
|
|
1356
|
+
...options?.extensionRegistry?.extensions.flatMap(
|
|
1357
|
+
(extension) => (extension.constraintTags ?? []).map((tag) => tag.tagName)
|
|
1358
|
+
) ?? []
|
|
1359
|
+
].sort();
|
|
1360
|
+
const cacheKey = extensionTagNames.join("|");
|
|
1361
|
+
const existing = parserCache.get(cacheKey);
|
|
1362
|
+
if (existing) {
|
|
1363
|
+
return existing;
|
|
1364
|
+
}
|
|
1365
|
+
const parser = new TSDocParser(createFormSpecTSDocConfig(extensionTagNames));
|
|
1366
|
+
parserCache.set(cacheKey, parser);
|
|
1367
|
+
return parser;
|
|
1368
|
+
}
|
|
1369
|
+
function parseTSDocTags(node, file = "", options) {
|
|
1237
1370
|
const constraints = [];
|
|
1238
1371
|
const annotations = [];
|
|
1372
|
+
let displayName;
|
|
1373
|
+
let description;
|
|
1374
|
+
let placeholder;
|
|
1375
|
+
let displayNameProvenance;
|
|
1376
|
+
let descriptionProvenance;
|
|
1377
|
+
let placeholderProvenance;
|
|
1239
1378
|
const sourceFile = node.getSourceFile();
|
|
1240
1379
|
const sourceText = sourceFile.getFullText();
|
|
1241
1380
|
const commentRanges = ts2.getLeadingCommentRanges(sourceText, node.getFullStart());
|
|
@@ -1248,52 +1387,92 @@ function parseTSDocTags(node, file = "") {
|
|
|
1248
1387
|
if (!commentText.startsWith("/**")) {
|
|
1249
1388
|
continue;
|
|
1250
1389
|
}
|
|
1251
|
-
const parser = getParser();
|
|
1390
|
+
const parser = getParser(options);
|
|
1252
1391
|
const parserContext = parser.parseRange(
|
|
1253
1392
|
TextRange.fromStringRange(sourceText, range.pos, range.end)
|
|
1254
1393
|
);
|
|
1255
1394
|
const docComment = parserContext.docComment;
|
|
1256
1395
|
for (const block of docComment.customBlocks) {
|
|
1257
1396
|
const tagName = normalizeConstraintTagName(block.blockTag.tagName.substring(1));
|
|
1258
|
-
if (tagName === "displayName" || tagName === "description") {
|
|
1397
|
+
if (tagName === "displayName" || tagName === "description" || tagName === "format" || tagName === "placeholder") {
|
|
1259
1398
|
const text2 = extractBlockText(block).trim();
|
|
1260
1399
|
if (text2 === "") continue;
|
|
1261
1400
|
const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
|
|
1262
1401
|
if (tagName === "displayName") {
|
|
1402
|
+
if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
|
|
1403
|
+
displayName = text2;
|
|
1404
|
+
displayNameProvenance = provenance2;
|
|
1405
|
+
}
|
|
1406
|
+
} else if (tagName === "format") {
|
|
1263
1407
|
annotations.push({
|
|
1264
1408
|
kind: "annotation",
|
|
1265
|
-
annotationKind: "
|
|
1409
|
+
annotationKind: "format",
|
|
1266
1410
|
value: text2,
|
|
1267
1411
|
provenance: provenance2
|
|
1268
1412
|
});
|
|
1269
1413
|
} else {
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1414
|
+
if (tagName === "description" && description === void 0) {
|
|
1415
|
+
description = text2;
|
|
1416
|
+
descriptionProvenance = provenance2;
|
|
1417
|
+
} else if (tagName === "placeholder" && placeholder === void 0) {
|
|
1418
|
+
placeholder = text2;
|
|
1419
|
+
placeholderProvenance = provenance2;
|
|
1420
|
+
}
|
|
1276
1421
|
}
|
|
1277
1422
|
continue;
|
|
1278
1423
|
}
|
|
1279
1424
|
if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
1280
1425
|
const text = extractBlockText(block).trim();
|
|
1281
|
-
|
|
1426
|
+
const expectedType = isBuiltinConstraintName(tagName) ? BUILTIN_CONSTRAINT_DEFINITIONS[tagName] : void 0;
|
|
1427
|
+
if (text === "" && expectedType !== "boolean") continue;
|
|
1282
1428
|
const provenance = provenanceForComment(range, sourceFile, file, tagName);
|
|
1283
|
-
const constraintNode = parseConstraintValue(tagName, text, provenance);
|
|
1429
|
+
const constraintNode = parseConstraintValue(tagName, text, provenance, options);
|
|
1284
1430
|
if (constraintNode) {
|
|
1285
1431
|
constraints.push(constraintNode);
|
|
1286
1432
|
}
|
|
1287
1433
|
}
|
|
1288
1434
|
if (docComment.deprecatedBlock !== void 0) {
|
|
1435
|
+
const message = extractBlockText(docComment.deprecatedBlock).trim();
|
|
1289
1436
|
annotations.push({
|
|
1290
1437
|
kind: "annotation",
|
|
1291
1438
|
annotationKind: "deprecated",
|
|
1439
|
+
...message !== "" && { message },
|
|
1292
1440
|
provenance: provenanceForComment(range, sourceFile, file, "deprecated")
|
|
1293
1441
|
});
|
|
1294
1442
|
}
|
|
1443
|
+
if (description === void 0 && docComment.remarksBlock !== void 0) {
|
|
1444
|
+
const remarks = extractBlockText(docComment.remarksBlock).trim();
|
|
1445
|
+
if (remarks !== "") {
|
|
1446
|
+
description = remarks;
|
|
1447
|
+
descriptionProvenance = provenanceForComment(range, sourceFile, file, "remarks");
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1295
1450
|
}
|
|
1296
1451
|
}
|
|
1452
|
+
if (displayName !== void 0 && displayNameProvenance !== void 0) {
|
|
1453
|
+
annotations.push({
|
|
1454
|
+
kind: "annotation",
|
|
1455
|
+
annotationKind: "displayName",
|
|
1456
|
+
value: displayName,
|
|
1457
|
+
provenance: displayNameProvenance
|
|
1458
|
+
});
|
|
1459
|
+
}
|
|
1460
|
+
if (description !== void 0 && descriptionProvenance !== void 0) {
|
|
1461
|
+
annotations.push({
|
|
1462
|
+
kind: "annotation",
|
|
1463
|
+
annotationKind: "description",
|
|
1464
|
+
value: description,
|
|
1465
|
+
provenance: descriptionProvenance
|
|
1466
|
+
});
|
|
1467
|
+
}
|
|
1468
|
+
if (placeholder !== void 0 && placeholderProvenance !== void 0) {
|
|
1469
|
+
annotations.push({
|
|
1470
|
+
kind: "annotation",
|
|
1471
|
+
annotationKind: "placeholder",
|
|
1472
|
+
value: placeholder,
|
|
1473
|
+
provenance: placeholderProvenance
|
|
1474
|
+
});
|
|
1475
|
+
}
|
|
1297
1476
|
const jsDocTagsAll = ts2.getJSDocTags(node);
|
|
1298
1477
|
for (const tag of jsDocTagsAll) {
|
|
1299
1478
|
const tagName = normalizeConstraintTagName(tag.tagName.text);
|
|
@@ -1302,13 +1481,40 @@ function parseTSDocTags(node, file = "") {
|
|
|
1302
1481
|
if (commentText === void 0 || commentText.trim() === "") continue;
|
|
1303
1482
|
const text = commentText.trim();
|
|
1304
1483
|
const provenance = provenanceForJSDocTag(tag, file);
|
|
1305
|
-
|
|
1484
|
+
if (tagName === "defaultValue") {
|
|
1485
|
+
const defaultValueNode = parseDefaultValueValue(text, provenance);
|
|
1486
|
+
annotations.push(defaultValueNode);
|
|
1487
|
+
continue;
|
|
1488
|
+
}
|
|
1489
|
+
const constraintNode = parseConstraintValue(tagName, text, provenance, options);
|
|
1306
1490
|
if (constraintNode) {
|
|
1307
1491
|
constraints.push(constraintNode);
|
|
1308
1492
|
}
|
|
1309
1493
|
}
|
|
1310
1494
|
return { constraints, annotations };
|
|
1311
1495
|
}
|
|
1496
|
+
function extractDisplayNameMetadata(node) {
|
|
1497
|
+
let displayName;
|
|
1498
|
+
const memberDisplayNames = /* @__PURE__ */ new Map();
|
|
1499
|
+
for (const tag of ts2.getJSDocTags(node)) {
|
|
1500
|
+
const tagName = normalizeConstraintTagName(tag.tagName.text);
|
|
1501
|
+
if (tagName !== "displayName") continue;
|
|
1502
|
+
const commentText = getTagCommentText(tag);
|
|
1503
|
+
if (commentText === void 0) continue;
|
|
1504
|
+
const text = commentText.trim();
|
|
1505
|
+
if (text === "") continue;
|
|
1506
|
+
const memberTarget = parseMemberTargetDisplayName(text);
|
|
1507
|
+
if (memberTarget) {
|
|
1508
|
+
memberDisplayNames.set(memberTarget.target, memberTarget.label);
|
|
1509
|
+
continue;
|
|
1510
|
+
}
|
|
1511
|
+
displayName ??= text;
|
|
1512
|
+
}
|
|
1513
|
+
return {
|
|
1514
|
+
...displayName !== void 0 && { displayName },
|
|
1515
|
+
memberDisplayNames
|
|
1516
|
+
};
|
|
1517
|
+
}
|
|
1312
1518
|
function extractPathTarget(text) {
|
|
1313
1519
|
const trimmed = text.trimStart();
|
|
1314
1520
|
const match = /^:([a-zA-Z_]\w*)\s+([\s\S]*)$/.exec(trimmed);
|
|
@@ -1336,7 +1542,11 @@ function extractPlainText(node) {
|
|
|
1336
1542
|
}
|
|
1337
1543
|
return result;
|
|
1338
1544
|
}
|
|
1339
|
-
function parseConstraintValue(tagName, text, provenance) {
|
|
1545
|
+
function parseConstraintValue(tagName, text, provenance, options) {
|
|
1546
|
+
const customConstraint = parseExtensionConstraintValue(tagName, text, provenance, options);
|
|
1547
|
+
if (customConstraint) {
|
|
1548
|
+
return customConstraint;
|
|
1549
|
+
}
|
|
1340
1550
|
if (!isBuiltinConstraintName(tagName)) {
|
|
1341
1551
|
return null;
|
|
1342
1552
|
}
|
|
@@ -1371,7 +1581,45 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
1371
1581
|
}
|
|
1372
1582
|
return null;
|
|
1373
1583
|
}
|
|
1584
|
+
if (expectedType === "boolean") {
|
|
1585
|
+
const trimmed = effectiveText.trim();
|
|
1586
|
+
if (trimmed !== "" && trimmed !== "true") {
|
|
1587
|
+
return null;
|
|
1588
|
+
}
|
|
1589
|
+
if (tagName === "uniqueItems") {
|
|
1590
|
+
return {
|
|
1591
|
+
kind: "constraint",
|
|
1592
|
+
constraintKind: "uniqueItems",
|
|
1593
|
+
value: true,
|
|
1594
|
+
...path3 && { path: path3 },
|
|
1595
|
+
provenance
|
|
1596
|
+
};
|
|
1597
|
+
}
|
|
1598
|
+
return null;
|
|
1599
|
+
}
|
|
1374
1600
|
if (expectedType === "json") {
|
|
1601
|
+
if (tagName === "const") {
|
|
1602
|
+
const trimmedText = effectiveText.trim();
|
|
1603
|
+
if (trimmedText === "") return null;
|
|
1604
|
+
try {
|
|
1605
|
+
const parsed2 = JSON.parse(trimmedText);
|
|
1606
|
+
return {
|
|
1607
|
+
kind: "constraint",
|
|
1608
|
+
constraintKind: "const",
|
|
1609
|
+
value: parsed2,
|
|
1610
|
+
...path3 && { path: path3 },
|
|
1611
|
+
provenance
|
|
1612
|
+
};
|
|
1613
|
+
} catch {
|
|
1614
|
+
return {
|
|
1615
|
+
kind: "constraint",
|
|
1616
|
+
constraintKind: "const",
|
|
1617
|
+
value: trimmedText,
|
|
1618
|
+
...path3 && { path: path3 },
|
|
1619
|
+
provenance
|
|
1620
|
+
};
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1375
1623
|
const parsed = tryParseJson(effectiveText);
|
|
1376
1624
|
if (!Array.isArray(parsed)) {
|
|
1377
1625
|
return null;
|
|
@@ -1403,6 +1651,111 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
1403
1651
|
provenance
|
|
1404
1652
|
};
|
|
1405
1653
|
}
|
|
1654
|
+
function parseExtensionConstraintValue(tagName, text, provenance, options) {
|
|
1655
|
+
const pathResult = extractPathTarget(text);
|
|
1656
|
+
const effectiveText = pathResult ? pathResult.remainingText : text;
|
|
1657
|
+
const path3 = pathResult?.path;
|
|
1658
|
+
const registry = options?.extensionRegistry;
|
|
1659
|
+
if (registry === void 0) {
|
|
1660
|
+
return null;
|
|
1661
|
+
}
|
|
1662
|
+
const directTag = registry.findConstraintTag(tagName);
|
|
1663
|
+
if (directTag !== void 0) {
|
|
1664
|
+
return makeCustomConstraintNode(
|
|
1665
|
+
directTag.extensionId,
|
|
1666
|
+
directTag.registration.constraintName,
|
|
1667
|
+
directTag.registration.parseValue(effectiveText),
|
|
1668
|
+
provenance,
|
|
1669
|
+
path3,
|
|
1670
|
+
registry
|
|
1671
|
+
);
|
|
1672
|
+
}
|
|
1673
|
+
if (!isBuiltinConstraintName(tagName)) {
|
|
1674
|
+
return null;
|
|
1675
|
+
}
|
|
1676
|
+
const broadenedTypeId = getBroadenedCustomTypeId(options?.fieldType);
|
|
1677
|
+
if (broadenedTypeId === void 0) {
|
|
1678
|
+
return null;
|
|
1679
|
+
}
|
|
1680
|
+
const broadened = registry.findBuiltinConstraintBroadening(broadenedTypeId, tagName);
|
|
1681
|
+
if (broadened === void 0) {
|
|
1682
|
+
return null;
|
|
1683
|
+
}
|
|
1684
|
+
return makeCustomConstraintNode(
|
|
1685
|
+
broadened.extensionId,
|
|
1686
|
+
broadened.registration.constraintName,
|
|
1687
|
+
broadened.registration.parseValue(effectiveText),
|
|
1688
|
+
provenance,
|
|
1689
|
+
path3,
|
|
1690
|
+
registry
|
|
1691
|
+
);
|
|
1692
|
+
}
|
|
1693
|
+
function getBroadenedCustomTypeId(fieldType) {
|
|
1694
|
+
if (fieldType?.kind === "custom") {
|
|
1695
|
+
return fieldType.typeId;
|
|
1696
|
+
}
|
|
1697
|
+
if (fieldType?.kind !== "union") {
|
|
1698
|
+
return void 0;
|
|
1699
|
+
}
|
|
1700
|
+
const customMembers = fieldType.members.filter(
|
|
1701
|
+
(member) => member.kind === "custom"
|
|
1702
|
+
);
|
|
1703
|
+
if (customMembers.length !== 1) {
|
|
1704
|
+
return void 0;
|
|
1705
|
+
}
|
|
1706
|
+
const nonCustomMembers = fieldType.members.filter((member) => member.kind !== "custom");
|
|
1707
|
+
const allOtherMembersAreNull = nonCustomMembers.every(
|
|
1708
|
+
(member) => member.kind === "primitive" && member.primitiveKind === "null"
|
|
1709
|
+
);
|
|
1710
|
+
const customMember = customMembers[0];
|
|
1711
|
+
return allOtherMembersAreNull && customMember !== void 0 ? customMember.typeId : void 0;
|
|
1712
|
+
}
|
|
1713
|
+
function makeCustomConstraintNode(extensionId, constraintName, payload, provenance, path3, registry) {
|
|
1714
|
+
const constraintId = `${extensionId}/${constraintName}`;
|
|
1715
|
+
const registration = registry.findConstraint(constraintId);
|
|
1716
|
+
if (registration === void 0) {
|
|
1717
|
+
throw new Error(
|
|
1718
|
+
`Custom TSDoc tag resolved to unregistered constraint "${constraintId}". Register the constraint before using its tag.`
|
|
1719
|
+
);
|
|
1720
|
+
}
|
|
1721
|
+
return {
|
|
1722
|
+
kind: "constraint",
|
|
1723
|
+
constraintKind: "custom",
|
|
1724
|
+
constraintId,
|
|
1725
|
+
payload,
|
|
1726
|
+
compositionRule: registration.compositionRule,
|
|
1727
|
+
...path3 && { path: path3 },
|
|
1728
|
+
provenance
|
|
1729
|
+
};
|
|
1730
|
+
}
|
|
1731
|
+
function parseDefaultValueValue(text, provenance) {
|
|
1732
|
+
const trimmed = text.trim();
|
|
1733
|
+
let value;
|
|
1734
|
+
if (trimmed === "null") {
|
|
1735
|
+
value = null;
|
|
1736
|
+
} else if (trimmed === "true") {
|
|
1737
|
+
value = true;
|
|
1738
|
+
} else if (trimmed === "false") {
|
|
1739
|
+
value = false;
|
|
1740
|
+
} else {
|
|
1741
|
+
const parsed = tryParseJson(trimmed);
|
|
1742
|
+
value = parsed !== null ? parsed : trimmed;
|
|
1743
|
+
}
|
|
1744
|
+
return {
|
|
1745
|
+
kind: "annotation",
|
|
1746
|
+
annotationKind: "defaultValue",
|
|
1747
|
+
value,
|
|
1748
|
+
provenance
|
|
1749
|
+
};
|
|
1750
|
+
}
|
|
1751
|
+
function isMemberTargetDisplayName(text) {
|
|
1752
|
+
return parseMemberTargetDisplayName(text) !== null;
|
|
1753
|
+
}
|
|
1754
|
+
function parseMemberTargetDisplayName(text) {
|
|
1755
|
+
const match = /^:([^\s]+)\s+([\s\S]+)$/.exec(text);
|
|
1756
|
+
if (!match?.[1] || !match[2]) return null;
|
|
1757
|
+
return { target: match[1], label: match[2].trim() };
|
|
1758
|
+
}
|
|
1406
1759
|
function provenanceForComment(range, sourceFile, file, tagName) {
|
|
1407
1760
|
const { line, character } = sourceFile.getLineAndCharacterOfPosition(range.pos);
|
|
1408
1761
|
return {
|
|
@@ -1435,12 +1788,12 @@ function getTagCommentText(tag) {
|
|
|
1435
1788
|
}
|
|
1436
1789
|
|
|
1437
1790
|
// src/analyzer/jsdoc-constraints.ts
|
|
1438
|
-
function extractJSDocConstraintNodes(node, file = "") {
|
|
1439
|
-
const result = parseTSDocTags(node, file);
|
|
1791
|
+
function extractJSDocConstraintNodes(node, file = "", options) {
|
|
1792
|
+
const result = parseTSDocTags(node, file, options);
|
|
1440
1793
|
return [...result.constraints];
|
|
1441
1794
|
}
|
|
1442
|
-
function extractJSDocAnnotationNodes(node, file = "") {
|
|
1443
|
-
const result = parseTSDocTags(node, file);
|
|
1795
|
+
function extractJSDocAnnotationNodes(node, file = "", options) {
|
|
1796
|
+
const result = parseTSDocTags(node, file, options);
|
|
1444
1797
|
return [...result.annotations];
|
|
1445
1798
|
}
|
|
1446
1799
|
function extractDefaultValueAnnotation(initializer, file = "") {
|
|
@@ -1484,17 +1837,43 @@ function isObjectType(type) {
|
|
|
1484
1837
|
function isTypeReference(type) {
|
|
1485
1838
|
return !!(type.flags & ts4.TypeFlags.Object) && !!(type.objectFlags & ts4.ObjectFlags.Reference);
|
|
1486
1839
|
}
|
|
1487
|
-
|
|
1840
|
+
var RESOLVING_TYPE_PLACEHOLDER = {
|
|
1841
|
+
kind: "object",
|
|
1842
|
+
properties: [],
|
|
1843
|
+
additionalProperties: true
|
|
1844
|
+
};
|
|
1845
|
+
function makeParseOptions(extensionRegistry, fieldType) {
|
|
1846
|
+
if (extensionRegistry === void 0 && fieldType === void 0) {
|
|
1847
|
+
return void 0;
|
|
1848
|
+
}
|
|
1849
|
+
return {
|
|
1850
|
+
...extensionRegistry !== void 0 && { extensionRegistry },
|
|
1851
|
+
...fieldType !== void 0 && { fieldType }
|
|
1852
|
+
};
|
|
1853
|
+
}
|
|
1854
|
+
function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
|
|
1488
1855
|
const name = classDecl.name?.text ?? "AnonymousClass";
|
|
1489
1856
|
const fields = [];
|
|
1490
1857
|
const fieldLayouts = [];
|
|
1491
1858
|
const typeRegistry = {};
|
|
1859
|
+
const annotations = extractJSDocAnnotationNodes(
|
|
1860
|
+
classDecl,
|
|
1861
|
+
file,
|
|
1862
|
+
makeParseOptions(extensionRegistry)
|
|
1863
|
+
);
|
|
1492
1864
|
const visiting = /* @__PURE__ */ new Set();
|
|
1493
1865
|
const instanceMethods = [];
|
|
1494
1866
|
const staticMethods = [];
|
|
1495
1867
|
for (const member of classDecl.members) {
|
|
1496
1868
|
if (ts4.isPropertyDeclaration(member)) {
|
|
1497
|
-
const fieldNode = analyzeFieldToIR(
|
|
1869
|
+
const fieldNode = analyzeFieldToIR(
|
|
1870
|
+
member,
|
|
1871
|
+
checker,
|
|
1872
|
+
file,
|
|
1873
|
+
typeRegistry,
|
|
1874
|
+
visiting,
|
|
1875
|
+
extensionRegistry
|
|
1876
|
+
);
|
|
1498
1877
|
if (fieldNode) {
|
|
1499
1878
|
fields.push(fieldNode);
|
|
1500
1879
|
fieldLayouts.push({});
|
|
@@ -1511,25 +1890,53 @@ function analyzeClassToIR(classDecl, checker, file = "") {
|
|
|
1511
1890
|
}
|
|
1512
1891
|
}
|
|
1513
1892
|
}
|
|
1514
|
-
return {
|
|
1893
|
+
return {
|
|
1894
|
+
name,
|
|
1895
|
+
fields,
|
|
1896
|
+
fieldLayouts,
|
|
1897
|
+
typeRegistry,
|
|
1898
|
+
...annotations.length > 0 && { annotations },
|
|
1899
|
+
instanceMethods,
|
|
1900
|
+
staticMethods
|
|
1901
|
+
};
|
|
1515
1902
|
}
|
|
1516
|
-
function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
|
|
1903
|
+
function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegistry) {
|
|
1517
1904
|
const name = interfaceDecl.name.text;
|
|
1518
1905
|
const fields = [];
|
|
1519
1906
|
const typeRegistry = {};
|
|
1907
|
+
const annotations = extractJSDocAnnotationNodes(
|
|
1908
|
+
interfaceDecl,
|
|
1909
|
+
file,
|
|
1910
|
+
makeParseOptions(extensionRegistry)
|
|
1911
|
+
);
|
|
1520
1912
|
const visiting = /* @__PURE__ */ new Set();
|
|
1521
1913
|
for (const member of interfaceDecl.members) {
|
|
1522
1914
|
if (ts4.isPropertySignature(member)) {
|
|
1523
|
-
const fieldNode = analyzeInterfacePropertyToIR(
|
|
1915
|
+
const fieldNode = analyzeInterfacePropertyToIR(
|
|
1916
|
+
member,
|
|
1917
|
+
checker,
|
|
1918
|
+
file,
|
|
1919
|
+
typeRegistry,
|
|
1920
|
+
visiting,
|
|
1921
|
+
extensionRegistry
|
|
1922
|
+
);
|
|
1524
1923
|
if (fieldNode) {
|
|
1525
1924
|
fields.push(fieldNode);
|
|
1526
1925
|
}
|
|
1527
1926
|
}
|
|
1528
1927
|
}
|
|
1529
1928
|
const fieldLayouts = fields.map(() => ({}));
|
|
1530
|
-
return {
|
|
1929
|
+
return {
|
|
1930
|
+
name,
|
|
1931
|
+
fields,
|
|
1932
|
+
fieldLayouts,
|
|
1933
|
+
typeRegistry,
|
|
1934
|
+
...annotations.length > 0 && { annotations },
|
|
1935
|
+
instanceMethods: [],
|
|
1936
|
+
staticMethods: []
|
|
1937
|
+
};
|
|
1531
1938
|
}
|
|
1532
|
-
function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
1939
|
+
function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry) {
|
|
1533
1940
|
if (!ts4.isTypeLiteralNode(typeAlias.type)) {
|
|
1534
1941
|
const sourceFile = typeAlias.getSourceFile();
|
|
1535
1942
|
const { line } = sourceFile.getLineAndCharacterOfPosition(typeAlias.getStart());
|
|
@@ -1542,10 +1949,22 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
|
1542
1949
|
const name = typeAlias.name.text;
|
|
1543
1950
|
const fields = [];
|
|
1544
1951
|
const typeRegistry = {};
|
|
1952
|
+
const annotations = extractJSDocAnnotationNodes(
|
|
1953
|
+
typeAlias,
|
|
1954
|
+
file,
|
|
1955
|
+
makeParseOptions(extensionRegistry)
|
|
1956
|
+
);
|
|
1545
1957
|
const visiting = /* @__PURE__ */ new Set();
|
|
1546
1958
|
for (const member of typeAlias.type.members) {
|
|
1547
1959
|
if (ts4.isPropertySignature(member)) {
|
|
1548
|
-
const fieldNode = analyzeInterfacePropertyToIR(
|
|
1960
|
+
const fieldNode = analyzeInterfacePropertyToIR(
|
|
1961
|
+
member,
|
|
1962
|
+
checker,
|
|
1963
|
+
file,
|
|
1964
|
+
typeRegistry,
|
|
1965
|
+
visiting,
|
|
1966
|
+
extensionRegistry
|
|
1967
|
+
);
|
|
1549
1968
|
if (fieldNode) {
|
|
1550
1969
|
fields.push(fieldNode);
|
|
1551
1970
|
}
|
|
@@ -1558,12 +1977,13 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
|
1558
1977
|
fields,
|
|
1559
1978
|
fieldLayouts: fields.map(() => ({})),
|
|
1560
1979
|
typeRegistry,
|
|
1980
|
+
...annotations.length > 0 && { annotations },
|
|
1561
1981
|
instanceMethods: [],
|
|
1562
1982
|
staticMethods: []
|
|
1563
1983
|
}
|
|
1564
1984
|
};
|
|
1565
1985
|
}
|
|
1566
|
-
function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
|
|
1986
|
+
function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1567
1987
|
if (!ts4.isIdentifier(prop.name)) {
|
|
1568
1988
|
return null;
|
|
1569
1989
|
}
|
|
@@ -1571,16 +1991,28 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
|
|
|
1571
1991
|
const tsType = checker.getTypeAtLocation(prop);
|
|
1572
1992
|
const optional = prop.questionToken !== void 0;
|
|
1573
1993
|
const provenance = provenanceForNode(prop, file);
|
|
1574
|
-
let type = resolveTypeNode(
|
|
1994
|
+
let type = resolveTypeNode(
|
|
1995
|
+
tsType,
|
|
1996
|
+
checker,
|
|
1997
|
+
file,
|
|
1998
|
+
typeRegistry,
|
|
1999
|
+
visiting,
|
|
2000
|
+
prop,
|
|
2001
|
+
extensionRegistry
|
|
2002
|
+
);
|
|
1575
2003
|
const constraints = [];
|
|
1576
2004
|
if (prop.type) {
|
|
1577
|
-
constraints.push(
|
|
2005
|
+
constraints.push(
|
|
2006
|
+
...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
|
|
2007
|
+
);
|
|
1578
2008
|
}
|
|
1579
|
-
constraints.push(...extractJSDocConstraintNodes(prop, file));
|
|
2009
|
+
constraints.push(...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type)));
|
|
1580
2010
|
let annotations = [];
|
|
1581
|
-
annotations.push(
|
|
2011
|
+
annotations.push(
|
|
2012
|
+
...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
2013
|
+
);
|
|
1582
2014
|
const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
|
|
1583
|
-
if (defaultAnnotation) {
|
|
2015
|
+
if (defaultAnnotation && !annotations.some((a) => a.annotationKind === "defaultValue")) {
|
|
1584
2016
|
annotations.push(defaultAnnotation);
|
|
1585
2017
|
}
|
|
1586
2018
|
({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
|
|
@@ -1594,7 +2026,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
|
|
|
1594
2026
|
provenance
|
|
1595
2027
|
};
|
|
1596
2028
|
}
|
|
1597
|
-
function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting) {
|
|
2029
|
+
function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1598
2030
|
if (!ts4.isIdentifier(prop.name)) {
|
|
1599
2031
|
return null;
|
|
1600
2032
|
}
|
|
@@ -1602,14 +2034,26 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
|
|
|
1602
2034
|
const tsType = checker.getTypeAtLocation(prop);
|
|
1603
2035
|
const optional = prop.questionToken !== void 0;
|
|
1604
2036
|
const provenance = provenanceForNode(prop, file);
|
|
1605
|
-
let type = resolveTypeNode(
|
|
2037
|
+
let type = resolveTypeNode(
|
|
2038
|
+
tsType,
|
|
2039
|
+
checker,
|
|
2040
|
+
file,
|
|
2041
|
+
typeRegistry,
|
|
2042
|
+
visiting,
|
|
2043
|
+
prop,
|
|
2044
|
+
extensionRegistry
|
|
2045
|
+
);
|
|
1606
2046
|
const constraints = [];
|
|
1607
2047
|
if (prop.type) {
|
|
1608
|
-
constraints.push(
|
|
2048
|
+
constraints.push(
|
|
2049
|
+
...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
|
|
2050
|
+
);
|
|
1609
2051
|
}
|
|
1610
|
-
constraints.push(...extractJSDocConstraintNodes(prop, file));
|
|
2052
|
+
constraints.push(...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type)));
|
|
1611
2053
|
let annotations = [];
|
|
1612
|
-
annotations.push(
|
|
2054
|
+
annotations.push(
|
|
2055
|
+
...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
2056
|
+
);
|
|
1613
2057
|
({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
|
|
1614
2058
|
return {
|
|
1615
2059
|
kind: "field",
|
|
@@ -1683,7 +2127,66 @@ function parseEnumMemberDisplayName(value) {
|
|
|
1683
2127
|
if (label === "") return null;
|
|
1684
2128
|
return { value: match[1], label };
|
|
1685
2129
|
}
|
|
1686
|
-
function
|
|
2130
|
+
function resolveRegisteredCustomType(sourceNode, extensionRegistry, checker) {
|
|
2131
|
+
if (sourceNode === void 0 || extensionRegistry === void 0) {
|
|
2132
|
+
return null;
|
|
2133
|
+
}
|
|
2134
|
+
const typeNode = extractTypeNodeFromSource(sourceNode);
|
|
2135
|
+
if (typeNode === void 0) {
|
|
2136
|
+
return null;
|
|
2137
|
+
}
|
|
2138
|
+
return resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker);
|
|
2139
|
+
}
|
|
2140
|
+
function resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker) {
|
|
2141
|
+
if (ts4.isParenthesizedTypeNode(typeNode)) {
|
|
2142
|
+
return resolveRegisteredCustomTypeFromTypeNode(typeNode.type, extensionRegistry, checker);
|
|
2143
|
+
}
|
|
2144
|
+
const typeName = getTypeNodeRegistrationName(typeNode);
|
|
2145
|
+
if (typeName === null) {
|
|
2146
|
+
return null;
|
|
2147
|
+
}
|
|
2148
|
+
const registration = extensionRegistry.findTypeByName(typeName);
|
|
2149
|
+
if (registration !== void 0) {
|
|
2150
|
+
return {
|
|
2151
|
+
kind: "custom",
|
|
2152
|
+
typeId: `${registration.extensionId}/${registration.registration.typeName}`,
|
|
2153
|
+
payload: null
|
|
2154
|
+
};
|
|
2155
|
+
}
|
|
2156
|
+
if (ts4.isTypeReferenceNode(typeNode) && ts4.isIdentifier(typeNode.typeName)) {
|
|
2157
|
+
const aliasDecl = checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts4.isTypeAliasDeclaration);
|
|
2158
|
+
if (aliasDecl !== void 0) {
|
|
2159
|
+
return resolveRegisteredCustomTypeFromTypeNode(aliasDecl.type, extensionRegistry, checker);
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
return null;
|
|
2163
|
+
}
|
|
2164
|
+
function extractTypeNodeFromSource(sourceNode) {
|
|
2165
|
+
if (ts4.isPropertyDeclaration(sourceNode) || ts4.isPropertySignature(sourceNode) || ts4.isParameter(sourceNode) || ts4.isTypeAliasDeclaration(sourceNode)) {
|
|
2166
|
+
return sourceNode.type;
|
|
2167
|
+
}
|
|
2168
|
+
if (ts4.isTypeNode(sourceNode)) {
|
|
2169
|
+
return sourceNode;
|
|
2170
|
+
}
|
|
2171
|
+
return void 0;
|
|
2172
|
+
}
|
|
2173
|
+
function getTypeNodeRegistrationName(typeNode) {
|
|
2174
|
+
if (ts4.isTypeReferenceNode(typeNode)) {
|
|
2175
|
+
return ts4.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : typeNode.typeName.right.text;
|
|
2176
|
+
}
|
|
2177
|
+
if (ts4.isParenthesizedTypeNode(typeNode)) {
|
|
2178
|
+
return getTypeNodeRegistrationName(typeNode.type);
|
|
2179
|
+
}
|
|
2180
|
+
if (typeNode.kind === ts4.SyntaxKind.BigIntKeyword || typeNode.kind === ts4.SyntaxKind.StringKeyword || typeNode.kind === ts4.SyntaxKind.NumberKeyword || typeNode.kind === ts4.SyntaxKind.BooleanKeyword) {
|
|
2181
|
+
return typeNode.getText();
|
|
2182
|
+
}
|
|
2183
|
+
return null;
|
|
2184
|
+
}
|
|
2185
|
+
function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
2186
|
+
const customType = resolveRegisteredCustomType(sourceNode, extensionRegistry, checker);
|
|
2187
|
+
if (customType) {
|
|
2188
|
+
return customType;
|
|
2189
|
+
}
|
|
1687
2190
|
if (type.flags & ts4.TypeFlags.String) {
|
|
1688
2191
|
return { kind: "primitive", primitiveKind: "string" };
|
|
1689
2192
|
}
|
|
@@ -1712,88 +2215,162 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
|
|
|
1712
2215
|
};
|
|
1713
2216
|
}
|
|
1714
2217
|
if (type.isUnion()) {
|
|
1715
|
-
return resolveUnionType(
|
|
2218
|
+
return resolveUnionType(
|
|
2219
|
+
type,
|
|
2220
|
+
checker,
|
|
2221
|
+
file,
|
|
2222
|
+
typeRegistry,
|
|
2223
|
+
visiting,
|
|
2224
|
+
sourceNode,
|
|
2225
|
+
extensionRegistry
|
|
2226
|
+
);
|
|
1716
2227
|
}
|
|
1717
2228
|
if (checker.isArrayType(type)) {
|
|
1718
|
-
return resolveArrayType(
|
|
2229
|
+
return resolveArrayType(
|
|
2230
|
+
type,
|
|
2231
|
+
checker,
|
|
2232
|
+
file,
|
|
2233
|
+
typeRegistry,
|
|
2234
|
+
visiting,
|
|
2235
|
+
sourceNode,
|
|
2236
|
+
extensionRegistry
|
|
2237
|
+
);
|
|
1719
2238
|
}
|
|
1720
2239
|
if (isObjectType(type)) {
|
|
1721
|
-
return resolveObjectType(type, checker, file, typeRegistry, visiting);
|
|
2240
|
+
return resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry);
|
|
1722
2241
|
}
|
|
1723
2242
|
return { kind: "primitive", primitiveKind: "string" };
|
|
1724
2243
|
}
|
|
1725
|
-
function resolveUnionType(type, checker, file, typeRegistry, visiting) {
|
|
2244
|
+
function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
2245
|
+
const typeName = getNamedTypeName(type);
|
|
2246
|
+
const namedDecl = getNamedTypeDeclaration(type);
|
|
2247
|
+
if (typeName && typeName in typeRegistry) {
|
|
2248
|
+
return { kind: "reference", name: typeName, typeArguments: [] };
|
|
2249
|
+
}
|
|
1726
2250
|
const allTypes = type.types;
|
|
2251
|
+
const unionMemberTypeNodes = extractUnionMemberTypeNodes(sourceNode, checker);
|
|
2252
|
+
const nonNullSourceNodes = unionMemberTypeNodes.filter(
|
|
2253
|
+
(memberTypeNode) => !isNullishTypeNode(resolveAliasedTypeNode(memberTypeNode, checker))
|
|
2254
|
+
);
|
|
1727
2255
|
const nonNullTypes = allTypes.filter(
|
|
1728
|
-
(
|
|
2256
|
+
(memberType) => !(memberType.flags & (ts4.TypeFlags.Null | ts4.TypeFlags.Undefined))
|
|
1729
2257
|
);
|
|
2258
|
+
const nonNullMembers = nonNullTypes.map((memberType, index) => ({
|
|
2259
|
+
memberType,
|
|
2260
|
+
sourceNode: nonNullSourceNodes.length === nonNullTypes.length ? nonNullSourceNodes[index] : void 0
|
|
2261
|
+
}));
|
|
1730
2262
|
const hasNull = allTypes.some((t) => t.flags & ts4.TypeFlags.Null);
|
|
2263
|
+
const memberDisplayNames = /* @__PURE__ */ new Map();
|
|
2264
|
+
if (namedDecl) {
|
|
2265
|
+
for (const [value, label] of extractDisplayNameMetadata(namedDecl).memberDisplayNames) {
|
|
2266
|
+
memberDisplayNames.set(value, label);
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
if (sourceNode) {
|
|
2270
|
+
for (const [value, label] of extractDisplayNameMetadata(sourceNode).memberDisplayNames) {
|
|
2271
|
+
memberDisplayNames.set(value, label);
|
|
2272
|
+
}
|
|
2273
|
+
}
|
|
2274
|
+
const registerNamed = (result) => {
|
|
2275
|
+
if (!typeName) {
|
|
2276
|
+
return result;
|
|
2277
|
+
}
|
|
2278
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
2279
|
+
typeRegistry[typeName] = {
|
|
2280
|
+
name: typeName,
|
|
2281
|
+
type: result,
|
|
2282
|
+
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
2283
|
+
provenance: provenanceForDeclaration(namedDecl ?? sourceNode, file)
|
|
2284
|
+
};
|
|
2285
|
+
return { kind: "reference", name: typeName, typeArguments: [] };
|
|
2286
|
+
};
|
|
2287
|
+
const applyMemberLabels = (members2) => members2.map((value) => {
|
|
2288
|
+
const displayName = memberDisplayNames.get(String(value));
|
|
2289
|
+
return displayName !== void 0 ? { value, displayName } : { value };
|
|
2290
|
+
});
|
|
1731
2291
|
const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts4.TypeFlags.BooleanLiteral);
|
|
1732
2292
|
if (isBooleanUnion2) {
|
|
1733
2293
|
const boolNode = { kind: "primitive", primitiveKind: "boolean" };
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
}
|
|
1740
|
-
return boolNode;
|
|
2294
|
+
const result = hasNull ? {
|
|
2295
|
+
kind: "union",
|
|
2296
|
+
members: [boolNode, { kind: "primitive", primitiveKind: "null" }]
|
|
2297
|
+
} : boolNode;
|
|
2298
|
+
return registerNamed(result);
|
|
1741
2299
|
}
|
|
1742
2300
|
const allStringLiterals = nonNullTypes.every((t) => t.isStringLiteral());
|
|
1743
2301
|
if (allStringLiterals && nonNullTypes.length > 0) {
|
|
1744
2302
|
const stringTypes = nonNullTypes.filter((t) => t.isStringLiteral());
|
|
1745
2303
|
const enumNode = {
|
|
1746
2304
|
kind: "enum",
|
|
1747
|
-
members: stringTypes.map((t) =>
|
|
2305
|
+
members: applyMemberLabels(stringTypes.map((t) => t.value))
|
|
1748
2306
|
};
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
}
|
|
1755
|
-
return enumNode;
|
|
2307
|
+
const result = hasNull ? {
|
|
2308
|
+
kind: "union",
|
|
2309
|
+
members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
|
|
2310
|
+
} : enumNode;
|
|
2311
|
+
return registerNamed(result);
|
|
1756
2312
|
}
|
|
1757
2313
|
const allNumberLiterals = nonNullTypes.every((t) => t.isNumberLiteral());
|
|
1758
2314
|
if (allNumberLiterals && nonNullTypes.length > 0) {
|
|
1759
2315
|
const numberTypes = nonNullTypes.filter((t) => t.isNumberLiteral());
|
|
1760
2316
|
const enumNode = {
|
|
1761
2317
|
kind: "enum",
|
|
1762
|
-
members: numberTypes.map((t) =>
|
|
2318
|
+
members: applyMemberLabels(numberTypes.map((t) => t.value))
|
|
1763
2319
|
};
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
2320
|
+
const result = hasNull ? {
|
|
2321
|
+
kind: "union",
|
|
2322
|
+
members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
|
|
2323
|
+
} : enumNode;
|
|
2324
|
+
return registerNamed(result);
|
|
2325
|
+
}
|
|
2326
|
+
if (nonNullMembers.length === 1 && nonNullMembers[0]) {
|
|
2327
|
+
const inner = resolveTypeNode(
|
|
2328
|
+
nonNullMembers[0].memberType,
|
|
2329
|
+
checker,
|
|
2330
|
+
file,
|
|
2331
|
+
typeRegistry,
|
|
2332
|
+
visiting,
|
|
2333
|
+
nonNullMembers[0].sourceNode ?? sourceNode,
|
|
2334
|
+
extensionRegistry
|
|
2335
|
+
);
|
|
2336
|
+
const result = hasNull ? {
|
|
2337
|
+
kind: "union",
|
|
2338
|
+
members: [inner, { kind: "primitive", primitiveKind: "null" }]
|
|
2339
|
+
} : inner;
|
|
2340
|
+
return registerNamed(result);
|
|
2341
|
+
}
|
|
2342
|
+
const members = nonNullMembers.map(
|
|
2343
|
+
({ memberType, sourceNode: memberSourceNode }) => resolveTypeNode(
|
|
2344
|
+
memberType,
|
|
2345
|
+
checker,
|
|
2346
|
+
file,
|
|
2347
|
+
typeRegistry,
|
|
2348
|
+
visiting,
|
|
2349
|
+
memberSourceNode ?? sourceNode,
|
|
2350
|
+
extensionRegistry
|
|
2351
|
+
)
|
|
1784
2352
|
);
|
|
1785
2353
|
if (hasNull) {
|
|
1786
2354
|
members.push({ kind: "primitive", primitiveKind: "null" });
|
|
1787
2355
|
}
|
|
1788
|
-
return { kind: "union", members };
|
|
2356
|
+
return registerNamed({ kind: "union", members });
|
|
1789
2357
|
}
|
|
1790
|
-
function resolveArrayType(type, checker, file, typeRegistry, visiting) {
|
|
2358
|
+
function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
1791
2359
|
const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
|
|
1792
2360
|
const elementType = typeArgs?.[0];
|
|
1793
|
-
const
|
|
2361
|
+
const elementSourceNode = extractArrayElementTypeNode(sourceNode, checker);
|
|
2362
|
+
const items = elementType ? resolveTypeNode(
|
|
2363
|
+
elementType,
|
|
2364
|
+
checker,
|
|
2365
|
+
file,
|
|
2366
|
+
typeRegistry,
|
|
2367
|
+
visiting,
|
|
2368
|
+
elementSourceNode,
|
|
2369
|
+
extensionRegistry
|
|
2370
|
+
) : { kind: "primitive", primitiveKind: "string" };
|
|
1794
2371
|
return { kind: "array", items };
|
|
1795
2372
|
}
|
|
1796
|
-
function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
|
|
2373
|
+
function tryResolveRecordType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1797
2374
|
if (type.getProperties().length > 0) {
|
|
1798
2375
|
return null;
|
|
1799
2376
|
}
|
|
@@ -1801,39 +2378,123 @@ function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
|
|
|
1801
2378
|
if (!indexInfo) {
|
|
1802
2379
|
return null;
|
|
1803
2380
|
}
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
}
|
|
2381
|
+
const valueType = resolveTypeNode(
|
|
2382
|
+
indexInfo.type,
|
|
2383
|
+
checker,
|
|
2384
|
+
file,
|
|
2385
|
+
typeRegistry,
|
|
2386
|
+
visiting,
|
|
2387
|
+
void 0,
|
|
2388
|
+
extensionRegistry
|
|
2389
|
+
);
|
|
2390
|
+
return { kind: "record", valueType };
|
|
1814
2391
|
}
|
|
1815
|
-
function
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
2392
|
+
function typeNodeContainsReference(type, targetName) {
|
|
2393
|
+
switch (type.kind) {
|
|
2394
|
+
case "reference":
|
|
2395
|
+
return type.name === targetName;
|
|
2396
|
+
case "array":
|
|
2397
|
+
return typeNodeContainsReference(type.items, targetName);
|
|
2398
|
+
case "record":
|
|
2399
|
+
return typeNodeContainsReference(type.valueType, targetName);
|
|
2400
|
+
case "union":
|
|
2401
|
+
return type.members.some((member) => typeNodeContainsReference(member, targetName));
|
|
2402
|
+
case "object":
|
|
2403
|
+
return type.properties.some(
|
|
2404
|
+
(property) => typeNodeContainsReference(property.type, targetName)
|
|
2405
|
+
);
|
|
2406
|
+
case "primitive":
|
|
2407
|
+
case "enum":
|
|
2408
|
+
case "dynamic":
|
|
2409
|
+
case "custom":
|
|
2410
|
+
return false;
|
|
2411
|
+
default: {
|
|
2412
|
+
const _exhaustive = type;
|
|
2413
|
+
return _exhaustive;
|
|
2414
|
+
}
|
|
1819
2415
|
}
|
|
2416
|
+
}
|
|
2417
|
+
function resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2418
|
+
const typeName = getNamedTypeName(type);
|
|
2419
|
+
const namedTypeName = typeName ?? void 0;
|
|
2420
|
+
const namedDecl = getNamedTypeDeclaration(type);
|
|
2421
|
+
const shouldRegisterNamedType = namedTypeName !== void 0 && !(namedTypeName === "Record" && namedDecl?.getSourceFile().fileName !== file);
|
|
2422
|
+
const clearNamedTypeRegistration = () => {
|
|
2423
|
+
if (namedTypeName === void 0 || !shouldRegisterNamedType) {
|
|
2424
|
+
return;
|
|
2425
|
+
}
|
|
2426
|
+
Reflect.deleteProperty(typeRegistry, namedTypeName);
|
|
2427
|
+
};
|
|
1820
2428
|
if (visiting.has(type)) {
|
|
2429
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
2430
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
2431
|
+
}
|
|
1821
2432
|
return { kind: "object", properties: [], additionalProperties: false };
|
|
1822
2433
|
}
|
|
2434
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType && !typeRegistry[namedTypeName]) {
|
|
2435
|
+
typeRegistry[namedTypeName] = {
|
|
2436
|
+
name: namedTypeName,
|
|
2437
|
+
type: RESOLVING_TYPE_PLACEHOLDER,
|
|
2438
|
+
provenance: provenanceForDeclaration(namedDecl, file)
|
|
2439
|
+
};
|
|
2440
|
+
}
|
|
1823
2441
|
visiting.add(type);
|
|
1824
|
-
|
|
1825
|
-
|
|
2442
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType && typeRegistry[namedTypeName]?.type !== void 0) {
|
|
2443
|
+
if (typeRegistry[namedTypeName].type !== RESOLVING_TYPE_PLACEHOLDER) {
|
|
2444
|
+
visiting.delete(type);
|
|
2445
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
2446
|
+
}
|
|
2447
|
+
}
|
|
2448
|
+
const recordNode = tryResolveRecordType(
|
|
2449
|
+
type,
|
|
2450
|
+
checker,
|
|
2451
|
+
file,
|
|
2452
|
+
typeRegistry,
|
|
2453
|
+
visiting,
|
|
2454
|
+
extensionRegistry
|
|
2455
|
+
);
|
|
2456
|
+
if (recordNode) {
|
|
1826
2457
|
visiting.delete(type);
|
|
1827
|
-
|
|
2458
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
2459
|
+
const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType, namedTypeName);
|
|
2460
|
+
if (!isRecursiveRecord) {
|
|
2461
|
+
clearNamedTypeRegistration();
|
|
2462
|
+
return recordNode;
|
|
2463
|
+
}
|
|
2464
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
2465
|
+
typeRegistry[namedTypeName] = {
|
|
2466
|
+
name: namedTypeName,
|
|
2467
|
+
type: recordNode,
|
|
2468
|
+
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
2469
|
+
provenance: provenanceForDeclaration(namedDecl, file)
|
|
2470
|
+
};
|
|
2471
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
2472
|
+
}
|
|
2473
|
+
return recordNode;
|
|
1828
2474
|
}
|
|
1829
2475
|
const properties = [];
|
|
1830
|
-
const fieldInfoMap = getNamedTypeFieldNodeInfoMap(
|
|
2476
|
+
const fieldInfoMap = getNamedTypeFieldNodeInfoMap(
|
|
2477
|
+
type,
|
|
2478
|
+
checker,
|
|
2479
|
+
file,
|
|
2480
|
+
typeRegistry,
|
|
2481
|
+
visiting,
|
|
2482
|
+
extensionRegistry
|
|
2483
|
+
);
|
|
1831
2484
|
for (const prop of type.getProperties()) {
|
|
1832
2485
|
const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
|
|
1833
2486
|
if (!declaration) continue;
|
|
1834
2487
|
const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
|
|
1835
2488
|
const optional = !!(prop.flags & ts4.SymbolFlags.Optional);
|
|
1836
|
-
const propTypeNode = resolveTypeNode(
|
|
2489
|
+
const propTypeNode = resolveTypeNode(
|
|
2490
|
+
propType,
|
|
2491
|
+
checker,
|
|
2492
|
+
file,
|
|
2493
|
+
typeRegistry,
|
|
2494
|
+
visiting,
|
|
2495
|
+
declaration,
|
|
2496
|
+
extensionRegistry
|
|
2497
|
+
);
|
|
1837
2498
|
const fieldNodeInfo = fieldInfoMap?.get(prop.name);
|
|
1838
2499
|
properties.push({
|
|
1839
2500
|
name: prop.name,
|
|
@@ -1850,17 +2511,19 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
|
1850
2511
|
properties,
|
|
1851
2512
|
additionalProperties: true
|
|
1852
2513
|
};
|
|
1853
|
-
if (
|
|
1854
|
-
|
|
1855
|
-
|
|
2514
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
2515
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
2516
|
+
typeRegistry[namedTypeName] = {
|
|
2517
|
+
name: namedTypeName,
|
|
1856
2518
|
type: objectNode,
|
|
1857
|
-
|
|
2519
|
+
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
2520
|
+
provenance: provenanceForDeclaration(namedDecl, file)
|
|
1858
2521
|
};
|
|
1859
|
-
return { kind: "reference", name:
|
|
2522
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
1860
2523
|
}
|
|
1861
2524
|
return objectNode;
|
|
1862
2525
|
}
|
|
1863
|
-
function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting) {
|
|
2526
|
+
function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1864
2527
|
const symbols = [type.getSymbol(), type.aliasSymbol].filter(
|
|
1865
2528
|
(s) => s?.declarations != null && s.declarations.length > 0
|
|
1866
2529
|
);
|
|
@@ -1872,7 +2535,14 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
1872
2535
|
const map = /* @__PURE__ */ new Map();
|
|
1873
2536
|
for (const member of classDecl.members) {
|
|
1874
2537
|
if (ts4.isPropertyDeclaration(member) && ts4.isIdentifier(member.name)) {
|
|
1875
|
-
const fieldNode = analyzeFieldToIR(
|
|
2538
|
+
const fieldNode = analyzeFieldToIR(
|
|
2539
|
+
member,
|
|
2540
|
+
checker,
|
|
2541
|
+
file,
|
|
2542
|
+
typeRegistry,
|
|
2543
|
+
visiting,
|
|
2544
|
+
extensionRegistry
|
|
2545
|
+
);
|
|
1876
2546
|
if (fieldNode) {
|
|
1877
2547
|
map.set(fieldNode.name, {
|
|
1878
2548
|
constraints: [...fieldNode.constraints],
|
|
@@ -1886,7 +2556,14 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
1886
2556
|
}
|
|
1887
2557
|
const interfaceDecl = declarations.find(ts4.isInterfaceDeclaration);
|
|
1888
2558
|
if (interfaceDecl) {
|
|
1889
|
-
return buildFieldNodeInfoMap(
|
|
2559
|
+
return buildFieldNodeInfoMap(
|
|
2560
|
+
interfaceDecl.members,
|
|
2561
|
+
checker,
|
|
2562
|
+
file,
|
|
2563
|
+
typeRegistry,
|
|
2564
|
+
visiting,
|
|
2565
|
+
extensionRegistry
|
|
2566
|
+
);
|
|
1890
2567
|
}
|
|
1891
2568
|
const typeAliasDecl = declarations.find(ts4.isTypeAliasDeclaration);
|
|
1892
2569
|
if (typeAliasDecl && ts4.isTypeLiteralNode(typeAliasDecl.type)) {
|
|
@@ -1895,17 +2572,68 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
1895
2572
|
checker,
|
|
1896
2573
|
file,
|
|
1897
2574
|
typeRegistry,
|
|
1898
|
-
visiting
|
|
2575
|
+
visiting,
|
|
2576
|
+
extensionRegistry
|
|
1899
2577
|
);
|
|
1900
2578
|
}
|
|
1901
2579
|
}
|
|
1902
2580
|
return null;
|
|
1903
2581
|
}
|
|
1904
|
-
function
|
|
2582
|
+
function extractArrayElementTypeNode(sourceNode, checker) {
|
|
2583
|
+
const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
|
|
2584
|
+
if (typeNode === void 0) {
|
|
2585
|
+
return void 0;
|
|
2586
|
+
}
|
|
2587
|
+
const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
|
|
2588
|
+
if (ts4.isArrayTypeNode(resolvedTypeNode)) {
|
|
2589
|
+
return resolvedTypeNode.elementType;
|
|
2590
|
+
}
|
|
2591
|
+
if (ts4.isTypeReferenceNode(resolvedTypeNode) && ts4.isIdentifier(resolvedTypeNode.typeName) && resolvedTypeNode.typeName.text === "Array" && resolvedTypeNode.typeArguments?.[0]) {
|
|
2592
|
+
return resolvedTypeNode.typeArguments[0];
|
|
2593
|
+
}
|
|
2594
|
+
return void 0;
|
|
2595
|
+
}
|
|
2596
|
+
function extractUnionMemberTypeNodes(sourceNode, checker) {
|
|
2597
|
+
const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
|
|
2598
|
+
if (!typeNode) {
|
|
2599
|
+
return [];
|
|
2600
|
+
}
|
|
2601
|
+
const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
|
|
2602
|
+
return ts4.isUnionTypeNode(resolvedTypeNode) ? [...resolvedTypeNode.types] : [];
|
|
2603
|
+
}
|
|
2604
|
+
function resolveAliasedTypeNode(typeNode, checker, visited = /* @__PURE__ */ new Set()) {
|
|
2605
|
+
if (ts4.isParenthesizedTypeNode(typeNode)) {
|
|
2606
|
+
return resolveAliasedTypeNode(typeNode.type, checker, visited);
|
|
2607
|
+
}
|
|
2608
|
+
if (!ts4.isTypeReferenceNode(typeNode) || !ts4.isIdentifier(typeNode.typeName)) {
|
|
2609
|
+
return typeNode;
|
|
2610
|
+
}
|
|
2611
|
+
const symbol = checker.getSymbolAtLocation(typeNode.typeName);
|
|
2612
|
+
const aliasDecl = symbol?.declarations?.find(ts4.isTypeAliasDeclaration);
|
|
2613
|
+
if (aliasDecl === void 0 || visited.has(aliasDecl)) {
|
|
2614
|
+
return typeNode;
|
|
2615
|
+
}
|
|
2616
|
+
visited.add(aliasDecl);
|
|
2617
|
+
return resolveAliasedTypeNode(aliasDecl.type, checker, visited);
|
|
2618
|
+
}
|
|
2619
|
+
function isNullishTypeNode(typeNode) {
|
|
2620
|
+
if (typeNode.kind === ts4.SyntaxKind.NullKeyword || typeNode.kind === ts4.SyntaxKind.UndefinedKeyword) {
|
|
2621
|
+
return true;
|
|
2622
|
+
}
|
|
2623
|
+
return ts4.isLiteralTypeNode(typeNode) && (typeNode.literal.kind === ts4.SyntaxKind.NullKeyword || typeNode.literal.kind === ts4.SyntaxKind.UndefinedKeyword);
|
|
2624
|
+
}
|
|
2625
|
+
function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1905
2626
|
const map = /* @__PURE__ */ new Map();
|
|
1906
2627
|
for (const member of members) {
|
|
1907
2628
|
if (ts4.isPropertySignature(member)) {
|
|
1908
|
-
const fieldNode = analyzeInterfacePropertyToIR(
|
|
2629
|
+
const fieldNode = analyzeInterfacePropertyToIR(
|
|
2630
|
+
member,
|
|
2631
|
+
checker,
|
|
2632
|
+
file,
|
|
2633
|
+
typeRegistry,
|
|
2634
|
+
visiting,
|
|
2635
|
+
extensionRegistry
|
|
2636
|
+
);
|
|
1909
2637
|
if (fieldNode) {
|
|
1910
2638
|
map.set(fieldNode.name, {
|
|
1911
2639
|
constraints: [...fieldNode.constraints],
|
|
@@ -1918,7 +2646,7 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting) {
|
|
|
1918
2646
|
return map;
|
|
1919
2647
|
}
|
|
1920
2648
|
var MAX_ALIAS_CHAIN_DEPTH = 8;
|
|
1921
|
-
function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
|
|
2649
|
+
function extractTypeAliasConstraintNodes(typeNode, checker, file, extensionRegistry, depth = 0) {
|
|
1922
2650
|
if (!ts4.isTypeReferenceNode(typeNode)) return [];
|
|
1923
2651
|
if (depth >= MAX_ALIAS_CHAIN_DEPTH) {
|
|
1924
2652
|
const aliasName = typeNode.typeName.getText();
|
|
@@ -1931,8 +2659,29 @@ function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
|
|
|
1931
2659
|
const aliasDecl = symbol.declarations.find(ts4.isTypeAliasDeclaration);
|
|
1932
2660
|
if (!aliasDecl) return [];
|
|
1933
2661
|
if (ts4.isTypeLiteralNode(aliasDecl.type)) return [];
|
|
1934
|
-
const
|
|
1935
|
-
|
|
2662
|
+
const aliasFieldType = resolveTypeNode(
|
|
2663
|
+
checker.getTypeAtLocation(aliasDecl.type),
|
|
2664
|
+
checker,
|
|
2665
|
+
file,
|
|
2666
|
+
{},
|
|
2667
|
+
/* @__PURE__ */ new Set(),
|
|
2668
|
+
aliasDecl.type,
|
|
2669
|
+
extensionRegistry
|
|
2670
|
+
);
|
|
2671
|
+
const constraints = extractJSDocConstraintNodes(
|
|
2672
|
+
aliasDecl,
|
|
2673
|
+
file,
|
|
2674
|
+
makeParseOptions(extensionRegistry, aliasFieldType)
|
|
2675
|
+
);
|
|
2676
|
+
constraints.push(
|
|
2677
|
+
...extractTypeAliasConstraintNodes(
|
|
2678
|
+
aliasDecl.type,
|
|
2679
|
+
checker,
|
|
2680
|
+
file,
|
|
2681
|
+
extensionRegistry,
|
|
2682
|
+
depth + 1
|
|
2683
|
+
)
|
|
2684
|
+
);
|
|
1936
2685
|
return constraints;
|
|
1937
2686
|
}
|
|
1938
2687
|
function provenanceForNode(node, file) {
|
|
@@ -1948,6 +2697,12 @@ function provenanceForNode(node, file) {
|
|
|
1948
2697
|
function provenanceForFile(file) {
|
|
1949
2698
|
return { surface: "tsdoc", file, line: 0, column: 0 };
|
|
1950
2699
|
}
|
|
2700
|
+
function provenanceForDeclaration(node, file) {
|
|
2701
|
+
if (!node) {
|
|
2702
|
+
return provenanceForFile(file);
|
|
2703
|
+
}
|
|
2704
|
+
return provenanceForNode(node, file);
|
|
2705
|
+
}
|
|
1951
2706
|
function getNamedTypeName(type) {
|
|
1952
2707
|
const symbol = type.getSymbol();
|
|
1953
2708
|
if (symbol?.declarations) {
|
|
@@ -1966,6 +2721,20 @@ function getNamedTypeName(type) {
|
|
|
1966
2721
|
}
|
|
1967
2722
|
return null;
|
|
1968
2723
|
}
|
|
2724
|
+
function getNamedTypeDeclaration(type) {
|
|
2725
|
+
const symbol = type.getSymbol();
|
|
2726
|
+
if (symbol?.declarations) {
|
|
2727
|
+
const decl = symbol.declarations[0];
|
|
2728
|
+
if (decl && (ts4.isClassDeclaration(decl) || ts4.isInterfaceDeclaration(decl) || ts4.isTypeAliasDeclaration(decl))) {
|
|
2729
|
+
return decl;
|
|
2730
|
+
}
|
|
2731
|
+
}
|
|
2732
|
+
const aliasSymbol = type.aliasSymbol;
|
|
2733
|
+
if (aliasSymbol?.declarations) {
|
|
2734
|
+
return aliasSymbol.declarations.find(ts4.isTypeAliasDeclaration);
|
|
2735
|
+
}
|
|
2736
|
+
return void 0;
|
|
2737
|
+
}
|
|
1969
2738
|
function analyzeMethod(method, checker) {
|
|
1970
2739
|
if (!ts4.isIdentifier(method.name)) {
|
|
1971
2740
|
return null;
|
|
@@ -2008,10 +2777,10 @@ function detectFormSpecReference(typeNode) {
|
|
|
2008
2777
|
}
|
|
2009
2778
|
|
|
2010
2779
|
// src/generators/class-schema.ts
|
|
2011
|
-
function generateClassSchemas(analysis, source) {
|
|
2780
|
+
function generateClassSchemas(analysis, source, options) {
|
|
2012
2781
|
const ir = canonicalizeTSDoc(analysis, source);
|
|
2013
2782
|
return {
|
|
2014
|
-
jsonSchema: generateJsonSchemaFromIR(ir),
|
|
2783
|
+
jsonSchema: generateJsonSchemaFromIR(ir, options),
|
|
2015
2784
|
uiSchema: generateUiSchemaFromIR(ir)
|
|
2016
2785
|
};
|
|
2017
2786
|
}
|
|
@@ -2021,27 +2790,54 @@ function generateSchemasFromClass(options) {
|
|
|
2021
2790
|
if (!classDecl) {
|
|
2022
2791
|
throw new Error(`Class "${options.className}" not found in ${options.filePath}`);
|
|
2023
2792
|
}
|
|
2024
|
-
const analysis = analyzeClassToIR(
|
|
2025
|
-
|
|
2793
|
+
const analysis = analyzeClassToIR(
|
|
2794
|
+
classDecl,
|
|
2795
|
+
ctx.checker,
|
|
2796
|
+
options.filePath,
|
|
2797
|
+
options.extensionRegistry
|
|
2798
|
+
);
|
|
2799
|
+
return generateClassSchemas(
|
|
2800
|
+
analysis,
|
|
2801
|
+
{ file: options.filePath },
|
|
2802
|
+
{
|
|
2803
|
+
extensionRegistry: options.extensionRegistry,
|
|
2804
|
+
vendorPrefix: options.vendorPrefix
|
|
2805
|
+
}
|
|
2806
|
+
);
|
|
2026
2807
|
}
|
|
2027
2808
|
function generateSchemas(options) {
|
|
2028
2809
|
const ctx = createProgramContext(options.filePath);
|
|
2029
2810
|
const source = { file: options.filePath };
|
|
2030
2811
|
const classDecl = findClassByName(ctx.sourceFile, options.typeName);
|
|
2031
2812
|
if (classDecl) {
|
|
2032
|
-
const analysis = analyzeClassToIR(
|
|
2033
|
-
|
|
2813
|
+
const analysis = analyzeClassToIR(
|
|
2814
|
+
classDecl,
|
|
2815
|
+
ctx.checker,
|
|
2816
|
+
options.filePath,
|
|
2817
|
+
options.extensionRegistry
|
|
2818
|
+
);
|
|
2819
|
+
return generateClassSchemas(analysis, source, options);
|
|
2034
2820
|
}
|
|
2035
2821
|
const interfaceDecl = findInterfaceByName(ctx.sourceFile, options.typeName);
|
|
2036
2822
|
if (interfaceDecl) {
|
|
2037
|
-
const analysis = analyzeInterfaceToIR(
|
|
2038
|
-
|
|
2823
|
+
const analysis = analyzeInterfaceToIR(
|
|
2824
|
+
interfaceDecl,
|
|
2825
|
+
ctx.checker,
|
|
2826
|
+
options.filePath,
|
|
2827
|
+
options.extensionRegistry
|
|
2828
|
+
);
|
|
2829
|
+
return generateClassSchemas(analysis, source, options);
|
|
2039
2830
|
}
|
|
2040
2831
|
const typeAlias = findTypeAliasByName(ctx.sourceFile, options.typeName);
|
|
2041
2832
|
if (typeAlias) {
|
|
2042
|
-
const result = analyzeTypeAliasToIR(
|
|
2833
|
+
const result = analyzeTypeAliasToIR(
|
|
2834
|
+
typeAlias,
|
|
2835
|
+
ctx.checker,
|
|
2836
|
+
options.filePath,
|
|
2837
|
+
options.extensionRegistry
|
|
2838
|
+
);
|
|
2043
2839
|
if (result.ok) {
|
|
2044
|
-
return generateClassSchemas(result.analysis, source);
|
|
2840
|
+
return generateClassSchemas(result.analysis, source, options);
|
|
2045
2841
|
}
|
|
2046
2842
|
throw new Error(result.error);
|
|
2047
2843
|
}
|
|
@@ -2050,6 +2846,205 @@ function generateSchemas(options) {
|
|
|
2050
2846
|
);
|
|
2051
2847
|
}
|
|
2052
2848
|
|
|
2849
|
+
// src/generators/mixed-authoring.ts
|
|
2850
|
+
function buildMixedAuthoringSchemas(options) {
|
|
2851
|
+
const { filePath, typeName, overlays, ...schemaOptions } = options;
|
|
2852
|
+
const analysis = analyzeNamedType(filePath, typeName, schemaOptions.extensionRegistry);
|
|
2853
|
+
const composedAnalysis = composeAnalysisWithOverlays(analysis, overlays);
|
|
2854
|
+
const ir = canonicalizeTSDoc(composedAnalysis, { file: filePath });
|
|
2855
|
+
return {
|
|
2856
|
+
jsonSchema: generateJsonSchemaFromIR(ir, schemaOptions),
|
|
2857
|
+
uiSchema: generateUiSchemaFromIR(ir)
|
|
2858
|
+
};
|
|
2859
|
+
}
|
|
2860
|
+
function analyzeNamedType(filePath, typeName, extensionRegistry) {
|
|
2861
|
+
const ctx = createProgramContext(filePath);
|
|
2862
|
+
const source = { file: filePath };
|
|
2863
|
+
const classDecl = findClassByName(ctx.sourceFile, typeName);
|
|
2864
|
+
if (classDecl !== null) {
|
|
2865
|
+
return analyzeClassToIR(classDecl, ctx.checker, source.file, extensionRegistry);
|
|
2866
|
+
}
|
|
2867
|
+
const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
|
|
2868
|
+
if (interfaceDecl !== null) {
|
|
2869
|
+
return analyzeInterfaceToIR(interfaceDecl, ctx.checker, source.file, extensionRegistry);
|
|
2870
|
+
}
|
|
2871
|
+
const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
|
|
2872
|
+
if (typeAlias !== null) {
|
|
2873
|
+
const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, source.file, extensionRegistry);
|
|
2874
|
+
if (result.ok) {
|
|
2875
|
+
return result.analysis;
|
|
2876
|
+
}
|
|
2877
|
+
throw new Error(result.error);
|
|
2878
|
+
}
|
|
2879
|
+
throw new Error(
|
|
2880
|
+
`Type "${typeName}" not found as a class, interface, or type alias in ${filePath}`
|
|
2881
|
+
);
|
|
2882
|
+
}
|
|
2883
|
+
function composeAnalysisWithOverlays(analysis, overlays) {
|
|
2884
|
+
const overlayIR = canonicalizeChainDSL(overlays);
|
|
2885
|
+
const overlayFields = collectOverlayFields(overlayIR.elements);
|
|
2886
|
+
if (overlayFields.length === 0) {
|
|
2887
|
+
return analysis;
|
|
2888
|
+
}
|
|
2889
|
+
const overlayByName = /* @__PURE__ */ new Map();
|
|
2890
|
+
for (const field of overlayFields) {
|
|
2891
|
+
if (overlayByName.has(field.name)) {
|
|
2892
|
+
throw new Error(`Mixed-authoring overlays define "${field.name}" more than once`);
|
|
2893
|
+
}
|
|
2894
|
+
overlayByName.set(field.name, field);
|
|
2895
|
+
}
|
|
2896
|
+
const mergedFields = [];
|
|
2897
|
+
for (const baseField of analysis.fields) {
|
|
2898
|
+
const overlayField = overlayByName.get(baseField.name);
|
|
2899
|
+
if (overlayField === void 0) {
|
|
2900
|
+
mergedFields.push(baseField);
|
|
2901
|
+
continue;
|
|
2902
|
+
}
|
|
2903
|
+
mergedFields.push(mergeFieldOverlay(baseField, overlayField, analysis.typeRegistry));
|
|
2904
|
+
overlayByName.delete(baseField.name);
|
|
2905
|
+
}
|
|
2906
|
+
if (overlayByName.size > 0) {
|
|
2907
|
+
const unknownFields = [...overlayByName.keys()].sort().join(", ");
|
|
2908
|
+
throw new Error(
|
|
2909
|
+
`Mixed-authoring overlays reference fields that are not present in the static model: ${unknownFields}`
|
|
2910
|
+
);
|
|
2911
|
+
}
|
|
2912
|
+
return {
|
|
2913
|
+
...analysis,
|
|
2914
|
+
fields: mergedFields
|
|
2915
|
+
};
|
|
2916
|
+
}
|
|
2917
|
+
function collectOverlayFields(elements) {
|
|
2918
|
+
const fields = [];
|
|
2919
|
+
for (const element of elements) {
|
|
2920
|
+
switch (element.kind) {
|
|
2921
|
+
case "field":
|
|
2922
|
+
fields.push(element);
|
|
2923
|
+
break;
|
|
2924
|
+
case "group":
|
|
2925
|
+
fields.push(...collectOverlayFields(element.elements));
|
|
2926
|
+
break;
|
|
2927
|
+
case "conditional":
|
|
2928
|
+
fields.push(...collectOverlayFields(element.elements));
|
|
2929
|
+
break;
|
|
2930
|
+
default: {
|
|
2931
|
+
const _exhaustive = element;
|
|
2932
|
+
void _exhaustive;
|
|
2933
|
+
}
|
|
2934
|
+
}
|
|
2935
|
+
}
|
|
2936
|
+
return fields;
|
|
2937
|
+
}
|
|
2938
|
+
function mergeFieldOverlay(baseField, overlayField, typeRegistry) {
|
|
2939
|
+
assertSupportedOverlayField(baseField, overlayField);
|
|
2940
|
+
return {
|
|
2941
|
+
...baseField,
|
|
2942
|
+
type: mergeFieldType(baseField, overlayField, typeRegistry),
|
|
2943
|
+
annotations: mergeAnnotations(baseField.annotations, overlayField.annotations)
|
|
2944
|
+
};
|
|
2945
|
+
}
|
|
2946
|
+
function assertSupportedOverlayField(baseField, overlayField) {
|
|
2947
|
+
if (overlayField.constraints.length > 0) {
|
|
2948
|
+
throw new Error(
|
|
2949
|
+
`Mixed-authoring overlay for "${baseField.name}" cannot define constraints; keep constraints on the static model`
|
|
2950
|
+
);
|
|
2951
|
+
}
|
|
2952
|
+
if (overlayField.required && !baseField.required) {
|
|
2953
|
+
throw new Error(
|
|
2954
|
+
`Mixed-authoring overlay for "${baseField.name}" cannot change requiredness; keep requiredness on the static model`
|
|
2955
|
+
);
|
|
2956
|
+
}
|
|
2957
|
+
}
|
|
2958
|
+
function mergeFieldType(baseField, overlayField, typeRegistry) {
|
|
2959
|
+
const { type: baseType } = baseField;
|
|
2960
|
+
const { type: overlayType } = overlayField;
|
|
2961
|
+
if (overlayType.kind === "object" || overlayType.kind === "array") {
|
|
2962
|
+
throw new Error(
|
|
2963
|
+
`Mixed-authoring overlays do not support nested object or array overlays for "${baseField.name}"`
|
|
2964
|
+
);
|
|
2965
|
+
}
|
|
2966
|
+
if (overlayType.kind === "dynamic") {
|
|
2967
|
+
if (!isCompatibleDynamicOverlay(baseField, overlayField, typeRegistry)) {
|
|
2968
|
+
throw new Error(
|
|
2969
|
+
`Mixed-authoring overlay for "${baseField.name}" is incompatible with the static field type`
|
|
2970
|
+
);
|
|
2971
|
+
}
|
|
2972
|
+
return overlayType;
|
|
2973
|
+
}
|
|
2974
|
+
if (!isSameStaticTypeShape(baseType, overlayType)) {
|
|
2975
|
+
throw new Error(
|
|
2976
|
+
`Mixed-authoring overlay for "${baseField.name}" must preserve the static field type`
|
|
2977
|
+
);
|
|
2978
|
+
}
|
|
2979
|
+
return baseType;
|
|
2980
|
+
}
|
|
2981
|
+
function isCompatibleDynamicOverlay(baseField, overlayField, typeRegistry) {
|
|
2982
|
+
const overlayType = overlayField.type;
|
|
2983
|
+
if (overlayType.kind !== "dynamic") {
|
|
2984
|
+
return false;
|
|
2985
|
+
}
|
|
2986
|
+
const resolvedBaseType = resolveReferenceType(baseField.type, typeRegistry);
|
|
2987
|
+
if (resolvedBaseType === null) {
|
|
2988
|
+
return false;
|
|
2989
|
+
}
|
|
2990
|
+
if (overlayType.dynamicKind === "enum") {
|
|
2991
|
+
return resolvedBaseType.kind === "primitive" ? resolvedBaseType.primitiveKind === "string" : resolvedBaseType.kind === "enum";
|
|
2992
|
+
}
|
|
2993
|
+
return resolvedBaseType.kind === "object" || resolvedBaseType.kind === "record";
|
|
2994
|
+
}
|
|
2995
|
+
function resolveReferenceType(type, typeRegistry, seen = /* @__PURE__ */ new Set()) {
|
|
2996
|
+
if (type.kind !== "reference") {
|
|
2997
|
+
return type;
|
|
2998
|
+
}
|
|
2999
|
+
if (seen.has(type.name)) {
|
|
3000
|
+
return null;
|
|
3001
|
+
}
|
|
3002
|
+
const definition = typeRegistry[type.name];
|
|
3003
|
+
if (definition === void 0) {
|
|
3004
|
+
return null;
|
|
3005
|
+
}
|
|
3006
|
+
seen.add(type.name);
|
|
3007
|
+
return resolveReferenceType(definition.type, typeRegistry, seen);
|
|
3008
|
+
}
|
|
3009
|
+
function isSameStaticTypeShape(baseType, overlayType) {
|
|
3010
|
+
if (baseType.kind !== overlayType.kind) {
|
|
3011
|
+
return false;
|
|
3012
|
+
}
|
|
3013
|
+
switch (baseType.kind) {
|
|
3014
|
+
case "primitive":
|
|
3015
|
+
return overlayType.kind === "primitive" && baseType.primitiveKind === overlayType.primitiveKind;
|
|
3016
|
+
case "enum":
|
|
3017
|
+
return overlayType.kind === "enum";
|
|
3018
|
+
case "dynamic":
|
|
3019
|
+
return overlayType.kind === "dynamic" && baseType.dynamicKind === overlayType.dynamicKind && baseType.sourceKey === overlayType.sourceKey;
|
|
3020
|
+
case "record":
|
|
3021
|
+
return overlayType.kind === "record";
|
|
3022
|
+
case "reference":
|
|
3023
|
+
return overlayType.kind === "reference" && baseType.name === overlayType.name;
|
|
3024
|
+
case "union":
|
|
3025
|
+
return overlayType.kind === "union";
|
|
3026
|
+
case "custom":
|
|
3027
|
+
return overlayType.kind === "custom" && baseType.typeId === overlayType.typeId;
|
|
3028
|
+
case "object":
|
|
3029
|
+
case "array":
|
|
3030
|
+
return true;
|
|
3031
|
+
default: {
|
|
3032
|
+
const _exhaustive = baseType;
|
|
3033
|
+
return _exhaustive;
|
|
3034
|
+
}
|
|
3035
|
+
}
|
|
3036
|
+
}
|
|
3037
|
+
function mergeAnnotations(baseAnnotations, overlayAnnotations) {
|
|
3038
|
+
const baseKeys = new Set(baseAnnotations.map(annotationKey));
|
|
3039
|
+
const overlayOnly = overlayAnnotations.filter(
|
|
3040
|
+
(annotation) => !baseKeys.has(annotationKey(annotation))
|
|
3041
|
+
);
|
|
3042
|
+
return [...baseAnnotations, ...overlayOnly];
|
|
3043
|
+
}
|
|
3044
|
+
function annotationKey(annotation) {
|
|
3045
|
+
return annotation.annotationKind === "custom" ? `${annotation.annotationKind}:${annotation.annotationId}` : annotation.annotationKind;
|
|
3046
|
+
}
|
|
3047
|
+
|
|
2053
3048
|
// src/index.ts
|
|
2054
3049
|
function buildFormSchemas(form, options) {
|
|
2055
3050
|
return {
|
|
@@ -2075,6 +3070,7 @@ function writeSchemas(form, options) {
|
|
|
2075
3070
|
}
|
|
2076
3071
|
export {
|
|
2077
3072
|
buildFormSchemas,
|
|
3073
|
+
buildMixedAuthoringSchemas,
|
|
2078
3074
|
categorizationSchema,
|
|
2079
3075
|
categorySchema,
|
|
2080
3076
|
controlSchema,
|