@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.cjs
CHANGED
|
@@ -31,6 +31,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
buildFormSchemas: () => buildFormSchemas,
|
|
34
|
+
buildMixedAuthoringSchemas: () => buildMixedAuthoringSchemas,
|
|
34
35
|
categorizationSchema: () => categorizationSchema,
|
|
35
36
|
categorySchema: () => categorySchema,
|
|
36
37
|
controlSchema: () => controlSchema,
|
|
@@ -386,6 +387,7 @@ function canonicalizeTSDoc(analysis, source) {
|
|
|
386
387
|
irVersion: import_core2.IR_VERSION,
|
|
387
388
|
elements,
|
|
388
389
|
typeRegistry: analysis.typeRegistry,
|
|
390
|
+
...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { annotations: analysis.annotations },
|
|
389
391
|
provenance
|
|
390
392
|
};
|
|
391
393
|
}
|
|
@@ -462,6 +464,9 @@ function generateJsonSchemaFromIR(ir, options) {
|
|
|
462
464
|
const ctx = makeContext(options);
|
|
463
465
|
for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
|
|
464
466
|
ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
|
|
467
|
+
if (typeDef.annotations && typeDef.annotations.length > 0) {
|
|
468
|
+
applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
|
|
469
|
+
}
|
|
465
470
|
}
|
|
466
471
|
const properties = {};
|
|
467
472
|
const required = [];
|
|
@@ -473,6 +478,9 @@ function generateJsonSchemaFromIR(ir, options) {
|
|
|
473
478
|
properties,
|
|
474
479
|
...uniqueRequired.length > 0 && { required: uniqueRequired }
|
|
475
480
|
};
|
|
481
|
+
if (ir.annotations && ir.annotations.length > 0) {
|
|
482
|
+
applyAnnotations(result, ir.annotations, ctx);
|
|
483
|
+
}
|
|
476
484
|
if (Object.keys(ctx.defs).length > 0) {
|
|
477
485
|
result.$defs = ctx.defs;
|
|
478
486
|
}
|
|
@@ -502,22 +510,51 @@ function collectFields(elements, properties, required, ctx) {
|
|
|
502
510
|
}
|
|
503
511
|
function generateFieldSchema(field, ctx) {
|
|
504
512
|
const schema = generateTypeNode(field.type, ctx);
|
|
513
|
+
const itemStringSchema = schema.type === "array" && schema.items?.type === "string" ? schema.items : void 0;
|
|
505
514
|
const directConstraints = [];
|
|
515
|
+
const itemConstraints = [];
|
|
506
516
|
const pathConstraints = [];
|
|
507
517
|
for (const c of field.constraints) {
|
|
508
518
|
if (c.path) {
|
|
509
519
|
pathConstraints.push(c);
|
|
520
|
+
} else if (itemStringSchema !== void 0 && isStringItemConstraint(c)) {
|
|
521
|
+
itemConstraints.push(c);
|
|
510
522
|
} else {
|
|
511
523
|
directConstraints.push(c);
|
|
512
524
|
}
|
|
513
525
|
}
|
|
514
526
|
applyConstraints(schema, directConstraints, ctx);
|
|
515
|
-
|
|
527
|
+
if (itemStringSchema !== void 0) {
|
|
528
|
+
applyConstraints(itemStringSchema, itemConstraints, ctx);
|
|
529
|
+
}
|
|
530
|
+
const rootAnnotations = [];
|
|
531
|
+
const itemAnnotations = [];
|
|
532
|
+
for (const annotation of field.annotations) {
|
|
533
|
+
if (itemStringSchema !== void 0 && annotation.annotationKind === "format") {
|
|
534
|
+
itemAnnotations.push(annotation);
|
|
535
|
+
} else {
|
|
536
|
+
rootAnnotations.push(annotation);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
applyAnnotations(schema, rootAnnotations, ctx);
|
|
540
|
+
if (itemStringSchema !== void 0) {
|
|
541
|
+
applyAnnotations(itemStringSchema, itemAnnotations, ctx);
|
|
542
|
+
}
|
|
516
543
|
if (pathConstraints.length === 0) {
|
|
517
544
|
return schema;
|
|
518
545
|
}
|
|
519
546
|
return applyPathTargetedConstraints(schema, pathConstraints, ctx);
|
|
520
547
|
}
|
|
548
|
+
function isStringItemConstraint(constraint) {
|
|
549
|
+
switch (constraint.constraintKind) {
|
|
550
|
+
case "minLength":
|
|
551
|
+
case "maxLength":
|
|
552
|
+
case "pattern":
|
|
553
|
+
return true;
|
|
554
|
+
default:
|
|
555
|
+
return false;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
521
558
|
function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
|
|
522
559
|
if (schema.type === "array" && schema.items) {
|
|
523
560
|
schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx);
|
|
@@ -735,6 +772,9 @@ function applyConstraints(schema, constraints, ctx) {
|
|
|
735
772
|
case "uniqueItems":
|
|
736
773
|
schema.uniqueItems = constraint.value;
|
|
737
774
|
break;
|
|
775
|
+
case "const":
|
|
776
|
+
schema.const = constraint.value;
|
|
777
|
+
break;
|
|
738
778
|
case "allowedMembers":
|
|
739
779
|
break;
|
|
740
780
|
case "custom":
|
|
@@ -759,8 +799,14 @@ function applyAnnotations(schema, annotations, ctx) {
|
|
|
759
799
|
case "defaultValue":
|
|
760
800
|
schema.default = annotation.value;
|
|
761
801
|
break;
|
|
802
|
+
case "format":
|
|
803
|
+
schema.format = annotation.value;
|
|
804
|
+
break;
|
|
762
805
|
case "deprecated":
|
|
763
806
|
schema.deprecated = true;
|
|
807
|
+
if (annotation.message !== void 0 && annotation.message !== "") {
|
|
808
|
+
schema["x-formspec-deprecation-description"] = annotation.message;
|
|
809
|
+
}
|
|
764
810
|
break;
|
|
765
811
|
case "placeholder":
|
|
766
812
|
break;
|
|
@@ -792,7 +838,12 @@ function applyCustomConstraint(schema, constraint, ctx) {
|
|
|
792
838
|
`Cannot generate JSON Schema for custom constraint "${constraint.constraintId}" without a matching extension registration`
|
|
793
839
|
);
|
|
794
840
|
}
|
|
795
|
-
|
|
841
|
+
assignVendorPrefixedExtensionKeywords(
|
|
842
|
+
schema,
|
|
843
|
+
registration.toJsonSchema(constraint.payload, ctx.vendorPrefix),
|
|
844
|
+
ctx.vendorPrefix,
|
|
845
|
+
`custom constraint "${constraint.constraintId}"`
|
|
846
|
+
);
|
|
796
847
|
}
|
|
797
848
|
function applyCustomAnnotation(schema, annotation, ctx) {
|
|
798
849
|
const registration = ctx.extensionRegistry?.findAnnotation(annotation.annotationId);
|
|
@@ -804,7 +855,22 @@ function applyCustomAnnotation(schema, annotation, ctx) {
|
|
|
804
855
|
if (registration.toJsonSchema === void 0) {
|
|
805
856
|
return;
|
|
806
857
|
}
|
|
807
|
-
|
|
858
|
+
assignVendorPrefixedExtensionKeywords(
|
|
859
|
+
schema,
|
|
860
|
+
registration.toJsonSchema(annotation.value, ctx.vendorPrefix),
|
|
861
|
+
ctx.vendorPrefix,
|
|
862
|
+
`custom annotation "${annotation.annotationId}"`
|
|
863
|
+
);
|
|
864
|
+
}
|
|
865
|
+
function assignVendorPrefixedExtensionKeywords(schema, extensionSchema, vendorPrefix, source) {
|
|
866
|
+
for (const [key, value] of Object.entries(extensionSchema)) {
|
|
867
|
+
if (!key.startsWith(`${vendorPrefix}-`)) {
|
|
868
|
+
throw new Error(
|
|
869
|
+
`Cannot apply ${source}: extension hooks may only emit "${vendorPrefix}-*" JSON Schema keywords`
|
|
870
|
+
);
|
|
871
|
+
}
|
|
872
|
+
schema[key] = value;
|
|
873
|
+
}
|
|
808
874
|
}
|
|
809
875
|
|
|
810
876
|
// src/json-schema/generator.ts
|
|
@@ -948,25 +1014,31 @@ function createShowRule(fieldName, value) {
|
|
|
948
1014
|
}
|
|
949
1015
|
};
|
|
950
1016
|
}
|
|
1017
|
+
function flattenConditionSchema(scope, schema) {
|
|
1018
|
+
if (schema.allOf === void 0) {
|
|
1019
|
+
if (scope === "#") {
|
|
1020
|
+
return [schema];
|
|
1021
|
+
}
|
|
1022
|
+
const fieldName = scope.replace("#/properties/", "");
|
|
1023
|
+
return [
|
|
1024
|
+
{
|
|
1025
|
+
properties: {
|
|
1026
|
+
[fieldName]: schema
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
];
|
|
1030
|
+
}
|
|
1031
|
+
return schema.allOf.flatMap((member) => flattenConditionSchema(scope, member));
|
|
1032
|
+
}
|
|
951
1033
|
function combineRules(parentRule, childRule) {
|
|
952
|
-
const parentCondition = parentRule.condition;
|
|
953
|
-
const childCondition = childRule.condition;
|
|
954
1034
|
return {
|
|
955
1035
|
effect: "SHOW",
|
|
956
1036
|
condition: {
|
|
957
1037
|
scope: "#",
|
|
958
1038
|
schema: {
|
|
959
1039
|
allOf: [
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
[parentCondition.scope.replace("#/properties/", "")]: parentCondition.schema
|
|
963
|
-
}
|
|
964
|
-
},
|
|
965
|
-
{
|
|
966
|
-
properties: {
|
|
967
|
-
[childCondition.scope.replace("#/properties/", "")]: childCondition.schema
|
|
968
|
-
}
|
|
969
|
-
}
|
|
1040
|
+
...flattenConditionSchema(parentRule.condition.scope, parentRule.condition.schema),
|
|
1041
|
+
...flattenConditionSchema(childRule.condition.scope, childRule.condition.schema)
|
|
970
1042
|
]
|
|
971
1043
|
}
|
|
972
1044
|
}
|
|
@@ -974,10 +1046,14 @@ function combineRules(parentRule, childRule) {
|
|
|
974
1046
|
}
|
|
975
1047
|
function fieldNodeToControl(field, parentRule) {
|
|
976
1048
|
const displayNameAnnotation = field.annotations.find((a) => a.annotationKind === "displayName");
|
|
1049
|
+
const placeholderAnnotation = field.annotations.find((a) => a.annotationKind === "placeholder");
|
|
977
1050
|
const control = {
|
|
978
1051
|
type: "Control",
|
|
979
1052
|
scope: fieldToScope(field.name),
|
|
980
1053
|
...displayNameAnnotation !== void 0 && { label: displayNameAnnotation.value },
|
|
1054
|
+
...placeholderAnnotation !== void 0 && {
|
|
1055
|
+
options: { placeholder: placeholderAnnotation.value }
|
|
1056
|
+
},
|
|
981
1057
|
...parentRule !== void 0 && { rule: parentRule }
|
|
982
1058
|
};
|
|
983
1059
|
return control;
|
|
@@ -1047,7 +1123,10 @@ function getSchemaExtension(schema, key) {
|
|
|
1047
1123
|
// src/extensions/registry.ts
|
|
1048
1124
|
function createExtensionRegistry(extensions) {
|
|
1049
1125
|
const typeMap = /* @__PURE__ */ new Map();
|
|
1126
|
+
const typeNameMap = /* @__PURE__ */ new Map();
|
|
1050
1127
|
const constraintMap = /* @__PURE__ */ new Map();
|
|
1128
|
+
const constraintTagMap = /* @__PURE__ */ new Map();
|
|
1129
|
+
const builtinBroadeningMap = /* @__PURE__ */ new Map();
|
|
1051
1130
|
const annotationMap = /* @__PURE__ */ new Map();
|
|
1052
1131
|
for (const ext of extensions) {
|
|
1053
1132
|
if (ext.types !== void 0) {
|
|
@@ -1057,6 +1136,27 @@ function createExtensionRegistry(extensions) {
|
|
|
1057
1136
|
throw new Error(`Duplicate custom type ID: "${qualifiedId}"`);
|
|
1058
1137
|
}
|
|
1059
1138
|
typeMap.set(qualifiedId, type);
|
|
1139
|
+
for (const sourceTypeName of type.tsTypeNames ?? [type.typeName]) {
|
|
1140
|
+
if (typeNameMap.has(sourceTypeName)) {
|
|
1141
|
+
throw new Error(`Duplicate custom type source name: "${sourceTypeName}"`);
|
|
1142
|
+
}
|
|
1143
|
+
typeNameMap.set(sourceTypeName, {
|
|
1144
|
+
extensionId: ext.extensionId,
|
|
1145
|
+
registration: type
|
|
1146
|
+
});
|
|
1147
|
+
}
|
|
1148
|
+
if (type.builtinConstraintBroadenings !== void 0) {
|
|
1149
|
+
for (const broadening of type.builtinConstraintBroadenings) {
|
|
1150
|
+
const key = `${qualifiedId}:${broadening.tagName}`;
|
|
1151
|
+
if (builtinBroadeningMap.has(key)) {
|
|
1152
|
+
throw new Error(`Duplicate built-in constraint broadening: "${key}"`);
|
|
1153
|
+
}
|
|
1154
|
+
builtinBroadeningMap.set(key, {
|
|
1155
|
+
extensionId: ext.extensionId,
|
|
1156
|
+
registration: broadening
|
|
1157
|
+
});
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1060
1160
|
}
|
|
1061
1161
|
}
|
|
1062
1162
|
if (ext.constraints !== void 0) {
|
|
@@ -1068,6 +1168,17 @@ function createExtensionRegistry(extensions) {
|
|
|
1068
1168
|
constraintMap.set(qualifiedId, constraint);
|
|
1069
1169
|
}
|
|
1070
1170
|
}
|
|
1171
|
+
if (ext.constraintTags !== void 0) {
|
|
1172
|
+
for (const tag of ext.constraintTags) {
|
|
1173
|
+
if (constraintTagMap.has(tag.tagName)) {
|
|
1174
|
+
throw new Error(`Duplicate custom constraint tag: "@${tag.tagName}"`);
|
|
1175
|
+
}
|
|
1176
|
+
constraintTagMap.set(tag.tagName, {
|
|
1177
|
+
extensionId: ext.extensionId,
|
|
1178
|
+
registration: tag
|
|
1179
|
+
});
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1071
1182
|
if (ext.annotations !== void 0) {
|
|
1072
1183
|
for (const annotation of ext.annotations) {
|
|
1073
1184
|
const qualifiedId = `${ext.extensionId}/${annotation.annotationName}`;
|
|
@@ -1081,7 +1192,10 @@ function createExtensionRegistry(extensions) {
|
|
|
1081
1192
|
return {
|
|
1082
1193
|
extensions,
|
|
1083
1194
|
findType: (typeId) => typeMap.get(typeId),
|
|
1195
|
+
findTypeByName: (typeName) => typeNameMap.get(typeName),
|
|
1084
1196
|
findConstraint: (constraintId) => constraintMap.get(constraintId),
|
|
1197
|
+
findConstraintTag: (tagName) => constraintTagMap.get(tagName),
|
|
1198
|
+
findBuiltinConstraintBroadening: (typeId, tagName) => builtinBroadeningMap.get(`${typeId}:${tagName}`),
|
|
1085
1199
|
findAnnotation: (annotationId) => annotationMap.get(annotationId)
|
|
1086
1200
|
};
|
|
1087
1201
|
}
|
|
@@ -1254,8 +1368,8 @@ var LENGTH_CONSTRAINT_MAP = {
|
|
|
1254
1368
|
minItems: "minItems",
|
|
1255
1369
|
maxItems: "maxItems"
|
|
1256
1370
|
};
|
|
1257
|
-
var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions"]);
|
|
1258
|
-
function createFormSpecTSDocConfig() {
|
|
1371
|
+
var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions", "defaultValue"]);
|
|
1372
|
+
function createFormSpecTSDocConfig(extensionTagNames = []) {
|
|
1259
1373
|
const config = new import_tsdoc.TSDocConfiguration();
|
|
1260
1374
|
for (const tagName of Object.keys(import_core3.BUILTIN_CONSTRAINT_DEFINITIONS)) {
|
|
1261
1375
|
config.addTagDefinition(
|
|
@@ -1266,7 +1380,16 @@ function createFormSpecTSDocConfig() {
|
|
|
1266
1380
|
})
|
|
1267
1381
|
);
|
|
1268
1382
|
}
|
|
1269
|
-
for (const tagName of ["displayName", "description"]) {
|
|
1383
|
+
for (const tagName of ["displayName", "description", "format", "placeholder"]) {
|
|
1384
|
+
config.addTagDefinition(
|
|
1385
|
+
new import_tsdoc.TSDocTagDefinition({
|
|
1386
|
+
tagName: "@" + tagName,
|
|
1387
|
+
syntaxKind: import_tsdoc.TSDocTagSyntaxKind.BlockTag,
|
|
1388
|
+
allowMultiple: true
|
|
1389
|
+
})
|
|
1390
|
+
);
|
|
1391
|
+
}
|
|
1392
|
+
for (const tagName of extensionTagNames) {
|
|
1270
1393
|
config.addTagDefinition(
|
|
1271
1394
|
new import_tsdoc.TSDocTagDefinition({
|
|
1272
1395
|
tagName: "@" + tagName,
|
|
@@ -1277,14 +1400,31 @@ function createFormSpecTSDocConfig() {
|
|
|
1277
1400
|
}
|
|
1278
1401
|
return config;
|
|
1279
1402
|
}
|
|
1280
|
-
var
|
|
1281
|
-
function getParser() {
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1403
|
+
var parserCache = /* @__PURE__ */ new Map();
|
|
1404
|
+
function getParser(options) {
|
|
1405
|
+
const extensionTagNames = [
|
|
1406
|
+
...options?.extensionRegistry?.extensions.flatMap(
|
|
1407
|
+
(extension) => (extension.constraintTags ?? []).map((tag) => tag.tagName)
|
|
1408
|
+
) ?? []
|
|
1409
|
+
].sort();
|
|
1410
|
+
const cacheKey = extensionTagNames.join("|");
|
|
1411
|
+
const existing = parserCache.get(cacheKey);
|
|
1412
|
+
if (existing) {
|
|
1413
|
+
return existing;
|
|
1414
|
+
}
|
|
1415
|
+
const parser = new import_tsdoc.TSDocParser(createFormSpecTSDocConfig(extensionTagNames));
|
|
1416
|
+
parserCache.set(cacheKey, parser);
|
|
1417
|
+
return parser;
|
|
1418
|
+
}
|
|
1419
|
+
function parseTSDocTags(node, file = "", options) {
|
|
1286
1420
|
const constraints = [];
|
|
1287
1421
|
const annotations = [];
|
|
1422
|
+
let displayName;
|
|
1423
|
+
let description;
|
|
1424
|
+
let placeholder;
|
|
1425
|
+
let displayNameProvenance;
|
|
1426
|
+
let descriptionProvenance;
|
|
1427
|
+
let placeholderProvenance;
|
|
1288
1428
|
const sourceFile = node.getSourceFile();
|
|
1289
1429
|
const sourceText = sourceFile.getFullText();
|
|
1290
1430
|
const commentRanges = ts2.getLeadingCommentRanges(sourceText, node.getFullStart());
|
|
@@ -1297,52 +1437,92 @@ function parseTSDocTags(node, file = "") {
|
|
|
1297
1437
|
if (!commentText.startsWith("/**")) {
|
|
1298
1438
|
continue;
|
|
1299
1439
|
}
|
|
1300
|
-
const parser = getParser();
|
|
1440
|
+
const parser = getParser(options);
|
|
1301
1441
|
const parserContext = parser.parseRange(
|
|
1302
1442
|
import_tsdoc.TextRange.fromStringRange(sourceText, range.pos, range.end)
|
|
1303
1443
|
);
|
|
1304
1444
|
const docComment = parserContext.docComment;
|
|
1305
1445
|
for (const block of docComment.customBlocks) {
|
|
1306
1446
|
const tagName = (0, import_core3.normalizeConstraintTagName)(block.blockTag.tagName.substring(1));
|
|
1307
|
-
if (tagName === "displayName" || tagName === "description") {
|
|
1447
|
+
if (tagName === "displayName" || tagName === "description" || tagName === "format" || tagName === "placeholder") {
|
|
1308
1448
|
const text2 = extractBlockText(block).trim();
|
|
1309
1449
|
if (text2 === "") continue;
|
|
1310
1450
|
const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
|
|
1311
1451
|
if (tagName === "displayName") {
|
|
1452
|
+
if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
|
|
1453
|
+
displayName = text2;
|
|
1454
|
+
displayNameProvenance = provenance2;
|
|
1455
|
+
}
|
|
1456
|
+
} else if (tagName === "format") {
|
|
1312
1457
|
annotations.push({
|
|
1313
1458
|
kind: "annotation",
|
|
1314
|
-
annotationKind: "
|
|
1459
|
+
annotationKind: "format",
|
|
1315
1460
|
value: text2,
|
|
1316
1461
|
provenance: provenance2
|
|
1317
1462
|
});
|
|
1318
1463
|
} else {
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1464
|
+
if (tagName === "description" && description === void 0) {
|
|
1465
|
+
description = text2;
|
|
1466
|
+
descriptionProvenance = provenance2;
|
|
1467
|
+
} else if (tagName === "placeholder" && placeholder === void 0) {
|
|
1468
|
+
placeholder = text2;
|
|
1469
|
+
placeholderProvenance = provenance2;
|
|
1470
|
+
}
|
|
1325
1471
|
}
|
|
1326
1472
|
continue;
|
|
1327
1473
|
}
|
|
1328
1474
|
if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
1329
1475
|
const text = extractBlockText(block).trim();
|
|
1330
|
-
|
|
1476
|
+
const expectedType = (0, import_core3.isBuiltinConstraintName)(tagName) ? import_core3.BUILTIN_CONSTRAINT_DEFINITIONS[tagName] : void 0;
|
|
1477
|
+
if (text === "" && expectedType !== "boolean") continue;
|
|
1331
1478
|
const provenance = provenanceForComment(range, sourceFile, file, tagName);
|
|
1332
|
-
const constraintNode = parseConstraintValue(tagName, text, provenance);
|
|
1479
|
+
const constraintNode = parseConstraintValue(tagName, text, provenance, options);
|
|
1333
1480
|
if (constraintNode) {
|
|
1334
1481
|
constraints.push(constraintNode);
|
|
1335
1482
|
}
|
|
1336
1483
|
}
|
|
1337
1484
|
if (docComment.deprecatedBlock !== void 0) {
|
|
1485
|
+
const message = extractBlockText(docComment.deprecatedBlock).trim();
|
|
1338
1486
|
annotations.push({
|
|
1339
1487
|
kind: "annotation",
|
|
1340
1488
|
annotationKind: "deprecated",
|
|
1489
|
+
...message !== "" && { message },
|
|
1341
1490
|
provenance: provenanceForComment(range, sourceFile, file, "deprecated")
|
|
1342
1491
|
});
|
|
1343
1492
|
}
|
|
1493
|
+
if (description === void 0 && docComment.remarksBlock !== void 0) {
|
|
1494
|
+
const remarks = extractBlockText(docComment.remarksBlock).trim();
|
|
1495
|
+
if (remarks !== "") {
|
|
1496
|
+
description = remarks;
|
|
1497
|
+
descriptionProvenance = provenanceForComment(range, sourceFile, file, "remarks");
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1344
1500
|
}
|
|
1345
1501
|
}
|
|
1502
|
+
if (displayName !== void 0 && displayNameProvenance !== void 0) {
|
|
1503
|
+
annotations.push({
|
|
1504
|
+
kind: "annotation",
|
|
1505
|
+
annotationKind: "displayName",
|
|
1506
|
+
value: displayName,
|
|
1507
|
+
provenance: displayNameProvenance
|
|
1508
|
+
});
|
|
1509
|
+
}
|
|
1510
|
+
if (description !== void 0 && descriptionProvenance !== void 0) {
|
|
1511
|
+
annotations.push({
|
|
1512
|
+
kind: "annotation",
|
|
1513
|
+
annotationKind: "description",
|
|
1514
|
+
value: description,
|
|
1515
|
+
provenance: descriptionProvenance
|
|
1516
|
+
});
|
|
1517
|
+
}
|
|
1518
|
+
if (placeholder !== void 0 && placeholderProvenance !== void 0) {
|
|
1519
|
+
annotations.push({
|
|
1520
|
+
kind: "annotation",
|
|
1521
|
+
annotationKind: "placeholder",
|
|
1522
|
+
value: placeholder,
|
|
1523
|
+
provenance: placeholderProvenance
|
|
1524
|
+
});
|
|
1525
|
+
}
|
|
1346
1526
|
const jsDocTagsAll = ts2.getJSDocTags(node);
|
|
1347
1527
|
for (const tag of jsDocTagsAll) {
|
|
1348
1528
|
const tagName = (0, import_core3.normalizeConstraintTagName)(tag.tagName.text);
|
|
@@ -1351,13 +1531,40 @@ function parseTSDocTags(node, file = "") {
|
|
|
1351
1531
|
if (commentText === void 0 || commentText.trim() === "") continue;
|
|
1352
1532
|
const text = commentText.trim();
|
|
1353
1533
|
const provenance = provenanceForJSDocTag(tag, file);
|
|
1354
|
-
|
|
1534
|
+
if (tagName === "defaultValue") {
|
|
1535
|
+
const defaultValueNode = parseDefaultValueValue(text, provenance);
|
|
1536
|
+
annotations.push(defaultValueNode);
|
|
1537
|
+
continue;
|
|
1538
|
+
}
|
|
1539
|
+
const constraintNode = parseConstraintValue(tagName, text, provenance, options);
|
|
1355
1540
|
if (constraintNode) {
|
|
1356
1541
|
constraints.push(constraintNode);
|
|
1357
1542
|
}
|
|
1358
1543
|
}
|
|
1359
1544
|
return { constraints, annotations };
|
|
1360
1545
|
}
|
|
1546
|
+
function extractDisplayNameMetadata(node) {
|
|
1547
|
+
let displayName;
|
|
1548
|
+
const memberDisplayNames = /* @__PURE__ */ new Map();
|
|
1549
|
+
for (const tag of ts2.getJSDocTags(node)) {
|
|
1550
|
+
const tagName = (0, import_core3.normalizeConstraintTagName)(tag.tagName.text);
|
|
1551
|
+
if (tagName !== "displayName") continue;
|
|
1552
|
+
const commentText = getTagCommentText(tag);
|
|
1553
|
+
if (commentText === void 0) continue;
|
|
1554
|
+
const text = commentText.trim();
|
|
1555
|
+
if (text === "") continue;
|
|
1556
|
+
const memberTarget = parseMemberTargetDisplayName(text);
|
|
1557
|
+
if (memberTarget) {
|
|
1558
|
+
memberDisplayNames.set(memberTarget.target, memberTarget.label);
|
|
1559
|
+
continue;
|
|
1560
|
+
}
|
|
1561
|
+
displayName ??= text;
|
|
1562
|
+
}
|
|
1563
|
+
return {
|
|
1564
|
+
...displayName !== void 0 && { displayName },
|
|
1565
|
+
memberDisplayNames
|
|
1566
|
+
};
|
|
1567
|
+
}
|
|
1361
1568
|
function extractPathTarget(text) {
|
|
1362
1569
|
const trimmed = text.trimStart();
|
|
1363
1570
|
const match = /^:([a-zA-Z_]\w*)\s+([\s\S]*)$/.exec(trimmed);
|
|
@@ -1385,7 +1592,11 @@ function extractPlainText(node) {
|
|
|
1385
1592
|
}
|
|
1386
1593
|
return result;
|
|
1387
1594
|
}
|
|
1388
|
-
function parseConstraintValue(tagName, text, provenance) {
|
|
1595
|
+
function parseConstraintValue(tagName, text, provenance, options) {
|
|
1596
|
+
const customConstraint = parseExtensionConstraintValue(tagName, text, provenance, options);
|
|
1597
|
+
if (customConstraint) {
|
|
1598
|
+
return customConstraint;
|
|
1599
|
+
}
|
|
1389
1600
|
if (!(0, import_core3.isBuiltinConstraintName)(tagName)) {
|
|
1390
1601
|
return null;
|
|
1391
1602
|
}
|
|
@@ -1420,7 +1631,45 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
1420
1631
|
}
|
|
1421
1632
|
return null;
|
|
1422
1633
|
}
|
|
1634
|
+
if (expectedType === "boolean") {
|
|
1635
|
+
const trimmed = effectiveText.trim();
|
|
1636
|
+
if (trimmed !== "" && trimmed !== "true") {
|
|
1637
|
+
return null;
|
|
1638
|
+
}
|
|
1639
|
+
if (tagName === "uniqueItems") {
|
|
1640
|
+
return {
|
|
1641
|
+
kind: "constraint",
|
|
1642
|
+
constraintKind: "uniqueItems",
|
|
1643
|
+
value: true,
|
|
1644
|
+
...path3 && { path: path3 },
|
|
1645
|
+
provenance
|
|
1646
|
+
};
|
|
1647
|
+
}
|
|
1648
|
+
return null;
|
|
1649
|
+
}
|
|
1423
1650
|
if (expectedType === "json") {
|
|
1651
|
+
if (tagName === "const") {
|
|
1652
|
+
const trimmedText = effectiveText.trim();
|
|
1653
|
+
if (trimmedText === "") return null;
|
|
1654
|
+
try {
|
|
1655
|
+
const parsed2 = JSON.parse(trimmedText);
|
|
1656
|
+
return {
|
|
1657
|
+
kind: "constraint",
|
|
1658
|
+
constraintKind: "const",
|
|
1659
|
+
value: parsed2,
|
|
1660
|
+
...path3 && { path: path3 },
|
|
1661
|
+
provenance
|
|
1662
|
+
};
|
|
1663
|
+
} catch {
|
|
1664
|
+
return {
|
|
1665
|
+
kind: "constraint",
|
|
1666
|
+
constraintKind: "const",
|
|
1667
|
+
value: trimmedText,
|
|
1668
|
+
...path3 && { path: path3 },
|
|
1669
|
+
provenance
|
|
1670
|
+
};
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1424
1673
|
const parsed = tryParseJson(effectiveText);
|
|
1425
1674
|
if (!Array.isArray(parsed)) {
|
|
1426
1675
|
return null;
|
|
@@ -1452,6 +1701,111 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
1452
1701
|
provenance
|
|
1453
1702
|
};
|
|
1454
1703
|
}
|
|
1704
|
+
function parseExtensionConstraintValue(tagName, text, provenance, options) {
|
|
1705
|
+
const pathResult = extractPathTarget(text);
|
|
1706
|
+
const effectiveText = pathResult ? pathResult.remainingText : text;
|
|
1707
|
+
const path3 = pathResult?.path;
|
|
1708
|
+
const registry = options?.extensionRegistry;
|
|
1709
|
+
if (registry === void 0) {
|
|
1710
|
+
return null;
|
|
1711
|
+
}
|
|
1712
|
+
const directTag = registry.findConstraintTag(tagName);
|
|
1713
|
+
if (directTag !== void 0) {
|
|
1714
|
+
return makeCustomConstraintNode(
|
|
1715
|
+
directTag.extensionId,
|
|
1716
|
+
directTag.registration.constraintName,
|
|
1717
|
+
directTag.registration.parseValue(effectiveText),
|
|
1718
|
+
provenance,
|
|
1719
|
+
path3,
|
|
1720
|
+
registry
|
|
1721
|
+
);
|
|
1722
|
+
}
|
|
1723
|
+
if (!(0, import_core3.isBuiltinConstraintName)(tagName)) {
|
|
1724
|
+
return null;
|
|
1725
|
+
}
|
|
1726
|
+
const broadenedTypeId = getBroadenedCustomTypeId(options?.fieldType);
|
|
1727
|
+
if (broadenedTypeId === void 0) {
|
|
1728
|
+
return null;
|
|
1729
|
+
}
|
|
1730
|
+
const broadened = registry.findBuiltinConstraintBroadening(broadenedTypeId, tagName);
|
|
1731
|
+
if (broadened === void 0) {
|
|
1732
|
+
return null;
|
|
1733
|
+
}
|
|
1734
|
+
return makeCustomConstraintNode(
|
|
1735
|
+
broadened.extensionId,
|
|
1736
|
+
broadened.registration.constraintName,
|
|
1737
|
+
broadened.registration.parseValue(effectiveText),
|
|
1738
|
+
provenance,
|
|
1739
|
+
path3,
|
|
1740
|
+
registry
|
|
1741
|
+
);
|
|
1742
|
+
}
|
|
1743
|
+
function getBroadenedCustomTypeId(fieldType) {
|
|
1744
|
+
if (fieldType?.kind === "custom") {
|
|
1745
|
+
return fieldType.typeId;
|
|
1746
|
+
}
|
|
1747
|
+
if (fieldType?.kind !== "union") {
|
|
1748
|
+
return void 0;
|
|
1749
|
+
}
|
|
1750
|
+
const customMembers = fieldType.members.filter(
|
|
1751
|
+
(member) => member.kind === "custom"
|
|
1752
|
+
);
|
|
1753
|
+
if (customMembers.length !== 1) {
|
|
1754
|
+
return void 0;
|
|
1755
|
+
}
|
|
1756
|
+
const nonCustomMembers = fieldType.members.filter((member) => member.kind !== "custom");
|
|
1757
|
+
const allOtherMembersAreNull = nonCustomMembers.every(
|
|
1758
|
+
(member) => member.kind === "primitive" && member.primitiveKind === "null"
|
|
1759
|
+
);
|
|
1760
|
+
const customMember = customMembers[0];
|
|
1761
|
+
return allOtherMembersAreNull && customMember !== void 0 ? customMember.typeId : void 0;
|
|
1762
|
+
}
|
|
1763
|
+
function makeCustomConstraintNode(extensionId, constraintName, payload, provenance, path3, registry) {
|
|
1764
|
+
const constraintId = `${extensionId}/${constraintName}`;
|
|
1765
|
+
const registration = registry.findConstraint(constraintId);
|
|
1766
|
+
if (registration === void 0) {
|
|
1767
|
+
throw new Error(
|
|
1768
|
+
`Custom TSDoc tag resolved to unregistered constraint "${constraintId}". Register the constraint before using its tag.`
|
|
1769
|
+
);
|
|
1770
|
+
}
|
|
1771
|
+
return {
|
|
1772
|
+
kind: "constraint",
|
|
1773
|
+
constraintKind: "custom",
|
|
1774
|
+
constraintId,
|
|
1775
|
+
payload,
|
|
1776
|
+
compositionRule: registration.compositionRule,
|
|
1777
|
+
...path3 && { path: path3 },
|
|
1778
|
+
provenance
|
|
1779
|
+
};
|
|
1780
|
+
}
|
|
1781
|
+
function parseDefaultValueValue(text, provenance) {
|
|
1782
|
+
const trimmed = text.trim();
|
|
1783
|
+
let value;
|
|
1784
|
+
if (trimmed === "null") {
|
|
1785
|
+
value = null;
|
|
1786
|
+
} else if (trimmed === "true") {
|
|
1787
|
+
value = true;
|
|
1788
|
+
} else if (trimmed === "false") {
|
|
1789
|
+
value = false;
|
|
1790
|
+
} else {
|
|
1791
|
+
const parsed = tryParseJson(trimmed);
|
|
1792
|
+
value = parsed !== null ? parsed : trimmed;
|
|
1793
|
+
}
|
|
1794
|
+
return {
|
|
1795
|
+
kind: "annotation",
|
|
1796
|
+
annotationKind: "defaultValue",
|
|
1797
|
+
value,
|
|
1798
|
+
provenance
|
|
1799
|
+
};
|
|
1800
|
+
}
|
|
1801
|
+
function isMemberTargetDisplayName(text) {
|
|
1802
|
+
return parseMemberTargetDisplayName(text) !== null;
|
|
1803
|
+
}
|
|
1804
|
+
function parseMemberTargetDisplayName(text) {
|
|
1805
|
+
const match = /^:([^\s]+)\s+([\s\S]+)$/.exec(text);
|
|
1806
|
+
if (!match?.[1] || !match[2]) return null;
|
|
1807
|
+
return { target: match[1], label: match[2].trim() };
|
|
1808
|
+
}
|
|
1455
1809
|
function provenanceForComment(range, sourceFile, file, tagName) {
|
|
1456
1810
|
const { line, character } = sourceFile.getLineAndCharacterOfPosition(range.pos);
|
|
1457
1811
|
return {
|
|
@@ -1484,12 +1838,12 @@ function getTagCommentText(tag) {
|
|
|
1484
1838
|
}
|
|
1485
1839
|
|
|
1486
1840
|
// src/analyzer/jsdoc-constraints.ts
|
|
1487
|
-
function extractJSDocConstraintNodes(node, file = "") {
|
|
1488
|
-
const result = parseTSDocTags(node, file);
|
|
1841
|
+
function extractJSDocConstraintNodes(node, file = "", options) {
|
|
1842
|
+
const result = parseTSDocTags(node, file, options);
|
|
1489
1843
|
return [...result.constraints];
|
|
1490
1844
|
}
|
|
1491
|
-
function extractJSDocAnnotationNodes(node, file = "") {
|
|
1492
|
-
const result = parseTSDocTags(node, file);
|
|
1845
|
+
function extractJSDocAnnotationNodes(node, file = "", options) {
|
|
1846
|
+
const result = parseTSDocTags(node, file, options);
|
|
1493
1847
|
return [...result.annotations];
|
|
1494
1848
|
}
|
|
1495
1849
|
function extractDefaultValueAnnotation(initializer, file = "") {
|
|
@@ -1533,17 +1887,43 @@ function isObjectType(type) {
|
|
|
1533
1887
|
function isTypeReference(type) {
|
|
1534
1888
|
return !!(type.flags & ts4.TypeFlags.Object) && !!(type.objectFlags & ts4.ObjectFlags.Reference);
|
|
1535
1889
|
}
|
|
1536
|
-
|
|
1890
|
+
var RESOLVING_TYPE_PLACEHOLDER = {
|
|
1891
|
+
kind: "object",
|
|
1892
|
+
properties: [],
|
|
1893
|
+
additionalProperties: true
|
|
1894
|
+
};
|
|
1895
|
+
function makeParseOptions(extensionRegistry, fieldType) {
|
|
1896
|
+
if (extensionRegistry === void 0 && fieldType === void 0) {
|
|
1897
|
+
return void 0;
|
|
1898
|
+
}
|
|
1899
|
+
return {
|
|
1900
|
+
...extensionRegistry !== void 0 && { extensionRegistry },
|
|
1901
|
+
...fieldType !== void 0 && { fieldType }
|
|
1902
|
+
};
|
|
1903
|
+
}
|
|
1904
|
+
function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
|
|
1537
1905
|
const name = classDecl.name?.text ?? "AnonymousClass";
|
|
1538
1906
|
const fields = [];
|
|
1539
1907
|
const fieldLayouts = [];
|
|
1540
1908
|
const typeRegistry = {};
|
|
1909
|
+
const annotations = extractJSDocAnnotationNodes(
|
|
1910
|
+
classDecl,
|
|
1911
|
+
file,
|
|
1912
|
+
makeParseOptions(extensionRegistry)
|
|
1913
|
+
);
|
|
1541
1914
|
const visiting = /* @__PURE__ */ new Set();
|
|
1542
1915
|
const instanceMethods = [];
|
|
1543
1916
|
const staticMethods = [];
|
|
1544
1917
|
for (const member of classDecl.members) {
|
|
1545
1918
|
if (ts4.isPropertyDeclaration(member)) {
|
|
1546
|
-
const fieldNode = analyzeFieldToIR(
|
|
1919
|
+
const fieldNode = analyzeFieldToIR(
|
|
1920
|
+
member,
|
|
1921
|
+
checker,
|
|
1922
|
+
file,
|
|
1923
|
+
typeRegistry,
|
|
1924
|
+
visiting,
|
|
1925
|
+
extensionRegistry
|
|
1926
|
+
);
|
|
1547
1927
|
if (fieldNode) {
|
|
1548
1928
|
fields.push(fieldNode);
|
|
1549
1929
|
fieldLayouts.push({});
|
|
@@ -1560,25 +1940,53 @@ function analyzeClassToIR(classDecl, checker, file = "") {
|
|
|
1560
1940
|
}
|
|
1561
1941
|
}
|
|
1562
1942
|
}
|
|
1563
|
-
return {
|
|
1943
|
+
return {
|
|
1944
|
+
name,
|
|
1945
|
+
fields,
|
|
1946
|
+
fieldLayouts,
|
|
1947
|
+
typeRegistry,
|
|
1948
|
+
...annotations.length > 0 && { annotations },
|
|
1949
|
+
instanceMethods,
|
|
1950
|
+
staticMethods
|
|
1951
|
+
};
|
|
1564
1952
|
}
|
|
1565
|
-
function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
|
|
1953
|
+
function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegistry) {
|
|
1566
1954
|
const name = interfaceDecl.name.text;
|
|
1567
1955
|
const fields = [];
|
|
1568
1956
|
const typeRegistry = {};
|
|
1957
|
+
const annotations = extractJSDocAnnotationNodes(
|
|
1958
|
+
interfaceDecl,
|
|
1959
|
+
file,
|
|
1960
|
+
makeParseOptions(extensionRegistry)
|
|
1961
|
+
);
|
|
1569
1962
|
const visiting = /* @__PURE__ */ new Set();
|
|
1570
1963
|
for (const member of interfaceDecl.members) {
|
|
1571
1964
|
if (ts4.isPropertySignature(member)) {
|
|
1572
|
-
const fieldNode = analyzeInterfacePropertyToIR(
|
|
1965
|
+
const fieldNode = analyzeInterfacePropertyToIR(
|
|
1966
|
+
member,
|
|
1967
|
+
checker,
|
|
1968
|
+
file,
|
|
1969
|
+
typeRegistry,
|
|
1970
|
+
visiting,
|
|
1971
|
+
extensionRegistry
|
|
1972
|
+
);
|
|
1573
1973
|
if (fieldNode) {
|
|
1574
1974
|
fields.push(fieldNode);
|
|
1575
1975
|
}
|
|
1576
1976
|
}
|
|
1577
1977
|
}
|
|
1578
1978
|
const fieldLayouts = fields.map(() => ({}));
|
|
1579
|
-
return {
|
|
1979
|
+
return {
|
|
1980
|
+
name,
|
|
1981
|
+
fields,
|
|
1982
|
+
fieldLayouts,
|
|
1983
|
+
typeRegistry,
|
|
1984
|
+
...annotations.length > 0 && { annotations },
|
|
1985
|
+
instanceMethods: [],
|
|
1986
|
+
staticMethods: []
|
|
1987
|
+
};
|
|
1580
1988
|
}
|
|
1581
|
-
function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
1989
|
+
function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry) {
|
|
1582
1990
|
if (!ts4.isTypeLiteralNode(typeAlias.type)) {
|
|
1583
1991
|
const sourceFile = typeAlias.getSourceFile();
|
|
1584
1992
|
const { line } = sourceFile.getLineAndCharacterOfPosition(typeAlias.getStart());
|
|
@@ -1591,10 +1999,22 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
|
1591
1999
|
const name = typeAlias.name.text;
|
|
1592
2000
|
const fields = [];
|
|
1593
2001
|
const typeRegistry = {};
|
|
2002
|
+
const annotations = extractJSDocAnnotationNodes(
|
|
2003
|
+
typeAlias,
|
|
2004
|
+
file,
|
|
2005
|
+
makeParseOptions(extensionRegistry)
|
|
2006
|
+
);
|
|
1594
2007
|
const visiting = /* @__PURE__ */ new Set();
|
|
1595
2008
|
for (const member of typeAlias.type.members) {
|
|
1596
2009
|
if (ts4.isPropertySignature(member)) {
|
|
1597
|
-
const fieldNode = analyzeInterfacePropertyToIR(
|
|
2010
|
+
const fieldNode = analyzeInterfacePropertyToIR(
|
|
2011
|
+
member,
|
|
2012
|
+
checker,
|
|
2013
|
+
file,
|
|
2014
|
+
typeRegistry,
|
|
2015
|
+
visiting,
|
|
2016
|
+
extensionRegistry
|
|
2017
|
+
);
|
|
1598
2018
|
if (fieldNode) {
|
|
1599
2019
|
fields.push(fieldNode);
|
|
1600
2020
|
}
|
|
@@ -1607,12 +2027,13 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
|
1607
2027
|
fields,
|
|
1608
2028
|
fieldLayouts: fields.map(() => ({})),
|
|
1609
2029
|
typeRegistry,
|
|
2030
|
+
...annotations.length > 0 && { annotations },
|
|
1610
2031
|
instanceMethods: [],
|
|
1611
2032
|
staticMethods: []
|
|
1612
2033
|
}
|
|
1613
2034
|
};
|
|
1614
2035
|
}
|
|
1615
|
-
function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
|
|
2036
|
+
function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1616
2037
|
if (!ts4.isIdentifier(prop.name)) {
|
|
1617
2038
|
return null;
|
|
1618
2039
|
}
|
|
@@ -1620,16 +2041,28 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
|
|
|
1620
2041
|
const tsType = checker.getTypeAtLocation(prop);
|
|
1621
2042
|
const optional = prop.questionToken !== void 0;
|
|
1622
2043
|
const provenance = provenanceForNode(prop, file);
|
|
1623
|
-
let type = resolveTypeNode(
|
|
2044
|
+
let type = resolveTypeNode(
|
|
2045
|
+
tsType,
|
|
2046
|
+
checker,
|
|
2047
|
+
file,
|
|
2048
|
+
typeRegistry,
|
|
2049
|
+
visiting,
|
|
2050
|
+
prop,
|
|
2051
|
+
extensionRegistry
|
|
2052
|
+
);
|
|
1624
2053
|
const constraints = [];
|
|
1625
2054
|
if (prop.type) {
|
|
1626
|
-
constraints.push(
|
|
2055
|
+
constraints.push(
|
|
2056
|
+
...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
|
|
2057
|
+
);
|
|
1627
2058
|
}
|
|
1628
|
-
constraints.push(...extractJSDocConstraintNodes(prop, file));
|
|
2059
|
+
constraints.push(...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type)));
|
|
1629
2060
|
let annotations = [];
|
|
1630
|
-
annotations.push(
|
|
2061
|
+
annotations.push(
|
|
2062
|
+
...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
2063
|
+
);
|
|
1631
2064
|
const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
|
|
1632
|
-
if (defaultAnnotation) {
|
|
2065
|
+
if (defaultAnnotation && !annotations.some((a) => a.annotationKind === "defaultValue")) {
|
|
1633
2066
|
annotations.push(defaultAnnotation);
|
|
1634
2067
|
}
|
|
1635
2068
|
({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
|
|
@@ -1643,7 +2076,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
|
|
|
1643
2076
|
provenance
|
|
1644
2077
|
};
|
|
1645
2078
|
}
|
|
1646
|
-
function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting) {
|
|
2079
|
+
function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1647
2080
|
if (!ts4.isIdentifier(prop.name)) {
|
|
1648
2081
|
return null;
|
|
1649
2082
|
}
|
|
@@ -1651,14 +2084,26 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
|
|
|
1651
2084
|
const tsType = checker.getTypeAtLocation(prop);
|
|
1652
2085
|
const optional = prop.questionToken !== void 0;
|
|
1653
2086
|
const provenance = provenanceForNode(prop, file);
|
|
1654
|
-
let type = resolveTypeNode(
|
|
2087
|
+
let type = resolveTypeNode(
|
|
2088
|
+
tsType,
|
|
2089
|
+
checker,
|
|
2090
|
+
file,
|
|
2091
|
+
typeRegistry,
|
|
2092
|
+
visiting,
|
|
2093
|
+
prop,
|
|
2094
|
+
extensionRegistry
|
|
2095
|
+
);
|
|
1655
2096
|
const constraints = [];
|
|
1656
2097
|
if (prop.type) {
|
|
1657
|
-
constraints.push(
|
|
2098
|
+
constraints.push(
|
|
2099
|
+
...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
|
|
2100
|
+
);
|
|
1658
2101
|
}
|
|
1659
|
-
constraints.push(...extractJSDocConstraintNodes(prop, file));
|
|
2102
|
+
constraints.push(...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type)));
|
|
1660
2103
|
let annotations = [];
|
|
1661
|
-
annotations.push(
|
|
2104
|
+
annotations.push(
|
|
2105
|
+
...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
2106
|
+
);
|
|
1662
2107
|
({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
|
|
1663
2108
|
return {
|
|
1664
2109
|
kind: "field",
|
|
@@ -1732,7 +2177,66 @@ function parseEnumMemberDisplayName(value) {
|
|
|
1732
2177
|
if (label === "") return null;
|
|
1733
2178
|
return { value: match[1], label };
|
|
1734
2179
|
}
|
|
1735
|
-
function
|
|
2180
|
+
function resolveRegisteredCustomType(sourceNode, extensionRegistry, checker) {
|
|
2181
|
+
if (sourceNode === void 0 || extensionRegistry === void 0) {
|
|
2182
|
+
return null;
|
|
2183
|
+
}
|
|
2184
|
+
const typeNode = extractTypeNodeFromSource(sourceNode);
|
|
2185
|
+
if (typeNode === void 0) {
|
|
2186
|
+
return null;
|
|
2187
|
+
}
|
|
2188
|
+
return resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker);
|
|
2189
|
+
}
|
|
2190
|
+
function resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker) {
|
|
2191
|
+
if (ts4.isParenthesizedTypeNode(typeNode)) {
|
|
2192
|
+
return resolveRegisteredCustomTypeFromTypeNode(typeNode.type, extensionRegistry, checker);
|
|
2193
|
+
}
|
|
2194
|
+
const typeName = getTypeNodeRegistrationName(typeNode);
|
|
2195
|
+
if (typeName === null) {
|
|
2196
|
+
return null;
|
|
2197
|
+
}
|
|
2198
|
+
const registration = extensionRegistry.findTypeByName(typeName);
|
|
2199
|
+
if (registration !== void 0) {
|
|
2200
|
+
return {
|
|
2201
|
+
kind: "custom",
|
|
2202
|
+
typeId: `${registration.extensionId}/${registration.registration.typeName}`,
|
|
2203
|
+
payload: null
|
|
2204
|
+
};
|
|
2205
|
+
}
|
|
2206
|
+
if (ts4.isTypeReferenceNode(typeNode) && ts4.isIdentifier(typeNode.typeName)) {
|
|
2207
|
+
const aliasDecl = checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts4.isTypeAliasDeclaration);
|
|
2208
|
+
if (aliasDecl !== void 0) {
|
|
2209
|
+
return resolveRegisteredCustomTypeFromTypeNode(aliasDecl.type, extensionRegistry, checker);
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
return null;
|
|
2213
|
+
}
|
|
2214
|
+
function extractTypeNodeFromSource(sourceNode) {
|
|
2215
|
+
if (ts4.isPropertyDeclaration(sourceNode) || ts4.isPropertySignature(sourceNode) || ts4.isParameter(sourceNode) || ts4.isTypeAliasDeclaration(sourceNode)) {
|
|
2216
|
+
return sourceNode.type;
|
|
2217
|
+
}
|
|
2218
|
+
if (ts4.isTypeNode(sourceNode)) {
|
|
2219
|
+
return sourceNode;
|
|
2220
|
+
}
|
|
2221
|
+
return void 0;
|
|
2222
|
+
}
|
|
2223
|
+
function getTypeNodeRegistrationName(typeNode) {
|
|
2224
|
+
if (ts4.isTypeReferenceNode(typeNode)) {
|
|
2225
|
+
return ts4.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : typeNode.typeName.right.text;
|
|
2226
|
+
}
|
|
2227
|
+
if (ts4.isParenthesizedTypeNode(typeNode)) {
|
|
2228
|
+
return getTypeNodeRegistrationName(typeNode.type);
|
|
2229
|
+
}
|
|
2230
|
+
if (typeNode.kind === ts4.SyntaxKind.BigIntKeyword || typeNode.kind === ts4.SyntaxKind.StringKeyword || typeNode.kind === ts4.SyntaxKind.NumberKeyword || typeNode.kind === ts4.SyntaxKind.BooleanKeyword) {
|
|
2231
|
+
return typeNode.getText();
|
|
2232
|
+
}
|
|
2233
|
+
return null;
|
|
2234
|
+
}
|
|
2235
|
+
function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
2236
|
+
const customType = resolveRegisteredCustomType(sourceNode, extensionRegistry, checker);
|
|
2237
|
+
if (customType) {
|
|
2238
|
+
return customType;
|
|
2239
|
+
}
|
|
1736
2240
|
if (type.flags & ts4.TypeFlags.String) {
|
|
1737
2241
|
return { kind: "primitive", primitiveKind: "string" };
|
|
1738
2242
|
}
|
|
@@ -1761,88 +2265,162 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
|
|
|
1761
2265
|
};
|
|
1762
2266
|
}
|
|
1763
2267
|
if (type.isUnion()) {
|
|
1764
|
-
return resolveUnionType(
|
|
2268
|
+
return resolveUnionType(
|
|
2269
|
+
type,
|
|
2270
|
+
checker,
|
|
2271
|
+
file,
|
|
2272
|
+
typeRegistry,
|
|
2273
|
+
visiting,
|
|
2274
|
+
sourceNode,
|
|
2275
|
+
extensionRegistry
|
|
2276
|
+
);
|
|
1765
2277
|
}
|
|
1766
2278
|
if (checker.isArrayType(type)) {
|
|
1767
|
-
return resolveArrayType(
|
|
2279
|
+
return resolveArrayType(
|
|
2280
|
+
type,
|
|
2281
|
+
checker,
|
|
2282
|
+
file,
|
|
2283
|
+
typeRegistry,
|
|
2284
|
+
visiting,
|
|
2285
|
+
sourceNode,
|
|
2286
|
+
extensionRegistry
|
|
2287
|
+
);
|
|
1768
2288
|
}
|
|
1769
2289
|
if (isObjectType(type)) {
|
|
1770
|
-
return resolveObjectType(type, checker, file, typeRegistry, visiting);
|
|
2290
|
+
return resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry);
|
|
1771
2291
|
}
|
|
1772
2292
|
return { kind: "primitive", primitiveKind: "string" };
|
|
1773
2293
|
}
|
|
1774
|
-
function resolveUnionType(type, checker, file, typeRegistry, visiting) {
|
|
2294
|
+
function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
2295
|
+
const typeName = getNamedTypeName(type);
|
|
2296
|
+
const namedDecl = getNamedTypeDeclaration(type);
|
|
2297
|
+
if (typeName && typeName in typeRegistry) {
|
|
2298
|
+
return { kind: "reference", name: typeName, typeArguments: [] };
|
|
2299
|
+
}
|
|
1775
2300
|
const allTypes = type.types;
|
|
2301
|
+
const unionMemberTypeNodes = extractUnionMemberTypeNodes(sourceNode, checker);
|
|
2302
|
+
const nonNullSourceNodes = unionMemberTypeNodes.filter(
|
|
2303
|
+
(memberTypeNode) => !isNullishTypeNode(resolveAliasedTypeNode(memberTypeNode, checker))
|
|
2304
|
+
);
|
|
1776
2305
|
const nonNullTypes = allTypes.filter(
|
|
1777
|
-
(
|
|
2306
|
+
(memberType) => !(memberType.flags & (ts4.TypeFlags.Null | ts4.TypeFlags.Undefined))
|
|
1778
2307
|
);
|
|
2308
|
+
const nonNullMembers = nonNullTypes.map((memberType, index) => ({
|
|
2309
|
+
memberType,
|
|
2310
|
+
sourceNode: nonNullSourceNodes.length === nonNullTypes.length ? nonNullSourceNodes[index] : void 0
|
|
2311
|
+
}));
|
|
1779
2312
|
const hasNull = allTypes.some((t) => t.flags & ts4.TypeFlags.Null);
|
|
2313
|
+
const memberDisplayNames = /* @__PURE__ */ new Map();
|
|
2314
|
+
if (namedDecl) {
|
|
2315
|
+
for (const [value, label] of extractDisplayNameMetadata(namedDecl).memberDisplayNames) {
|
|
2316
|
+
memberDisplayNames.set(value, label);
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
if (sourceNode) {
|
|
2320
|
+
for (const [value, label] of extractDisplayNameMetadata(sourceNode).memberDisplayNames) {
|
|
2321
|
+
memberDisplayNames.set(value, label);
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2324
|
+
const registerNamed = (result) => {
|
|
2325
|
+
if (!typeName) {
|
|
2326
|
+
return result;
|
|
2327
|
+
}
|
|
2328
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
2329
|
+
typeRegistry[typeName] = {
|
|
2330
|
+
name: typeName,
|
|
2331
|
+
type: result,
|
|
2332
|
+
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
2333
|
+
provenance: provenanceForDeclaration(namedDecl ?? sourceNode, file)
|
|
2334
|
+
};
|
|
2335
|
+
return { kind: "reference", name: typeName, typeArguments: [] };
|
|
2336
|
+
};
|
|
2337
|
+
const applyMemberLabels = (members2) => members2.map((value) => {
|
|
2338
|
+
const displayName = memberDisplayNames.get(String(value));
|
|
2339
|
+
return displayName !== void 0 ? { value, displayName } : { value };
|
|
2340
|
+
});
|
|
1780
2341
|
const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts4.TypeFlags.BooleanLiteral);
|
|
1781
2342
|
if (isBooleanUnion2) {
|
|
1782
2343
|
const boolNode = { kind: "primitive", primitiveKind: "boolean" };
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
}
|
|
1789
|
-
return boolNode;
|
|
2344
|
+
const result = hasNull ? {
|
|
2345
|
+
kind: "union",
|
|
2346
|
+
members: [boolNode, { kind: "primitive", primitiveKind: "null" }]
|
|
2347
|
+
} : boolNode;
|
|
2348
|
+
return registerNamed(result);
|
|
1790
2349
|
}
|
|
1791
2350
|
const allStringLiterals = nonNullTypes.every((t) => t.isStringLiteral());
|
|
1792
2351
|
if (allStringLiterals && nonNullTypes.length > 0) {
|
|
1793
2352
|
const stringTypes = nonNullTypes.filter((t) => t.isStringLiteral());
|
|
1794
2353
|
const enumNode = {
|
|
1795
2354
|
kind: "enum",
|
|
1796
|
-
members: stringTypes.map((t) =>
|
|
2355
|
+
members: applyMemberLabels(stringTypes.map((t) => t.value))
|
|
1797
2356
|
};
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
}
|
|
1804
|
-
return enumNode;
|
|
2357
|
+
const result = hasNull ? {
|
|
2358
|
+
kind: "union",
|
|
2359
|
+
members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
|
|
2360
|
+
} : enumNode;
|
|
2361
|
+
return registerNamed(result);
|
|
1805
2362
|
}
|
|
1806
2363
|
const allNumberLiterals = nonNullTypes.every((t) => t.isNumberLiteral());
|
|
1807
2364
|
if (allNumberLiterals && nonNullTypes.length > 0) {
|
|
1808
2365
|
const numberTypes = nonNullTypes.filter((t) => t.isNumberLiteral());
|
|
1809
2366
|
const enumNode = {
|
|
1810
2367
|
kind: "enum",
|
|
1811
|
-
members: numberTypes.map((t) =>
|
|
2368
|
+
members: applyMemberLabels(numberTypes.map((t) => t.value))
|
|
1812
2369
|
};
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
2370
|
+
const result = hasNull ? {
|
|
2371
|
+
kind: "union",
|
|
2372
|
+
members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
|
|
2373
|
+
} : enumNode;
|
|
2374
|
+
return registerNamed(result);
|
|
2375
|
+
}
|
|
2376
|
+
if (nonNullMembers.length === 1 && nonNullMembers[0]) {
|
|
2377
|
+
const inner = resolveTypeNode(
|
|
2378
|
+
nonNullMembers[0].memberType,
|
|
2379
|
+
checker,
|
|
2380
|
+
file,
|
|
2381
|
+
typeRegistry,
|
|
2382
|
+
visiting,
|
|
2383
|
+
nonNullMembers[0].sourceNode ?? sourceNode,
|
|
2384
|
+
extensionRegistry
|
|
2385
|
+
);
|
|
2386
|
+
const result = hasNull ? {
|
|
2387
|
+
kind: "union",
|
|
2388
|
+
members: [inner, { kind: "primitive", primitiveKind: "null" }]
|
|
2389
|
+
} : inner;
|
|
2390
|
+
return registerNamed(result);
|
|
2391
|
+
}
|
|
2392
|
+
const members = nonNullMembers.map(
|
|
2393
|
+
({ memberType, sourceNode: memberSourceNode }) => resolveTypeNode(
|
|
2394
|
+
memberType,
|
|
2395
|
+
checker,
|
|
2396
|
+
file,
|
|
2397
|
+
typeRegistry,
|
|
2398
|
+
visiting,
|
|
2399
|
+
memberSourceNode ?? sourceNode,
|
|
2400
|
+
extensionRegistry
|
|
2401
|
+
)
|
|
1833
2402
|
);
|
|
1834
2403
|
if (hasNull) {
|
|
1835
2404
|
members.push({ kind: "primitive", primitiveKind: "null" });
|
|
1836
2405
|
}
|
|
1837
|
-
return { kind: "union", members };
|
|
2406
|
+
return registerNamed({ kind: "union", members });
|
|
1838
2407
|
}
|
|
1839
|
-
function resolveArrayType(type, checker, file, typeRegistry, visiting) {
|
|
2408
|
+
function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
1840
2409
|
const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
|
|
1841
2410
|
const elementType = typeArgs?.[0];
|
|
1842
|
-
const
|
|
2411
|
+
const elementSourceNode = extractArrayElementTypeNode(sourceNode, checker);
|
|
2412
|
+
const items = elementType ? resolveTypeNode(
|
|
2413
|
+
elementType,
|
|
2414
|
+
checker,
|
|
2415
|
+
file,
|
|
2416
|
+
typeRegistry,
|
|
2417
|
+
visiting,
|
|
2418
|
+
elementSourceNode,
|
|
2419
|
+
extensionRegistry
|
|
2420
|
+
) : { kind: "primitive", primitiveKind: "string" };
|
|
1843
2421
|
return { kind: "array", items };
|
|
1844
2422
|
}
|
|
1845
|
-
function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
|
|
2423
|
+
function tryResolveRecordType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1846
2424
|
if (type.getProperties().length > 0) {
|
|
1847
2425
|
return null;
|
|
1848
2426
|
}
|
|
@@ -1850,39 +2428,123 @@ function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
|
|
|
1850
2428
|
if (!indexInfo) {
|
|
1851
2429
|
return null;
|
|
1852
2430
|
}
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
}
|
|
2431
|
+
const valueType = resolveTypeNode(
|
|
2432
|
+
indexInfo.type,
|
|
2433
|
+
checker,
|
|
2434
|
+
file,
|
|
2435
|
+
typeRegistry,
|
|
2436
|
+
visiting,
|
|
2437
|
+
void 0,
|
|
2438
|
+
extensionRegistry
|
|
2439
|
+
);
|
|
2440
|
+
return { kind: "record", valueType };
|
|
1863
2441
|
}
|
|
1864
|
-
function
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
2442
|
+
function typeNodeContainsReference(type, targetName) {
|
|
2443
|
+
switch (type.kind) {
|
|
2444
|
+
case "reference":
|
|
2445
|
+
return type.name === targetName;
|
|
2446
|
+
case "array":
|
|
2447
|
+
return typeNodeContainsReference(type.items, targetName);
|
|
2448
|
+
case "record":
|
|
2449
|
+
return typeNodeContainsReference(type.valueType, targetName);
|
|
2450
|
+
case "union":
|
|
2451
|
+
return type.members.some((member) => typeNodeContainsReference(member, targetName));
|
|
2452
|
+
case "object":
|
|
2453
|
+
return type.properties.some(
|
|
2454
|
+
(property) => typeNodeContainsReference(property.type, targetName)
|
|
2455
|
+
);
|
|
2456
|
+
case "primitive":
|
|
2457
|
+
case "enum":
|
|
2458
|
+
case "dynamic":
|
|
2459
|
+
case "custom":
|
|
2460
|
+
return false;
|
|
2461
|
+
default: {
|
|
2462
|
+
const _exhaustive = type;
|
|
2463
|
+
return _exhaustive;
|
|
2464
|
+
}
|
|
1868
2465
|
}
|
|
2466
|
+
}
|
|
2467
|
+
function resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2468
|
+
const typeName = getNamedTypeName(type);
|
|
2469
|
+
const namedTypeName = typeName ?? void 0;
|
|
2470
|
+
const namedDecl = getNamedTypeDeclaration(type);
|
|
2471
|
+
const shouldRegisterNamedType = namedTypeName !== void 0 && !(namedTypeName === "Record" && namedDecl?.getSourceFile().fileName !== file);
|
|
2472
|
+
const clearNamedTypeRegistration = () => {
|
|
2473
|
+
if (namedTypeName === void 0 || !shouldRegisterNamedType) {
|
|
2474
|
+
return;
|
|
2475
|
+
}
|
|
2476
|
+
Reflect.deleteProperty(typeRegistry, namedTypeName);
|
|
2477
|
+
};
|
|
1869
2478
|
if (visiting.has(type)) {
|
|
2479
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
2480
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
2481
|
+
}
|
|
1870
2482
|
return { kind: "object", properties: [], additionalProperties: false };
|
|
1871
2483
|
}
|
|
2484
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType && !typeRegistry[namedTypeName]) {
|
|
2485
|
+
typeRegistry[namedTypeName] = {
|
|
2486
|
+
name: namedTypeName,
|
|
2487
|
+
type: RESOLVING_TYPE_PLACEHOLDER,
|
|
2488
|
+
provenance: provenanceForDeclaration(namedDecl, file)
|
|
2489
|
+
};
|
|
2490
|
+
}
|
|
1872
2491
|
visiting.add(type);
|
|
1873
|
-
|
|
1874
|
-
|
|
2492
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType && typeRegistry[namedTypeName]?.type !== void 0) {
|
|
2493
|
+
if (typeRegistry[namedTypeName].type !== RESOLVING_TYPE_PLACEHOLDER) {
|
|
2494
|
+
visiting.delete(type);
|
|
2495
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2498
|
+
const recordNode = tryResolveRecordType(
|
|
2499
|
+
type,
|
|
2500
|
+
checker,
|
|
2501
|
+
file,
|
|
2502
|
+
typeRegistry,
|
|
2503
|
+
visiting,
|
|
2504
|
+
extensionRegistry
|
|
2505
|
+
);
|
|
2506
|
+
if (recordNode) {
|
|
1875
2507
|
visiting.delete(type);
|
|
1876
|
-
|
|
2508
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
2509
|
+
const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType, namedTypeName);
|
|
2510
|
+
if (!isRecursiveRecord) {
|
|
2511
|
+
clearNamedTypeRegistration();
|
|
2512
|
+
return recordNode;
|
|
2513
|
+
}
|
|
2514
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
2515
|
+
typeRegistry[namedTypeName] = {
|
|
2516
|
+
name: namedTypeName,
|
|
2517
|
+
type: recordNode,
|
|
2518
|
+
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
2519
|
+
provenance: provenanceForDeclaration(namedDecl, file)
|
|
2520
|
+
};
|
|
2521
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
2522
|
+
}
|
|
2523
|
+
return recordNode;
|
|
1877
2524
|
}
|
|
1878
2525
|
const properties = [];
|
|
1879
|
-
const fieldInfoMap = getNamedTypeFieldNodeInfoMap(
|
|
2526
|
+
const fieldInfoMap = getNamedTypeFieldNodeInfoMap(
|
|
2527
|
+
type,
|
|
2528
|
+
checker,
|
|
2529
|
+
file,
|
|
2530
|
+
typeRegistry,
|
|
2531
|
+
visiting,
|
|
2532
|
+
extensionRegistry
|
|
2533
|
+
);
|
|
1880
2534
|
for (const prop of type.getProperties()) {
|
|
1881
2535
|
const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
|
|
1882
2536
|
if (!declaration) continue;
|
|
1883
2537
|
const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
|
|
1884
2538
|
const optional = !!(prop.flags & ts4.SymbolFlags.Optional);
|
|
1885
|
-
const propTypeNode = resolveTypeNode(
|
|
2539
|
+
const propTypeNode = resolveTypeNode(
|
|
2540
|
+
propType,
|
|
2541
|
+
checker,
|
|
2542
|
+
file,
|
|
2543
|
+
typeRegistry,
|
|
2544
|
+
visiting,
|
|
2545
|
+
declaration,
|
|
2546
|
+
extensionRegistry
|
|
2547
|
+
);
|
|
1886
2548
|
const fieldNodeInfo = fieldInfoMap?.get(prop.name);
|
|
1887
2549
|
properties.push({
|
|
1888
2550
|
name: prop.name,
|
|
@@ -1899,17 +2561,19 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
|
1899
2561
|
properties,
|
|
1900
2562
|
additionalProperties: true
|
|
1901
2563
|
};
|
|
1902
|
-
if (
|
|
1903
|
-
|
|
1904
|
-
|
|
2564
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
2565
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
2566
|
+
typeRegistry[namedTypeName] = {
|
|
2567
|
+
name: namedTypeName,
|
|
1905
2568
|
type: objectNode,
|
|
1906
|
-
|
|
2569
|
+
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
2570
|
+
provenance: provenanceForDeclaration(namedDecl, file)
|
|
1907
2571
|
};
|
|
1908
|
-
return { kind: "reference", name:
|
|
2572
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
1909
2573
|
}
|
|
1910
2574
|
return objectNode;
|
|
1911
2575
|
}
|
|
1912
|
-
function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting) {
|
|
2576
|
+
function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1913
2577
|
const symbols = [type.getSymbol(), type.aliasSymbol].filter(
|
|
1914
2578
|
(s) => s?.declarations != null && s.declarations.length > 0
|
|
1915
2579
|
);
|
|
@@ -1921,7 +2585,14 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
1921
2585
|
const map = /* @__PURE__ */ new Map();
|
|
1922
2586
|
for (const member of classDecl.members) {
|
|
1923
2587
|
if (ts4.isPropertyDeclaration(member) && ts4.isIdentifier(member.name)) {
|
|
1924
|
-
const fieldNode = analyzeFieldToIR(
|
|
2588
|
+
const fieldNode = analyzeFieldToIR(
|
|
2589
|
+
member,
|
|
2590
|
+
checker,
|
|
2591
|
+
file,
|
|
2592
|
+
typeRegistry,
|
|
2593
|
+
visiting,
|
|
2594
|
+
extensionRegistry
|
|
2595
|
+
);
|
|
1925
2596
|
if (fieldNode) {
|
|
1926
2597
|
map.set(fieldNode.name, {
|
|
1927
2598
|
constraints: [...fieldNode.constraints],
|
|
@@ -1935,7 +2606,14 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
1935
2606
|
}
|
|
1936
2607
|
const interfaceDecl = declarations.find(ts4.isInterfaceDeclaration);
|
|
1937
2608
|
if (interfaceDecl) {
|
|
1938
|
-
return buildFieldNodeInfoMap(
|
|
2609
|
+
return buildFieldNodeInfoMap(
|
|
2610
|
+
interfaceDecl.members,
|
|
2611
|
+
checker,
|
|
2612
|
+
file,
|
|
2613
|
+
typeRegistry,
|
|
2614
|
+
visiting,
|
|
2615
|
+
extensionRegistry
|
|
2616
|
+
);
|
|
1939
2617
|
}
|
|
1940
2618
|
const typeAliasDecl = declarations.find(ts4.isTypeAliasDeclaration);
|
|
1941
2619
|
if (typeAliasDecl && ts4.isTypeLiteralNode(typeAliasDecl.type)) {
|
|
@@ -1944,17 +2622,68 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
1944
2622
|
checker,
|
|
1945
2623
|
file,
|
|
1946
2624
|
typeRegistry,
|
|
1947
|
-
visiting
|
|
2625
|
+
visiting,
|
|
2626
|
+
extensionRegistry
|
|
1948
2627
|
);
|
|
1949
2628
|
}
|
|
1950
2629
|
}
|
|
1951
2630
|
return null;
|
|
1952
2631
|
}
|
|
1953
|
-
function
|
|
2632
|
+
function extractArrayElementTypeNode(sourceNode, checker) {
|
|
2633
|
+
const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
|
|
2634
|
+
if (typeNode === void 0) {
|
|
2635
|
+
return void 0;
|
|
2636
|
+
}
|
|
2637
|
+
const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
|
|
2638
|
+
if (ts4.isArrayTypeNode(resolvedTypeNode)) {
|
|
2639
|
+
return resolvedTypeNode.elementType;
|
|
2640
|
+
}
|
|
2641
|
+
if (ts4.isTypeReferenceNode(resolvedTypeNode) && ts4.isIdentifier(resolvedTypeNode.typeName) && resolvedTypeNode.typeName.text === "Array" && resolvedTypeNode.typeArguments?.[0]) {
|
|
2642
|
+
return resolvedTypeNode.typeArguments[0];
|
|
2643
|
+
}
|
|
2644
|
+
return void 0;
|
|
2645
|
+
}
|
|
2646
|
+
function extractUnionMemberTypeNodes(sourceNode, checker) {
|
|
2647
|
+
const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
|
|
2648
|
+
if (!typeNode) {
|
|
2649
|
+
return [];
|
|
2650
|
+
}
|
|
2651
|
+
const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
|
|
2652
|
+
return ts4.isUnionTypeNode(resolvedTypeNode) ? [...resolvedTypeNode.types] : [];
|
|
2653
|
+
}
|
|
2654
|
+
function resolveAliasedTypeNode(typeNode, checker, visited = /* @__PURE__ */ new Set()) {
|
|
2655
|
+
if (ts4.isParenthesizedTypeNode(typeNode)) {
|
|
2656
|
+
return resolveAliasedTypeNode(typeNode.type, checker, visited);
|
|
2657
|
+
}
|
|
2658
|
+
if (!ts4.isTypeReferenceNode(typeNode) || !ts4.isIdentifier(typeNode.typeName)) {
|
|
2659
|
+
return typeNode;
|
|
2660
|
+
}
|
|
2661
|
+
const symbol = checker.getSymbolAtLocation(typeNode.typeName);
|
|
2662
|
+
const aliasDecl = symbol?.declarations?.find(ts4.isTypeAliasDeclaration);
|
|
2663
|
+
if (aliasDecl === void 0 || visited.has(aliasDecl)) {
|
|
2664
|
+
return typeNode;
|
|
2665
|
+
}
|
|
2666
|
+
visited.add(aliasDecl);
|
|
2667
|
+
return resolveAliasedTypeNode(aliasDecl.type, checker, visited);
|
|
2668
|
+
}
|
|
2669
|
+
function isNullishTypeNode(typeNode) {
|
|
2670
|
+
if (typeNode.kind === ts4.SyntaxKind.NullKeyword || typeNode.kind === ts4.SyntaxKind.UndefinedKeyword) {
|
|
2671
|
+
return true;
|
|
2672
|
+
}
|
|
2673
|
+
return ts4.isLiteralTypeNode(typeNode) && (typeNode.literal.kind === ts4.SyntaxKind.NullKeyword || typeNode.literal.kind === ts4.SyntaxKind.UndefinedKeyword);
|
|
2674
|
+
}
|
|
2675
|
+
function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1954
2676
|
const map = /* @__PURE__ */ new Map();
|
|
1955
2677
|
for (const member of members) {
|
|
1956
2678
|
if (ts4.isPropertySignature(member)) {
|
|
1957
|
-
const fieldNode = analyzeInterfacePropertyToIR(
|
|
2679
|
+
const fieldNode = analyzeInterfacePropertyToIR(
|
|
2680
|
+
member,
|
|
2681
|
+
checker,
|
|
2682
|
+
file,
|
|
2683
|
+
typeRegistry,
|
|
2684
|
+
visiting,
|
|
2685
|
+
extensionRegistry
|
|
2686
|
+
);
|
|
1958
2687
|
if (fieldNode) {
|
|
1959
2688
|
map.set(fieldNode.name, {
|
|
1960
2689
|
constraints: [...fieldNode.constraints],
|
|
@@ -1967,7 +2696,7 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting) {
|
|
|
1967
2696
|
return map;
|
|
1968
2697
|
}
|
|
1969
2698
|
var MAX_ALIAS_CHAIN_DEPTH = 8;
|
|
1970
|
-
function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
|
|
2699
|
+
function extractTypeAliasConstraintNodes(typeNode, checker, file, extensionRegistry, depth = 0) {
|
|
1971
2700
|
if (!ts4.isTypeReferenceNode(typeNode)) return [];
|
|
1972
2701
|
if (depth >= MAX_ALIAS_CHAIN_DEPTH) {
|
|
1973
2702
|
const aliasName = typeNode.typeName.getText();
|
|
@@ -1980,8 +2709,29 @@ function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
|
|
|
1980
2709
|
const aliasDecl = symbol.declarations.find(ts4.isTypeAliasDeclaration);
|
|
1981
2710
|
if (!aliasDecl) return [];
|
|
1982
2711
|
if (ts4.isTypeLiteralNode(aliasDecl.type)) return [];
|
|
1983
|
-
const
|
|
1984
|
-
|
|
2712
|
+
const aliasFieldType = resolveTypeNode(
|
|
2713
|
+
checker.getTypeAtLocation(aliasDecl.type),
|
|
2714
|
+
checker,
|
|
2715
|
+
file,
|
|
2716
|
+
{},
|
|
2717
|
+
/* @__PURE__ */ new Set(),
|
|
2718
|
+
aliasDecl.type,
|
|
2719
|
+
extensionRegistry
|
|
2720
|
+
);
|
|
2721
|
+
const constraints = extractJSDocConstraintNodes(
|
|
2722
|
+
aliasDecl,
|
|
2723
|
+
file,
|
|
2724
|
+
makeParseOptions(extensionRegistry, aliasFieldType)
|
|
2725
|
+
);
|
|
2726
|
+
constraints.push(
|
|
2727
|
+
...extractTypeAliasConstraintNodes(
|
|
2728
|
+
aliasDecl.type,
|
|
2729
|
+
checker,
|
|
2730
|
+
file,
|
|
2731
|
+
extensionRegistry,
|
|
2732
|
+
depth + 1
|
|
2733
|
+
)
|
|
2734
|
+
);
|
|
1985
2735
|
return constraints;
|
|
1986
2736
|
}
|
|
1987
2737
|
function provenanceForNode(node, file) {
|
|
@@ -1997,6 +2747,12 @@ function provenanceForNode(node, file) {
|
|
|
1997
2747
|
function provenanceForFile(file) {
|
|
1998
2748
|
return { surface: "tsdoc", file, line: 0, column: 0 };
|
|
1999
2749
|
}
|
|
2750
|
+
function provenanceForDeclaration(node, file) {
|
|
2751
|
+
if (!node) {
|
|
2752
|
+
return provenanceForFile(file);
|
|
2753
|
+
}
|
|
2754
|
+
return provenanceForNode(node, file);
|
|
2755
|
+
}
|
|
2000
2756
|
function getNamedTypeName(type) {
|
|
2001
2757
|
const symbol = type.getSymbol();
|
|
2002
2758
|
if (symbol?.declarations) {
|
|
@@ -2015,6 +2771,20 @@ function getNamedTypeName(type) {
|
|
|
2015
2771
|
}
|
|
2016
2772
|
return null;
|
|
2017
2773
|
}
|
|
2774
|
+
function getNamedTypeDeclaration(type) {
|
|
2775
|
+
const symbol = type.getSymbol();
|
|
2776
|
+
if (symbol?.declarations) {
|
|
2777
|
+
const decl = symbol.declarations[0];
|
|
2778
|
+
if (decl && (ts4.isClassDeclaration(decl) || ts4.isInterfaceDeclaration(decl) || ts4.isTypeAliasDeclaration(decl))) {
|
|
2779
|
+
return decl;
|
|
2780
|
+
}
|
|
2781
|
+
}
|
|
2782
|
+
const aliasSymbol = type.aliasSymbol;
|
|
2783
|
+
if (aliasSymbol?.declarations) {
|
|
2784
|
+
return aliasSymbol.declarations.find(ts4.isTypeAliasDeclaration);
|
|
2785
|
+
}
|
|
2786
|
+
return void 0;
|
|
2787
|
+
}
|
|
2018
2788
|
function analyzeMethod(method, checker) {
|
|
2019
2789
|
if (!ts4.isIdentifier(method.name)) {
|
|
2020
2790
|
return null;
|
|
@@ -2057,10 +2827,10 @@ function detectFormSpecReference(typeNode) {
|
|
|
2057
2827
|
}
|
|
2058
2828
|
|
|
2059
2829
|
// src/generators/class-schema.ts
|
|
2060
|
-
function generateClassSchemas(analysis, source) {
|
|
2830
|
+
function generateClassSchemas(analysis, source, options) {
|
|
2061
2831
|
const ir = canonicalizeTSDoc(analysis, source);
|
|
2062
2832
|
return {
|
|
2063
|
-
jsonSchema: generateJsonSchemaFromIR(ir),
|
|
2833
|
+
jsonSchema: generateJsonSchemaFromIR(ir, options),
|
|
2064
2834
|
uiSchema: generateUiSchemaFromIR(ir)
|
|
2065
2835
|
};
|
|
2066
2836
|
}
|
|
@@ -2070,27 +2840,54 @@ function generateSchemasFromClass(options) {
|
|
|
2070
2840
|
if (!classDecl) {
|
|
2071
2841
|
throw new Error(`Class "${options.className}" not found in ${options.filePath}`);
|
|
2072
2842
|
}
|
|
2073
|
-
const analysis = analyzeClassToIR(
|
|
2074
|
-
|
|
2843
|
+
const analysis = analyzeClassToIR(
|
|
2844
|
+
classDecl,
|
|
2845
|
+
ctx.checker,
|
|
2846
|
+
options.filePath,
|
|
2847
|
+
options.extensionRegistry
|
|
2848
|
+
);
|
|
2849
|
+
return generateClassSchemas(
|
|
2850
|
+
analysis,
|
|
2851
|
+
{ file: options.filePath },
|
|
2852
|
+
{
|
|
2853
|
+
extensionRegistry: options.extensionRegistry,
|
|
2854
|
+
vendorPrefix: options.vendorPrefix
|
|
2855
|
+
}
|
|
2856
|
+
);
|
|
2075
2857
|
}
|
|
2076
2858
|
function generateSchemas(options) {
|
|
2077
2859
|
const ctx = createProgramContext(options.filePath);
|
|
2078
2860
|
const source = { file: options.filePath };
|
|
2079
2861
|
const classDecl = findClassByName(ctx.sourceFile, options.typeName);
|
|
2080
2862
|
if (classDecl) {
|
|
2081
|
-
const analysis = analyzeClassToIR(
|
|
2082
|
-
|
|
2863
|
+
const analysis = analyzeClassToIR(
|
|
2864
|
+
classDecl,
|
|
2865
|
+
ctx.checker,
|
|
2866
|
+
options.filePath,
|
|
2867
|
+
options.extensionRegistry
|
|
2868
|
+
);
|
|
2869
|
+
return generateClassSchemas(analysis, source, options);
|
|
2083
2870
|
}
|
|
2084
2871
|
const interfaceDecl = findInterfaceByName(ctx.sourceFile, options.typeName);
|
|
2085
2872
|
if (interfaceDecl) {
|
|
2086
|
-
const analysis = analyzeInterfaceToIR(
|
|
2087
|
-
|
|
2873
|
+
const analysis = analyzeInterfaceToIR(
|
|
2874
|
+
interfaceDecl,
|
|
2875
|
+
ctx.checker,
|
|
2876
|
+
options.filePath,
|
|
2877
|
+
options.extensionRegistry
|
|
2878
|
+
);
|
|
2879
|
+
return generateClassSchemas(analysis, source, options);
|
|
2088
2880
|
}
|
|
2089
2881
|
const typeAlias = findTypeAliasByName(ctx.sourceFile, options.typeName);
|
|
2090
2882
|
if (typeAlias) {
|
|
2091
|
-
const result = analyzeTypeAliasToIR(
|
|
2883
|
+
const result = analyzeTypeAliasToIR(
|
|
2884
|
+
typeAlias,
|
|
2885
|
+
ctx.checker,
|
|
2886
|
+
options.filePath,
|
|
2887
|
+
options.extensionRegistry
|
|
2888
|
+
);
|
|
2092
2889
|
if (result.ok) {
|
|
2093
|
-
return generateClassSchemas(result.analysis, source);
|
|
2890
|
+
return generateClassSchemas(result.analysis, source, options);
|
|
2094
2891
|
}
|
|
2095
2892
|
throw new Error(result.error);
|
|
2096
2893
|
}
|
|
@@ -2099,6 +2896,205 @@ function generateSchemas(options) {
|
|
|
2099
2896
|
);
|
|
2100
2897
|
}
|
|
2101
2898
|
|
|
2899
|
+
// src/generators/mixed-authoring.ts
|
|
2900
|
+
function buildMixedAuthoringSchemas(options) {
|
|
2901
|
+
const { filePath, typeName, overlays, ...schemaOptions } = options;
|
|
2902
|
+
const analysis = analyzeNamedType(filePath, typeName, schemaOptions.extensionRegistry);
|
|
2903
|
+
const composedAnalysis = composeAnalysisWithOverlays(analysis, overlays);
|
|
2904
|
+
const ir = canonicalizeTSDoc(composedAnalysis, { file: filePath });
|
|
2905
|
+
return {
|
|
2906
|
+
jsonSchema: generateJsonSchemaFromIR(ir, schemaOptions),
|
|
2907
|
+
uiSchema: generateUiSchemaFromIR(ir)
|
|
2908
|
+
};
|
|
2909
|
+
}
|
|
2910
|
+
function analyzeNamedType(filePath, typeName, extensionRegistry) {
|
|
2911
|
+
const ctx = createProgramContext(filePath);
|
|
2912
|
+
const source = { file: filePath };
|
|
2913
|
+
const classDecl = findClassByName(ctx.sourceFile, typeName);
|
|
2914
|
+
if (classDecl !== null) {
|
|
2915
|
+
return analyzeClassToIR(classDecl, ctx.checker, source.file, extensionRegistry);
|
|
2916
|
+
}
|
|
2917
|
+
const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
|
|
2918
|
+
if (interfaceDecl !== null) {
|
|
2919
|
+
return analyzeInterfaceToIR(interfaceDecl, ctx.checker, source.file, extensionRegistry);
|
|
2920
|
+
}
|
|
2921
|
+
const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
|
|
2922
|
+
if (typeAlias !== null) {
|
|
2923
|
+
const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, source.file, extensionRegistry);
|
|
2924
|
+
if (result.ok) {
|
|
2925
|
+
return result.analysis;
|
|
2926
|
+
}
|
|
2927
|
+
throw new Error(result.error);
|
|
2928
|
+
}
|
|
2929
|
+
throw new Error(
|
|
2930
|
+
`Type "${typeName}" not found as a class, interface, or type alias in ${filePath}`
|
|
2931
|
+
);
|
|
2932
|
+
}
|
|
2933
|
+
function composeAnalysisWithOverlays(analysis, overlays) {
|
|
2934
|
+
const overlayIR = canonicalizeChainDSL(overlays);
|
|
2935
|
+
const overlayFields = collectOverlayFields(overlayIR.elements);
|
|
2936
|
+
if (overlayFields.length === 0) {
|
|
2937
|
+
return analysis;
|
|
2938
|
+
}
|
|
2939
|
+
const overlayByName = /* @__PURE__ */ new Map();
|
|
2940
|
+
for (const field of overlayFields) {
|
|
2941
|
+
if (overlayByName.has(field.name)) {
|
|
2942
|
+
throw new Error(`Mixed-authoring overlays define "${field.name}" more than once`);
|
|
2943
|
+
}
|
|
2944
|
+
overlayByName.set(field.name, field);
|
|
2945
|
+
}
|
|
2946
|
+
const mergedFields = [];
|
|
2947
|
+
for (const baseField of analysis.fields) {
|
|
2948
|
+
const overlayField = overlayByName.get(baseField.name);
|
|
2949
|
+
if (overlayField === void 0) {
|
|
2950
|
+
mergedFields.push(baseField);
|
|
2951
|
+
continue;
|
|
2952
|
+
}
|
|
2953
|
+
mergedFields.push(mergeFieldOverlay(baseField, overlayField, analysis.typeRegistry));
|
|
2954
|
+
overlayByName.delete(baseField.name);
|
|
2955
|
+
}
|
|
2956
|
+
if (overlayByName.size > 0) {
|
|
2957
|
+
const unknownFields = [...overlayByName.keys()].sort().join(", ");
|
|
2958
|
+
throw new Error(
|
|
2959
|
+
`Mixed-authoring overlays reference fields that are not present in the static model: ${unknownFields}`
|
|
2960
|
+
);
|
|
2961
|
+
}
|
|
2962
|
+
return {
|
|
2963
|
+
...analysis,
|
|
2964
|
+
fields: mergedFields
|
|
2965
|
+
};
|
|
2966
|
+
}
|
|
2967
|
+
function collectOverlayFields(elements) {
|
|
2968
|
+
const fields = [];
|
|
2969
|
+
for (const element of elements) {
|
|
2970
|
+
switch (element.kind) {
|
|
2971
|
+
case "field":
|
|
2972
|
+
fields.push(element);
|
|
2973
|
+
break;
|
|
2974
|
+
case "group":
|
|
2975
|
+
fields.push(...collectOverlayFields(element.elements));
|
|
2976
|
+
break;
|
|
2977
|
+
case "conditional":
|
|
2978
|
+
fields.push(...collectOverlayFields(element.elements));
|
|
2979
|
+
break;
|
|
2980
|
+
default: {
|
|
2981
|
+
const _exhaustive = element;
|
|
2982
|
+
void _exhaustive;
|
|
2983
|
+
}
|
|
2984
|
+
}
|
|
2985
|
+
}
|
|
2986
|
+
return fields;
|
|
2987
|
+
}
|
|
2988
|
+
function mergeFieldOverlay(baseField, overlayField, typeRegistry) {
|
|
2989
|
+
assertSupportedOverlayField(baseField, overlayField);
|
|
2990
|
+
return {
|
|
2991
|
+
...baseField,
|
|
2992
|
+
type: mergeFieldType(baseField, overlayField, typeRegistry),
|
|
2993
|
+
annotations: mergeAnnotations(baseField.annotations, overlayField.annotations)
|
|
2994
|
+
};
|
|
2995
|
+
}
|
|
2996
|
+
function assertSupportedOverlayField(baseField, overlayField) {
|
|
2997
|
+
if (overlayField.constraints.length > 0) {
|
|
2998
|
+
throw new Error(
|
|
2999
|
+
`Mixed-authoring overlay for "${baseField.name}" cannot define constraints; keep constraints on the static model`
|
|
3000
|
+
);
|
|
3001
|
+
}
|
|
3002
|
+
if (overlayField.required && !baseField.required) {
|
|
3003
|
+
throw new Error(
|
|
3004
|
+
`Mixed-authoring overlay for "${baseField.name}" cannot change requiredness; keep requiredness on the static model`
|
|
3005
|
+
);
|
|
3006
|
+
}
|
|
3007
|
+
}
|
|
3008
|
+
function mergeFieldType(baseField, overlayField, typeRegistry) {
|
|
3009
|
+
const { type: baseType } = baseField;
|
|
3010
|
+
const { type: overlayType } = overlayField;
|
|
3011
|
+
if (overlayType.kind === "object" || overlayType.kind === "array") {
|
|
3012
|
+
throw new Error(
|
|
3013
|
+
`Mixed-authoring overlays do not support nested object or array overlays for "${baseField.name}"`
|
|
3014
|
+
);
|
|
3015
|
+
}
|
|
3016
|
+
if (overlayType.kind === "dynamic") {
|
|
3017
|
+
if (!isCompatibleDynamicOverlay(baseField, overlayField, typeRegistry)) {
|
|
3018
|
+
throw new Error(
|
|
3019
|
+
`Mixed-authoring overlay for "${baseField.name}" is incompatible with the static field type`
|
|
3020
|
+
);
|
|
3021
|
+
}
|
|
3022
|
+
return overlayType;
|
|
3023
|
+
}
|
|
3024
|
+
if (!isSameStaticTypeShape(baseType, overlayType)) {
|
|
3025
|
+
throw new Error(
|
|
3026
|
+
`Mixed-authoring overlay for "${baseField.name}" must preserve the static field type`
|
|
3027
|
+
);
|
|
3028
|
+
}
|
|
3029
|
+
return baseType;
|
|
3030
|
+
}
|
|
3031
|
+
function isCompatibleDynamicOverlay(baseField, overlayField, typeRegistry) {
|
|
3032
|
+
const overlayType = overlayField.type;
|
|
3033
|
+
if (overlayType.kind !== "dynamic") {
|
|
3034
|
+
return false;
|
|
3035
|
+
}
|
|
3036
|
+
const resolvedBaseType = resolveReferenceType(baseField.type, typeRegistry);
|
|
3037
|
+
if (resolvedBaseType === null) {
|
|
3038
|
+
return false;
|
|
3039
|
+
}
|
|
3040
|
+
if (overlayType.dynamicKind === "enum") {
|
|
3041
|
+
return resolvedBaseType.kind === "primitive" ? resolvedBaseType.primitiveKind === "string" : resolvedBaseType.kind === "enum";
|
|
3042
|
+
}
|
|
3043
|
+
return resolvedBaseType.kind === "object" || resolvedBaseType.kind === "record";
|
|
3044
|
+
}
|
|
3045
|
+
function resolveReferenceType(type, typeRegistry, seen = /* @__PURE__ */ new Set()) {
|
|
3046
|
+
if (type.kind !== "reference") {
|
|
3047
|
+
return type;
|
|
3048
|
+
}
|
|
3049
|
+
if (seen.has(type.name)) {
|
|
3050
|
+
return null;
|
|
3051
|
+
}
|
|
3052
|
+
const definition = typeRegistry[type.name];
|
|
3053
|
+
if (definition === void 0) {
|
|
3054
|
+
return null;
|
|
3055
|
+
}
|
|
3056
|
+
seen.add(type.name);
|
|
3057
|
+
return resolveReferenceType(definition.type, typeRegistry, seen);
|
|
3058
|
+
}
|
|
3059
|
+
function isSameStaticTypeShape(baseType, overlayType) {
|
|
3060
|
+
if (baseType.kind !== overlayType.kind) {
|
|
3061
|
+
return false;
|
|
3062
|
+
}
|
|
3063
|
+
switch (baseType.kind) {
|
|
3064
|
+
case "primitive":
|
|
3065
|
+
return overlayType.kind === "primitive" && baseType.primitiveKind === overlayType.primitiveKind;
|
|
3066
|
+
case "enum":
|
|
3067
|
+
return overlayType.kind === "enum";
|
|
3068
|
+
case "dynamic":
|
|
3069
|
+
return overlayType.kind === "dynamic" && baseType.dynamicKind === overlayType.dynamicKind && baseType.sourceKey === overlayType.sourceKey;
|
|
3070
|
+
case "record":
|
|
3071
|
+
return overlayType.kind === "record";
|
|
3072
|
+
case "reference":
|
|
3073
|
+
return overlayType.kind === "reference" && baseType.name === overlayType.name;
|
|
3074
|
+
case "union":
|
|
3075
|
+
return overlayType.kind === "union";
|
|
3076
|
+
case "custom":
|
|
3077
|
+
return overlayType.kind === "custom" && baseType.typeId === overlayType.typeId;
|
|
3078
|
+
case "object":
|
|
3079
|
+
case "array":
|
|
3080
|
+
return true;
|
|
3081
|
+
default: {
|
|
3082
|
+
const _exhaustive = baseType;
|
|
3083
|
+
return _exhaustive;
|
|
3084
|
+
}
|
|
3085
|
+
}
|
|
3086
|
+
}
|
|
3087
|
+
function mergeAnnotations(baseAnnotations, overlayAnnotations) {
|
|
3088
|
+
const baseKeys = new Set(baseAnnotations.map(annotationKey));
|
|
3089
|
+
const overlayOnly = overlayAnnotations.filter(
|
|
3090
|
+
(annotation) => !baseKeys.has(annotationKey(annotation))
|
|
3091
|
+
);
|
|
3092
|
+
return [...baseAnnotations, ...overlayOnly];
|
|
3093
|
+
}
|
|
3094
|
+
function annotationKey(annotation) {
|
|
3095
|
+
return annotation.annotationKind === "custom" ? `${annotation.annotationKind}:${annotation.annotationId}` : annotation.annotationKind;
|
|
3096
|
+
}
|
|
3097
|
+
|
|
2102
3098
|
// src/index.ts
|
|
2103
3099
|
function buildFormSchemas(form, options) {
|
|
2104
3100
|
return {
|
|
@@ -2125,6 +3121,7 @@ function writeSchemas(form, options) {
|
|
|
2125
3121
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2126
3122
|
0 && (module.exports = {
|
|
2127
3123
|
buildFormSchemas,
|
|
3124
|
+
buildMixedAuthoringSchemas,
|
|
2128
3125
|
categorizationSchema,
|
|
2129
3126
|
categorySchema,
|
|
2130
3127
|
controlSchema,
|