@formspec/build 0.1.0-alpha.15 → 0.1.0-alpha.16
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/mixed-authoring-shipping-address.d.ts +30 -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__/parity/utils.d.ts +5 -3
- package/dist/__tests__/parity/utils.d.ts.map +1 -1
- package/dist/analyzer/class-analyzer.d.ts +4 -2
- package/dist/analyzer/class-analyzer.d.ts.map +1 -1
- package/dist/analyzer/tsdoc-parser.d.ts +20 -2
- package/dist/analyzer/tsdoc-parser.d.ts.map +1 -1
- package/dist/browser.cjs +172 -17
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +172 -17
- package/dist/browser.js.map +1 -1
- package/dist/build.d.ts +39 -1
- package/dist/canonicalize/tsdoc-canonicalizer.d.ts.map +1 -1
- package/dist/cli.cjs +634 -88
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +634 -88
- package/dist/cli.js.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 +622 -87
- 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 +621 -87
- package/dist/index.js.map +1 -1
- package/dist/internals.cjs +526 -91
- package/dist/internals.cjs.map +1 -1
- package/dist/internals.js +526 -91
- 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;
|
|
@@ -887,25 +932,31 @@ function createShowRule(fieldName, value) {
|
|
|
887
932
|
}
|
|
888
933
|
};
|
|
889
934
|
}
|
|
935
|
+
function flattenConditionSchema(scope, schema) {
|
|
936
|
+
if (schema.allOf === void 0) {
|
|
937
|
+
if (scope === "#") {
|
|
938
|
+
return [schema];
|
|
939
|
+
}
|
|
940
|
+
const fieldName = scope.replace("#/properties/", "");
|
|
941
|
+
return [
|
|
942
|
+
{
|
|
943
|
+
properties: {
|
|
944
|
+
[fieldName]: schema
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
];
|
|
948
|
+
}
|
|
949
|
+
return schema.allOf.flatMap((member) => flattenConditionSchema(scope, member));
|
|
950
|
+
}
|
|
890
951
|
function combineRules(parentRule, childRule) {
|
|
891
|
-
const parentCondition = parentRule.condition;
|
|
892
|
-
const childCondition = childRule.condition;
|
|
893
952
|
return {
|
|
894
953
|
effect: "SHOW",
|
|
895
954
|
condition: {
|
|
896
955
|
scope: "#",
|
|
897
956
|
schema: {
|
|
898
957
|
allOf: [
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
[parentCondition.scope.replace("#/properties/", "")]: parentCondition.schema
|
|
902
|
-
}
|
|
903
|
-
},
|
|
904
|
-
{
|
|
905
|
-
properties: {
|
|
906
|
-
[childCondition.scope.replace("#/properties/", "")]: childCondition.schema
|
|
907
|
-
}
|
|
908
|
-
}
|
|
958
|
+
...flattenConditionSchema(parentRule.condition.scope, parentRule.condition.schema),
|
|
959
|
+
...flattenConditionSchema(childRule.condition.scope, childRule.condition.schema)
|
|
909
960
|
]
|
|
910
961
|
}
|
|
911
962
|
}
|
|
@@ -913,10 +964,14 @@ function combineRules(parentRule, childRule) {
|
|
|
913
964
|
}
|
|
914
965
|
function fieldNodeToControl(field, parentRule) {
|
|
915
966
|
const displayNameAnnotation = field.annotations.find((a) => a.annotationKind === "displayName");
|
|
967
|
+
const placeholderAnnotation = field.annotations.find((a) => a.annotationKind === "placeholder");
|
|
916
968
|
const control = {
|
|
917
969
|
type: "Control",
|
|
918
970
|
scope: fieldToScope(field.name),
|
|
919
971
|
...displayNameAnnotation !== void 0 && { label: displayNameAnnotation.value },
|
|
972
|
+
...placeholderAnnotation !== void 0 && {
|
|
973
|
+
options: { placeholder: placeholderAnnotation.value }
|
|
974
|
+
},
|
|
920
975
|
...parentRule !== void 0 && { rule: parentRule }
|
|
921
976
|
};
|
|
922
977
|
return control;
|
|
@@ -1205,7 +1260,7 @@ var LENGTH_CONSTRAINT_MAP = {
|
|
|
1205
1260
|
minItems: "minItems",
|
|
1206
1261
|
maxItems: "maxItems"
|
|
1207
1262
|
};
|
|
1208
|
-
var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions"]);
|
|
1263
|
+
var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions", "defaultValue"]);
|
|
1209
1264
|
function createFormSpecTSDocConfig() {
|
|
1210
1265
|
const config = new TSDocConfiguration();
|
|
1211
1266
|
for (const tagName of Object.keys(BUILTIN_CONSTRAINT_DEFINITIONS)) {
|
|
@@ -1217,7 +1272,7 @@ function createFormSpecTSDocConfig() {
|
|
|
1217
1272
|
})
|
|
1218
1273
|
);
|
|
1219
1274
|
}
|
|
1220
|
-
for (const tagName of ["displayName", "description"]) {
|
|
1275
|
+
for (const tagName of ["displayName", "description", "format", "placeholder"]) {
|
|
1221
1276
|
config.addTagDefinition(
|
|
1222
1277
|
new TSDocTagDefinition({
|
|
1223
1278
|
tagName: "@" + tagName,
|
|
@@ -1236,6 +1291,12 @@ function getParser() {
|
|
|
1236
1291
|
function parseTSDocTags(node, file = "") {
|
|
1237
1292
|
const constraints = [];
|
|
1238
1293
|
const annotations = [];
|
|
1294
|
+
let displayName;
|
|
1295
|
+
let description;
|
|
1296
|
+
let placeholder;
|
|
1297
|
+
let displayNameProvenance;
|
|
1298
|
+
let descriptionProvenance;
|
|
1299
|
+
let placeholderProvenance;
|
|
1239
1300
|
const sourceFile = node.getSourceFile();
|
|
1240
1301
|
const sourceText = sourceFile.getFullText();
|
|
1241
1302
|
const commentRanges = ts2.getLeadingCommentRanges(sourceText, node.getFullStart());
|
|
@@ -1255,30 +1316,37 @@ function parseTSDocTags(node, file = "") {
|
|
|
1255
1316
|
const docComment = parserContext.docComment;
|
|
1256
1317
|
for (const block of docComment.customBlocks) {
|
|
1257
1318
|
const tagName = normalizeConstraintTagName(block.blockTag.tagName.substring(1));
|
|
1258
|
-
if (tagName === "displayName" || tagName === "description") {
|
|
1319
|
+
if (tagName === "displayName" || tagName === "description" || tagName === "format" || tagName === "placeholder") {
|
|
1259
1320
|
const text2 = extractBlockText(block).trim();
|
|
1260
1321
|
if (text2 === "") continue;
|
|
1261
1322
|
const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
|
|
1262
1323
|
if (tagName === "displayName") {
|
|
1324
|
+
if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
|
|
1325
|
+
displayName = text2;
|
|
1326
|
+
displayNameProvenance = provenance2;
|
|
1327
|
+
}
|
|
1328
|
+
} else if (tagName === "format") {
|
|
1263
1329
|
annotations.push({
|
|
1264
1330
|
kind: "annotation",
|
|
1265
|
-
annotationKind: "
|
|
1331
|
+
annotationKind: "format",
|
|
1266
1332
|
value: text2,
|
|
1267
1333
|
provenance: provenance2
|
|
1268
1334
|
});
|
|
1269
1335
|
} else {
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1336
|
+
if (tagName === "description" && description === void 0) {
|
|
1337
|
+
description = text2;
|
|
1338
|
+
descriptionProvenance = provenance2;
|
|
1339
|
+
} else if (tagName === "placeholder" && placeholder === void 0) {
|
|
1340
|
+
placeholder = text2;
|
|
1341
|
+
placeholderProvenance = provenance2;
|
|
1342
|
+
}
|
|
1276
1343
|
}
|
|
1277
1344
|
continue;
|
|
1278
1345
|
}
|
|
1279
1346
|
if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
1280
1347
|
const text = extractBlockText(block).trim();
|
|
1281
|
-
|
|
1348
|
+
const expectedType = isBuiltinConstraintName(tagName) ? BUILTIN_CONSTRAINT_DEFINITIONS[tagName] : void 0;
|
|
1349
|
+
if (text === "" && expectedType !== "boolean") continue;
|
|
1282
1350
|
const provenance = provenanceForComment(range, sourceFile, file, tagName);
|
|
1283
1351
|
const constraintNode = parseConstraintValue(tagName, text, provenance);
|
|
1284
1352
|
if (constraintNode) {
|
|
@@ -1286,14 +1354,47 @@ function parseTSDocTags(node, file = "") {
|
|
|
1286
1354
|
}
|
|
1287
1355
|
}
|
|
1288
1356
|
if (docComment.deprecatedBlock !== void 0) {
|
|
1357
|
+
const message = extractBlockText(docComment.deprecatedBlock).trim();
|
|
1289
1358
|
annotations.push({
|
|
1290
1359
|
kind: "annotation",
|
|
1291
1360
|
annotationKind: "deprecated",
|
|
1361
|
+
...message !== "" && { message },
|
|
1292
1362
|
provenance: provenanceForComment(range, sourceFile, file, "deprecated")
|
|
1293
1363
|
});
|
|
1294
1364
|
}
|
|
1365
|
+
if (description === void 0 && docComment.remarksBlock !== void 0) {
|
|
1366
|
+
const remarks = extractBlockText(docComment.remarksBlock).trim();
|
|
1367
|
+
if (remarks !== "") {
|
|
1368
|
+
description = remarks;
|
|
1369
|
+
descriptionProvenance = provenanceForComment(range, sourceFile, file, "remarks");
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1295
1372
|
}
|
|
1296
1373
|
}
|
|
1374
|
+
if (displayName !== void 0 && displayNameProvenance !== void 0) {
|
|
1375
|
+
annotations.push({
|
|
1376
|
+
kind: "annotation",
|
|
1377
|
+
annotationKind: "displayName",
|
|
1378
|
+
value: displayName,
|
|
1379
|
+
provenance: displayNameProvenance
|
|
1380
|
+
});
|
|
1381
|
+
}
|
|
1382
|
+
if (description !== void 0 && descriptionProvenance !== void 0) {
|
|
1383
|
+
annotations.push({
|
|
1384
|
+
kind: "annotation",
|
|
1385
|
+
annotationKind: "description",
|
|
1386
|
+
value: description,
|
|
1387
|
+
provenance: descriptionProvenance
|
|
1388
|
+
});
|
|
1389
|
+
}
|
|
1390
|
+
if (placeholder !== void 0 && placeholderProvenance !== void 0) {
|
|
1391
|
+
annotations.push({
|
|
1392
|
+
kind: "annotation",
|
|
1393
|
+
annotationKind: "placeholder",
|
|
1394
|
+
value: placeholder,
|
|
1395
|
+
provenance: placeholderProvenance
|
|
1396
|
+
});
|
|
1397
|
+
}
|
|
1297
1398
|
const jsDocTagsAll = ts2.getJSDocTags(node);
|
|
1298
1399
|
for (const tag of jsDocTagsAll) {
|
|
1299
1400
|
const tagName = normalizeConstraintTagName(tag.tagName.text);
|
|
@@ -1302,6 +1403,11 @@ function parseTSDocTags(node, file = "") {
|
|
|
1302
1403
|
if (commentText === void 0 || commentText.trim() === "") continue;
|
|
1303
1404
|
const text = commentText.trim();
|
|
1304
1405
|
const provenance = provenanceForJSDocTag(tag, file);
|
|
1406
|
+
if (tagName === "defaultValue") {
|
|
1407
|
+
const defaultValueNode = parseDefaultValueValue(text, provenance);
|
|
1408
|
+
annotations.push(defaultValueNode);
|
|
1409
|
+
continue;
|
|
1410
|
+
}
|
|
1305
1411
|
const constraintNode = parseConstraintValue(tagName, text, provenance);
|
|
1306
1412
|
if (constraintNode) {
|
|
1307
1413
|
constraints.push(constraintNode);
|
|
@@ -1309,6 +1415,28 @@ function parseTSDocTags(node, file = "") {
|
|
|
1309
1415
|
}
|
|
1310
1416
|
return { constraints, annotations };
|
|
1311
1417
|
}
|
|
1418
|
+
function extractDisplayNameMetadata(node) {
|
|
1419
|
+
let displayName;
|
|
1420
|
+
const memberDisplayNames = /* @__PURE__ */ new Map();
|
|
1421
|
+
for (const tag of ts2.getJSDocTags(node)) {
|
|
1422
|
+
const tagName = normalizeConstraintTagName(tag.tagName.text);
|
|
1423
|
+
if (tagName !== "displayName") continue;
|
|
1424
|
+
const commentText = getTagCommentText(tag);
|
|
1425
|
+
if (commentText === void 0) continue;
|
|
1426
|
+
const text = commentText.trim();
|
|
1427
|
+
if (text === "") continue;
|
|
1428
|
+
const memberTarget = parseMemberTargetDisplayName(text);
|
|
1429
|
+
if (memberTarget) {
|
|
1430
|
+
memberDisplayNames.set(memberTarget.target, memberTarget.label);
|
|
1431
|
+
continue;
|
|
1432
|
+
}
|
|
1433
|
+
displayName ??= text;
|
|
1434
|
+
}
|
|
1435
|
+
return {
|
|
1436
|
+
...displayName !== void 0 && { displayName },
|
|
1437
|
+
memberDisplayNames
|
|
1438
|
+
};
|
|
1439
|
+
}
|
|
1312
1440
|
function extractPathTarget(text) {
|
|
1313
1441
|
const trimmed = text.trimStart();
|
|
1314
1442
|
const match = /^:([a-zA-Z_]\w*)\s+([\s\S]*)$/.exec(trimmed);
|
|
@@ -1371,7 +1499,45 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
1371
1499
|
}
|
|
1372
1500
|
return null;
|
|
1373
1501
|
}
|
|
1502
|
+
if (expectedType === "boolean") {
|
|
1503
|
+
const trimmed = effectiveText.trim();
|
|
1504
|
+
if (trimmed !== "" && trimmed !== "true") {
|
|
1505
|
+
return null;
|
|
1506
|
+
}
|
|
1507
|
+
if (tagName === "uniqueItems") {
|
|
1508
|
+
return {
|
|
1509
|
+
kind: "constraint",
|
|
1510
|
+
constraintKind: "uniqueItems",
|
|
1511
|
+
value: true,
|
|
1512
|
+
...path3 && { path: path3 },
|
|
1513
|
+
provenance
|
|
1514
|
+
};
|
|
1515
|
+
}
|
|
1516
|
+
return null;
|
|
1517
|
+
}
|
|
1374
1518
|
if (expectedType === "json") {
|
|
1519
|
+
if (tagName === "const") {
|
|
1520
|
+
const trimmedText = effectiveText.trim();
|
|
1521
|
+
if (trimmedText === "") return null;
|
|
1522
|
+
try {
|
|
1523
|
+
const parsed2 = JSON.parse(trimmedText);
|
|
1524
|
+
return {
|
|
1525
|
+
kind: "constraint",
|
|
1526
|
+
constraintKind: "const",
|
|
1527
|
+
value: parsed2,
|
|
1528
|
+
...path3 && { path: path3 },
|
|
1529
|
+
provenance
|
|
1530
|
+
};
|
|
1531
|
+
} catch {
|
|
1532
|
+
return {
|
|
1533
|
+
kind: "constraint",
|
|
1534
|
+
constraintKind: "const",
|
|
1535
|
+
value: trimmedText,
|
|
1536
|
+
...path3 && { path: path3 },
|
|
1537
|
+
provenance
|
|
1538
|
+
};
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1375
1541
|
const parsed = tryParseJson(effectiveText);
|
|
1376
1542
|
if (!Array.isArray(parsed)) {
|
|
1377
1543
|
return null;
|
|
@@ -1403,6 +1569,34 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
1403
1569
|
provenance
|
|
1404
1570
|
};
|
|
1405
1571
|
}
|
|
1572
|
+
function parseDefaultValueValue(text, provenance) {
|
|
1573
|
+
const trimmed = text.trim();
|
|
1574
|
+
let value;
|
|
1575
|
+
if (trimmed === "null") {
|
|
1576
|
+
value = null;
|
|
1577
|
+
} else if (trimmed === "true") {
|
|
1578
|
+
value = true;
|
|
1579
|
+
} else if (trimmed === "false") {
|
|
1580
|
+
value = false;
|
|
1581
|
+
} else {
|
|
1582
|
+
const parsed = tryParseJson(trimmed);
|
|
1583
|
+
value = parsed !== null ? parsed : trimmed;
|
|
1584
|
+
}
|
|
1585
|
+
return {
|
|
1586
|
+
kind: "annotation",
|
|
1587
|
+
annotationKind: "defaultValue",
|
|
1588
|
+
value,
|
|
1589
|
+
provenance
|
|
1590
|
+
};
|
|
1591
|
+
}
|
|
1592
|
+
function isMemberTargetDisplayName(text) {
|
|
1593
|
+
return parseMemberTargetDisplayName(text) !== null;
|
|
1594
|
+
}
|
|
1595
|
+
function parseMemberTargetDisplayName(text) {
|
|
1596
|
+
const match = /^:([^\s]+)\s+([\s\S]+)$/.exec(text);
|
|
1597
|
+
if (!match?.[1] || !match[2]) return null;
|
|
1598
|
+
return { target: match[1], label: match[2].trim() };
|
|
1599
|
+
}
|
|
1406
1600
|
function provenanceForComment(range, sourceFile, file, tagName) {
|
|
1407
1601
|
const { line, character } = sourceFile.getLineAndCharacterOfPosition(range.pos);
|
|
1408
1602
|
return {
|
|
@@ -1484,11 +1678,17 @@ function isObjectType(type) {
|
|
|
1484
1678
|
function isTypeReference(type) {
|
|
1485
1679
|
return !!(type.flags & ts4.TypeFlags.Object) && !!(type.objectFlags & ts4.ObjectFlags.Reference);
|
|
1486
1680
|
}
|
|
1681
|
+
var RESOLVING_TYPE_PLACEHOLDER = {
|
|
1682
|
+
kind: "object",
|
|
1683
|
+
properties: [],
|
|
1684
|
+
additionalProperties: true
|
|
1685
|
+
};
|
|
1487
1686
|
function analyzeClassToIR(classDecl, checker, file = "") {
|
|
1488
1687
|
const name = classDecl.name?.text ?? "AnonymousClass";
|
|
1489
1688
|
const fields = [];
|
|
1490
1689
|
const fieldLayouts = [];
|
|
1491
1690
|
const typeRegistry = {};
|
|
1691
|
+
const annotations = extractJSDocAnnotationNodes(classDecl, file);
|
|
1492
1692
|
const visiting = /* @__PURE__ */ new Set();
|
|
1493
1693
|
const instanceMethods = [];
|
|
1494
1694
|
const staticMethods = [];
|
|
@@ -1511,12 +1711,21 @@ function analyzeClassToIR(classDecl, checker, file = "") {
|
|
|
1511
1711
|
}
|
|
1512
1712
|
}
|
|
1513
1713
|
}
|
|
1514
|
-
return {
|
|
1714
|
+
return {
|
|
1715
|
+
name,
|
|
1716
|
+
fields,
|
|
1717
|
+
fieldLayouts,
|
|
1718
|
+
typeRegistry,
|
|
1719
|
+
...annotations.length > 0 && { annotations },
|
|
1720
|
+
instanceMethods,
|
|
1721
|
+
staticMethods
|
|
1722
|
+
};
|
|
1515
1723
|
}
|
|
1516
1724
|
function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
|
|
1517
1725
|
const name = interfaceDecl.name.text;
|
|
1518
1726
|
const fields = [];
|
|
1519
1727
|
const typeRegistry = {};
|
|
1728
|
+
const annotations = extractJSDocAnnotationNodes(interfaceDecl, file);
|
|
1520
1729
|
const visiting = /* @__PURE__ */ new Set();
|
|
1521
1730
|
for (const member of interfaceDecl.members) {
|
|
1522
1731
|
if (ts4.isPropertySignature(member)) {
|
|
@@ -1527,7 +1736,15 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
|
|
|
1527
1736
|
}
|
|
1528
1737
|
}
|
|
1529
1738
|
const fieldLayouts = fields.map(() => ({}));
|
|
1530
|
-
return {
|
|
1739
|
+
return {
|
|
1740
|
+
name,
|
|
1741
|
+
fields,
|
|
1742
|
+
fieldLayouts,
|
|
1743
|
+
typeRegistry,
|
|
1744
|
+
...annotations.length > 0 && { annotations },
|
|
1745
|
+
instanceMethods: [],
|
|
1746
|
+
staticMethods: []
|
|
1747
|
+
};
|
|
1531
1748
|
}
|
|
1532
1749
|
function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
1533
1750
|
if (!ts4.isTypeLiteralNode(typeAlias.type)) {
|
|
@@ -1542,6 +1759,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
|
1542
1759
|
const name = typeAlias.name.text;
|
|
1543
1760
|
const fields = [];
|
|
1544
1761
|
const typeRegistry = {};
|
|
1762
|
+
const annotations = extractJSDocAnnotationNodes(typeAlias, file);
|
|
1545
1763
|
const visiting = /* @__PURE__ */ new Set();
|
|
1546
1764
|
for (const member of typeAlias.type.members) {
|
|
1547
1765
|
if (ts4.isPropertySignature(member)) {
|
|
@@ -1558,6 +1776,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
|
1558
1776
|
fields,
|
|
1559
1777
|
fieldLayouts: fields.map(() => ({})),
|
|
1560
1778
|
typeRegistry,
|
|
1779
|
+
...annotations.length > 0 && { annotations },
|
|
1561
1780
|
instanceMethods: [],
|
|
1562
1781
|
staticMethods: []
|
|
1563
1782
|
}
|
|
@@ -1571,7 +1790,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
|
|
|
1571
1790
|
const tsType = checker.getTypeAtLocation(prop);
|
|
1572
1791
|
const optional = prop.questionToken !== void 0;
|
|
1573
1792
|
const provenance = provenanceForNode(prop, file);
|
|
1574
|
-
let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
|
|
1793
|
+
let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting, prop);
|
|
1575
1794
|
const constraints = [];
|
|
1576
1795
|
if (prop.type) {
|
|
1577
1796
|
constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
|
|
@@ -1580,7 +1799,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
|
|
|
1580
1799
|
let annotations = [];
|
|
1581
1800
|
annotations.push(...extractJSDocAnnotationNodes(prop, file));
|
|
1582
1801
|
const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
|
|
1583
|
-
if (defaultAnnotation) {
|
|
1802
|
+
if (defaultAnnotation && !annotations.some((a) => a.annotationKind === "defaultValue")) {
|
|
1584
1803
|
annotations.push(defaultAnnotation);
|
|
1585
1804
|
}
|
|
1586
1805
|
({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
|
|
@@ -1602,7 +1821,7 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
|
|
|
1602
1821
|
const tsType = checker.getTypeAtLocation(prop);
|
|
1603
1822
|
const optional = prop.questionToken !== void 0;
|
|
1604
1823
|
const provenance = provenanceForNode(prop, file);
|
|
1605
|
-
let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
|
|
1824
|
+
let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting, prop);
|
|
1606
1825
|
const constraints = [];
|
|
1607
1826
|
if (prop.type) {
|
|
1608
1827
|
constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
|
|
@@ -1683,7 +1902,7 @@ function parseEnumMemberDisplayName(value) {
|
|
|
1683
1902
|
if (label === "") return null;
|
|
1684
1903
|
return { value: match[1], label };
|
|
1685
1904
|
}
|
|
1686
|
-
function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
|
|
1905
|
+
function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode) {
|
|
1687
1906
|
if (type.flags & ts4.TypeFlags.String) {
|
|
1688
1907
|
return { kind: "primitive", primitiveKind: "string" };
|
|
1689
1908
|
}
|
|
@@ -1712,7 +1931,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
|
|
|
1712
1931
|
};
|
|
1713
1932
|
}
|
|
1714
1933
|
if (type.isUnion()) {
|
|
1715
|
-
return resolveUnionType(type, checker, file, typeRegistry, visiting);
|
|
1934
|
+
return resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode);
|
|
1716
1935
|
}
|
|
1717
1936
|
if (checker.isArrayType(type)) {
|
|
1718
1937
|
return resolveArrayType(type, checker, file, typeRegistry, visiting);
|
|
@@ -1722,70 +1941,102 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
|
|
|
1722
1941
|
}
|
|
1723
1942
|
return { kind: "primitive", primitiveKind: "string" };
|
|
1724
1943
|
}
|
|
1725
|
-
function resolveUnionType(type, checker, file, typeRegistry, visiting) {
|
|
1944
|
+
function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode) {
|
|
1945
|
+
const typeName = getNamedTypeName(type);
|
|
1946
|
+
const namedDecl = getNamedTypeDeclaration(type);
|
|
1947
|
+
if (typeName && typeName in typeRegistry) {
|
|
1948
|
+
return { kind: "reference", name: typeName, typeArguments: [] };
|
|
1949
|
+
}
|
|
1726
1950
|
const allTypes = type.types;
|
|
1727
1951
|
const nonNullTypes = allTypes.filter(
|
|
1728
1952
|
(t) => !(t.flags & (ts4.TypeFlags.Null | ts4.TypeFlags.Undefined))
|
|
1729
1953
|
);
|
|
1730
1954
|
const hasNull = allTypes.some((t) => t.flags & ts4.TypeFlags.Null);
|
|
1955
|
+
const memberDisplayNames = /* @__PURE__ */ new Map();
|
|
1956
|
+
if (namedDecl) {
|
|
1957
|
+
for (const [value, label] of extractDisplayNameMetadata(namedDecl).memberDisplayNames) {
|
|
1958
|
+
memberDisplayNames.set(value, label);
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
if (sourceNode) {
|
|
1962
|
+
for (const [value, label] of extractDisplayNameMetadata(sourceNode).memberDisplayNames) {
|
|
1963
|
+
memberDisplayNames.set(value, label);
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
const registerNamed = (result) => {
|
|
1967
|
+
if (!typeName) {
|
|
1968
|
+
return result;
|
|
1969
|
+
}
|
|
1970
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
|
|
1971
|
+
typeRegistry[typeName] = {
|
|
1972
|
+
name: typeName,
|
|
1973
|
+
type: result,
|
|
1974
|
+
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
1975
|
+
provenance: provenanceForDeclaration(namedDecl ?? sourceNode, file)
|
|
1976
|
+
};
|
|
1977
|
+
return { kind: "reference", name: typeName, typeArguments: [] };
|
|
1978
|
+
};
|
|
1979
|
+
const applyMemberLabels = (members2) => members2.map((value) => {
|
|
1980
|
+
const displayName = memberDisplayNames.get(String(value));
|
|
1981
|
+
return displayName !== void 0 ? { value, displayName } : { value };
|
|
1982
|
+
});
|
|
1731
1983
|
const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts4.TypeFlags.BooleanLiteral);
|
|
1732
1984
|
if (isBooleanUnion2) {
|
|
1733
1985
|
const boolNode = { kind: "primitive", primitiveKind: "boolean" };
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
}
|
|
1740
|
-
return boolNode;
|
|
1986
|
+
const result = hasNull ? {
|
|
1987
|
+
kind: "union",
|
|
1988
|
+
members: [boolNode, { kind: "primitive", primitiveKind: "null" }]
|
|
1989
|
+
} : boolNode;
|
|
1990
|
+
return registerNamed(result);
|
|
1741
1991
|
}
|
|
1742
1992
|
const allStringLiterals = nonNullTypes.every((t) => t.isStringLiteral());
|
|
1743
1993
|
if (allStringLiterals && nonNullTypes.length > 0) {
|
|
1744
1994
|
const stringTypes = nonNullTypes.filter((t) => t.isStringLiteral());
|
|
1745
1995
|
const enumNode = {
|
|
1746
1996
|
kind: "enum",
|
|
1747
|
-
members: stringTypes.map((t) =>
|
|
1997
|
+
members: applyMemberLabels(stringTypes.map((t) => t.value))
|
|
1748
1998
|
};
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
}
|
|
1755
|
-
return enumNode;
|
|
1999
|
+
const result = hasNull ? {
|
|
2000
|
+
kind: "union",
|
|
2001
|
+
members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
|
|
2002
|
+
} : enumNode;
|
|
2003
|
+
return registerNamed(result);
|
|
1756
2004
|
}
|
|
1757
2005
|
const allNumberLiterals = nonNullTypes.every((t) => t.isNumberLiteral());
|
|
1758
2006
|
if (allNumberLiterals && nonNullTypes.length > 0) {
|
|
1759
2007
|
const numberTypes = nonNullTypes.filter((t) => t.isNumberLiteral());
|
|
1760
2008
|
const enumNode = {
|
|
1761
2009
|
kind: "enum",
|
|
1762
|
-
members: numberTypes.map((t) =>
|
|
2010
|
+
members: applyMemberLabels(numberTypes.map((t) => t.value))
|
|
1763
2011
|
};
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
}
|
|
1770
|
-
return enumNode;
|
|
2012
|
+
const result = hasNull ? {
|
|
2013
|
+
kind: "union",
|
|
2014
|
+
members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
|
|
2015
|
+
} : enumNode;
|
|
2016
|
+
return registerNamed(result);
|
|
1771
2017
|
}
|
|
1772
2018
|
if (nonNullTypes.length === 1 && nonNullTypes[0]) {
|
|
1773
|
-
const inner = resolveTypeNode(
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
2019
|
+
const inner = resolveTypeNode(
|
|
2020
|
+
nonNullTypes[0],
|
|
2021
|
+
checker,
|
|
2022
|
+
file,
|
|
2023
|
+
typeRegistry,
|
|
2024
|
+
visiting,
|
|
2025
|
+
sourceNode
|
|
2026
|
+
);
|
|
2027
|
+
const result = hasNull ? {
|
|
2028
|
+
kind: "union",
|
|
2029
|
+
members: [inner, { kind: "primitive", primitiveKind: "null" }]
|
|
2030
|
+
} : inner;
|
|
2031
|
+
return registerNamed(result);
|
|
1781
2032
|
}
|
|
1782
2033
|
const members = nonNullTypes.map(
|
|
1783
|
-
(t) => resolveTypeNode(t, checker, file, typeRegistry, visiting)
|
|
2034
|
+
(t) => resolveTypeNode(t, checker, file, typeRegistry, visiting, sourceNode)
|
|
1784
2035
|
);
|
|
1785
2036
|
if (hasNull) {
|
|
1786
2037
|
members.push({ kind: "primitive", primitiveKind: "null" });
|
|
1787
2038
|
}
|
|
1788
|
-
return { kind: "union", members };
|
|
2039
|
+
return registerNamed({ kind: "union", members });
|
|
1789
2040
|
}
|
|
1790
2041
|
function resolveArrayType(type, checker, file, typeRegistry, visiting) {
|
|
1791
2042
|
const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
|
|
@@ -1801,30 +2052,84 @@ function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
|
|
|
1801
2052
|
if (!indexInfo) {
|
|
1802
2053
|
return null;
|
|
1803
2054
|
}
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
2055
|
+
const valueType = resolveTypeNode(indexInfo.type, checker, file, typeRegistry, visiting);
|
|
2056
|
+
return { kind: "record", valueType };
|
|
2057
|
+
}
|
|
2058
|
+
function typeNodeContainsReference(type, targetName) {
|
|
2059
|
+
switch (type.kind) {
|
|
2060
|
+
case "reference":
|
|
2061
|
+
return type.name === targetName;
|
|
2062
|
+
case "array":
|
|
2063
|
+
return typeNodeContainsReference(type.items, targetName);
|
|
2064
|
+
case "record":
|
|
2065
|
+
return typeNodeContainsReference(type.valueType, targetName);
|
|
2066
|
+
case "union":
|
|
2067
|
+
return type.members.some((member) => typeNodeContainsReference(member, targetName));
|
|
2068
|
+
case "object":
|
|
2069
|
+
return type.properties.some(
|
|
2070
|
+
(property) => typeNodeContainsReference(property.type, targetName)
|
|
2071
|
+
);
|
|
2072
|
+
case "primitive":
|
|
2073
|
+
case "enum":
|
|
2074
|
+
case "dynamic":
|
|
2075
|
+
case "custom":
|
|
2076
|
+
return false;
|
|
2077
|
+
default: {
|
|
2078
|
+
const _exhaustive = type;
|
|
2079
|
+
return _exhaustive;
|
|
2080
|
+
}
|
|
1813
2081
|
}
|
|
1814
2082
|
}
|
|
1815
2083
|
function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
1816
|
-
const
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
2084
|
+
const typeName = getNamedTypeName(type);
|
|
2085
|
+
const namedTypeName = typeName ?? void 0;
|
|
2086
|
+
const namedDecl = getNamedTypeDeclaration(type);
|
|
2087
|
+
const shouldRegisterNamedType = namedTypeName !== void 0 && !(namedTypeName === "Record" && namedDecl?.getSourceFile().fileName !== file);
|
|
2088
|
+
const clearNamedTypeRegistration = () => {
|
|
2089
|
+
if (namedTypeName === void 0 || !shouldRegisterNamedType) {
|
|
2090
|
+
return;
|
|
2091
|
+
}
|
|
2092
|
+
Reflect.deleteProperty(typeRegistry, namedTypeName);
|
|
2093
|
+
};
|
|
1820
2094
|
if (visiting.has(type)) {
|
|
2095
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
2096
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
2097
|
+
}
|
|
1821
2098
|
return { kind: "object", properties: [], additionalProperties: false };
|
|
1822
2099
|
}
|
|
2100
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType && !typeRegistry[namedTypeName]) {
|
|
2101
|
+
typeRegistry[namedTypeName] = {
|
|
2102
|
+
name: namedTypeName,
|
|
2103
|
+
type: RESOLVING_TYPE_PLACEHOLDER,
|
|
2104
|
+
provenance: provenanceForDeclaration(namedDecl, file)
|
|
2105
|
+
};
|
|
2106
|
+
}
|
|
1823
2107
|
visiting.add(type);
|
|
1824
|
-
|
|
1825
|
-
|
|
2108
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType && typeRegistry[namedTypeName]?.type !== void 0) {
|
|
2109
|
+
if (typeRegistry[namedTypeName].type !== RESOLVING_TYPE_PLACEHOLDER) {
|
|
2110
|
+
visiting.delete(type);
|
|
2111
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
const recordNode = tryResolveRecordType(type, checker, file, typeRegistry, visiting);
|
|
2115
|
+
if (recordNode) {
|
|
1826
2116
|
visiting.delete(type);
|
|
1827
|
-
|
|
2117
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
2118
|
+
const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType, namedTypeName);
|
|
2119
|
+
if (!isRecursiveRecord) {
|
|
2120
|
+
clearNamedTypeRegistration();
|
|
2121
|
+
return recordNode;
|
|
2122
|
+
}
|
|
2123
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
|
|
2124
|
+
typeRegistry[namedTypeName] = {
|
|
2125
|
+
name: namedTypeName,
|
|
2126
|
+
type: recordNode,
|
|
2127
|
+
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
2128
|
+
provenance: provenanceForDeclaration(namedDecl, file)
|
|
2129
|
+
};
|
|
2130
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
2131
|
+
}
|
|
2132
|
+
return recordNode;
|
|
1828
2133
|
}
|
|
1829
2134
|
const properties = [];
|
|
1830
2135
|
const fieldInfoMap = getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting);
|
|
@@ -1833,7 +2138,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
|
1833
2138
|
if (!declaration) continue;
|
|
1834
2139
|
const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
|
|
1835
2140
|
const optional = !!(prop.flags & ts4.SymbolFlags.Optional);
|
|
1836
|
-
const propTypeNode = resolveTypeNode(
|
|
2141
|
+
const propTypeNode = resolveTypeNode(
|
|
2142
|
+
propType,
|
|
2143
|
+
checker,
|
|
2144
|
+
file,
|
|
2145
|
+
typeRegistry,
|
|
2146
|
+
visiting,
|
|
2147
|
+
declaration
|
|
2148
|
+
);
|
|
1837
2149
|
const fieldNodeInfo = fieldInfoMap?.get(prop.name);
|
|
1838
2150
|
properties.push({
|
|
1839
2151
|
name: prop.name,
|
|
@@ -1850,13 +2162,15 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
|
1850
2162
|
properties,
|
|
1851
2163
|
additionalProperties: true
|
|
1852
2164
|
};
|
|
1853
|
-
if (
|
|
1854
|
-
|
|
1855
|
-
|
|
2165
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
2166
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
|
|
2167
|
+
typeRegistry[namedTypeName] = {
|
|
2168
|
+
name: namedTypeName,
|
|
1856
2169
|
type: objectNode,
|
|
1857
|
-
|
|
2170
|
+
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
2171
|
+
provenance: provenanceForDeclaration(namedDecl, file)
|
|
1858
2172
|
};
|
|
1859
|
-
return { kind: "reference", name:
|
|
2173
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
1860
2174
|
}
|
|
1861
2175
|
return objectNode;
|
|
1862
2176
|
}
|
|
@@ -1948,6 +2262,12 @@ function provenanceForNode(node, file) {
|
|
|
1948
2262
|
function provenanceForFile(file) {
|
|
1949
2263
|
return { surface: "tsdoc", file, line: 0, column: 0 };
|
|
1950
2264
|
}
|
|
2265
|
+
function provenanceForDeclaration(node, file) {
|
|
2266
|
+
if (!node) {
|
|
2267
|
+
return provenanceForFile(file);
|
|
2268
|
+
}
|
|
2269
|
+
return provenanceForNode(node, file);
|
|
2270
|
+
}
|
|
1951
2271
|
function getNamedTypeName(type) {
|
|
1952
2272
|
const symbol = type.getSymbol();
|
|
1953
2273
|
if (symbol?.declarations) {
|
|
@@ -1966,6 +2286,20 @@ function getNamedTypeName(type) {
|
|
|
1966
2286
|
}
|
|
1967
2287
|
return null;
|
|
1968
2288
|
}
|
|
2289
|
+
function getNamedTypeDeclaration(type) {
|
|
2290
|
+
const symbol = type.getSymbol();
|
|
2291
|
+
if (symbol?.declarations) {
|
|
2292
|
+
const decl = symbol.declarations[0];
|
|
2293
|
+
if (decl && (ts4.isClassDeclaration(decl) || ts4.isInterfaceDeclaration(decl) || ts4.isTypeAliasDeclaration(decl))) {
|
|
2294
|
+
return decl;
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2297
|
+
const aliasSymbol = type.aliasSymbol;
|
|
2298
|
+
if (aliasSymbol?.declarations) {
|
|
2299
|
+
return aliasSymbol.declarations.find(ts4.isTypeAliasDeclaration);
|
|
2300
|
+
}
|
|
2301
|
+
return void 0;
|
|
2302
|
+
}
|
|
1969
2303
|
function analyzeMethod(method, checker) {
|
|
1970
2304
|
if (!ts4.isIdentifier(method.name)) {
|
|
1971
2305
|
return null;
|
|
@@ -2050,6 +2384,205 @@ function generateSchemas(options) {
|
|
|
2050
2384
|
);
|
|
2051
2385
|
}
|
|
2052
2386
|
|
|
2387
|
+
// src/generators/mixed-authoring.ts
|
|
2388
|
+
function buildMixedAuthoringSchemas(options) {
|
|
2389
|
+
const { filePath, typeName, overlays, ...schemaOptions } = options;
|
|
2390
|
+
const analysis = analyzeNamedType(filePath, typeName);
|
|
2391
|
+
const composedAnalysis = composeAnalysisWithOverlays(analysis, overlays);
|
|
2392
|
+
const ir = canonicalizeTSDoc(composedAnalysis, { file: filePath });
|
|
2393
|
+
return {
|
|
2394
|
+
jsonSchema: generateJsonSchemaFromIR(ir, schemaOptions),
|
|
2395
|
+
uiSchema: generateUiSchemaFromIR(ir)
|
|
2396
|
+
};
|
|
2397
|
+
}
|
|
2398
|
+
function analyzeNamedType(filePath, typeName) {
|
|
2399
|
+
const ctx = createProgramContext(filePath);
|
|
2400
|
+
const source = { file: filePath };
|
|
2401
|
+
const classDecl = findClassByName(ctx.sourceFile, typeName);
|
|
2402
|
+
if (classDecl !== null) {
|
|
2403
|
+
return analyzeClassToIR(classDecl, ctx.checker, source.file);
|
|
2404
|
+
}
|
|
2405
|
+
const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
|
|
2406
|
+
if (interfaceDecl !== null) {
|
|
2407
|
+
return analyzeInterfaceToIR(interfaceDecl, ctx.checker, source.file);
|
|
2408
|
+
}
|
|
2409
|
+
const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
|
|
2410
|
+
if (typeAlias !== null) {
|
|
2411
|
+
const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, source.file);
|
|
2412
|
+
if (result.ok) {
|
|
2413
|
+
return result.analysis;
|
|
2414
|
+
}
|
|
2415
|
+
throw new Error(result.error);
|
|
2416
|
+
}
|
|
2417
|
+
throw new Error(
|
|
2418
|
+
`Type "${typeName}" not found as a class, interface, or type alias in ${filePath}`
|
|
2419
|
+
);
|
|
2420
|
+
}
|
|
2421
|
+
function composeAnalysisWithOverlays(analysis, overlays) {
|
|
2422
|
+
const overlayIR = canonicalizeChainDSL(overlays);
|
|
2423
|
+
const overlayFields = collectOverlayFields(overlayIR.elements);
|
|
2424
|
+
if (overlayFields.length === 0) {
|
|
2425
|
+
return analysis;
|
|
2426
|
+
}
|
|
2427
|
+
const overlayByName = /* @__PURE__ */ new Map();
|
|
2428
|
+
for (const field of overlayFields) {
|
|
2429
|
+
if (overlayByName.has(field.name)) {
|
|
2430
|
+
throw new Error(`Mixed-authoring overlays define "${field.name}" more than once`);
|
|
2431
|
+
}
|
|
2432
|
+
overlayByName.set(field.name, field);
|
|
2433
|
+
}
|
|
2434
|
+
const mergedFields = [];
|
|
2435
|
+
for (const baseField of analysis.fields) {
|
|
2436
|
+
const overlayField = overlayByName.get(baseField.name);
|
|
2437
|
+
if (overlayField === void 0) {
|
|
2438
|
+
mergedFields.push(baseField);
|
|
2439
|
+
continue;
|
|
2440
|
+
}
|
|
2441
|
+
mergedFields.push(mergeFieldOverlay(baseField, overlayField, analysis.typeRegistry));
|
|
2442
|
+
overlayByName.delete(baseField.name);
|
|
2443
|
+
}
|
|
2444
|
+
if (overlayByName.size > 0) {
|
|
2445
|
+
const unknownFields = [...overlayByName.keys()].sort().join(", ");
|
|
2446
|
+
throw new Error(
|
|
2447
|
+
`Mixed-authoring overlays reference fields that are not present in the static model: ${unknownFields}`
|
|
2448
|
+
);
|
|
2449
|
+
}
|
|
2450
|
+
return {
|
|
2451
|
+
...analysis,
|
|
2452
|
+
fields: mergedFields
|
|
2453
|
+
};
|
|
2454
|
+
}
|
|
2455
|
+
function collectOverlayFields(elements) {
|
|
2456
|
+
const fields = [];
|
|
2457
|
+
for (const element of elements) {
|
|
2458
|
+
switch (element.kind) {
|
|
2459
|
+
case "field":
|
|
2460
|
+
fields.push(element);
|
|
2461
|
+
break;
|
|
2462
|
+
case "group":
|
|
2463
|
+
fields.push(...collectOverlayFields(element.elements));
|
|
2464
|
+
break;
|
|
2465
|
+
case "conditional":
|
|
2466
|
+
fields.push(...collectOverlayFields(element.elements));
|
|
2467
|
+
break;
|
|
2468
|
+
default: {
|
|
2469
|
+
const _exhaustive = element;
|
|
2470
|
+
void _exhaustive;
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
return fields;
|
|
2475
|
+
}
|
|
2476
|
+
function mergeFieldOverlay(baseField, overlayField, typeRegistry) {
|
|
2477
|
+
assertSupportedOverlayField(baseField, overlayField);
|
|
2478
|
+
return {
|
|
2479
|
+
...baseField,
|
|
2480
|
+
type: mergeFieldType(baseField, overlayField, typeRegistry),
|
|
2481
|
+
annotations: mergeAnnotations(baseField.annotations, overlayField.annotations)
|
|
2482
|
+
};
|
|
2483
|
+
}
|
|
2484
|
+
function assertSupportedOverlayField(baseField, overlayField) {
|
|
2485
|
+
if (overlayField.constraints.length > 0) {
|
|
2486
|
+
throw new Error(
|
|
2487
|
+
`Mixed-authoring overlay for "${baseField.name}" cannot define constraints; keep constraints on the static model`
|
|
2488
|
+
);
|
|
2489
|
+
}
|
|
2490
|
+
if (overlayField.required) {
|
|
2491
|
+
throw new Error(
|
|
2492
|
+
`Mixed-authoring overlay for "${baseField.name}" cannot change requiredness; keep requiredness on the static model`
|
|
2493
|
+
);
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
2496
|
+
function mergeFieldType(baseField, overlayField, typeRegistry) {
|
|
2497
|
+
const { type: baseType } = baseField;
|
|
2498
|
+
const { type: overlayType } = overlayField;
|
|
2499
|
+
if (overlayType.kind === "object" || overlayType.kind === "array") {
|
|
2500
|
+
throw new Error(
|
|
2501
|
+
`Mixed-authoring overlays do not support nested object or array overlays for "${baseField.name}"`
|
|
2502
|
+
);
|
|
2503
|
+
}
|
|
2504
|
+
if (overlayType.kind === "dynamic") {
|
|
2505
|
+
if (!isCompatibleDynamicOverlay(baseField, overlayField, typeRegistry)) {
|
|
2506
|
+
throw new Error(
|
|
2507
|
+
`Mixed-authoring overlay for "${baseField.name}" is incompatible with the static field type`
|
|
2508
|
+
);
|
|
2509
|
+
}
|
|
2510
|
+
return overlayType;
|
|
2511
|
+
}
|
|
2512
|
+
if (!isSameStaticTypeShape(baseType, overlayType)) {
|
|
2513
|
+
throw new Error(
|
|
2514
|
+
`Mixed-authoring overlay for "${baseField.name}" must preserve the static field type`
|
|
2515
|
+
);
|
|
2516
|
+
}
|
|
2517
|
+
return baseType;
|
|
2518
|
+
}
|
|
2519
|
+
function isCompatibleDynamicOverlay(baseField, overlayField, typeRegistry) {
|
|
2520
|
+
const overlayType = overlayField.type;
|
|
2521
|
+
if (overlayType.kind !== "dynamic") {
|
|
2522
|
+
return false;
|
|
2523
|
+
}
|
|
2524
|
+
const resolvedBaseType = resolveReferenceType(baseField.type, typeRegistry);
|
|
2525
|
+
if (resolvedBaseType === null) {
|
|
2526
|
+
return false;
|
|
2527
|
+
}
|
|
2528
|
+
if (overlayType.dynamicKind === "enum") {
|
|
2529
|
+
return resolvedBaseType.kind === "primitive" ? resolvedBaseType.primitiveKind === "string" : resolvedBaseType.kind === "enum";
|
|
2530
|
+
}
|
|
2531
|
+
return resolvedBaseType.kind === "object" || resolvedBaseType.kind === "record";
|
|
2532
|
+
}
|
|
2533
|
+
function resolveReferenceType(type, typeRegistry, seen = /* @__PURE__ */ new Set()) {
|
|
2534
|
+
if (type.kind !== "reference") {
|
|
2535
|
+
return type;
|
|
2536
|
+
}
|
|
2537
|
+
if (seen.has(type.name)) {
|
|
2538
|
+
return null;
|
|
2539
|
+
}
|
|
2540
|
+
const definition = typeRegistry[type.name];
|
|
2541
|
+
if (definition === void 0) {
|
|
2542
|
+
return null;
|
|
2543
|
+
}
|
|
2544
|
+
seen.add(type.name);
|
|
2545
|
+
return resolveReferenceType(definition.type, typeRegistry, seen);
|
|
2546
|
+
}
|
|
2547
|
+
function isSameStaticTypeShape(baseType, overlayType) {
|
|
2548
|
+
if (baseType.kind !== overlayType.kind) {
|
|
2549
|
+
return false;
|
|
2550
|
+
}
|
|
2551
|
+
switch (baseType.kind) {
|
|
2552
|
+
case "primitive":
|
|
2553
|
+
return overlayType.kind === "primitive" && baseType.primitiveKind === overlayType.primitiveKind;
|
|
2554
|
+
case "enum":
|
|
2555
|
+
return overlayType.kind === "enum";
|
|
2556
|
+
case "dynamic":
|
|
2557
|
+
return overlayType.kind === "dynamic" && baseType.dynamicKind === overlayType.dynamicKind && baseType.sourceKey === overlayType.sourceKey;
|
|
2558
|
+
case "record":
|
|
2559
|
+
return overlayType.kind === "record";
|
|
2560
|
+
case "reference":
|
|
2561
|
+
return overlayType.kind === "reference" && baseType.name === overlayType.name;
|
|
2562
|
+
case "union":
|
|
2563
|
+
return overlayType.kind === "union";
|
|
2564
|
+
case "custom":
|
|
2565
|
+
return overlayType.kind === "custom" && baseType.typeId === overlayType.typeId;
|
|
2566
|
+
case "object":
|
|
2567
|
+
case "array":
|
|
2568
|
+
return true;
|
|
2569
|
+
default: {
|
|
2570
|
+
const _exhaustive = baseType;
|
|
2571
|
+
return _exhaustive;
|
|
2572
|
+
}
|
|
2573
|
+
}
|
|
2574
|
+
}
|
|
2575
|
+
function mergeAnnotations(baseAnnotations, overlayAnnotations) {
|
|
2576
|
+
const baseKeys = new Set(baseAnnotations.map(annotationKey));
|
|
2577
|
+
const overlayOnly = overlayAnnotations.filter(
|
|
2578
|
+
(annotation) => !baseKeys.has(annotationKey(annotation))
|
|
2579
|
+
);
|
|
2580
|
+
return [...overlayOnly, ...baseAnnotations];
|
|
2581
|
+
}
|
|
2582
|
+
function annotationKey(annotation) {
|
|
2583
|
+
return annotation.annotationKind === "custom" ? `${annotation.annotationKind}:${annotation.annotationId}` : annotation.annotationKind;
|
|
2584
|
+
}
|
|
2585
|
+
|
|
2053
2586
|
// src/index.ts
|
|
2054
2587
|
function buildFormSchemas(form, options) {
|
|
2055
2588
|
return {
|
|
@@ -2075,6 +2608,7 @@ function writeSchemas(form, options) {
|
|
|
2075
2608
|
}
|
|
2076
2609
|
export {
|
|
2077
2610
|
buildFormSchemas,
|
|
2611
|
+
buildMixedAuthoringSchemas,
|
|
2078
2612
|
categorizationSchema,
|
|
2079
2613
|
categorySchema,
|
|
2080
2614
|
controlSchema,
|