@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.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;
|
|
@@ -948,25 +994,31 @@ function createShowRule(fieldName, value) {
|
|
|
948
994
|
}
|
|
949
995
|
};
|
|
950
996
|
}
|
|
997
|
+
function flattenConditionSchema(scope, schema) {
|
|
998
|
+
if (schema.allOf === void 0) {
|
|
999
|
+
if (scope === "#") {
|
|
1000
|
+
return [schema];
|
|
1001
|
+
}
|
|
1002
|
+
const fieldName = scope.replace("#/properties/", "");
|
|
1003
|
+
return [
|
|
1004
|
+
{
|
|
1005
|
+
properties: {
|
|
1006
|
+
[fieldName]: schema
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
];
|
|
1010
|
+
}
|
|
1011
|
+
return schema.allOf.flatMap((member) => flattenConditionSchema(scope, member));
|
|
1012
|
+
}
|
|
951
1013
|
function combineRules(parentRule, childRule) {
|
|
952
|
-
const parentCondition = parentRule.condition;
|
|
953
|
-
const childCondition = childRule.condition;
|
|
954
1014
|
return {
|
|
955
1015
|
effect: "SHOW",
|
|
956
1016
|
condition: {
|
|
957
1017
|
scope: "#",
|
|
958
1018
|
schema: {
|
|
959
1019
|
allOf: [
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
[parentCondition.scope.replace("#/properties/", "")]: parentCondition.schema
|
|
963
|
-
}
|
|
964
|
-
},
|
|
965
|
-
{
|
|
966
|
-
properties: {
|
|
967
|
-
[childCondition.scope.replace("#/properties/", "")]: childCondition.schema
|
|
968
|
-
}
|
|
969
|
-
}
|
|
1020
|
+
...flattenConditionSchema(parentRule.condition.scope, parentRule.condition.schema),
|
|
1021
|
+
...flattenConditionSchema(childRule.condition.scope, childRule.condition.schema)
|
|
970
1022
|
]
|
|
971
1023
|
}
|
|
972
1024
|
}
|
|
@@ -974,10 +1026,14 @@ function combineRules(parentRule, childRule) {
|
|
|
974
1026
|
}
|
|
975
1027
|
function fieldNodeToControl(field, parentRule) {
|
|
976
1028
|
const displayNameAnnotation = field.annotations.find((a) => a.annotationKind === "displayName");
|
|
1029
|
+
const placeholderAnnotation = field.annotations.find((a) => a.annotationKind === "placeholder");
|
|
977
1030
|
const control = {
|
|
978
1031
|
type: "Control",
|
|
979
1032
|
scope: fieldToScope(field.name),
|
|
980
1033
|
...displayNameAnnotation !== void 0 && { label: displayNameAnnotation.value },
|
|
1034
|
+
...placeholderAnnotation !== void 0 && {
|
|
1035
|
+
options: { placeholder: placeholderAnnotation.value }
|
|
1036
|
+
},
|
|
981
1037
|
...parentRule !== void 0 && { rule: parentRule }
|
|
982
1038
|
};
|
|
983
1039
|
return control;
|
|
@@ -1254,7 +1310,7 @@ var LENGTH_CONSTRAINT_MAP = {
|
|
|
1254
1310
|
minItems: "minItems",
|
|
1255
1311
|
maxItems: "maxItems"
|
|
1256
1312
|
};
|
|
1257
|
-
var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions"]);
|
|
1313
|
+
var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions", "defaultValue"]);
|
|
1258
1314
|
function createFormSpecTSDocConfig() {
|
|
1259
1315
|
const config = new import_tsdoc.TSDocConfiguration();
|
|
1260
1316
|
for (const tagName of Object.keys(import_core3.BUILTIN_CONSTRAINT_DEFINITIONS)) {
|
|
@@ -1266,7 +1322,7 @@ function createFormSpecTSDocConfig() {
|
|
|
1266
1322
|
})
|
|
1267
1323
|
);
|
|
1268
1324
|
}
|
|
1269
|
-
for (const tagName of ["displayName", "description"]) {
|
|
1325
|
+
for (const tagName of ["displayName", "description", "format", "placeholder"]) {
|
|
1270
1326
|
config.addTagDefinition(
|
|
1271
1327
|
new import_tsdoc.TSDocTagDefinition({
|
|
1272
1328
|
tagName: "@" + tagName,
|
|
@@ -1285,6 +1341,12 @@ function getParser() {
|
|
|
1285
1341
|
function parseTSDocTags(node, file = "") {
|
|
1286
1342
|
const constraints = [];
|
|
1287
1343
|
const annotations = [];
|
|
1344
|
+
let displayName;
|
|
1345
|
+
let description;
|
|
1346
|
+
let placeholder;
|
|
1347
|
+
let displayNameProvenance;
|
|
1348
|
+
let descriptionProvenance;
|
|
1349
|
+
let placeholderProvenance;
|
|
1288
1350
|
const sourceFile = node.getSourceFile();
|
|
1289
1351
|
const sourceText = sourceFile.getFullText();
|
|
1290
1352
|
const commentRanges = ts2.getLeadingCommentRanges(sourceText, node.getFullStart());
|
|
@@ -1304,30 +1366,37 @@ function parseTSDocTags(node, file = "") {
|
|
|
1304
1366
|
const docComment = parserContext.docComment;
|
|
1305
1367
|
for (const block of docComment.customBlocks) {
|
|
1306
1368
|
const tagName = (0, import_core3.normalizeConstraintTagName)(block.blockTag.tagName.substring(1));
|
|
1307
|
-
if (tagName === "displayName" || tagName === "description") {
|
|
1369
|
+
if (tagName === "displayName" || tagName === "description" || tagName === "format" || tagName === "placeholder") {
|
|
1308
1370
|
const text2 = extractBlockText(block).trim();
|
|
1309
1371
|
if (text2 === "") continue;
|
|
1310
1372
|
const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
|
|
1311
1373
|
if (tagName === "displayName") {
|
|
1374
|
+
if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
|
|
1375
|
+
displayName = text2;
|
|
1376
|
+
displayNameProvenance = provenance2;
|
|
1377
|
+
}
|
|
1378
|
+
} else if (tagName === "format") {
|
|
1312
1379
|
annotations.push({
|
|
1313
1380
|
kind: "annotation",
|
|
1314
|
-
annotationKind: "
|
|
1381
|
+
annotationKind: "format",
|
|
1315
1382
|
value: text2,
|
|
1316
1383
|
provenance: provenance2
|
|
1317
1384
|
});
|
|
1318
1385
|
} else {
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1386
|
+
if (tagName === "description" && description === void 0) {
|
|
1387
|
+
description = text2;
|
|
1388
|
+
descriptionProvenance = provenance2;
|
|
1389
|
+
} else if (tagName === "placeholder" && placeholder === void 0) {
|
|
1390
|
+
placeholder = text2;
|
|
1391
|
+
placeholderProvenance = provenance2;
|
|
1392
|
+
}
|
|
1325
1393
|
}
|
|
1326
1394
|
continue;
|
|
1327
1395
|
}
|
|
1328
1396
|
if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
1329
1397
|
const text = extractBlockText(block).trim();
|
|
1330
|
-
|
|
1398
|
+
const expectedType = (0, import_core3.isBuiltinConstraintName)(tagName) ? import_core3.BUILTIN_CONSTRAINT_DEFINITIONS[tagName] : void 0;
|
|
1399
|
+
if (text === "" && expectedType !== "boolean") continue;
|
|
1331
1400
|
const provenance = provenanceForComment(range, sourceFile, file, tagName);
|
|
1332
1401
|
const constraintNode = parseConstraintValue(tagName, text, provenance);
|
|
1333
1402
|
if (constraintNode) {
|
|
@@ -1335,14 +1404,47 @@ function parseTSDocTags(node, file = "") {
|
|
|
1335
1404
|
}
|
|
1336
1405
|
}
|
|
1337
1406
|
if (docComment.deprecatedBlock !== void 0) {
|
|
1407
|
+
const message = extractBlockText(docComment.deprecatedBlock).trim();
|
|
1338
1408
|
annotations.push({
|
|
1339
1409
|
kind: "annotation",
|
|
1340
1410
|
annotationKind: "deprecated",
|
|
1411
|
+
...message !== "" && { message },
|
|
1341
1412
|
provenance: provenanceForComment(range, sourceFile, file, "deprecated")
|
|
1342
1413
|
});
|
|
1343
1414
|
}
|
|
1415
|
+
if (description === void 0 && docComment.remarksBlock !== void 0) {
|
|
1416
|
+
const remarks = extractBlockText(docComment.remarksBlock).trim();
|
|
1417
|
+
if (remarks !== "") {
|
|
1418
|
+
description = remarks;
|
|
1419
|
+
descriptionProvenance = provenanceForComment(range, sourceFile, file, "remarks");
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1344
1422
|
}
|
|
1345
1423
|
}
|
|
1424
|
+
if (displayName !== void 0 && displayNameProvenance !== void 0) {
|
|
1425
|
+
annotations.push({
|
|
1426
|
+
kind: "annotation",
|
|
1427
|
+
annotationKind: "displayName",
|
|
1428
|
+
value: displayName,
|
|
1429
|
+
provenance: displayNameProvenance
|
|
1430
|
+
});
|
|
1431
|
+
}
|
|
1432
|
+
if (description !== void 0 && descriptionProvenance !== void 0) {
|
|
1433
|
+
annotations.push({
|
|
1434
|
+
kind: "annotation",
|
|
1435
|
+
annotationKind: "description",
|
|
1436
|
+
value: description,
|
|
1437
|
+
provenance: descriptionProvenance
|
|
1438
|
+
});
|
|
1439
|
+
}
|
|
1440
|
+
if (placeholder !== void 0 && placeholderProvenance !== void 0) {
|
|
1441
|
+
annotations.push({
|
|
1442
|
+
kind: "annotation",
|
|
1443
|
+
annotationKind: "placeholder",
|
|
1444
|
+
value: placeholder,
|
|
1445
|
+
provenance: placeholderProvenance
|
|
1446
|
+
});
|
|
1447
|
+
}
|
|
1346
1448
|
const jsDocTagsAll = ts2.getJSDocTags(node);
|
|
1347
1449
|
for (const tag of jsDocTagsAll) {
|
|
1348
1450
|
const tagName = (0, import_core3.normalizeConstraintTagName)(tag.tagName.text);
|
|
@@ -1351,6 +1453,11 @@ function parseTSDocTags(node, file = "") {
|
|
|
1351
1453
|
if (commentText === void 0 || commentText.trim() === "") continue;
|
|
1352
1454
|
const text = commentText.trim();
|
|
1353
1455
|
const provenance = provenanceForJSDocTag(tag, file);
|
|
1456
|
+
if (tagName === "defaultValue") {
|
|
1457
|
+
const defaultValueNode = parseDefaultValueValue(text, provenance);
|
|
1458
|
+
annotations.push(defaultValueNode);
|
|
1459
|
+
continue;
|
|
1460
|
+
}
|
|
1354
1461
|
const constraintNode = parseConstraintValue(tagName, text, provenance);
|
|
1355
1462
|
if (constraintNode) {
|
|
1356
1463
|
constraints.push(constraintNode);
|
|
@@ -1358,6 +1465,28 @@ function parseTSDocTags(node, file = "") {
|
|
|
1358
1465
|
}
|
|
1359
1466
|
return { constraints, annotations };
|
|
1360
1467
|
}
|
|
1468
|
+
function extractDisplayNameMetadata(node) {
|
|
1469
|
+
let displayName;
|
|
1470
|
+
const memberDisplayNames = /* @__PURE__ */ new Map();
|
|
1471
|
+
for (const tag of ts2.getJSDocTags(node)) {
|
|
1472
|
+
const tagName = (0, import_core3.normalizeConstraintTagName)(tag.tagName.text);
|
|
1473
|
+
if (tagName !== "displayName") continue;
|
|
1474
|
+
const commentText = getTagCommentText(tag);
|
|
1475
|
+
if (commentText === void 0) continue;
|
|
1476
|
+
const text = commentText.trim();
|
|
1477
|
+
if (text === "") continue;
|
|
1478
|
+
const memberTarget = parseMemberTargetDisplayName(text);
|
|
1479
|
+
if (memberTarget) {
|
|
1480
|
+
memberDisplayNames.set(memberTarget.target, memberTarget.label);
|
|
1481
|
+
continue;
|
|
1482
|
+
}
|
|
1483
|
+
displayName ??= text;
|
|
1484
|
+
}
|
|
1485
|
+
return {
|
|
1486
|
+
...displayName !== void 0 && { displayName },
|
|
1487
|
+
memberDisplayNames
|
|
1488
|
+
};
|
|
1489
|
+
}
|
|
1361
1490
|
function extractPathTarget(text) {
|
|
1362
1491
|
const trimmed = text.trimStart();
|
|
1363
1492
|
const match = /^:([a-zA-Z_]\w*)\s+([\s\S]*)$/.exec(trimmed);
|
|
@@ -1420,7 +1549,45 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
1420
1549
|
}
|
|
1421
1550
|
return null;
|
|
1422
1551
|
}
|
|
1552
|
+
if (expectedType === "boolean") {
|
|
1553
|
+
const trimmed = effectiveText.trim();
|
|
1554
|
+
if (trimmed !== "" && trimmed !== "true") {
|
|
1555
|
+
return null;
|
|
1556
|
+
}
|
|
1557
|
+
if (tagName === "uniqueItems") {
|
|
1558
|
+
return {
|
|
1559
|
+
kind: "constraint",
|
|
1560
|
+
constraintKind: "uniqueItems",
|
|
1561
|
+
value: true,
|
|
1562
|
+
...path3 && { path: path3 },
|
|
1563
|
+
provenance
|
|
1564
|
+
};
|
|
1565
|
+
}
|
|
1566
|
+
return null;
|
|
1567
|
+
}
|
|
1423
1568
|
if (expectedType === "json") {
|
|
1569
|
+
if (tagName === "const") {
|
|
1570
|
+
const trimmedText = effectiveText.trim();
|
|
1571
|
+
if (trimmedText === "") return null;
|
|
1572
|
+
try {
|
|
1573
|
+
const parsed2 = JSON.parse(trimmedText);
|
|
1574
|
+
return {
|
|
1575
|
+
kind: "constraint",
|
|
1576
|
+
constraintKind: "const",
|
|
1577
|
+
value: parsed2,
|
|
1578
|
+
...path3 && { path: path3 },
|
|
1579
|
+
provenance
|
|
1580
|
+
};
|
|
1581
|
+
} catch {
|
|
1582
|
+
return {
|
|
1583
|
+
kind: "constraint",
|
|
1584
|
+
constraintKind: "const",
|
|
1585
|
+
value: trimmedText,
|
|
1586
|
+
...path3 && { path: path3 },
|
|
1587
|
+
provenance
|
|
1588
|
+
};
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1424
1591
|
const parsed = tryParseJson(effectiveText);
|
|
1425
1592
|
if (!Array.isArray(parsed)) {
|
|
1426
1593
|
return null;
|
|
@@ -1452,6 +1619,34 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
1452
1619
|
provenance
|
|
1453
1620
|
};
|
|
1454
1621
|
}
|
|
1622
|
+
function parseDefaultValueValue(text, provenance) {
|
|
1623
|
+
const trimmed = text.trim();
|
|
1624
|
+
let value;
|
|
1625
|
+
if (trimmed === "null") {
|
|
1626
|
+
value = null;
|
|
1627
|
+
} else if (trimmed === "true") {
|
|
1628
|
+
value = true;
|
|
1629
|
+
} else if (trimmed === "false") {
|
|
1630
|
+
value = false;
|
|
1631
|
+
} else {
|
|
1632
|
+
const parsed = tryParseJson(trimmed);
|
|
1633
|
+
value = parsed !== null ? parsed : trimmed;
|
|
1634
|
+
}
|
|
1635
|
+
return {
|
|
1636
|
+
kind: "annotation",
|
|
1637
|
+
annotationKind: "defaultValue",
|
|
1638
|
+
value,
|
|
1639
|
+
provenance
|
|
1640
|
+
};
|
|
1641
|
+
}
|
|
1642
|
+
function isMemberTargetDisplayName(text) {
|
|
1643
|
+
return parseMemberTargetDisplayName(text) !== null;
|
|
1644
|
+
}
|
|
1645
|
+
function parseMemberTargetDisplayName(text) {
|
|
1646
|
+
const match = /^:([^\s]+)\s+([\s\S]+)$/.exec(text);
|
|
1647
|
+
if (!match?.[1] || !match[2]) return null;
|
|
1648
|
+
return { target: match[1], label: match[2].trim() };
|
|
1649
|
+
}
|
|
1455
1650
|
function provenanceForComment(range, sourceFile, file, tagName) {
|
|
1456
1651
|
const { line, character } = sourceFile.getLineAndCharacterOfPosition(range.pos);
|
|
1457
1652
|
return {
|
|
@@ -1533,11 +1728,17 @@ function isObjectType(type) {
|
|
|
1533
1728
|
function isTypeReference(type) {
|
|
1534
1729
|
return !!(type.flags & ts4.TypeFlags.Object) && !!(type.objectFlags & ts4.ObjectFlags.Reference);
|
|
1535
1730
|
}
|
|
1731
|
+
var RESOLVING_TYPE_PLACEHOLDER = {
|
|
1732
|
+
kind: "object",
|
|
1733
|
+
properties: [],
|
|
1734
|
+
additionalProperties: true
|
|
1735
|
+
};
|
|
1536
1736
|
function analyzeClassToIR(classDecl, checker, file = "") {
|
|
1537
1737
|
const name = classDecl.name?.text ?? "AnonymousClass";
|
|
1538
1738
|
const fields = [];
|
|
1539
1739
|
const fieldLayouts = [];
|
|
1540
1740
|
const typeRegistry = {};
|
|
1741
|
+
const annotations = extractJSDocAnnotationNodes(classDecl, file);
|
|
1541
1742
|
const visiting = /* @__PURE__ */ new Set();
|
|
1542
1743
|
const instanceMethods = [];
|
|
1543
1744
|
const staticMethods = [];
|
|
@@ -1560,12 +1761,21 @@ function analyzeClassToIR(classDecl, checker, file = "") {
|
|
|
1560
1761
|
}
|
|
1561
1762
|
}
|
|
1562
1763
|
}
|
|
1563
|
-
return {
|
|
1764
|
+
return {
|
|
1765
|
+
name,
|
|
1766
|
+
fields,
|
|
1767
|
+
fieldLayouts,
|
|
1768
|
+
typeRegistry,
|
|
1769
|
+
...annotations.length > 0 && { annotations },
|
|
1770
|
+
instanceMethods,
|
|
1771
|
+
staticMethods
|
|
1772
|
+
};
|
|
1564
1773
|
}
|
|
1565
1774
|
function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
|
|
1566
1775
|
const name = interfaceDecl.name.text;
|
|
1567
1776
|
const fields = [];
|
|
1568
1777
|
const typeRegistry = {};
|
|
1778
|
+
const annotations = extractJSDocAnnotationNodes(interfaceDecl, file);
|
|
1569
1779
|
const visiting = /* @__PURE__ */ new Set();
|
|
1570
1780
|
for (const member of interfaceDecl.members) {
|
|
1571
1781
|
if (ts4.isPropertySignature(member)) {
|
|
@@ -1576,7 +1786,15 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
|
|
|
1576
1786
|
}
|
|
1577
1787
|
}
|
|
1578
1788
|
const fieldLayouts = fields.map(() => ({}));
|
|
1579
|
-
return {
|
|
1789
|
+
return {
|
|
1790
|
+
name,
|
|
1791
|
+
fields,
|
|
1792
|
+
fieldLayouts,
|
|
1793
|
+
typeRegistry,
|
|
1794
|
+
...annotations.length > 0 && { annotations },
|
|
1795
|
+
instanceMethods: [],
|
|
1796
|
+
staticMethods: []
|
|
1797
|
+
};
|
|
1580
1798
|
}
|
|
1581
1799
|
function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
1582
1800
|
if (!ts4.isTypeLiteralNode(typeAlias.type)) {
|
|
@@ -1591,6 +1809,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
|
1591
1809
|
const name = typeAlias.name.text;
|
|
1592
1810
|
const fields = [];
|
|
1593
1811
|
const typeRegistry = {};
|
|
1812
|
+
const annotations = extractJSDocAnnotationNodes(typeAlias, file);
|
|
1594
1813
|
const visiting = /* @__PURE__ */ new Set();
|
|
1595
1814
|
for (const member of typeAlias.type.members) {
|
|
1596
1815
|
if (ts4.isPropertySignature(member)) {
|
|
@@ -1607,6 +1826,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
|
1607
1826
|
fields,
|
|
1608
1827
|
fieldLayouts: fields.map(() => ({})),
|
|
1609
1828
|
typeRegistry,
|
|
1829
|
+
...annotations.length > 0 && { annotations },
|
|
1610
1830
|
instanceMethods: [],
|
|
1611
1831
|
staticMethods: []
|
|
1612
1832
|
}
|
|
@@ -1620,7 +1840,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
|
|
|
1620
1840
|
const tsType = checker.getTypeAtLocation(prop);
|
|
1621
1841
|
const optional = prop.questionToken !== void 0;
|
|
1622
1842
|
const provenance = provenanceForNode(prop, file);
|
|
1623
|
-
let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
|
|
1843
|
+
let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting, prop);
|
|
1624
1844
|
const constraints = [];
|
|
1625
1845
|
if (prop.type) {
|
|
1626
1846
|
constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
|
|
@@ -1629,7 +1849,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
|
|
|
1629
1849
|
let annotations = [];
|
|
1630
1850
|
annotations.push(...extractJSDocAnnotationNodes(prop, file));
|
|
1631
1851
|
const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
|
|
1632
|
-
if (defaultAnnotation) {
|
|
1852
|
+
if (defaultAnnotation && !annotations.some((a) => a.annotationKind === "defaultValue")) {
|
|
1633
1853
|
annotations.push(defaultAnnotation);
|
|
1634
1854
|
}
|
|
1635
1855
|
({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
|
|
@@ -1651,7 +1871,7 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
|
|
|
1651
1871
|
const tsType = checker.getTypeAtLocation(prop);
|
|
1652
1872
|
const optional = prop.questionToken !== void 0;
|
|
1653
1873
|
const provenance = provenanceForNode(prop, file);
|
|
1654
|
-
let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
|
|
1874
|
+
let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting, prop);
|
|
1655
1875
|
const constraints = [];
|
|
1656
1876
|
if (prop.type) {
|
|
1657
1877
|
constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
|
|
@@ -1732,7 +1952,7 @@ function parseEnumMemberDisplayName(value) {
|
|
|
1732
1952
|
if (label === "") return null;
|
|
1733
1953
|
return { value: match[1], label };
|
|
1734
1954
|
}
|
|
1735
|
-
function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
|
|
1955
|
+
function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode) {
|
|
1736
1956
|
if (type.flags & ts4.TypeFlags.String) {
|
|
1737
1957
|
return { kind: "primitive", primitiveKind: "string" };
|
|
1738
1958
|
}
|
|
@@ -1761,7 +1981,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
|
|
|
1761
1981
|
};
|
|
1762
1982
|
}
|
|
1763
1983
|
if (type.isUnion()) {
|
|
1764
|
-
return resolveUnionType(type, checker, file, typeRegistry, visiting);
|
|
1984
|
+
return resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode);
|
|
1765
1985
|
}
|
|
1766
1986
|
if (checker.isArrayType(type)) {
|
|
1767
1987
|
return resolveArrayType(type, checker, file, typeRegistry, visiting);
|
|
@@ -1771,70 +1991,102 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
|
|
|
1771
1991
|
}
|
|
1772
1992
|
return { kind: "primitive", primitiveKind: "string" };
|
|
1773
1993
|
}
|
|
1774
|
-
function resolveUnionType(type, checker, file, typeRegistry, visiting) {
|
|
1994
|
+
function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode) {
|
|
1995
|
+
const typeName = getNamedTypeName(type);
|
|
1996
|
+
const namedDecl = getNamedTypeDeclaration(type);
|
|
1997
|
+
if (typeName && typeName in typeRegistry) {
|
|
1998
|
+
return { kind: "reference", name: typeName, typeArguments: [] };
|
|
1999
|
+
}
|
|
1775
2000
|
const allTypes = type.types;
|
|
1776
2001
|
const nonNullTypes = allTypes.filter(
|
|
1777
2002
|
(t) => !(t.flags & (ts4.TypeFlags.Null | ts4.TypeFlags.Undefined))
|
|
1778
2003
|
);
|
|
1779
2004
|
const hasNull = allTypes.some((t) => t.flags & ts4.TypeFlags.Null);
|
|
2005
|
+
const memberDisplayNames = /* @__PURE__ */ new Map();
|
|
2006
|
+
if (namedDecl) {
|
|
2007
|
+
for (const [value, label] of extractDisplayNameMetadata(namedDecl).memberDisplayNames) {
|
|
2008
|
+
memberDisplayNames.set(value, label);
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
if (sourceNode) {
|
|
2012
|
+
for (const [value, label] of extractDisplayNameMetadata(sourceNode).memberDisplayNames) {
|
|
2013
|
+
memberDisplayNames.set(value, label);
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
const registerNamed = (result) => {
|
|
2017
|
+
if (!typeName) {
|
|
2018
|
+
return result;
|
|
2019
|
+
}
|
|
2020
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
|
|
2021
|
+
typeRegistry[typeName] = {
|
|
2022
|
+
name: typeName,
|
|
2023
|
+
type: result,
|
|
2024
|
+
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
2025
|
+
provenance: provenanceForDeclaration(namedDecl ?? sourceNode, file)
|
|
2026
|
+
};
|
|
2027
|
+
return { kind: "reference", name: typeName, typeArguments: [] };
|
|
2028
|
+
};
|
|
2029
|
+
const applyMemberLabels = (members2) => members2.map((value) => {
|
|
2030
|
+
const displayName = memberDisplayNames.get(String(value));
|
|
2031
|
+
return displayName !== void 0 ? { value, displayName } : { value };
|
|
2032
|
+
});
|
|
1780
2033
|
const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts4.TypeFlags.BooleanLiteral);
|
|
1781
2034
|
if (isBooleanUnion2) {
|
|
1782
2035
|
const boolNode = { kind: "primitive", primitiveKind: "boolean" };
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
}
|
|
1789
|
-
return boolNode;
|
|
2036
|
+
const result = hasNull ? {
|
|
2037
|
+
kind: "union",
|
|
2038
|
+
members: [boolNode, { kind: "primitive", primitiveKind: "null" }]
|
|
2039
|
+
} : boolNode;
|
|
2040
|
+
return registerNamed(result);
|
|
1790
2041
|
}
|
|
1791
2042
|
const allStringLiterals = nonNullTypes.every((t) => t.isStringLiteral());
|
|
1792
2043
|
if (allStringLiterals && nonNullTypes.length > 0) {
|
|
1793
2044
|
const stringTypes = nonNullTypes.filter((t) => t.isStringLiteral());
|
|
1794
2045
|
const enumNode = {
|
|
1795
2046
|
kind: "enum",
|
|
1796
|
-
members: stringTypes.map((t) =>
|
|
2047
|
+
members: applyMemberLabels(stringTypes.map((t) => t.value))
|
|
1797
2048
|
};
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
}
|
|
1804
|
-
return enumNode;
|
|
2049
|
+
const result = hasNull ? {
|
|
2050
|
+
kind: "union",
|
|
2051
|
+
members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
|
|
2052
|
+
} : enumNode;
|
|
2053
|
+
return registerNamed(result);
|
|
1805
2054
|
}
|
|
1806
2055
|
const allNumberLiterals = nonNullTypes.every((t) => t.isNumberLiteral());
|
|
1807
2056
|
if (allNumberLiterals && nonNullTypes.length > 0) {
|
|
1808
2057
|
const numberTypes = nonNullTypes.filter((t) => t.isNumberLiteral());
|
|
1809
2058
|
const enumNode = {
|
|
1810
2059
|
kind: "enum",
|
|
1811
|
-
members: numberTypes.map((t) =>
|
|
2060
|
+
members: applyMemberLabels(numberTypes.map((t) => t.value))
|
|
1812
2061
|
};
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
}
|
|
1819
|
-
return enumNode;
|
|
2062
|
+
const result = hasNull ? {
|
|
2063
|
+
kind: "union",
|
|
2064
|
+
members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
|
|
2065
|
+
} : enumNode;
|
|
2066
|
+
return registerNamed(result);
|
|
1820
2067
|
}
|
|
1821
2068
|
if (nonNullTypes.length === 1 && nonNullTypes[0]) {
|
|
1822
|
-
const inner = resolveTypeNode(
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
2069
|
+
const inner = resolveTypeNode(
|
|
2070
|
+
nonNullTypes[0],
|
|
2071
|
+
checker,
|
|
2072
|
+
file,
|
|
2073
|
+
typeRegistry,
|
|
2074
|
+
visiting,
|
|
2075
|
+
sourceNode
|
|
2076
|
+
);
|
|
2077
|
+
const result = hasNull ? {
|
|
2078
|
+
kind: "union",
|
|
2079
|
+
members: [inner, { kind: "primitive", primitiveKind: "null" }]
|
|
2080
|
+
} : inner;
|
|
2081
|
+
return registerNamed(result);
|
|
1830
2082
|
}
|
|
1831
2083
|
const members = nonNullTypes.map(
|
|
1832
|
-
(t) => resolveTypeNode(t, checker, file, typeRegistry, visiting)
|
|
2084
|
+
(t) => resolveTypeNode(t, checker, file, typeRegistry, visiting, sourceNode)
|
|
1833
2085
|
);
|
|
1834
2086
|
if (hasNull) {
|
|
1835
2087
|
members.push({ kind: "primitive", primitiveKind: "null" });
|
|
1836
2088
|
}
|
|
1837
|
-
return { kind: "union", members };
|
|
2089
|
+
return registerNamed({ kind: "union", members });
|
|
1838
2090
|
}
|
|
1839
2091
|
function resolveArrayType(type, checker, file, typeRegistry, visiting) {
|
|
1840
2092
|
const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
|
|
@@ -1850,30 +2102,84 @@ function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
|
|
|
1850
2102
|
if (!indexInfo) {
|
|
1851
2103
|
return null;
|
|
1852
2104
|
}
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
2105
|
+
const valueType = resolveTypeNode(indexInfo.type, checker, file, typeRegistry, visiting);
|
|
2106
|
+
return { kind: "record", valueType };
|
|
2107
|
+
}
|
|
2108
|
+
function typeNodeContainsReference(type, targetName) {
|
|
2109
|
+
switch (type.kind) {
|
|
2110
|
+
case "reference":
|
|
2111
|
+
return type.name === targetName;
|
|
2112
|
+
case "array":
|
|
2113
|
+
return typeNodeContainsReference(type.items, targetName);
|
|
2114
|
+
case "record":
|
|
2115
|
+
return typeNodeContainsReference(type.valueType, targetName);
|
|
2116
|
+
case "union":
|
|
2117
|
+
return type.members.some((member) => typeNodeContainsReference(member, targetName));
|
|
2118
|
+
case "object":
|
|
2119
|
+
return type.properties.some(
|
|
2120
|
+
(property) => typeNodeContainsReference(property.type, targetName)
|
|
2121
|
+
);
|
|
2122
|
+
case "primitive":
|
|
2123
|
+
case "enum":
|
|
2124
|
+
case "dynamic":
|
|
2125
|
+
case "custom":
|
|
2126
|
+
return false;
|
|
2127
|
+
default: {
|
|
2128
|
+
const _exhaustive = type;
|
|
2129
|
+
return _exhaustive;
|
|
2130
|
+
}
|
|
1862
2131
|
}
|
|
1863
2132
|
}
|
|
1864
2133
|
function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
1865
|
-
const
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
2134
|
+
const typeName = getNamedTypeName(type);
|
|
2135
|
+
const namedTypeName = typeName ?? void 0;
|
|
2136
|
+
const namedDecl = getNamedTypeDeclaration(type);
|
|
2137
|
+
const shouldRegisterNamedType = namedTypeName !== void 0 && !(namedTypeName === "Record" && namedDecl?.getSourceFile().fileName !== file);
|
|
2138
|
+
const clearNamedTypeRegistration = () => {
|
|
2139
|
+
if (namedTypeName === void 0 || !shouldRegisterNamedType) {
|
|
2140
|
+
return;
|
|
2141
|
+
}
|
|
2142
|
+
Reflect.deleteProperty(typeRegistry, namedTypeName);
|
|
2143
|
+
};
|
|
1869
2144
|
if (visiting.has(type)) {
|
|
2145
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
2146
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
2147
|
+
}
|
|
1870
2148
|
return { kind: "object", properties: [], additionalProperties: false };
|
|
1871
2149
|
}
|
|
2150
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType && !typeRegistry[namedTypeName]) {
|
|
2151
|
+
typeRegistry[namedTypeName] = {
|
|
2152
|
+
name: namedTypeName,
|
|
2153
|
+
type: RESOLVING_TYPE_PLACEHOLDER,
|
|
2154
|
+
provenance: provenanceForDeclaration(namedDecl, file)
|
|
2155
|
+
};
|
|
2156
|
+
}
|
|
1872
2157
|
visiting.add(type);
|
|
1873
|
-
|
|
1874
|
-
|
|
2158
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType && typeRegistry[namedTypeName]?.type !== void 0) {
|
|
2159
|
+
if (typeRegistry[namedTypeName].type !== RESOLVING_TYPE_PLACEHOLDER) {
|
|
2160
|
+
visiting.delete(type);
|
|
2161
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
const recordNode = tryResolveRecordType(type, checker, file, typeRegistry, visiting);
|
|
2165
|
+
if (recordNode) {
|
|
1875
2166
|
visiting.delete(type);
|
|
1876
|
-
|
|
2167
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
2168
|
+
const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType, namedTypeName);
|
|
2169
|
+
if (!isRecursiveRecord) {
|
|
2170
|
+
clearNamedTypeRegistration();
|
|
2171
|
+
return recordNode;
|
|
2172
|
+
}
|
|
2173
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
|
|
2174
|
+
typeRegistry[namedTypeName] = {
|
|
2175
|
+
name: namedTypeName,
|
|
2176
|
+
type: recordNode,
|
|
2177
|
+
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
2178
|
+
provenance: provenanceForDeclaration(namedDecl, file)
|
|
2179
|
+
};
|
|
2180
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
2181
|
+
}
|
|
2182
|
+
return recordNode;
|
|
1877
2183
|
}
|
|
1878
2184
|
const properties = [];
|
|
1879
2185
|
const fieldInfoMap = getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting);
|
|
@@ -1882,7 +2188,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
|
1882
2188
|
if (!declaration) continue;
|
|
1883
2189
|
const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
|
|
1884
2190
|
const optional = !!(prop.flags & ts4.SymbolFlags.Optional);
|
|
1885
|
-
const propTypeNode = resolveTypeNode(
|
|
2191
|
+
const propTypeNode = resolveTypeNode(
|
|
2192
|
+
propType,
|
|
2193
|
+
checker,
|
|
2194
|
+
file,
|
|
2195
|
+
typeRegistry,
|
|
2196
|
+
visiting,
|
|
2197
|
+
declaration
|
|
2198
|
+
);
|
|
1886
2199
|
const fieldNodeInfo = fieldInfoMap?.get(prop.name);
|
|
1887
2200
|
properties.push({
|
|
1888
2201
|
name: prop.name,
|
|
@@ -1899,13 +2212,15 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
|
1899
2212
|
properties,
|
|
1900
2213
|
additionalProperties: true
|
|
1901
2214
|
};
|
|
1902
|
-
if (
|
|
1903
|
-
|
|
1904
|
-
|
|
2215
|
+
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
2216
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
|
|
2217
|
+
typeRegistry[namedTypeName] = {
|
|
2218
|
+
name: namedTypeName,
|
|
1905
2219
|
type: objectNode,
|
|
1906
|
-
|
|
2220
|
+
...annotations !== void 0 && annotations.length > 0 && { annotations },
|
|
2221
|
+
provenance: provenanceForDeclaration(namedDecl, file)
|
|
1907
2222
|
};
|
|
1908
|
-
return { kind: "reference", name:
|
|
2223
|
+
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
1909
2224
|
}
|
|
1910
2225
|
return objectNode;
|
|
1911
2226
|
}
|
|
@@ -1997,6 +2312,12 @@ function provenanceForNode(node, file) {
|
|
|
1997
2312
|
function provenanceForFile(file) {
|
|
1998
2313
|
return { surface: "tsdoc", file, line: 0, column: 0 };
|
|
1999
2314
|
}
|
|
2315
|
+
function provenanceForDeclaration(node, file) {
|
|
2316
|
+
if (!node) {
|
|
2317
|
+
return provenanceForFile(file);
|
|
2318
|
+
}
|
|
2319
|
+
return provenanceForNode(node, file);
|
|
2320
|
+
}
|
|
2000
2321
|
function getNamedTypeName(type) {
|
|
2001
2322
|
const symbol = type.getSymbol();
|
|
2002
2323
|
if (symbol?.declarations) {
|
|
@@ -2015,6 +2336,20 @@ function getNamedTypeName(type) {
|
|
|
2015
2336
|
}
|
|
2016
2337
|
return null;
|
|
2017
2338
|
}
|
|
2339
|
+
function getNamedTypeDeclaration(type) {
|
|
2340
|
+
const symbol = type.getSymbol();
|
|
2341
|
+
if (symbol?.declarations) {
|
|
2342
|
+
const decl = symbol.declarations[0];
|
|
2343
|
+
if (decl && (ts4.isClassDeclaration(decl) || ts4.isInterfaceDeclaration(decl) || ts4.isTypeAliasDeclaration(decl))) {
|
|
2344
|
+
return decl;
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
const aliasSymbol = type.aliasSymbol;
|
|
2348
|
+
if (aliasSymbol?.declarations) {
|
|
2349
|
+
return aliasSymbol.declarations.find(ts4.isTypeAliasDeclaration);
|
|
2350
|
+
}
|
|
2351
|
+
return void 0;
|
|
2352
|
+
}
|
|
2018
2353
|
function analyzeMethod(method, checker) {
|
|
2019
2354
|
if (!ts4.isIdentifier(method.name)) {
|
|
2020
2355
|
return null;
|
|
@@ -2099,6 +2434,205 @@ function generateSchemas(options) {
|
|
|
2099
2434
|
);
|
|
2100
2435
|
}
|
|
2101
2436
|
|
|
2437
|
+
// src/generators/mixed-authoring.ts
|
|
2438
|
+
function buildMixedAuthoringSchemas(options) {
|
|
2439
|
+
const { filePath, typeName, overlays, ...schemaOptions } = options;
|
|
2440
|
+
const analysis = analyzeNamedType(filePath, typeName);
|
|
2441
|
+
const composedAnalysis = composeAnalysisWithOverlays(analysis, overlays);
|
|
2442
|
+
const ir = canonicalizeTSDoc(composedAnalysis, { file: filePath });
|
|
2443
|
+
return {
|
|
2444
|
+
jsonSchema: generateJsonSchemaFromIR(ir, schemaOptions),
|
|
2445
|
+
uiSchema: generateUiSchemaFromIR(ir)
|
|
2446
|
+
};
|
|
2447
|
+
}
|
|
2448
|
+
function analyzeNamedType(filePath, typeName) {
|
|
2449
|
+
const ctx = createProgramContext(filePath);
|
|
2450
|
+
const source = { file: filePath };
|
|
2451
|
+
const classDecl = findClassByName(ctx.sourceFile, typeName);
|
|
2452
|
+
if (classDecl !== null) {
|
|
2453
|
+
return analyzeClassToIR(classDecl, ctx.checker, source.file);
|
|
2454
|
+
}
|
|
2455
|
+
const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
|
|
2456
|
+
if (interfaceDecl !== null) {
|
|
2457
|
+
return analyzeInterfaceToIR(interfaceDecl, ctx.checker, source.file);
|
|
2458
|
+
}
|
|
2459
|
+
const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
|
|
2460
|
+
if (typeAlias !== null) {
|
|
2461
|
+
const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, source.file);
|
|
2462
|
+
if (result.ok) {
|
|
2463
|
+
return result.analysis;
|
|
2464
|
+
}
|
|
2465
|
+
throw new Error(result.error);
|
|
2466
|
+
}
|
|
2467
|
+
throw new Error(
|
|
2468
|
+
`Type "${typeName}" not found as a class, interface, or type alias in ${filePath}`
|
|
2469
|
+
);
|
|
2470
|
+
}
|
|
2471
|
+
function composeAnalysisWithOverlays(analysis, overlays) {
|
|
2472
|
+
const overlayIR = canonicalizeChainDSL(overlays);
|
|
2473
|
+
const overlayFields = collectOverlayFields(overlayIR.elements);
|
|
2474
|
+
if (overlayFields.length === 0) {
|
|
2475
|
+
return analysis;
|
|
2476
|
+
}
|
|
2477
|
+
const overlayByName = /* @__PURE__ */ new Map();
|
|
2478
|
+
for (const field of overlayFields) {
|
|
2479
|
+
if (overlayByName.has(field.name)) {
|
|
2480
|
+
throw new Error(`Mixed-authoring overlays define "${field.name}" more than once`);
|
|
2481
|
+
}
|
|
2482
|
+
overlayByName.set(field.name, field);
|
|
2483
|
+
}
|
|
2484
|
+
const mergedFields = [];
|
|
2485
|
+
for (const baseField of analysis.fields) {
|
|
2486
|
+
const overlayField = overlayByName.get(baseField.name);
|
|
2487
|
+
if (overlayField === void 0) {
|
|
2488
|
+
mergedFields.push(baseField);
|
|
2489
|
+
continue;
|
|
2490
|
+
}
|
|
2491
|
+
mergedFields.push(mergeFieldOverlay(baseField, overlayField, analysis.typeRegistry));
|
|
2492
|
+
overlayByName.delete(baseField.name);
|
|
2493
|
+
}
|
|
2494
|
+
if (overlayByName.size > 0) {
|
|
2495
|
+
const unknownFields = [...overlayByName.keys()].sort().join(", ");
|
|
2496
|
+
throw new Error(
|
|
2497
|
+
`Mixed-authoring overlays reference fields that are not present in the static model: ${unknownFields}`
|
|
2498
|
+
);
|
|
2499
|
+
}
|
|
2500
|
+
return {
|
|
2501
|
+
...analysis,
|
|
2502
|
+
fields: mergedFields
|
|
2503
|
+
};
|
|
2504
|
+
}
|
|
2505
|
+
function collectOverlayFields(elements) {
|
|
2506
|
+
const fields = [];
|
|
2507
|
+
for (const element of elements) {
|
|
2508
|
+
switch (element.kind) {
|
|
2509
|
+
case "field":
|
|
2510
|
+
fields.push(element);
|
|
2511
|
+
break;
|
|
2512
|
+
case "group":
|
|
2513
|
+
fields.push(...collectOverlayFields(element.elements));
|
|
2514
|
+
break;
|
|
2515
|
+
case "conditional":
|
|
2516
|
+
fields.push(...collectOverlayFields(element.elements));
|
|
2517
|
+
break;
|
|
2518
|
+
default: {
|
|
2519
|
+
const _exhaustive = element;
|
|
2520
|
+
void _exhaustive;
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
return fields;
|
|
2525
|
+
}
|
|
2526
|
+
function mergeFieldOverlay(baseField, overlayField, typeRegistry) {
|
|
2527
|
+
assertSupportedOverlayField(baseField, overlayField);
|
|
2528
|
+
return {
|
|
2529
|
+
...baseField,
|
|
2530
|
+
type: mergeFieldType(baseField, overlayField, typeRegistry),
|
|
2531
|
+
annotations: mergeAnnotations(baseField.annotations, overlayField.annotations)
|
|
2532
|
+
};
|
|
2533
|
+
}
|
|
2534
|
+
function assertSupportedOverlayField(baseField, overlayField) {
|
|
2535
|
+
if (overlayField.constraints.length > 0) {
|
|
2536
|
+
throw new Error(
|
|
2537
|
+
`Mixed-authoring overlay for "${baseField.name}" cannot define constraints; keep constraints on the static model`
|
|
2538
|
+
);
|
|
2539
|
+
}
|
|
2540
|
+
if (overlayField.required) {
|
|
2541
|
+
throw new Error(
|
|
2542
|
+
`Mixed-authoring overlay for "${baseField.name}" cannot change requiredness; keep requiredness on the static model`
|
|
2543
|
+
);
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2546
|
+
function mergeFieldType(baseField, overlayField, typeRegistry) {
|
|
2547
|
+
const { type: baseType } = baseField;
|
|
2548
|
+
const { type: overlayType } = overlayField;
|
|
2549
|
+
if (overlayType.kind === "object" || overlayType.kind === "array") {
|
|
2550
|
+
throw new Error(
|
|
2551
|
+
`Mixed-authoring overlays do not support nested object or array overlays for "${baseField.name}"`
|
|
2552
|
+
);
|
|
2553
|
+
}
|
|
2554
|
+
if (overlayType.kind === "dynamic") {
|
|
2555
|
+
if (!isCompatibleDynamicOverlay(baseField, overlayField, typeRegistry)) {
|
|
2556
|
+
throw new Error(
|
|
2557
|
+
`Mixed-authoring overlay for "${baseField.name}" is incompatible with the static field type`
|
|
2558
|
+
);
|
|
2559
|
+
}
|
|
2560
|
+
return overlayType;
|
|
2561
|
+
}
|
|
2562
|
+
if (!isSameStaticTypeShape(baseType, overlayType)) {
|
|
2563
|
+
throw new Error(
|
|
2564
|
+
`Mixed-authoring overlay for "${baseField.name}" must preserve the static field type`
|
|
2565
|
+
);
|
|
2566
|
+
}
|
|
2567
|
+
return baseType;
|
|
2568
|
+
}
|
|
2569
|
+
function isCompatibleDynamicOverlay(baseField, overlayField, typeRegistry) {
|
|
2570
|
+
const overlayType = overlayField.type;
|
|
2571
|
+
if (overlayType.kind !== "dynamic") {
|
|
2572
|
+
return false;
|
|
2573
|
+
}
|
|
2574
|
+
const resolvedBaseType = resolveReferenceType(baseField.type, typeRegistry);
|
|
2575
|
+
if (resolvedBaseType === null) {
|
|
2576
|
+
return false;
|
|
2577
|
+
}
|
|
2578
|
+
if (overlayType.dynamicKind === "enum") {
|
|
2579
|
+
return resolvedBaseType.kind === "primitive" ? resolvedBaseType.primitiveKind === "string" : resolvedBaseType.kind === "enum";
|
|
2580
|
+
}
|
|
2581
|
+
return resolvedBaseType.kind === "object" || resolvedBaseType.kind === "record";
|
|
2582
|
+
}
|
|
2583
|
+
function resolveReferenceType(type, typeRegistry, seen = /* @__PURE__ */ new Set()) {
|
|
2584
|
+
if (type.kind !== "reference") {
|
|
2585
|
+
return type;
|
|
2586
|
+
}
|
|
2587
|
+
if (seen.has(type.name)) {
|
|
2588
|
+
return null;
|
|
2589
|
+
}
|
|
2590
|
+
const definition = typeRegistry[type.name];
|
|
2591
|
+
if (definition === void 0) {
|
|
2592
|
+
return null;
|
|
2593
|
+
}
|
|
2594
|
+
seen.add(type.name);
|
|
2595
|
+
return resolveReferenceType(definition.type, typeRegistry, seen);
|
|
2596
|
+
}
|
|
2597
|
+
function isSameStaticTypeShape(baseType, overlayType) {
|
|
2598
|
+
if (baseType.kind !== overlayType.kind) {
|
|
2599
|
+
return false;
|
|
2600
|
+
}
|
|
2601
|
+
switch (baseType.kind) {
|
|
2602
|
+
case "primitive":
|
|
2603
|
+
return overlayType.kind === "primitive" && baseType.primitiveKind === overlayType.primitiveKind;
|
|
2604
|
+
case "enum":
|
|
2605
|
+
return overlayType.kind === "enum";
|
|
2606
|
+
case "dynamic":
|
|
2607
|
+
return overlayType.kind === "dynamic" && baseType.dynamicKind === overlayType.dynamicKind && baseType.sourceKey === overlayType.sourceKey;
|
|
2608
|
+
case "record":
|
|
2609
|
+
return overlayType.kind === "record";
|
|
2610
|
+
case "reference":
|
|
2611
|
+
return overlayType.kind === "reference" && baseType.name === overlayType.name;
|
|
2612
|
+
case "union":
|
|
2613
|
+
return overlayType.kind === "union";
|
|
2614
|
+
case "custom":
|
|
2615
|
+
return overlayType.kind === "custom" && baseType.typeId === overlayType.typeId;
|
|
2616
|
+
case "object":
|
|
2617
|
+
case "array":
|
|
2618
|
+
return true;
|
|
2619
|
+
default: {
|
|
2620
|
+
const _exhaustive = baseType;
|
|
2621
|
+
return _exhaustive;
|
|
2622
|
+
}
|
|
2623
|
+
}
|
|
2624
|
+
}
|
|
2625
|
+
function mergeAnnotations(baseAnnotations, overlayAnnotations) {
|
|
2626
|
+
const baseKeys = new Set(baseAnnotations.map(annotationKey));
|
|
2627
|
+
const overlayOnly = overlayAnnotations.filter(
|
|
2628
|
+
(annotation) => !baseKeys.has(annotationKey(annotation))
|
|
2629
|
+
);
|
|
2630
|
+
return [...overlayOnly, ...baseAnnotations];
|
|
2631
|
+
}
|
|
2632
|
+
function annotationKey(annotation) {
|
|
2633
|
+
return annotation.annotationKind === "custom" ? `${annotation.annotationKind}:${annotation.annotationId}` : annotation.annotationKind;
|
|
2634
|
+
}
|
|
2635
|
+
|
|
2102
2636
|
// src/index.ts
|
|
2103
2637
|
function buildFormSchemas(form, options) {
|
|
2104
2638
|
return {
|
|
@@ -2125,6 +2659,7 @@ function writeSchemas(form, options) {
|
|
|
2125
2659
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2126
2660
|
0 && (module.exports = {
|
|
2127
2661
|
buildFormSchemas,
|
|
2662
|
+
buildMixedAuthoringSchemas,
|
|
2128
2663
|
categorizationSchema,
|
|
2129
2664
|
categorySchema,
|
|
2130
2665
|
controlSchema,
|