@formspec/build 0.1.0-alpha.19 → 0.1.0-alpha.20
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/class-schema-regressions.d.ts +4 -0
- package/dist/__tests__/fixtures/class-schema-regressions.d.ts.map +1 -1
- package/dist/__tests__/parity/utils.d.ts.map +1 -1
- package/dist/analyzer/class-analyzer.d.ts +4 -1
- package/dist/analyzer/class-analyzer.d.ts.map +1 -1
- package/dist/analyzer/jsdoc-constraints.d.ts +2 -1
- package/dist/analyzer/jsdoc-constraints.d.ts.map +1 -1
- package/dist/analyzer/tsdoc-parser.d.ts +13 -3
- package/dist/analyzer/tsdoc-parser.d.ts.map +1 -1
- package/dist/browser.cjs +30 -748
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.js +32 -748
- package/dist/browser.js.map +1 -1
- package/dist/build.d.ts +14 -14
- package/dist/cli.cjs +691 -1101
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +704 -1100
- package/dist/cli.js.map +1 -1
- package/dist/generators/class-schema.d.ts.map +1 -1
- package/dist/index.cjs +689 -1095
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +703 -1095
- package/dist/index.js.map +1 -1
- package/dist/internals.cjs +663 -1069
- package/dist/internals.cjs.map +1 -1
- package/dist/internals.js +675 -1067
- package/dist/internals.js.map +1 -1
- package/dist/ui-schema/schema.d.ts +14 -14
- package/dist/validate/constraint-validator.d.ts +6 -45
- package/dist/validate/constraint-validator.d.ts.map +1 -1
- package/package.json +2 -1
- package/dist/__tests__/json-utils.test.d.ts +0 -5
- package/dist/__tests__/json-utils.test.d.ts.map +0 -1
- package/dist/analyzer/json-utils.d.ts +0 -22
- package/dist/analyzer/json-utils.d.ts.map +0 -1
package/dist/cli.cjs
CHANGED
|
@@ -1312,20 +1312,6 @@ var init_schema2 = __esm({
|
|
|
1312
1312
|
}
|
|
1313
1313
|
});
|
|
1314
1314
|
|
|
1315
|
-
// src/analyzer/json-utils.ts
|
|
1316
|
-
function tryParseJson(text) {
|
|
1317
|
-
try {
|
|
1318
|
-
return JSON.parse(text);
|
|
1319
|
-
} catch {
|
|
1320
|
-
return null;
|
|
1321
|
-
}
|
|
1322
|
-
}
|
|
1323
|
-
var init_json_utils = __esm({
|
|
1324
|
-
"src/analyzer/json-utils.ts"() {
|
|
1325
|
-
"use strict";
|
|
1326
|
-
}
|
|
1327
|
-
});
|
|
1328
|
-
|
|
1329
1315
|
// src/analyzer/tsdoc-parser.ts
|
|
1330
1316
|
function createFormSpecTSDocConfig(extensionTagNames = []) {
|
|
1331
1317
|
const config = new import_tsdoc.TSDocConfiguration();
|
|
@@ -1358,6 +1344,291 @@ function createFormSpecTSDocConfig(extensionTagNames = []) {
|
|
|
1358
1344
|
}
|
|
1359
1345
|
return config;
|
|
1360
1346
|
}
|
|
1347
|
+
function sharedCommentSyntaxOptions(options, offset) {
|
|
1348
|
+
const extensions = options?.extensionRegistry?.extensions;
|
|
1349
|
+
return {
|
|
1350
|
+
...offset !== void 0 ? { offset } : {},
|
|
1351
|
+
...extensions !== void 0 ? { extensions } : {}
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1354
|
+
function sharedTagValueOptions(options) {
|
|
1355
|
+
return {
|
|
1356
|
+
...options?.extensionRegistry !== void 0 ? { registry: options.extensionRegistry } : {},
|
|
1357
|
+
...options?.fieldType !== void 0 ? { fieldType: options.fieldType } : {}
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1360
|
+
function buildSupportingDeclarations(sourceFile) {
|
|
1361
|
+
return sourceFile.statements.filter(
|
|
1362
|
+
(statement) => !ts.isImportDeclaration(statement) && !ts.isImportEqualsDeclaration(statement) && !(ts.isExportDeclaration(statement) && statement.moduleSpecifier !== void 0)
|
|
1363
|
+
).map((statement) => statement.getText(sourceFile));
|
|
1364
|
+
}
|
|
1365
|
+
function renderSyntheticArgumentExpression(valueKind, argumentText) {
|
|
1366
|
+
const trimmed = argumentText.trim();
|
|
1367
|
+
if (trimmed === "") {
|
|
1368
|
+
return null;
|
|
1369
|
+
}
|
|
1370
|
+
switch (valueKind) {
|
|
1371
|
+
case "number":
|
|
1372
|
+
case "integer":
|
|
1373
|
+
case "signedInteger":
|
|
1374
|
+
return Number.isFinite(Number(trimmed)) ? trimmed : JSON.stringify(trimmed);
|
|
1375
|
+
case "string":
|
|
1376
|
+
return JSON.stringify(argumentText);
|
|
1377
|
+
case "json":
|
|
1378
|
+
try {
|
|
1379
|
+
JSON.parse(trimmed);
|
|
1380
|
+
return `(${trimmed})`;
|
|
1381
|
+
} catch {
|
|
1382
|
+
return JSON.stringify(trimmed);
|
|
1383
|
+
}
|
|
1384
|
+
case "boolean":
|
|
1385
|
+
return trimmed === "true" || trimmed === "false" ? trimmed : JSON.stringify(trimmed);
|
|
1386
|
+
case "condition":
|
|
1387
|
+
return "undefined as unknown as FormSpecCondition";
|
|
1388
|
+
case null:
|
|
1389
|
+
return null;
|
|
1390
|
+
default: {
|
|
1391
|
+
return String(valueKind);
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
function getArrayElementType(type, checker) {
|
|
1396
|
+
if (!checker.isArrayType(type)) {
|
|
1397
|
+
return null;
|
|
1398
|
+
}
|
|
1399
|
+
return checker.getTypeArguments(type)[0] ?? null;
|
|
1400
|
+
}
|
|
1401
|
+
function supportsConstraintCapability(type, checker, capability) {
|
|
1402
|
+
if (capability === void 0) {
|
|
1403
|
+
return true;
|
|
1404
|
+
}
|
|
1405
|
+
if ((0, import_analysis.hasTypeSemanticCapability)(type, checker, capability)) {
|
|
1406
|
+
return true;
|
|
1407
|
+
}
|
|
1408
|
+
if (capability === "string-like") {
|
|
1409
|
+
const itemType = getArrayElementType(type, checker);
|
|
1410
|
+
return itemType !== null && (0, import_analysis.hasTypeSemanticCapability)(itemType, checker, capability);
|
|
1411
|
+
}
|
|
1412
|
+
return false;
|
|
1413
|
+
}
|
|
1414
|
+
function makeDiagnostic(code, message, provenance) {
|
|
1415
|
+
return {
|
|
1416
|
+
code,
|
|
1417
|
+
message,
|
|
1418
|
+
severity: "error",
|
|
1419
|
+
primaryLocation: provenance,
|
|
1420
|
+
relatedLocations: []
|
|
1421
|
+
};
|
|
1422
|
+
}
|
|
1423
|
+
function placementLabel(placement) {
|
|
1424
|
+
switch (placement) {
|
|
1425
|
+
case "class":
|
|
1426
|
+
return "class declarations";
|
|
1427
|
+
case "class-field":
|
|
1428
|
+
return "class fields";
|
|
1429
|
+
case "class-method":
|
|
1430
|
+
return "class methods";
|
|
1431
|
+
case "interface":
|
|
1432
|
+
return "interface declarations";
|
|
1433
|
+
case "interface-field":
|
|
1434
|
+
return "interface fields";
|
|
1435
|
+
case "type-alias":
|
|
1436
|
+
return "type aliases";
|
|
1437
|
+
case "type-alias-field":
|
|
1438
|
+
return "type-alias properties";
|
|
1439
|
+
case "variable":
|
|
1440
|
+
return "variables";
|
|
1441
|
+
case "function":
|
|
1442
|
+
return "functions";
|
|
1443
|
+
case "function-parameter":
|
|
1444
|
+
return "function parameters";
|
|
1445
|
+
case "method-parameter":
|
|
1446
|
+
return "method parameters";
|
|
1447
|
+
default: {
|
|
1448
|
+
const exhaustive = placement;
|
|
1449
|
+
return String(exhaustive);
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
function capabilityLabel(capability) {
|
|
1454
|
+
switch (capability) {
|
|
1455
|
+
case "numeric-comparable":
|
|
1456
|
+
return "number";
|
|
1457
|
+
case "string-like":
|
|
1458
|
+
return "string";
|
|
1459
|
+
case "array-like":
|
|
1460
|
+
return "array";
|
|
1461
|
+
case "enum-member-addressable":
|
|
1462
|
+
return "enum";
|
|
1463
|
+
case "json-like":
|
|
1464
|
+
return "JSON-compatible";
|
|
1465
|
+
case "object-like":
|
|
1466
|
+
return "object";
|
|
1467
|
+
case "condition-like":
|
|
1468
|
+
return "conditional";
|
|
1469
|
+
case void 0:
|
|
1470
|
+
return "compatible";
|
|
1471
|
+
default:
|
|
1472
|
+
return capability;
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
function getBroadenedCustomTypeId(fieldType) {
|
|
1476
|
+
if (fieldType?.kind === "custom") {
|
|
1477
|
+
return fieldType.typeId;
|
|
1478
|
+
}
|
|
1479
|
+
if (fieldType?.kind !== "union") {
|
|
1480
|
+
return void 0;
|
|
1481
|
+
}
|
|
1482
|
+
const customMembers = fieldType.members.filter(
|
|
1483
|
+
(member) => member.kind === "custom"
|
|
1484
|
+
);
|
|
1485
|
+
if (customMembers.length !== 1) {
|
|
1486
|
+
return void 0;
|
|
1487
|
+
}
|
|
1488
|
+
const nonCustomMembers = fieldType.members.filter((member) => member.kind !== "custom");
|
|
1489
|
+
const allOtherMembersAreNull = nonCustomMembers.every(
|
|
1490
|
+
(member) => member.kind === "primitive" && member.primitiveKind === "null"
|
|
1491
|
+
);
|
|
1492
|
+
const customMember = customMembers[0];
|
|
1493
|
+
return allOtherMembersAreNull && customMember !== void 0 ? customMember.typeId : void 0;
|
|
1494
|
+
}
|
|
1495
|
+
function hasBuiltinConstraintBroadening(tagName, options) {
|
|
1496
|
+
const broadenedTypeId = getBroadenedCustomTypeId(options?.fieldType);
|
|
1497
|
+
return broadenedTypeId !== void 0 && options?.extensionRegistry?.findBuiltinConstraintBroadening(broadenedTypeId, tagName) !== void 0;
|
|
1498
|
+
}
|
|
1499
|
+
function buildCompilerBackedConstraintDiagnostics(node, sourceFile, tagName, parsedTag, provenance, supportingDeclarations, options) {
|
|
1500
|
+
if (!(0, import_core3.isBuiltinConstraintName)(tagName)) {
|
|
1501
|
+
return [];
|
|
1502
|
+
}
|
|
1503
|
+
const checker = options?.checker;
|
|
1504
|
+
const subjectType = options?.subjectType;
|
|
1505
|
+
if (checker === void 0 || subjectType === void 0) {
|
|
1506
|
+
return [];
|
|
1507
|
+
}
|
|
1508
|
+
const placement = (0, import_analysis.resolveDeclarationPlacement)(node);
|
|
1509
|
+
if (placement === null) {
|
|
1510
|
+
return [];
|
|
1511
|
+
}
|
|
1512
|
+
const definition = (0, import_analysis.getTagDefinition)(tagName, options?.extensionRegistry?.extensions);
|
|
1513
|
+
if (definition === null) {
|
|
1514
|
+
return [];
|
|
1515
|
+
}
|
|
1516
|
+
if (!definition.placements.includes(placement)) {
|
|
1517
|
+
return [
|
|
1518
|
+
makeDiagnostic(
|
|
1519
|
+
"INVALID_TAG_PLACEMENT",
|
|
1520
|
+
`Tag "@${tagName}" is not allowed on ${placementLabel(placement)}.`,
|
|
1521
|
+
provenance
|
|
1522
|
+
)
|
|
1523
|
+
];
|
|
1524
|
+
}
|
|
1525
|
+
const target = parsedTag?.target ?? null;
|
|
1526
|
+
const hasBroadening = target === null && hasBuiltinConstraintBroadening(tagName, options);
|
|
1527
|
+
if (target !== null) {
|
|
1528
|
+
if (target.kind !== "path") {
|
|
1529
|
+
return [
|
|
1530
|
+
makeDiagnostic(
|
|
1531
|
+
"UNSUPPORTED_TARGETING_SYNTAX",
|
|
1532
|
+
`Tag "@${tagName}" does not support ${target.kind} targeting syntax.`,
|
|
1533
|
+
provenance
|
|
1534
|
+
)
|
|
1535
|
+
];
|
|
1536
|
+
}
|
|
1537
|
+
if (!target.valid || target.path === null) {
|
|
1538
|
+
return [
|
|
1539
|
+
makeDiagnostic(
|
|
1540
|
+
"UNSUPPORTED_TARGETING_SYNTAX",
|
|
1541
|
+
`Tag "@${tagName}" has invalid path targeting syntax.`,
|
|
1542
|
+
provenance
|
|
1543
|
+
)
|
|
1544
|
+
];
|
|
1545
|
+
}
|
|
1546
|
+
const resolution = (0, import_analysis.resolvePathTargetType)(subjectType, checker, target.path.segments);
|
|
1547
|
+
if (resolution.kind === "missing-property") {
|
|
1548
|
+
return [
|
|
1549
|
+
makeDiagnostic(
|
|
1550
|
+
"UNKNOWN_PATH_TARGET",
|
|
1551
|
+
`Target "${target.rawText}": path-targeted constraint "${tagName}" references unknown path segment "${resolution.segment}"`,
|
|
1552
|
+
provenance
|
|
1553
|
+
)
|
|
1554
|
+
];
|
|
1555
|
+
}
|
|
1556
|
+
if (resolution.kind === "unresolvable") {
|
|
1557
|
+
const actualType = checker.typeToString(resolution.type, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
|
|
1558
|
+
return [
|
|
1559
|
+
makeDiagnostic(
|
|
1560
|
+
"TYPE_MISMATCH",
|
|
1561
|
+
`Target "${target.rawText}": path-targeted constraint "${tagName}" is invalid because type "${actualType}" cannot be traversed`,
|
|
1562
|
+
provenance
|
|
1563
|
+
)
|
|
1564
|
+
];
|
|
1565
|
+
}
|
|
1566
|
+
const requiredCapability = definition.capabilities[0];
|
|
1567
|
+
if (requiredCapability !== void 0 && !supportsConstraintCapability(resolution.type, checker, requiredCapability)) {
|
|
1568
|
+
const actualType = checker.typeToString(resolution.type, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
|
|
1569
|
+
return [
|
|
1570
|
+
makeDiagnostic(
|
|
1571
|
+
"TYPE_MISMATCH",
|
|
1572
|
+
`Target "${target.rawText}": constraint "${tagName}" is only valid on ${capabilityLabel(requiredCapability)} targets, but field type is "${actualType}"`,
|
|
1573
|
+
provenance
|
|
1574
|
+
)
|
|
1575
|
+
];
|
|
1576
|
+
}
|
|
1577
|
+
} else if (!hasBroadening) {
|
|
1578
|
+
const requiredCapability = definition.capabilities[0];
|
|
1579
|
+
if (requiredCapability !== void 0 && !supportsConstraintCapability(subjectType, checker, requiredCapability)) {
|
|
1580
|
+
const actualType = checker.typeToString(subjectType, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
|
|
1581
|
+
return [
|
|
1582
|
+
makeDiagnostic(
|
|
1583
|
+
"TYPE_MISMATCH",
|
|
1584
|
+
`Target "${node.getText(sourceFile)}": constraint "${tagName}" is only valid on ${capabilityLabel(requiredCapability)} targets, but field type is "${actualType}"`,
|
|
1585
|
+
provenance
|
|
1586
|
+
)
|
|
1587
|
+
];
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
const argumentExpression = renderSyntheticArgumentExpression(
|
|
1591
|
+
definition.valueKind,
|
|
1592
|
+
parsedTag?.argumentText ?? ""
|
|
1593
|
+
);
|
|
1594
|
+
if (definition.requiresArgument && argumentExpression === null) {
|
|
1595
|
+
return [];
|
|
1596
|
+
}
|
|
1597
|
+
if (hasBroadening) {
|
|
1598
|
+
return [];
|
|
1599
|
+
}
|
|
1600
|
+
const subjectTypeText = checker.typeToString(subjectType, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
|
|
1601
|
+
const hostType = options?.hostType ?? subjectType;
|
|
1602
|
+
const hostTypeText = checker.typeToString(hostType, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
|
|
1603
|
+
const result = (0, import_analysis.checkSyntheticTagApplication)({
|
|
1604
|
+
tagName,
|
|
1605
|
+
placement,
|
|
1606
|
+
hostType: hostTypeText,
|
|
1607
|
+
subjectType: subjectTypeText,
|
|
1608
|
+
...target?.kind === "path" ? { target: { kind: "path", text: target.rawText } } : {},
|
|
1609
|
+
...argumentExpression !== null ? { argumentExpression } : {},
|
|
1610
|
+
supportingDeclarations,
|
|
1611
|
+
...options?.extensionRegistry !== void 0 ? {
|
|
1612
|
+
extensions: options.extensionRegistry.extensions.map((extension) => ({
|
|
1613
|
+
extensionId: extension.extensionId,
|
|
1614
|
+
...extension.constraintTags !== void 0 ? {
|
|
1615
|
+
constraintTags: extension.constraintTags.map((tag) => ({ tagName: tag.tagName }))
|
|
1616
|
+
} : {}
|
|
1617
|
+
}))
|
|
1618
|
+
} : {}
|
|
1619
|
+
});
|
|
1620
|
+
if (result.diagnostics.length === 0) {
|
|
1621
|
+
return [];
|
|
1622
|
+
}
|
|
1623
|
+
const expectedLabel = definition.valueKind === null ? "compatible argument" : capabilityLabel(definition.valueKind);
|
|
1624
|
+
return [
|
|
1625
|
+
makeDiagnostic(
|
|
1626
|
+
"TYPE_MISMATCH",
|
|
1627
|
+
`Tag "@${tagName}" received an invalid argument for ${expectedLabel}.`,
|
|
1628
|
+
provenance
|
|
1629
|
+
)
|
|
1630
|
+
];
|
|
1631
|
+
}
|
|
1361
1632
|
function getParser(options) {
|
|
1362
1633
|
const extensionTagNames = [
|
|
1363
1634
|
...options?.extensionRegistry?.extensions.flatMap(
|
|
@@ -1373,18 +1644,54 @@ function getParser(options) {
|
|
|
1373
1644
|
parserCache.set(cacheKey, parser);
|
|
1374
1645
|
return parser;
|
|
1375
1646
|
}
|
|
1647
|
+
function getExtensionRegistryCacheKey(registry) {
|
|
1648
|
+
if (registry === void 0) {
|
|
1649
|
+
return "";
|
|
1650
|
+
}
|
|
1651
|
+
return registry.extensions.map(
|
|
1652
|
+
(extension) => JSON.stringify({
|
|
1653
|
+
extensionId: extension.extensionId,
|
|
1654
|
+
typeNames: extension.types?.map((type) => type.typeName) ?? [],
|
|
1655
|
+
constraintTags: extension.constraintTags?.map((tag) => tag.tagName) ?? []
|
|
1656
|
+
})
|
|
1657
|
+
).join("|");
|
|
1658
|
+
}
|
|
1659
|
+
function getParseCacheKey(node, file, options) {
|
|
1660
|
+
const sourceFile = node.getSourceFile();
|
|
1661
|
+
const checker = options?.checker;
|
|
1662
|
+
return JSON.stringify({
|
|
1663
|
+
file,
|
|
1664
|
+
sourceFile: sourceFile.fileName,
|
|
1665
|
+
sourceText: sourceFile.text,
|
|
1666
|
+
start: node.getFullStart(),
|
|
1667
|
+
end: node.getEnd(),
|
|
1668
|
+
fieldType: options?.fieldType ?? null,
|
|
1669
|
+
subjectType: checker !== void 0 && options?.subjectType !== void 0 ? checker.typeToString(options.subjectType, node, SYNTHETIC_TYPE_FORMAT_FLAGS) : null,
|
|
1670
|
+
hostType: checker !== void 0 && options?.hostType !== void 0 ? checker.typeToString(options.hostType, node, SYNTHETIC_TYPE_FORMAT_FLAGS) : null,
|
|
1671
|
+
extensions: getExtensionRegistryCacheKey(options?.extensionRegistry)
|
|
1672
|
+
});
|
|
1673
|
+
}
|
|
1376
1674
|
function parseTSDocTags(node, file = "", options) {
|
|
1675
|
+
const cacheKey = getParseCacheKey(node, file, options);
|
|
1676
|
+
const cached = parseResultCache.get(cacheKey);
|
|
1677
|
+
if (cached !== void 0) {
|
|
1678
|
+
return cached;
|
|
1679
|
+
}
|
|
1377
1680
|
const constraints = [];
|
|
1378
1681
|
const annotations = [];
|
|
1682
|
+
const diagnostics = [];
|
|
1379
1683
|
let displayName;
|
|
1380
1684
|
let description;
|
|
1381
1685
|
let placeholder;
|
|
1382
1686
|
let displayNameProvenance;
|
|
1383
1687
|
let descriptionProvenance;
|
|
1384
1688
|
let placeholderProvenance;
|
|
1689
|
+
const rawTextTags = [];
|
|
1385
1690
|
const sourceFile = node.getSourceFile();
|
|
1386
1691
|
const sourceText = sourceFile.getFullText();
|
|
1692
|
+
const supportingDeclarations = buildSupportingDeclarations(sourceFile);
|
|
1387
1693
|
const commentRanges = ts.getLeadingCommentRanges(sourceText, node.getFullStart());
|
|
1694
|
+
const rawTextFallbacks = collectRawTextFallbacks(node, file);
|
|
1388
1695
|
if (commentRanges) {
|
|
1389
1696
|
for (const range of commentRanges) {
|
|
1390
1697
|
if (range.kind !== ts.SyntaxKind.MultiLineCommentTrivia) {
|
|
@@ -1399,12 +1706,33 @@ function parseTSDocTags(node, file = "", options) {
|
|
|
1399
1706
|
import_tsdoc.TextRange.fromStringRange(sourceText, range.pos, range.end)
|
|
1400
1707
|
);
|
|
1401
1708
|
const docComment = parserContext.docComment;
|
|
1709
|
+
const parsedComment = (0, import_analysis.parseCommentBlock)(
|
|
1710
|
+
commentText,
|
|
1711
|
+
sharedCommentSyntaxOptions(options, range.pos)
|
|
1712
|
+
);
|
|
1713
|
+
let parsedTagCursor = 0;
|
|
1714
|
+
const nextParsedTag = (normalizedTagName) => {
|
|
1715
|
+
while (parsedTagCursor < parsedComment.tags.length) {
|
|
1716
|
+
const candidate = parsedComment.tags[parsedTagCursor];
|
|
1717
|
+
parsedTagCursor += 1;
|
|
1718
|
+
if (candidate?.normalizedTagName === normalizedTagName) {
|
|
1719
|
+
return candidate;
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
return null;
|
|
1723
|
+
};
|
|
1724
|
+
for (const parsedTag of parsedComment.tags) {
|
|
1725
|
+
if (TAGS_REQUIRING_RAW_TEXT.has(parsedTag.normalizedTagName)) {
|
|
1726
|
+
rawTextTags.push({ tag: parsedTag, commentText, commentOffset: range.pos });
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1402
1729
|
for (const block of docComment.customBlocks) {
|
|
1403
1730
|
const tagName = (0, import_core3.normalizeConstraintTagName)(block.blockTag.tagName.substring(1));
|
|
1731
|
+
const parsedTag = nextParsedTag(tagName);
|
|
1404
1732
|
if (tagName === "displayName" || tagName === "description" || tagName === "format" || tagName === "placeholder") {
|
|
1405
|
-
const text2 =
|
|
1733
|
+
const text2 = getBestBlockPayloadText(parsedTag, commentText, range.pos, block);
|
|
1406
1734
|
if (text2 === "") continue;
|
|
1407
|
-
const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
|
|
1735
|
+
const provenance2 = parsedTag !== null ? provenanceForParsedTag(parsedTag, sourceFile, file) : provenanceForComment(range, sourceFile, file, tagName);
|
|
1408
1736
|
switch (tagName) {
|
|
1409
1737
|
case "displayName":
|
|
1410
1738
|
if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
|
|
@@ -1434,11 +1762,29 @@ function parseTSDocTags(node, file = "", options) {
|
|
|
1434
1762
|
continue;
|
|
1435
1763
|
}
|
|
1436
1764
|
if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
1437
|
-
const text =
|
|
1765
|
+
const text = getBestBlockPayloadText(parsedTag, commentText, range.pos, block);
|
|
1438
1766
|
const expectedType = (0, import_core3.isBuiltinConstraintName)(tagName) ? import_core3.BUILTIN_CONSTRAINT_DEFINITIONS[tagName] : void 0;
|
|
1439
1767
|
if (text === "" && expectedType !== "boolean") continue;
|
|
1440
|
-
const provenance = provenanceForComment(range, sourceFile, file, tagName);
|
|
1441
|
-
const
|
|
1768
|
+
const provenance = parsedTag !== null ? provenanceForParsedTag(parsedTag, sourceFile, file) : provenanceForComment(range, sourceFile, file, tagName);
|
|
1769
|
+
const compilerDiagnostics = buildCompilerBackedConstraintDiagnostics(
|
|
1770
|
+
node,
|
|
1771
|
+
sourceFile,
|
|
1772
|
+
tagName,
|
|
1773
|
+
parsedTag,
|
|
1774
|
+
provenance,
|
|
1775
|
+
supportingDeclarations,
|
|
1776
|
+
options
|
|
1777
|
+
);
|
|
1778
|
+
if (compilerDiagnostics.length > 0) {
|
|
1779
|
+
diagnostics.push(...compilerDiagnostics);
|
|
1780
|
+
continue;
|
|
1781
|
+
}
|
|
1782
|
+
const constraintNode = (0, import_analysis.parseConstraintTagValue)(
|
|
1783
|
+
tagName,
|
|
1784
|
+
text,
|
|
1785
|
+
provenance,
|
|
1786
|
+
sharedTagValueOptions(options)
|
|
1787
|
+
);
|
|
1442
1788
|
if (constraintNode) {
|
|
1443
1789
|
constraints.push(constraintNode);
|
|
1444
1790
|
}
|
|
@@ -1492,57 +1838,114 @@ function parseTSDocTags(node, file = "", options) {
|
|
|
1492
1838
|
provenance: placeholderProvenance
|
|
1493
1839
|
});
|
|
1494
1840
|
}
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
const
|
|
1505
|
-
|
|
1506
|
-
|
|
1841
|
+
if (rawTextTags.length > 0) {
|
|
1842
|
+
for (const rawTextTag of rawTextTags) {
|
|
1843
|
+
const fallbackQueue = rawTextFallbacks.get(rawTextTag.tag.normalizedTagName);
|
|
1844
|
+
const fallback = fallbackQueue?.shift();
|
|
1845
|
+
const text = choosePreferredPayloadText(
|
|
1846
|
+
getSharedPayloadText(rawTextTag.tag, rawTextTag.commentText, rawTextTag.commentOffset),
|
|
1847
|
+
fallback?.text ?? ""
|
|
1848
|
+
);
|
|
1849
|
+
if (text === "") continue;
|
|
1850
|
+
const provenance = provenanceForParsedTag(rawTextTag.tag, sourceFile, file);
|
|
1851
|
+
if (rawTextTag.tag.normalizedTagName === "defaultValue") {
|
|
1852
|
+
const defaultValueNode = (0, import_analysis.parseDefaultValueTagValue)(text, provenance);
|
|
1853
|
+
annotations.push(defaultValueNode);
|
|
1854
|
+
continue;
|
|
1855
|
+
}
|
|
1856
|
+
const compilerDiagnostics = buildCompilerBackedConstraintDiagnostics(
|
|
1857
|
+
node,
|
|
1858
|
+
sourceFile,
|
|
1859
|
+
rawTextTag.tag.normalizedTagName,
|
|
1860
|
+
rawTextTag.tag,
|
|
1861
|
+
provenance,
|
|
1862
|
+
supportingDeclarations,
|
|
1863
|
+
options
|
|
1864
|
+
);
|
|
1865
|
+
if (compilerDiagnostics.length > 0) {
|
|
1866
|
+
diagnostics.push(...compilerDiagnostics);
|
|
1867
|
+
continue;
|
|
1868
|
+
}
|
|
1869
|
+
const constraintNode = (0, import_analysis.parseConstraintTagValue)(
|
|
1870
|
+
rawTextTag.tag.normalizedTagName,
|
|
1871
|
+
text,
|
|
1872
|
+
provenance,
|
|
1873
|
+
sharedTagValueOptions(options)
|
|
1874
|
+
);
|
|
1875
|
+
if (constraintNode) {
|
|
1876
|
+
constraints.push(constraintNode);
|
|
1877
|
+
}
|
|
1507
1878
|
}
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1879
|
+
}
|
|
1880
|
+
for (const [tagName, fallbacks] of rawTextFallbacks) {
|
|
1881
|
+
for (const fallback of fallbacks) {
|
|
1882
|
+
const text = fallback.text.trim();
|
|
1883
|
+
if (text === "") continue;
|
|
1884
|
+
const provenance = fallback.provenance;
|
|
1885
|
+
if (tagName === "defaultValue") {
|
|
1886
|
+
const defaultValueNode = (0, import_analysis.parseDefaultValueTagValue)(text, provenance);
|
|
1887
|
+
annotations.push(defaultValueNode);
|
|
1888
|
+
continue;
|
|
1889
|
+
}
|
|
1890
|
+
const compilerDiagnostics = buildCompilerBackedConstraintDiagnostics(
|
|
1891
|
+
node,
|
|
1892
|
+
sourceFile,
|
|
1893
|
+
tagName,
|
|
1894
|
+
null,
|
|
1895
|
+
provenance,
|
|
1896
|
+
supportingDeclarations,
|
|
1897
|
+
options
|
|
1898
|
+
);
|
|
1899
|
+
if (compilerDiagnostics.length > 0) {
|
|
1900
|
+
diagnostics.push(...compilerDiagnostics);
|
|
1901
|
+
continue;
|
|
1902
|
+
}
|
|
1903
|
+
const constraintNode = (0, import_analysis.parseConstraintTagValue)(
|
|
1904
|
+
tagName,
|
|
1905
|
+
text,
|
|
1906
|
+
provenance,
|
|
1907
|
+
sharedTagValueOptions(options)
|
|
1908
|
+
);
|
|
1909
|
+
if (constraintNode) {
|
|
1910
|
+
constraints.push(constraintNode);
|
|
1911
|
+
}
|
|
1511
1912
|
}
|
|
1512
1913
|
}
|
|
1513
|
-
|
|
1914
|
+
const result = { constraints, annotations, diagnostics };
|
|
1915
|
+
parseResultCache.set(cacheKey, result);
|
|
1916
|
+
return result;
|
|
1514
1917
|
}
|
|
1515
1918
|
function extractDisplayNameMetadata(node) {
|
|
1516
1919
|
let displayName;
|
|
1517
1920
|
const memberDisplayNames = /* @__PURE__ */ new Map();
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1921
|
+
const sourceFile = node.getSourceFile();
|
|
1922
|
+
const sourceText = sourceFile.getFullText();
|
|
1923
|
+
const commentRanges = ts.getLeadingCommentRanges(sourceText, node.getFullStart());
|
|
1924
|
+
if (commentRanges) {
|
|
1925
|
+
for (const range of commentRanges) {
|
|
1926
|
+
if (range.kind !== ts.SyntaxKind.MultiLineCommentTrivia) continue;
|
|
1927
|
+
const commentText = sourceText.substring(range.pos, range.end);
|
|
1928
|
+
if (!commentText.startsWith("/**")) continue;
|
|
1929
|
+
const parsed = (0, import_analysis.parseCommentBlock)(commentText);
|
|
1930
|
+
for (const tag of parsed.tags) {
|
|
1931
|
+
if (tag.normalizedTagName !== "displayName") {
|
|
1932
|
+
continue;
|
|
1933
|
+
}
|
|
1934
|
+
if (tag.target !== null && tag.argumentText !== "") {
|
|
1935
|
+
memberDisplayNames.set(tag.target.rawText, tag.argumentText);
|
|
1936
|
+
continue;
|
|
1937
|
+
}
|
|
1938
|
+
if (tag.argumentText !== "") {
|
|
1939
|
+
displayName ??= tag.argumentText;
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1529
1942
|
}
|
|
1530
|
-
displayName ??= text;
|
|
1531
1943
|
}
|
|
1532
1944
|
return {
|
|
1533
1945
|
...displayName !== void 0 && { displayName },
|
|
1534
1946
|
memberDisplayNames
|
|
1535
1947
|
};
|
|
1536
1948
|
}
|
|
1537
|
-
function extractPathTarget(text) {
|
|
1538
|
-
const trimmed = text.trimStart();
|
|
1539
|
-
const match = /^:([a-zA-Z_]\w*)(?:\s+([\s\S]*))?$/.exec(trimmed);
|
|
1540
|
-
if (!match?.[1]) return null;
|
|
1541
|
-
return {
|
|
1542
|
-
path: { segments: [match[1]] },
|
|
1543
|
-
remainingText: match[2] ?? ""
|
|
1544
|
-
};
|
|
1545
|
-
}
|
|
1546
1949
|
function extractBlockText(block) {
|
|
1547
1950
|
return extractPlainText(block.content);
|
|
1548
1951
|
}
|
|
@@ -1561,283 +1964,114 @@ function extractPlainText(node) {
|
|
|
1561
1964
|
}
|
|
1562
1965
|
return result;
|
|
1563
1966
|
}
|
|
1564
|
-
function
|
|
1565
|
-
const
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
if (
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
const pathResult = extractPathTarget(text);
|
|
1573
|
-
const effectiveText = pathResult ? pathResult.remainingText : text;
|
|
1574
|
-
const path4 = pathResult?.path;
|
|
1575
|
-
const expectedType = import_core3.BUILTIN_CONSTRAINT_DEFINITIONS[tagName];
|
|
1576
|
-
if (expectedType === "number") {
|
|
1577
|
-
const value = Number(effectiveText);
|
|
1578
|
-
if (Number.isNaN(value)) {
|
|
1579
|
-
return null;
|
|
1580
|
-
}
|
|
1581
|
-
const numericKind = NUMERIC_CONSTRAINT_MAP[tagName];
|
|
1582
|
-
if (numericKind) {
|
|
1583
|
-
return {
|
|
1584
|
-
kind: "constraint",
|
|
1585
|
-
constraintKind: numericKind,
|
|
1586
|
-
value,
|
|
1587
|
-
...path4 && { path: path4 },
|
|
1588
|
-
provenance
|
|
1589
|
-
};
|
|
1590
|
-
}
|
|
1591
|
-
const lengthKind = LENGTH_CONSTRAINT_MAP[tagName];
|
|
1592
|
-
if (lengthKind) {
|
|
1593
|
-
return {
|
|
1594
|
-
kind: "constraint",
|
|
1595
|
-
constraintKind: lengthKind,
|
|
1596
|
-
value,
|
|
1597
|
-
...path4 && { path: path4 },
|
|
1598
|
-
provenance
|
|
1599
|
-
};
|
|
1600
|
-
}
|
|
1601
|
-
return null;
|
|
1967
|
+
function choosePreferredPayloadText(primary, fallback) {
|
|
1968
|
+
const preferred = primary.trim();
|
|
1969
|
+
const alternate = fallback.trim();
|
|
1970
|
+
if (preferred === "") return alternate;
|
|
1971
|
+
if (alternate === "") return preferred;
|
|
1972
|
+
if (alternate.includes("\n")) return alternate;
|
|
1973
|
+
if (alternate.length > preferred.length && alternate.startsWith(preferred)) {
|
|
1974
|
+
return alternate;
|
|
1602
1975
|
}
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
if (tagName === "uniqueItems") {
|
|
1609
|
-
return {
|
|
1610
|
-
kind: "constraint",
|
|
1611
|
-
constraintKind: "uniqueItems",
|
|
1612
|
-
value: true,
|
|
1613
|
-
...path4 && { path: path4 },
|
|
1614
|
-
provenance
|
|
1615
|
-
};
|
|
1616
|
-
}
|
|
1617
|
-
return null;
|
|
1976
|
+
return preferred;
|
|
1977
|
+
}
|
|
1978
|
+
function getSharedPayloadText(tag, commentText, commentOffset) {
|
|
1979
|
+
if (tag.payloadSpan === null) {
|
|
1980
|
+
return "";
|
|
1618
1981
|
}
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
}
|
|
1642
|
-
const parsed = tryParseJson(effectiveText);
|
|
1643
|
-
if (!Array.isArray(parsed)) {
|
|
1644
|
-
return null;
|
|
1645
|
-
}
|
|
1646
|
-
const members = [];
|
|
1647
|
-
for (const item of parsed) {
|
|
1648
|
-
if (typeof item === "string" || typeof item === "number") {
|
|
1649
|
-
members.push(item);
|
|
1650
|
-
} else if (typeof item === "object" && item !== null && "id" in item) {
|
|
1651
|
-
const id = item["id"];
|
|
1652
|
-
if (typeof id === "string" || typeof id === "number") {
|
|
1653
|
-
members.push(id);
|
|
1654
|
-
}
|
|
1655
|
-
}
|
|
1656
|
-
}
|
|
1657
|
-
return {
|
|
1658
|
-
kind: "constraint",
|
|
1659
|
-
constraintKind: "allowedMembers",
|
|
1660
|
-
members,
|
|
1661
|
-
...path4 && { path: path4 },
|
|
1662
|
-
provenance
|
|
1663
|
-
};
|
|
1982
|
+
return (0, import_analysis.sliceCommentSpan)(commentText, tag.payloadSpan, {
|
|
1983
|
+
offset: commentOffset
|
|
1984
|
+
}).trim();
|
|
1985
|
+
}
|
|
1986
|
+
function getBestBlockPayloadText(tag, commentText, commentOffset, block) {
|
|
1987
|
+
const sharedText = tag === null ? "" : getSharedPayloadText(tag, commentText, commentOffset);
|
|
1988
|
+
const blockText = extractBlockText(block).replace(/\s+/g, " ").trim();
|
|
1989
|
+
return choosePreferredPayloadText(sharedText, blockText);
|
|
1990
|
+
}
|
|
1991
|
+
function collectRawTextFallbacks(node, file) {
|
|
1992
|
+
const fallbacks = /* @__PURE__ */ new Map();
|
|
1993
|
+
for (const tag of ts.getJSDocTags(node)) {
|
|
1994
|
+
const tagName = (0, import_core3.normalizeConstraintTagName)(tag.tagName.text);
|
|
1995
|
+
if (!TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
1996
|
+
const commentText = getTagCommentText(tag)?.trim() ?? "";
|
|
1997
|
+
if (commentText === "") continue;
|
|
1998
|
+
const entries = fallbacks.get(tagName) ?? [];
|
|
1999
|
+
entries.push({
|
|
2000
|
+
text: commentText,
|
|
2001
|
+
provenance: provenanceForJSDocTag(tag, file)
|
|
2002
|
+
});
|
|
2003
|
+
fallbacks.set(tagName, entries);
|
|
1664
2004
|
}
|
|
2005
|
+
return fallbacks;
|
|
2006
|
+
}
|
|
2007
|
+
function isMemberTargetDisplayName(text) {
|
|
2008
|
+
return (0, import_analysis.parseTagSyntax)("displayName", text).target !== null;
|
|
2009
|
+
}
|
|
2010
|
+
function provenanceForComment(range, sourceFile, file, tagName) {
|
|
2011
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(range.pos);
|
|
1665
2012
|
return {
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
2013
|
+
surface: "tsdoc",
|
|
2014
|
+
file,
|
|
2015
|
+
line: line + 1,
|
|
2016
|
+
column: character,
|
|
2017
|
+
tagName: "@" + tagName
|
|
1671
2018
|
};
|
|
1672
2019
|
}
|
|
1673
|
-
function
|
|
1674
|
-
const
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
2020
|
+
function provenanceForParsedTag(tag, sourceFile, file) {
|
|
2021
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(tag.tagNameSpan.start);
|
|
2022
|
+
return {
|
|
2023
|
+
surface: "tsdoc",
|
|
2024
|
+
file,
|
|
2025
|
+
line: line + 1,
|
|
2026
|
+
column: character,
|
|
2027
|
+
tagName: "@" + tag.normalizedTagName
|
|
2028
|
+
};
|
|
2029
|
+
}
|
|
2030
|
+
function provenanceForJSDocTag(tag, file) {
|
|
2031
|
+
const sourceFile = tag.getSourceFile();
|
|
2032
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(tag.getStart());
|
|
2033
|
+
return {
|
|
2034
|
+
surface: "tsdoc",
|
|
2035
|
+
file,
|
|
2036
|
+
line: line + 1,
|
|
2037
|
+
column: character,
|
|
2038
|
+
tagName: "@" + tag.tagName.text
|
|
2039
|
+
};
|
|
2040
|
+
}
|
|
2041
|
+
function getTagCommentText(tag) {
|
|
2042
|
+
if (tag.comment === void 0) {
|
|
2043
|
+
return void 0;
|
|
1680
2044
|
}
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
return makeCustomConstraintNode(
|
|
1684
|
-
directTag.extensionId,
|
|
1685
|
-
directTag.registration.constraintName,
|
|
1686
|
-
directTag.registration.parseValue(effectiveText),
|
|
1687
|
-
provenance,
|
|
1688
|
-
path4,
|
|
1689
|
-
registry
|
|
1690
|
-
);
|
|
1691
|
-
}
|
|
1692
|
-
if (!(0, import_core3.isBuiltinConstraintName)(tagName)) {
|
|
1693
|
-
return null;
|
|
1694
|
-
}
|
|
1695
|
-
const broadenedTypeId = getBroadenedCustomTypeId(options?.fieldType);
|
|
1696
|
-
if (broadenedTypeId === void 0) {
|
|
1697
|
-
return null;
|
|
1698
|
-
}
|
|
1699
|
-
const broadened = registry.findBuiltinConstraintBroadening(broadenedTypeId, tagName);
|
|
1700
|
-
if (broadened === void 0) {
|
|
1701
|
-
return null;
|
|
1702
|
-
}
|
|
1703
|
-
return makeCustomConstraintNode(
|
|
1704
|
-
broadened.extensionId,
|
|
1705
|
-
broadened.registration.constraintName,
|
|
1706
|
-
broadened.registration.parseValue(effectiveText),
|
|
1707
|
-
provenance,
|
|
1708
|
-
path4,
|
|
1709
|
-
registry
|
|
1710
|
-
);
|
|
1711
|
-
}
|
|
1712
|
-
function getBroadenedCustomTypeId(fieldType) {
|
|
1713
|
-
if (fieldType?.kind === "custom") {
|
|
1714
|
-
return fieldType.typeId;
|
|
1715
|
-
}
|
|
1716
|
-
if (fieldType?.kind !== "union") {
|
|
1717
|
-
return void 0;
|
|
1718
|
-
}
|
|
1719
|
-
const customMembers = fieldType.members.filter(
|
|
1720
|
-
(member) => member.kind === "custom"
|
|
1721
|
-
);
|
|
1722
|
-
if (customMembers.length !== 1) {
|
|
1723
|
-
return void 0;
|
|
1724
|
-
}
|
|
1725
|
-
const nonCustomMembers = fieldType.members.filter((member) => member.kind !== "custom");
|
|
1726
|
-
const allOtherMembersAreNull = nonCustomMembers.every(
|
|
1727
|
-
(member) => member.kind === "primitive" && member.primitiveKind === "null"
|
|
1728
|
-
);
|
|
1729
|
-
const customMember = customMembers[0];
|
|
1730
|
-
return allOtherMembersAreNull && customMember !== void 0 ? customMember.typeId : void 0;
|
|
1731
|
-
}
|
|
1732
|
-
function makeCustomConstraintNode(extensionId, constraintName, payload, provenance, path4, registry) {
|
|
1733
|
-
const constraintId = `${extensionId}/${constraintName}`;
|
|
1734
|
-
const registration = registry.findConstraint(constraintId);
|
|
1735
|
-
if (registration === void 0) {
|
|
1736
|
-
throw new Error(
|
|
1737
|
-
`Custom TSDoc tag resolved to unregistered constraint "${constraintId}". Register the constraint before using its tag.`
|
|
1738
|
-
);
|
|
1739
|
-
}
|
|
1740
|
-
return {
|
|
1741
|
-
kind: "constraint",
|
|
1742
|
-
constraintKind: "custom",
|
|
1743
|
-
constraintId,
|
|
1744
|
-
payload,
|
|
1745
|
-
compositionRule: registration.compositionRule,
|
|
1746
|
-
...path4 && { path: path4 },
|
|
1747
|
-
provenance
|
|
1748
|
-
};
|
|
1749
|
-
}
|
|
1750
|
-
function parseDefaultValueValue(text, provenance) {
|
|
1751
|
-
const trimmed = text.trim();
|
|
1752
|
-
let value;
|
|
1753
|
-
if (trimmed === "null") {
|
|
1754
|
-
value = null;
|
|
1755
|
-
} else if (trimmed === "true") {
|
|
1756
|
-
value = true;
|
|
1757
|
-
} else if (trimmed === "false") {
|
|
1758
|
-
value = false;
|
|
1759
|
-
} else {
|
|
1760
|
-
const parsed = tryParseJson(trimmed);
|
|
1761
|
-
value = parsed !== null ? parsed : trimmed;
|
|
1762
|
-
}
|
|
1763
|
-
return {
|
|
1764
|
-
kind: "annotation",
|
|
1765
|
-
annotationKind: "defaultValue",
|
|
1766
|
-
value,
|
|
1767
|
-
provenance
|
|
1768
|
-
};
|
|
1769
|
-
}
|
|
1770
|
-
function isMemberTargetDisplayName(text) {
|
|
1771
|
-
return parseMemberTargetDisplayName(text) !== null;
|
|
1772
|
-
}
|
|
1773
|
-
function parseMemberTargetDisplayName(text) {
|
|
1774
|
-
const match = /^:([^\s]+)\s+([\s\S]+)$/.exec(text);
|
|
1775
|
-
if (!match?.[1] || !match[2]) return null;
|
|
1776
|
-
return { target: match[1], label: match[2].trim() };
|
|
1777
|
-
}
|
|
1778
|
-
function provenanceForComment(range, sourceFile, file, tagName) {
|
|
1779
|
-
const { line, character } = sourceFile.getLineAndCharacterOfPosition(range.pos);
|
|
1780
|
-
return {
|
|
1781
|
-
surface: "tsdoc",
|
|
1782
|
-
file,
|
|
1783
|
-
line: line + 1,
|
|
1784
|
-
column: character,
|
|
1785
|
-
tagName: "@" + tagName
|
|
1786
|
-
};
|
|
1787
|
-
}
|
|
1788
|
-
function provenanceForJSDocTag(tag, file) {
|
|
1789
|
-
const sourceFile = tag.getSourceFile();
|
|
1790
|
-
const { line, character } = sourceFile.getLineAndCharacterOfPosition(tag.getStart());
|
|
1791
|
-
return {
|
|
1792
|
-
surface: "tsdoc",
|
|
1793
|
-
file,
|
|
1794
|
-
line: line + 1,
|
|
1795
|
-
column: character,
|
|
1796
|
-
tagName: "@" + tag.tagName.text
|
|
1797
|
-
};
|
|
1798
|
-
}
|
|
1799
|
-
function getTagCommentText(tag) {
|
|
1800
|
-
if (tag.comment === void 0) {
|
|
1801
|
-
return void 0;
|
|
1802
|
-
}
|
|
1803
|
-
if (typeof tag.comment === "string") {
|
|
1804
|
-
return tag.comment;
|
|
2045
|
+
if (typeof tag.comment === "string") {
|
|
2046
|
+
return tag.comment;
|
|
1805
2047
|
}
|
|
1806
2048
|
return ts.getTextOfJSDocComment(tag.comment);
|
|
1807
2049
|
}
|
|
1808
|
-
var ts, import_tsdoc, import_core3,
|
|
2050
|
+
var ts, import_analysis, import_tsdoc, import_core3, TAGS_REQUIRING_RAW_TEXT, SYNTHETIC_TYPE_FORMAT_FLAGS, parserCache, parseResultCache;
|
|
1809
2051
|
var init_tsdoc_parser = __esm({
|
|
1810
2052
|
"src/analyzer/tsdoc-parser.ts"() {
|
|
1811
2053
|
"use strict";
|
|
1812
2054
|
ts = __toESM(require("typescript"), 1);
|
|
2055
|
+
import_analysis = require("@formspec/analysis");
|
|
1813
2056
|
import_tsdoc = require("@microsoft/tsdoc");
|
|
1814
2057
|
import_core3 = require("@formspec/core");
|
|
1815
|
-
init_json_utils();
|
|
1816
|
-
NUMERIC_CONSTRAINT_MAP = {
|
|
1817
|
-
minimum: "minimum",
|
|
1818
|
-
maximum: "maximum",
|
|
1819
|
-
exclusiveMinimum: "exclusiveMinimum",
|
|
1820
|
-
exclusiveMaximum: "exclusiveMaximum",
|
|
1821
|
-
multipleOf: "multipleOf"
|
|
1822
|
-
};
|
|
1823
|
-
LENGTH_CONSTRAINT_MAP = {
|
|
1824
|
-
minLength: "minLength",
|
|
1825
|
-
maxLength: "maxLength",
|
|
1826
|
-
minItems: "minItems",
|
|
1827
|
-
maxItems: "maxItems"
|
|
1828
|
-
};
|
|
1829
2058
|
TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions", "defaultValue"]);
|
|
2059
|
+
SYNTHETIC_TYPE_FORMAT_FLAGS = ts.TypeFormatFlags.NoTruncation | ts.TypeFormatFlags.UseAliasDefinedOutsideCurrentScope;
|
|
1830
2060
|
parserCache = /* @__PURE__ */ new Map();
|
|
2061
|
+
parseResultCache = /* @__PURE__ */ new Map();
|
|
1831
2062
|
}
|
|
1832
2063
|
});
|
|
1833
2064
|
|
|
1834
2065
|
// src/analyzer/jsdoc-constraints.ts
|
|
2066
|
+
function extractJSDocParseResult(node, file = "", options) {
|
|
2067
|
+
return parseTSDocTags(node, file, options);
|
|
2068
|
+
}
|
|
1835
2069
|
function extractJSDocConstraintNodes(node, file = "", options) {
|
|
1836
|
-
const result =
|
|
2070
|
+
const result = extractJSDocParseResult(node, file, options);
|
|
1837
2071
|
return [...result.constraints];
|
|
1838
2072
|
}
|
|
1839
2073
|
function extractJSDocAnnotationNodes(node, file = "", options) {
|
|
1840
|
-
const result =
|
|
2074
|
+
const result = extractJSDocParseResult(node, file, options);
|
|
1841
2075
|
return [...result.annotations];
|
|
1842
2076
|
}
|
|
1843
2077
|
function extractDefaultValueAnnotation(initializer, file = "") {
|
|
@@ -1889,13 +2123,16 @@ function isObjectType(type) {
|
|
|
1889
2123
|
function isTypeReference(type) {
|
|
1890
2124
|
return !!(type.flags & ts3.TypeFlags.Object) && !!(type.objectFlags & ts3.ObjectFlags.Reference);
|
|
1891
2125
|
}
|
|
1892
|
-
function makeParseOptions(extensionRegistry, fieldType) {
|
|
1893
|
-
if (extensionRegistry === void 0 && fieldType === void 0) {
|
|
2126
|
+
function makeParseOptions(extensionRegistry, fieldType, checker, subjectType, hostType) {
|
|
2127
|
+
if (extensionRegistry === void 0 && fieldType === void 0 && checker === void 0 && subjectType === void 0 && hostType === void 0) {
|
|
1894
2128
|
return void 0;
|
|
1895
2129
|
}
|
|
1896
2130
|
return {
|
|
1897
2131
|
...extensionRegistry !== void 0 && { extensionRegistry },
|
|
1898
|
-
...fieldType !== void 0 && { fieldType }
|
|
2132
|
+
...fieldType !== void 0 && { fieldType },
|
|
2133
|
+
...checker !== void 0 && { checker },
|
|
2134
|
+
...subjectType !== void 0 && { subjectType },
|
|
2135
|
+
...hostType !== void 0 && { hostType }
|
|
1899
2136
|
};
|
|
1900
2137
|
}
|
|
1901
2138
|
function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
|
|
@@ -1903,11 +2140,15 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
|
|
|
1903
2140
|
const fields = [];
|
|
1904
2141
|
const fieldLayouts = [];
|
|
1905
2142
|
const typeRegistry = {};
|
|
1906
|
-
const
|
|
2143
|
+
const diagnostics = [];
|
|
2144
|
+
const classType = checker.getTypeAtLocation(classDecl);
|
|
2145
|
+
const classDoc = extractJSDocParseResult(
|
|
1907
2146
|
classDecl,
|
|
1908
2147
|
file,
|
|
1909
|
-
makeParseOptions(extensionRegistry)
|
|
2148
|
+
makeParseOptions(extensionRegistry, void 0, checker, classType, classType)
|
|
1910
2149
|
);
|
|
2150
|
+
const annotations = [...classDoc.annotations];
|
|
2151
|
+
diagnostics.push(...classDoc.diagnostics);
|
|
1911
2152
|
const visiting = /* @__PURE__ */ new Set();
|
|
1912
2153
|
const instanceMethods = [];
|
|
1913
2154
|
const staticMethods = [];
|
|
@@ -1919,6 +2160,8 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
|
|
|
1919
2160
|
file,
|
|
1920
2161
|
typeRegistry,
|
|
1921
2162
|
visiting,
|
|
2163
|
+
diagnostics,
|
|
2164
|
+
classType,
|
|
1922
2165
|
extensionRegistry
|
|
1923
2166
|
);
|
|
1924
2167
|
if (fieldNode) {
|
|
@@ -1943,6 +2186,7 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
|
|
|
1943
2186
|
fieldLayouts,
|
|
1944
2187
|
typeRegistry,
|
|
1945
2188
|
...annotations.length > 0 && { annotations },
|
|
2189
|
+
...diagnostics.length > 0 && { diagnostics },
|
|
1946
2190
|
instanceMethods,
|
|
1947
2191
|
staticMethods
|
|
1948
2192
|
};
|
|
@@ -1951,11 +2195,15 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
|
|
|
1951
2195
|
const name = interfaceDecl.name.text;
|
|
1952
2196
|
const fields = [];
|
|
1953
2197
|
const typeRegistry = {};
|
|
1954
|
-
const
|
|
2198
|
+
const diagnostics = [];
|
|
2199
|
+
const interfaceType = checker.getTypeAtLocation(interfaceDecl);
|
|
2200
|
+
const interfaceDoc = extractJSDocParseResult(
|
|
1955
2201
|
interfaceDecl,
|
|
1956
2202
|
file,
|
|
1957
|
-
makeParseOptions(extensionRegistry)
|
|
2203
|
+
makeParseOptions(extensionRegistry, void 0, checker, interfaceType, interfaceType)
|
|
1958
2204
|
);
|
|
2205
|
+
const annotations = [...interfaceDoc.annotations];
|
|
2206
|
+
diagnostics.push(...interfaceDoc.diagnostics);
|
|
1959
2207
|
const visiting = /* @__PURE__ */ new Set();
|
|
1960
2208
|
for (const member of interfaceDecl.members) {
|
|
1961
2209
|
if (ts3.isPropertySignature(member)) {
|
|
@@ -1965,6 +2213,8 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
|
|
|
1965
2213
|
file,
|
|
1966
2214
|
typeRegistry,
|
|
1967
2215
|
visiting,
|
|
2216
|
+
diagnostics,
|
|
2217
|
+
interfaceType,
|
|
1968
2218
|
extensionRegistry
|
|
1969
2219
|
);
|
|
1970
2220
|
if (fieldNode) {
|
|
@@ -1979,6 +2229,7 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
|
|
|
1979
2229
|
fieldLayouts,
|
|
1980
2230
|
typeRegistry,
|
|
1981
2231
|
...annotations.length > 0 && { annotations },
|
|
2232
|
+
...diagnostics.length > 0 && { diagnostics },
|
|
1982
2233
|
instanceMethods: [],
|
|
1983
2234
|
staticMethods: []
|
|
1984
2235
|
};
|
|
@@ -1996,11 +2247,15 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
|
|
|
1996
2247
|
const name = typeAlias.name.text;
|
|
1997
2248
|
const fields = [];
|
|
1998
2249
|
const typeRegistry = {};
|
|
1999
|
-
const
|
|
2250
|
+
const diagnostics = [];
|
|
2251
|
+
const aliasType = checker.getTypeAtLocation(typeAlias);
|
|
2252
|
+
const typeAliasDoc = extractJSDocParseResult(
|
|
2000
2253
|
typeAlias,
|
|
2001
2254
|
file,
|
|
2002
|
-
makeParseOptions(extensionRegistry)
|
|
2255
|
+
makeParseOptions(extensionRegistry, void 0, checker, aliasType, aliasType)
|
|
2003
2256
|
);
|
|
2257
|
+
const annotations = [...typeAliasDoc.annotations];
|
|
2258
|
+
diagnostics.push(...typeAliasDoc.diagnostics);
|
|
2004
2259
|
const visiting = /* @__PURE__ */ new Set();
|
|
2005
2260
|
for (const member of typeAlias.type.members) {
|
|
2006
2261
|
if (ts3.isPropertySignature(member)) {
|
|
@@ -2010,6 +2265,8 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
|
|
|
2010
2265
|
file,
|
|
2011
2266
|
typeRegistry,
|
|
2012
2267
|
visiting,
|
|
2268
|
+
diagnostics,
|
|
2269
|
+
aliasType,
|
|
2013
2270
|
extensionRegistry
|
|
2014
2271
|
);
|
|
2015
2272
|
if (fieldNode) {
|
|
@@ -2025,12 +2282,13 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
|
|
|
2025
2282
|
fieldLayouts: fields.map(() => ({})),
|
|
2026
2283
|
typeRegistry,
|
|
2027
2284
|
...annotations.length > 0 && { annotations },
|
|
2285
|
+
...diagnostics.length > 0 && { diagnostics },
|
|
2028
2286
|
instanceMethods: [],
|
|
2029
2287
|
staticMethods: []
|
|
2030
2288
|
}
|
|
2031
2289
|
};
|
|
2032
2290
|
}
|
|
2033
|
-
function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2291
|
+
function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, extensionRegistry) {
|
|
2034
2292
|
if (!ts3.isIdentifier(prop.name)) {
|
|
2035
2293
|
return null;
|
|
2036
2294
|
}
|
|
@@ -2045,7 +2303,8 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extension
|
|
|
2045
2303
|
typeRegistry,
|
|
2046
2304
|
visiting,
|
|
2047
2305
|
prop,
|
|
2048
|
-
extensionRegistry
|
|
2306
|
+
extensionRegistry,
|
|
2307
|
+
diagnostics
|
|
2049
2308
|
);
|
|
2050
2309
|
const constraints = [];
|
|
2051
2310
|
if (prop.type && !shouldEmitPrimitiveAliasDefinition(prop.type, checker)) {
|
|
@@ -2053,13 +2312,15 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extension
|
|
|
2053
2312
|
...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
|
|
2054
2313
|
);
|
|
2055
2314
|
}
|
|
2056
|
-
|
|
2057
|
-
|
|
2315
|
+
const docResult = extractJSDocParseResult(
|
|
2316
|
+
prop,
|
|
2317
|
+
file,
|
|
2318
|
+
makeParseOptions(extensionRegistry, type, checker, tsType, hostType)
|
|
2058
2319
|
);
|
|
2320
|
+
constraints.push(...docResult.constraints);
|
|
2321
|
+
diagnostics.push(...docResult.diagnostics);
|
|
2059
2322
|
let annotations = [];
|
|
2060
|
-
annotations.push(
|
|
2061
|
-
...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
2062
|
-
);
|
|
2323
|
+
annotations.push(...docResult.annotations);
|
|
2063
2324
|
const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
|
|
2064
2325
|
if (defaultAnnotation && !annotations.some((a) => a.annotationKind === "defaultValue")) {
|
|
2065
2326
|
annotations.push(defaultAnnotation);
|
|
@@ -2075,7 +2336,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extension
|
|
|
2075
2336
|
provenance
|
|
2076
2337
|
};
|
|
2077
2338
|
}
|
|
2078
|
-
function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2339
|
+
function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, extensionRegistry) {
|
|
2079
2340
|
if (!ts3.isIdentifier(prop.name)) {
|
|
2080
2341
|
return null;
|
|
2081
2342
|
}
|
|
@@ -2090,7 +2351,8 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
|
|
|
2090
2351
|
typeRegistry,
|
|
2091
2352
|
visiting,
|
|
2092
2353
|
prop,
|
|
2093
|
-
extensionRegistry
|
|
2354
|
+
extensionRegistry,
|
|
2355
|
+
diagnostics
|
|
2094
2356
|
);
|
|
2095
2357
|
const constraints = [];
|
|
2096
2358
|
if (prop.type && !shouldEmitPrimitiveAliasDefinition(prop.type, checker)) {
|
|
@@ -2098,13 +2360,15 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
|
|
|
2098
2360
|
...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
|
|
2099
2361
|
);
|
|
2100
2362
|
}
|
|
2101
|
-
|
|
2102
|
-
|
|
2363
|
+
const docResult = extractJSDocParseResult(
|
|
2364
|
+
prop,
|
|
2365
|
+
file,
|
|
2366
|
+
makeParseOptions(extensionRegistry, type, checker, tsType, hostType)
|
|
2103
2367
|
);
|
|
2368
|
+
constraints.push(...docResult.constraints);
|
|
2369
|
+
diagnostics.push(...docResult.diagnostics);
|
|
2104
2370
|
let annotations = [];
|
|
2105
|
-
annotations.push(
|
|
2106
|
-
...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
2107
|
-
);
|
|
2371
|
+
annotations.push(...docResult.annotations);
|
|
2108
2372
|
({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
|
|
2109
2373
|
return {
|
|
2110
2374
|
kind: "field",
|
|
@@ -2233,7 +2497,7 @@ function getTypeNodeRegistrationName(typeNode) {
|
|
|
2233
2497
|
}
|
|
2234
2498
|
return null;
|
|
2235
2499
|
}
|
|
2236
|
-
function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
2500
|
+
function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
|
|
2237
2501
|
const customType = resolveRegisteredCustomType(sourceNode, extensionRegistry, checker);
|
|
2238
2502
|
if (customType) {
|
|
2239
2503
|
return customType;
|
|
@@ -2245,7 +2509,8 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
|
|
|
2245
2509
|
typeRegistry,
|
|
2246
2510
|
visiting,
|
|
2247
2511
|
sourceNode,
|
|
2248
|
-
extensionRegistry
|
|
2512
|
+
extensionRegistry,
|
|
2513
|
+
diagnostics
|
|
2249
2514
|
);
|
|
2250
2515
|
if (primitiveAlias) {
|
|
2251
2516
|
return primitiveAlias;
|
|
@@ -2288,7 +2553,8 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
|
|
|
2288
2553
|
typeRegistry,
|
|
2289
2554
|
visiting,
|
|
2290
2555
|
sourceNode,
|
|
2291
|
-
extensionRegistry
|
|
2556
|
+
extensionRegistry,
|
|
2557
|
+
diagnostics
|
|
2292
2558
|
);
|
|
2293
2559
|
}
|
|
2294
2560
|
if (checker.isArrayType(type)) {
|
|
@@ -2299,15 +2565,24 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
|
|
|
2299
2565
|
typeRegistry,
|
|
2300
2566
|
visiting,
|
|
2301
2567
|
sourceNode,
|
|
2302
|
-
extensionRegistry
|
|
2568
|
+
extensionRegistry,
|
|
2569
|
+
diagnostics
|
|
2303
2570
|
);
|
|
2304
2571
|
}
|
|
2305
2572
|
if (isObjectType(type)) {
|
|
2306
|
-
return resolveObjectType(
|
|
2573
|
+
return resolveObjectType(
|
|
2574
|
+
type,
|
|
2575
|
+
checker,
|
|
2576
|
+
file,
|
|
2577
|
+
typeRegistry,
|
|
2578
|
+
visiting,
|
|
2579
|
+
extensionRegistry,
|
|
2580
|
+
diagnostics
|
|
2581
|
+
);
|
|
2307
2582
|
}
|
|
2308
2583
|
return { kind: "primitive", primitiveKind: "string" };
|
|
2309
2584
|
}
|
|
2310
|
-
function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
2585
|
+
function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
|
|
2311
2586
|
if (!(type.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null))) {
|
|
2312
2587
|
return null;
|
|
2313
2588
|
}
|
|
@@ -2335,7 +2610,8 @@ function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiti
|
|
|
2335
2610
|
file,
|
|
2336
2611
|
typeRegistry,
|
|
2337
2612
|
visiting,
|
|
2338
|
-
extensionRegistry
|
|
2613
|
+
extensionRegistry,
|
|
2614
|
+
diagnostics
|
|
2339
2615
|
),
|
|
2340
2616
|
...constraints.length > 0 && { constraints },
|
|
2341
2617
|
...annotations.length > 0 && { annotations },
|
|
@@ -2362,7 +2638,7 @@ function shouldEmitPrimitiveAliasDefinition(typeNode, checker) {
|
|
|
2362
2638
|
const resolved = checker.getTypeFromTypeNode(aliasDecl.type);
|
|
2363
2639
|
return !!(resolved.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null));
|
|
2364
2640
|
}
|
|
2365
|
-
function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2641
|
+
function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
|
|
2366
2642
|
const nestedAliasDecl = type.aliasSymbol?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
2367
2643
|
if (nestedAliasDecl !== void 0) {
|
|
2368
2644
|
return resolveAliasedPrimitiveTarget(
|
|
@@ -2371,12 +2647,22 @@ function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiti
|
|
|
2371
2647
|
file,
|
|
2372
2648
|
typeRegistry,
|
|
2373
2649
|
visiting,
|
|
2374
|
-
extensionRegistry
|
|
2650
|
+
extensionRegistry,
|
|
2651
|
+
diagnostics
|
|
2375
2652
|
);
|
|
2376
2653
|
}
|
|
2377
|
-
return resolveTypeNode(
|
|
2654
|
+
return resolveTypeNode(
|
|
2655
|
+
type,
|
|
2656
|
+
checker,
|
|
2657
|
+
file,
|
|
2658
|
+
typeRegistry,
|
|
2659
|
+
visiting,
|
|
2660
|
+
void 0,
|
|
2661
|
+
extensionRegistry,
|
|
2662
|
+
diagnostics
|
|
2663
|
+
);
|
|
2378
2664
|
}
|
|
2379
|
-
function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
2665
|
+
function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
|
|
2380
2666
|
const typeName = getNamedTypeName(type);
|
|
2381
2667
|
const namedDecl = getNamedTypeDeclaration(type);
|
|
2382
2668
|
if (typeName && typeName in typeRegistry) {
|
|
@@ -2466,7 +2752,8 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
2466
2752
|
typeRegistry,
|
|
2467
2753
|
visiting,
|
|
2468
2754
|
nonNullMembers[0].sourceNode ?? sourceNode,
|
|
2469
|
-
extensionRegistry
|
|
2755
|
+
extensionRegistry,
|
|
2756
|
+
diagnostics
|
|
2470
2757
|
);
|
|
2471
2758
|
const result = hasNull ? {
|
|
2472
2759
|
kind: "union",
|
|
@@ -2482,7 +2769,8 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
2482
2769
|
typeRegistry,
|
|
2483
2770
|
visiting,
|
|
2484
2771
|
memberSourceNode ?? sourceNode,
|
|
2485
|
-
extensionRegistry
|
|
2772
|
+
extensionRegistry,
|
|
2773
|
+
diagnostics
|
|
2486
2774
|
)
|
|
2487
2775
|
);
|
|
2488
2776
|
if (hasNull) {
|
|
@@ -2490,7 +2778,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
2490
2778
|
}
|
|
2491
2779
|
return registerNamed({ kind: "union", members });
|
|
2492
2780
|
}
|
|
2493
|
-
function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
2781
|
+
function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
|
|
2494
2782
|
const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
|
|
2495
2783
|
const elementType = typeArgs?.[0];
|
|
2496
2784
|
const elementSourceNode = extractArrayElementTypeNode(sourceNode, checker);
|
|
@@ -2501,11 +2789,12 @@ function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
2501
2789
|
typeRegistry,
|
|
2502
2790
|
visiting,
|
|
2503
2791
|
elementSourceNode,
|
|
2504
|
-
extensionRegistry
|
|
2792
|
+
extensionRegistry,
|
|
2793
|
+
diagnostics
|
|
2505
2794
|
) : { kind: "primitive", primitiveKind: "string" };
|
|
2506
2795
|
return { kind: "array", items };
|
|
2507
2796
|
}
|
|
2508
|
-
function tryResolveRecordType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2797
|
+
function tryResolveRecordType(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
|
|
2509
2798
|
if (type.getProperties().length > 0) {
|
|
2510
2799
|
return null;
|
|
2511
2800
|
}
|
|
@@ -2520,7 +2809,8 @@ function tryResolveRecordType(type, checker, file, typeRegistry, visiting, exten
|
|
|
2520
2809
|
typeRegistry,
|
|
2521
2810
|
visiting,
|
|
2522
2811
|
void 0,
|
|
2523
|
-
extensionRegistry
|
|
2812
|
+
extensionRegistry,
|
|
2813
|
+
diagnostics
|
|
2524
2814
|
);
|
|
2525
2815
|
return { kind: "record", valueType };
|
|
2526
2816
|
}
|
|
@@ -2549,7 +2839,7 @@ function typeNodeContainsReference(type, targetName) {
|
|
|
2549
2839
|
}
|
|
2550
2840
|
}
|
|
2551
2841
|
}
|
|
2552
|
-
function resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2842
|
+
function resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
|
|
2553
2843
|
const typeName = getNamedTypeName(type);
|
|
2554
2844
|
const namedTypeName = typeName ?? void 0;
|
|
2555
2845
|
const namedDecl = getNamedTypeDeclaration(type);
|
|
@@ -2586,7 +2876,8 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
2586
2876
|
file,
|
|
2587
2877
|
typeRegistry,
|
|
2588
2878
|
visiting,
|
|
2589
|
-
extensionRegistry
|
|
2879
|
+
extensionRegistry,
|
|
2880
|
+
diagnostics
|
|
2590
2881
|
);
|
|
2591
2882
|
if (recordNode) {
|
|
2592
2883
|
visiting.delete(type);
|
|
@@ -2614,6 +2905,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
2614
2905
|
file,
|
|
2615
2906
|
typeRegistry,
|
|
2616
2907
|
visiting,
|
|
2908
|
+
diagnostics ?? [],
|
|
2617
2909
|
extensionRegistry
|
|
2618
2910
|
);
|
|
2619
2911
|
for (const prop of type.getProperties()) {
|
|
@@ -2628,7 +2920,8 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
2628
2920
|
typeRegistry,
|
|
2629
2921
|
visiting,
|
|
2630
2922
|
declaration,
|
|
2631
|
-
extensionRegistry
|
|
2923
|
+
extensionRegistry,
|
|
2924
|
+
diagnostics
|
|
2632
2925
|
);
|
|
2633
2926
|
const fieldNodeInfo = fieldInfoMap?.get(prop.name);
|
|
2634
2927
|
properties.push({
|
|
@@ -2658,7 +2951,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
2658
2951
|
}
|
|
2659
2952
|
return objectNode;
|
|
2660
2953
|
}
|
|
2661
|
-
function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2954
|
+
function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, diagnostics, extensionRegistry) {
|
|
2662
2955
|
const symbols = [type.getSymbol(), type.aliasSymbol].filter(
|
|
2663
2956
|
(s) => s?.declarations != null && s.declarations.length > 0
|
|
2664
2957
|
);
|
|
@@ -2668,6 +2961,7 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
2668
2961
|
const classDecl = declarations.find(ts3.isClassDeclaration);
|
|
2669
2962
|
if (classDecl) {
|
|
2670
2963
|
const map = /* @__PURE__ */ new Map();
|
|
2964
|
+
const hostType = checker.getTypeAtLocation(classDecl);
|
|
2671
2965
|
for (const member of classDecl.members) {
|
|
2672
2966
|
if (ts3.isPropertyDeclaration(member) && ts3.isIdentifier(member.name)) {
|
|
2673
2967
|
const fieldNode = analyzeFieldToIR(
|
|
@@ -2676,6 +2970,8 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
2676
2970
|
file,
|
|
2677
2971
|
typeRegistry,
|
|
2678
2972
|
visiting,
|
|
2973
|
+
diagnostics,
|
|
2974
|
+
hostType,
|
|
2679
2975
|
extensionRegistry
|
|
2680
2976
|
);
|
|
2681
2977
|
if (fieldNode) {
|
|
@@ -2697,6 +2993,8 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
2697
2993
|
file,
|
|
2698
2994
|
typeRegistry,
|
|
2699
2995
|
visiting,
|
|
2996
|
+
checker.getTypeAtLocation(interfaceDecl),
|
|
2997
|
+
diagnostics,
|
|
2700
2998
|
extensionRegistry
|
|
2701
2999
|
);
|
|
2702
3000
|
}
|
|
@@ -2708,6 +3006,8 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
2708
3006
|
file,
|
|
2709
3007
|
typeRegistry,
|
|
2710
3008
|
visiting,
|
|
3009
|
+
checker.getTypeAtLocation(typeAliasDecl),
|
|
3010
|
+
diagnostics,
|
|
2711
3011
|
extensionRegistry
|
|
2712
3012
|
);
|
|
2713
3013
|
}
|
|
@@ -2757,7 +3057,7 @@ function isNullishTypeNode(typeNode) {
|
|
|
2757
3057
|
}
|
|
2758
3058
|
return ts3.isLiteralTypeNode(typeNode) && (typeNode.literal.kind === ts3.SyntaxKind.NullKeyword || typeNode.literal.kind === ts3.SyntaxKind.UndefinedKeyword);
|
|
2759
3059
|
}
|
|
2760
|
-
function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
3060
|
+
function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, hostType, diagnostics, extensionRegistry) {
|
|
2761
3061
|
const map = /* @__PURE__ */ new Map();
|
|
2762
3062
|
for (const member of members) {
|
|
2763
3063
|
if (ts3.isPropertySignature(member)) {
|
|
@@ -2767,6 +3067,8 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, e
|
|
|
2767
3067
|
file,
|
|
2768
3068
|
typeRegistry,
|
|
2769
3069
|
visiting,
|
|
3070
|
+
diagnostics,
|
|
3071
|
+
hostType,
|
|
2770
3072
|
extensionRegistry
|
|
2771
3073
|
);
|
|
2772
3074
|
if (fieldNode) {
|
|
@@ -3021,759 +3323,41 @@ var init_program = __esm({
|
|
|
3021
3323
|
});
|
|
3022
3324
|
|
|
3023
3325
|
// src/validate/constraint-validator.ts
|
|
3024
|
-
function
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
}
|
|
3033
|
-
function addTypeMismatch(ctx, message, primary) {
|
|
3034
|
-
ctx.diagnostics.push({
|
|
3035
|
-
code: "TYPE_MISMATCH",
|
|
3036
|
-
message,
|
|
3037
|
-
severity: "error",
|
|
3038
|
-
primaryLocation: primary,
|
|
3039
|
-
relatedLocations: []
|
|
3040
|
-
});
|
|
3041
|
-
}
|
|
3042
|
-
function addUnknownExtension(ctx, message, primary) {
|
|
3043
|
-
ctx.diagnostics.push({
|
|
3044
|
-
code: "UNKNOWN_EXTENSION",
|
|
3045
|
-
message,
|
|
3046
|
-
severity: "warning",
|
|
3047
|
-
primaryLocation: primary,
|
|
3048
|
-
relatedLocations: []
|
|
3049
|
-
});
|
|
3050
|
-
}
|
|
3051
|
-
function addUnknownPathTarget(ctx, message, primary) {
|
|
3052
|
-
ctx.diagnostics.push({
|
|
3053
|
-
code: "UNKNOWN_PATH_TARGET",
|
|
3054
|
-
message,
|
|
3055
|
-
severity: "error",
|
|
3056
|
-
primaryLocation: primary,
|
|
3057
|
-
relatedLocations: []
|
|
3058
|
-
});
|
|
3059
|
-
}
|
|
3060
|
-
function addConstraintBroadening(ctx, message, primary, related) {
|
|
3061
|
-
ctx.diagnostics.push({
|
|
3062
|
-
code: "CONSTRAINT_BROADENING",
|
|
3063
|
-
message,
|
|
3064
|
-
severity: "error",
|
|
3065
|
-
primaryLocation: primary,
|
|
3066
|
-
relatedLocations: [related]
|
|
3067
|
-
});
|
|
3068
|
-
}
|
|
3069
|
-
function getExtensionIdFromConstraintId(constraintId) {
|
|
3070
|
-
const separator = constraintId.lastIndexOf("/");
|
|
3071
|
-
if (separator <= 0) {
|
|
3072
|
-
return null;
|
|
3073
|
-
}
|
|
3074
|
-
return constraintId.slice(0, separator);
|
|
3075
|
-
}
|
|
3076
|
-
function findNumeric(constraints, constraintKind) {
|
|
3077
|
-
return constraints.find((c) => c.constraintKind === constraintKind);
|
|
3078
|
-
}
|
|
3079
|
-
function findLength(constraints, constraintKind) {
|
|
3080
|
-
return constraints.find((c) => c.constraintKind === constraintKind);
|
|
3081
|
-
}
|
|
3082
|
-
function findAllowedMembers(constraints) {
|
|
3083
|
-
return constraints.filter(
|
|
3084
|
-
(c) => c.constraintKind === "allowedMembers"
|
|
3085
|
-
);
|
|
3086
|
-
}
|
|
3087
|
-
function findConstConstraints(constraints) {
|
|
3088
|
-
return constraints.filter(
|
|
3089
|
-
(c) => c.constraintKind === "const"
|
|
3090
|
-
);
|
|
3091
|
-
}
|
|
3092
|
-
function jsonValueEquals(left, right) {
|
|
3093
|
-
if (left === right) {
|
|
3094
|
-
return true;
|
|
3095
|
-
}
|
|
3096
|
-
if (Array.isArray(left) || Array.isArray(right)) {
|
|
3097
|
-
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
|
|
3098
|
-
return false;
|
|
3099
|
-
}
|
|
3100
|
-
return left.every((item, index) => jsonValueEquals(item, right[index]));
|
|
3101
|
-
}
|
|
3102
|
-
if (isJsonObject(left) || isJsonObject(right)) {
|
|
3103
|
-
if (!isJsonObject(left) || !isJsonObject(right)) {
|
|
3104
|
-
return false;
|
|
3105
|
-
}
|
|
3106
|
-
const leftKeys = Object.keys(left).sort();
|
|
3107
|
-
const rightKeys = Object.keys(right).sort();
|
|
3108
|
-
if (leftKeys.length !== rightKeys.length) {
|
|
3109
|
-
return false;
|
|
3110
|
-
}
|
|
3111
|
-
return leftKeys.every((key, index) => {
|
|
3112
|
-
const rightKey = rightKeys[index];
|
|
3113
|
-
if (rightKey !== key) {
|
|
3114
|
-
return false;
|
|
3115
|
-
}
|
|
3116
|
-
const leftValue = left[key];
|
|
3117
|
-
const rightValue = right[rightKey];
|
|
3118
|
-
return leftValue !== void 0 && rightValue !== void 0 && jsonValueEquals(leftValue, rightValue);
|
|
3119
|
-
});
|
|
3120
|
-
}
|
|
3121
|
-
return false;
|
|
3122
|
-
}
|
|
3123
|
-
function isJsonObject(value) {
|
|
3124
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3125
|
-
}
|
|
3126
|
-
function isOrderedBoundConstraint(constraint) {
|
|
3127
|
-
return constraint.constraintKind === "minimum" || constraint.constraintKind === "exclusiveMinimum" || constraint.constraintKind === "minLength" || constraint.constraintKind === "minItems" || constraint.constraintKind === "maximum" || constraint.constraintKind === "exclusiveMaximum" || constraint.constraintKind === "maxLength" || constraint.constraintKind === "maxItems";
|
|
3128
|
-
}
|
|
3129
|
-
function pathKey(constraint) {
|
|
3130
|
-
return constraint.path?.segments.join(".") ?? "";
|
|
3131
|
-
}
|
|
3132
|
-
function orderedBoundFamily(kind) {
|
|
3133
|
-
switch (kind) {
|
|
3134
|
-
case "minimum":
|
|
3135
|
-
case "exclusiveMinimum":
|
|
3136
|
-
return "numeric-lower";
|
|
3137
|
-
case "maximum":
|
|
3138
|
-
case "exclusiveMaximum":
|
|
3139
|
-
return "numeric-upper";
|
|
3140
|
-
case "minLength":
|
|
3141
|
-
return "minLength";
|
|
3142
|
-
case "minItems":
|
|
3143
|
-
return "minItems";
|
|
3144
|
-
case "maxLength":
|
|
3145
|
-
return "maxLength";
|
|
3146
|
-
case "maxItems":
|
|
3147
|
-
return "maxItems";
|
|
3148
|
-
default: {
|
|
3149
|
-
const _exhaustive = kind;
|
|
3150
|
-
return _exhaustive;
|
|
3151
|
-
}
|
|
3152
|
-
}
|
|
3153
|
-
}
|
|
3154
|
-
function isNumericLowerKind(kind) {
|
|
3155
|
-
return kind === "minimum" || kind === "exclusiveMinimum";
|
|
3156
|
-
}
|
|
3157
|
-
function isNumericUpperKind(kind) {
|
|
3158
|
-
return kind === "maximum" || kind === "exclusiveMaximum";
|
|
3159
|
-
}
|
|
3160
|
-
function describeConstraintTag(constraint) {
|
|
3161
|
-
return `@${constraint.constraintKind}`;
|
|
3162
|
-
}
|
|
3163
|
-
function compareConstraintStrength(current, previous) {
|
|
3164
|
-
const family = orderedBoundFamily(current.constraintKind);
|
|
3165
|
-
if (family === "numeric-lower") {
|
|
3166
|
-
if (!isNumericLowerKind(current.constraintKind) || !isNumericLowerKind(previous.constraintKind)) {
|
|
3167
|
-
throw new Error("numeric-lower family received non-numeric lower-bound constraint");
|
|
3168
|
-
}
|
|
3169
|
-
if (current.value !== previous.value) {
|
|
3170
|
-
return current.value > previous.value ? 1 : -1;
|
|
3171
|
-
}
|
|
3172
|
-
if (current.constraintKind === "exclusiveMinimum" && previous.constraintKind === "minimum") {
|
|
3173
|
-
return 1;
|
|
3174
|
-
}
|
|
3175
|
-
if (current.constraintKind === "minimum" && previous.constraintKind === "exclusiveMinimum") {
|
|
3176
|
-
return -1;
|
|
3177
|
-
}
|
|
3178
|
-
return 0;
|
|
3179
|
-
}
|
|
3180
|
-
if (family === "numeric-upper") {
|
|
3181
|
-
if (!isNumericUpperKind(current.constraintKind) || !isNumericUpperKind(previous.constraintKind)) {
|
|
3182
|
-
throw new Error("numeric-upper family received non-numeric upper-bound constraint");
|
|
3183
|
-
}
|
|
3184
|
-
if (current.value !== previous.value) {
|
|
3185
|
-
return current.value < previous.value ? 1 : -1;
|
|
3186
|
-
}
|
|
3187
|
-
if (current.constraintKind === "exclusiveMaximum" && previous.constraintKind === "maximum") {
|
|
3188
|
-
return 1;
|
|
3189
|
-
}
|
|
3190
|
-
if (current.constraintKind === "maximum" && previous.constraintKind === "exclusiveMaximum") {
|
|
3191
|
-
return -1;
|
|
3192
|
-
}
|
|
3193
|
-
return 0;
|
|
3194
|
-
}
|
|
3195
|
-
switch (family) {
|
|
3196
|
-
case "minLength":
|
|
3197
|
-
case "minItems":
|
|
3198
|
-
if (current.value === previous.value) {
|
|
3199
|
-
return 0;
|
|
3200
|
-
}
|
|
3201
|
-
return current.value > previous.value ? 1 : -1;
|
|
3202
|
-
case "maxLength":
|
|
3203
|
-
case "maxItems":
|
|
3204
|
-
if (current.value === previous.value) {
|
|
3205
|
-
return 0;
|
|
3206
|
-
}
|
|
3207
|
-
return current.value < previous.value ? 1 : -1;
|
|
3208
|
-
default: {
|
|
3209
|
-
const _exhaustive = family;
|
|
3210
|
-
return _exhaustive;
|
|
3211
|
-
}
|
|
3212
|
-
}
|
|
3213
|
-
}
|
|
3214
|
-
function checkConstraintBroadening(ctx, fieldName, constraints) {
|
|
3215
|
-
const strongestByKey = /* @__PURE__ */ new Map();
|
|
3216
|
-
for (const constraint of constraints) {
|
|
3217
|
-
if (!isOrderedBoundConstraint(constraint)) {
|
|
3218
|
-
continue;
|
|
3219
|
-
}
|
|
3220
|
-
const key = `${orderedBoundFamily(constraint.constraintKind)}:${pathKey(constraint)}`;
|
|
3221
|
-
const previous = strongestByKey.get(key);
|
|
3222
|
-
if (previous === void 0) {
|
|
3223
|
-
strongestByKey.set(key, constraint);
|
|
3224
|
-
continue;
|
|
3225
|
-
}
|
|
3226
|
-
const strength = compareConstraintStrength(constraint, previous);
|
|
3227
|
-
if (strength < 0) {
|
|
3228
|
-
const displayFieldName = formatPathTargetFieldName(
|
|
3229
|
-
fieldName,
|
|
3230
|
-
constraint.path?.segments ?? []
|
|
3231
|
-
);
|
|
3232
|
-
addConstraintBroadening(
|
|
3233
|
-
ctx,
|
|
3234
|
-
`Field "${displayFieldName}": ${describeConstraintTag(constraint)} (${String(constraint.value)}) is broader than earlier ${describeConstraintTag(previous)} (${String(previous.value)}). Constraints can only narrow.`,
|
|
3235
|
-
constraint.provenance,
|
|
3236
|
-
previous.provenance
|
|
3237
|
-
);
|
|
3238
|
-
continue;
|
|
3239
|
-
}
|
|
3240
|
-
if (strength <= 0) {
|
|
3241
|
-
continue;
|
|
3242
|
-
}
|
|
3243
|
-
strongestByKey.set(key, constraint);
|
|
3244
|
-
}
|
|
3245
|
-
}
|
|
3246
|
-
function compareCustomConstraintStrength(current, previous) {
|
|
3247
|
-
const order = current.comparePayloads(current.constraint.payload, previous.constraint.payload);
|
|
3248
|
-
const equalPayloadTiebreaker = order === 0 ? compareSemanticInclusivity(current.role.inclusive, previous.role.inclusive) : order;
|
|
3249
|
-
switch (current.role.bound) {
|
|
3250
|
-
case "lower":
|
|
3251
|
-
return equalPayloadTiebreaker;
|
|
3252
|
-
case "upper":
|
|
3253
|
-
return equalPayloadTiebreaker === 0 ? 0 : -equalPayloadTiebreaker;
|
|
3254
|
-
case "exact":
|
|
3255
|
-
return order === 0 ? 0 : Number.NaN;
|
|
3256
|
-
default: {
|
|
3257
|
-
const _exhaustive = current.role.bound;
|
|
3258
|
-
return _exhaustive;
|
|
3259
|
-
}
|
|
3260
|
-
}
|
|
3261
|
-
}
|
|
3262
|
-
function compareSemanticInclusivity(currentInclusive, previousInclusive) {
|
|
3263
|
-
if (currentInclusive === previousInclusive) {
|
|
3264
|
-
return 0;
|
|
3265
|
-
}
|
|
3266
|
-
return currentInclusive ? -1 : 1;
|
|
3267
|
-
}
|
|
3268
|
-
function customConstraintsContradict(lower, upper) {
|
|
3269
|
-
const order = lower.comparePayloads(lower.constraint.payload, upper.constraint.payload);
|
|
3270
|
-
if (order > 0) {
|
|
3271
|
-
return true;
|
|
3272
|
-
}
|
|
3273
|
-
if (order < 0) {
|
|
3274
|
-
return false;
|
|
3275
|
-
}
|
|
3276
|
-
return !lower.role.inclusive || !upper.role.inclusive;
|
|
3277
|
-
}
|
|
3278
|
-
function describeCustomConstraintTag(constraint) {
|
|
3279
|
-
return constraint.provenance.tagName ?? constraint.constraintId;
|
|
3280
|
-
}
|
|
3281
|
-
function checkCustomConstraintSemantics(ctx, fieldName, constraints) {
|
|
3282
|
-
if (ctx.extensionRegistry === void 0) {
|
|
3283
|
-
return;
|
|
3284
|
-
}
|
|
3285
|
-
const strongestByKey = /* @__PURE__ */ new Map();
|
|
3286
|
-
const lowerByFamily = /* @__PURE__ */ new Map();
|
|
3287
|
-
const upperByFamily = /* @__PURE__ */ new Map();
|
|
3288
|
-
for (const constraint of constraints) {
|
|
3289
|
-
if (constraint.constraintKind !== "custom") {
|
|
3290
|
-
continue;
|
|
3291
|
-
}
|
|
3292
|
-
const registration = ctx.extensionRegistry.findConstraint(constraint.constraintId);
|
|
3293
|
-
if (registration?.comparePayloads === void 0 || registration.semanticRole === void 0) {
|
|
3294
|
-
continue;
|
|
3295
|
-
}
|
|
3296
|
-
const entry = {
|
|
3297
|
-
constraint,
|
|
3298
|
-
comparePayloads: registration.comparePayloads,
|
|
3299
|
-
role: registration.semanticRole
|
|
3300
|
-
};
|
|
3301
|
-
const familyKey = `${registration.semanticRole.family}:${pathKey(constraint)}`;
|
|
3302
|
-
const boundKey = `${familyKey}:${registration.semanticRole.bound}`;
|
|
3303
|
-
const previous = strongestByKey.get(boundKey);
|
|
3304
|
-
if (previous !== void 0) {
|
|
3305
|
-
const strength = compareCustomConstraintStrength(entry, previous);
|
|
3306
|
-
if (Number.isNaN(strength)) {
|
|
3307
|
-
addContradiction(
|
|
3308
|
-
ctx,
|
|
3309
|
-
`Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} conflicts with ${describeCustomConstraintTag(previous.constraint)}`,
|
|
3310
|
-
constraint.provenance,
|
|
3311
|
-
previous.constraint.provenance
|
|
3312
|
-
);
|
|
3313
|
-
continue;
|
|
3314
|
-
}
|
|
3315
|
-
if (strength < 0) {
|
|
3316
|
-
addConstraintBroadening(
|
|
3317
|
-
ctx,
|
|
3318
|
-
`Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} is broader than earlier ${describeCustomConstraintTag(previous.constraint)}. Constraints can only narrow.`,
|
|
3319
|
-
constraint.provenance,
|
|
3320
|
-
previous.constraint.provenance
|
|
3321
|
-
);
|
|
3322
|
-
continue;
|
|
3323
|
-
}
|
|
3324
|
-
if (strength > 0) {
|
|
3325
|
-
strongestByKey.set(boundKey, entry);
|
|
3326
|
-
}
|
|
3327
|
-
} else {
|
|
3328
|
-
strongestByKey.set(boundKey, entry);
|
|
3329
|
-
}
|
|
3330
|
-
if (registration.semanticRole.bound === "lower") {
|
|
3331
|
-
lowerByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
|
|
3332
|
-
} else if (registration.semanticRole.bound === "upper") {
|
|
3333
|
-
upperByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
|
|
3334
|
-
}
|
|
3335
|
-
}
|
|
3336
|
-
for (const [familyKey, lower] of lowerByFamily) {
|
|
3337
|
-
const upper = upperByFamily.get(familyKey);
|
|
3338
|
-
if (upper === void 0) {
|
|
3339
|
-
continue;
|
|
3340
|
-
}
|
|
3341
|
-
if (!customConstraintsContradict(lower, upper)) {
|
|
3342
|
-
continue;
|
|
3343
|
-
}
|
|
3344
|
-
addContradiction(
|
|
3345
|
-
ctx,
|
|
3346
|
-
`Field "${formatPathTargetFieldName(fieldName, lower.constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(lower.constraint)} contradicts ${describeCustomConstraintTag(upper.constraint)}`,
|
|
3347
|
-
lower.constraint.provenance,
|
|
3348
|
-
upper.constraint.provenance
|
|
3349
|
-
);
|
|
3350
|
-
}
|
|
3351
|
-
}
|
|
3352
|
-
function checkNumericContradictions(ctx, fieldName, constraints) {
|
|
3353
|
-
const min = findNumeric(constraints, "minimum");
|
|
3354
|
-
const max = findNumeric(constraints, "maximum");
|
|
3355
|
-
const exMin = findNumeric(constraints, "exclusiveMinimum");
|
|
3356
|
-
const exMax = findNumeric(constraints, "exclusiveMaximum");
|
|
3357
|
-
if (min !== void 0 && max !== void 0 && min.value > max.value) {
|
|
3358
|
-
addContradiction(
|
|
3359
|
-
ctx,
|
|
3360
|
-
`Field "${fieldName}": minimum (${String(min.value)}) is greater than maximum (${String(max.value)})`,
|
|
3361
|
-
min.provenance,
|
|
3362
|
-
max.provenance
|
|
3363
|
-
);
|
|
3364
|
-
}
|
|
3365
|
-
if (exMin !== void 0 && max !== void 0 && exMin.value >= max.value) {
|
|
3366
|
-
addContradiction(
|
|
3367
|
-
ctx,
|
|
3368
|
-
`Field "${fieldName}": exclusiveMinimum (${String(exMin.value)}) is greater than or equal to maximum (${String(max.value)})`,
|
|
3369
|
-
exMin.provenance,
|
|
3370
|
-
max.provenance
|
|
3371
|
-
);
|
|
3372
|
-
}
|
|
3373
|
-
if (min !== void 0 && exMax !== void 0 && min.value >= exMax.value) {
|
|
3374
|
-
addContradiction(
|
|
3375
|
-
ctx,
|
|
3376
|
-
`Field "${fieldName}": minimum (${String(min.value)}) is greater than or equal to exclusiveMaximum (${String(exMax.value)})`,
|
|
3377
|
-
min.provenance,
|
|
3378
|
-
exMax.provenance
|
|
3379
|
-
);
|
|
3380
|
-
}
|
|
3381
|
-
if (exMin !== void 0 && exMax !== void 0 && exMin.value >= exMax.value) {
|
|
3382
|
-
addContradiction(
|
|
3383
|
-
ctx,
|
|
3384
|
-
`Field "${fieldName}": exclusiveMinimum (${String(exMin.value)}) is greater than or equal to exclusiveMaximum (${String(exMax.value)})`,
|
|
3385
|
-
exMin.provenance,
|
|
3386
|
-
exMax.provenance
|
|
3387
|
-
);
|
|
3388
|
-
}
|
|
3389
|
-
}
|
|
3390
|
-
function checkLengthContradictions(ctx, fieldName, constraints) {
|
|
3391
|
-
const minLen = findLength(constraints, "minLength");
|
|
3392
|
-
const maxLen = findLength(constraints, "maxLength");
|
|
3393
|
-
if (minLen !== void 0 && maxLen !== void 0 && minLen.value > maxLen.value) {
|
|
3394
|
-
addContradiction(
|
|
3395
|
-
ctx,
|
|
3396
|
-
`Field "${fieldName}": minLength (${String(minLen.value)}) is greater than maxLength (${String(maxLen.value)})`,
|
|
3397
|
-
minLen.provenance,
|
|
3398
|
-
maxLen.provenance
|
|
3399
|
-
);
|
|
3400
|
-
}
|
|
3401
|
-
const minItems = findLength(constraints, "minItems");
|
|
3402
|
-
const maxItems = findLength(constraints, "maxItems");
|
|
3403
|
-
if (minItems !== void 0 && maxItems !== void 0 && minItems.value > maxItems.value) {
|
|
3404
|
-
addContradiction(
|
|
3405
|
-
ctx,
|
|
3406
|
-
`Field "${fieldName}": minItems (${String(minItems.value)}) is greater than maxItems (${String(maxItems.value)})`,
|
|
3407
|
-
minItems.provenance,
|
|
3408
|
-
maxItems.provenance
|
|
3409
|
-
);
|
|
3410
|
-
}
|
|
3411
|
-
}
|
|
3412
|
-
function checkAllowedMembersContradiction(ctx, fieldName, constraints) {
|
|
3413
|
-
const members = findAllowedMembers(constraints);
|
|
3414
|
-
if (members.length < 2) return;
|
|
3415
|
-
const firstSet = new Set(members[0]?.members ?? []);
|
|
3416
|
-
for (let i = 1; i < members.length; i++) {
|
|
3417
|
-
const current = members[i];
|
|
3418
|
-
if (current === void 0) continue;
|
|
3419
|
-
for (const m of firstSet) {
|
|
3420
|
-
if (!current.members.includes(m)) {
|
|
3421
|
-
firstSet.delete(m);
|
|
3422
|
-
}
|
|
3423
|
-
}
|
|
3424
|
-
}
|
|
3425
|
-
if (firstSet.size === 0) {
|
|
3426
|
-
const first = members[0];
|
|
3427
|
-
const second = members[1];
|
|
3428
|
-
if (first !== void 0 && second !== void 0) {
|
|
3429
|
-
addContradiction(
|
|
3430
|
-
ctx,
|
|
3431
|
-
`Field "${fieldName}": allowedMembers constraints have an empty intersection (no valid values remain)`,
|
|
3432
|
-
first.provenance,
|
|
3433
|
-
second.provenance
|
|
3434
|
-
);
|
|
3435
|
-
}
|
|
3436
|
-
}
|
|
3437
|
-
}
|
|
3438
|
-
function checkConstContradictions(ctx, fieldName, constraints) {
|
|
3439
|
-
const constConstraints = findConstConstraints(constraints);
|
|
3440
|
-
if (constConstraints.length < 2) return;
|
|
3441
|
-
const first = constConstraints[0];
|
|
3442
|
-
if (first === void 0) return;
|
|
3443
|
-
for (let i = 1; i < constConstraints.length; i++) {
|
|
3444
|
-
const current = constConstraints[i];
|
|
3445
|
-
if (current === void 0) continue;
|
|
3446
|
-
if (jsonValueEquals(first.value, current.value)) {
|
|
3447
|
-
continue;
|
|
3448
|
-
}
|
|
3449
|
-
addContradiction(
|
|
3450
|
-
ctx,
|
|
3451
|
-
`Field "${fieldName}": conflicting @const constraints require both ${JSON.stringify(first.value)} and ${JSON.stringify(current.value)}`,
|
|
3452
|
-
first.provenance,
|
|
3453
|
-
current.provenance
|
|
3454
|
-
);
|
|
3455
|
-
}
|
|
3456
|
-
}
|
|
3457
|
-
function typeLabel(type) {
|
|
3458
|
-
switch (type.kind) {
|
|
3459
|
-
case "primitive":
|
|
3460
|
-
return type.primitiveKind;
|
|
3461
|
-
case "enum":
|
|
3462
|
-
return "enum";
|
|
3463
|
-
case "array":
|
|
3464
|
-
return "array";
|
|
3465
|
-
case "object":
|
|
3466
|
-
return "object";
|
|
3467
|
-
case "record":
|
|
3468
|
-
return "record";
|
|
3469
|
-
case "union":
|
|
3470
|
-
return "union";
|
|
3471
|
-
case "reference":
|
|
3472
|
-
return `reference(${type.name})`;
|
|
3473
|
-
case "dynamic":
|
|
3474
|
-
return `dynamic(${type.dynamicKind})`;
|
|
3475
|
-
case "custom":
|
|
3476
|
-
return `custom(${type.typeId})`;
|
|
3477
|
-
default: {
|
|
3478
|
-
const _exhaustive = type;
|
|
3479
|
-
return String(_exhaustive);
|
|
3480
|
-
}
|
|
3481
|
-
}
|
|
3482
|
-
}
|
|
3483
|
-
function dereferenceType(ctx, type) {
|
|
3484
|
-
let current = type;
|
|
3485
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3486
|
-
while (current.kind === "reference") {
|
|
3487
|
-
if (seen.has(current.name)) {
|
|
3488
|
-
return current;
|
|
3489
|
-
}
|
|
3490
|
-
seen.add(current.name);
|
|
3491
|
-
const definition = ctx.typeRegistry[current.name];
|
|
3492
|
-
if (definition === void 0) {
|
|
3493
|
-
return current;
|
|
3494
|
-
}
|
|
3495
|
-
current = definition.type;
|
|
3496
|
-
}
|
|
3497
|
-
return current;
|
|
3498
|
-
}
|
|
3499
|
-
function collectReferencedTypeConstraints(ctx, type) {
|
|
3500
|
-
const collected = [];
|
|
3501
|
-
let current = type;
|
|
3502
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3503
|
-
while (current.kind === "reference") {
|
|
3504
|
-
if (seen.has(current.name)) {
|
|
3505
|
-
break;
|
|
3506
|
-
}
|
|
3507
|
-
seen.add(current.name);
|
|
3508
|
-
const definition = ctx.typeRegistry[current.name];
|
|
3509
|
-
if (definition === void 0) {
|
|
3510
|
-
break;
|
|
3511
|
-
}
|
|
3512
|
-
if (definition.constraints !== void 0) {
|
|
3513
|
-
collected.push(...definition.constraints);
|
|
3514
|
-
}
|
|
3515
|
-
current = definition.type;
|
|
3516
|
-
}
|
|
3517
|
-
return collected;
|
|
3518
|
-
}
|
|
3519
|
-
function resolvePathTargetType(ctx, type, segments) {
|
|
3520
|
-
const effectiveType = dereferenceType(ctx, type);
|
|
3521
|
-
if (segments.length === 0) {
|
|
3522
|
-
return { kind: "resolved", type: effectiveType };
|
|
3523
|
-
}
|
|
3524
|
-
if (effectiveType.kind === "array") {
|
|
3525
|
-
return resolvePathTargetType(ctx, effectiveType.items, segments);
|
|
3526
|
-
}
|
|
3527
|
-
if (effectiveType.kind === "object") {
|
|
3528
|
-
const [segment, ...rest] = segments;
|
|
3529
|
-
if (segment === void 0) {
|
|
3530
|
-
throw new Error("Invariant violation: object path traversal requires a segment");
|
|
3531
|
-
}
|
|
3532
|
-
const property = effectiveType.properties.find((prop) => prop.name === segment);
|
|
3533
|
-
if (property === void 0) {
|
|
3534
|
-
return { kind: "missing-property", segment };
|
|
3535
|
-
}
|
|
3536
|
-
return resolvePathTargetType(ctx, property.type, rest);
|
|
3537
|
-
}
|
|
3538
|
-
return { kind: "unresolvable", type: effectiveType };
|
|
3539
|
-
}
|
|
3540
|
-
function isNullType(type) {
|
|
3541
|
-
return type.kind === "primitive" && type.primitiveKind === "null";
|
|
3542
|
-
}
|
|
3543
|
-
function collectCustomConstraintCandidateTypes(ctx, type) {
|
|
3544
|
-
const effectiveType = dereferenceType(ctx, type);
|
|
3545
|
-
const candidates = [effectiveType];
|
|
3546
|
-
if (effectiveType.kind === "array") {
|
|
3547
|
-
candidates.push(...collectCustomConstraintCandidateTypes(ctx, effectiveType.items));
|
|
3548
|
-
}
|
|
3549
|
-
if (effectiveType.kind === "union") {
|
|
3550
|
-
const memberTypes = effectiveType.members.map((member) => dereferenceType(ctx, member));
|
|
3551
|
-
const nonNullMembers = memberTypes.filter((member) => !isNullType(member));
|
|
3552
|
-
if (nonNullMembers.length === 1 && nonNullMembers.length < memberTypes.length) {
|
|
3553
|
-
const [nullableMember] = nonNullMembers;
|
|
3554
|
-
if (nullableMember !== void 0) {
|
|
3555
|
-
candidates.push(...collectCustomConstraintCandidateTypes(ctx, nullableMember));
|
|
3556
|
-
}
|
|
3557
|
-
}
|
|
3558
|
-
}
|
|
3559
|
-
return candidates;
|
|
3560
|
-
}
|
|
3561
|
-
function formatPathTargetFieldName(fieldName, path4) {
|
|
3562
|
-
return path4.length === 0 ? fieldName : `${fieldName}.${path4.join(".")}`;
|
|
3563
|
-
}
|
|
3564
|
-
function checkConstraintOnType(ctx, fieldName, type, constraint) {
|
|
3565
|
-
const effectiveType = dereferenceType(ctx, type);
|
|
3566
|
-
const isNumber = effectiveType.kind === "primitive" && ["number", "integer", "bigint"].includes(effectiveType.primitiveKind);
|
|
3567
|
-
const isString = effectiveType.kind === "primitive" && effectiveType.primitiveKind === "string";
|
|
3568
|
-
const isArray = effectiveType.kind === "array";
|
|
3569
|
-
const isEnum = effectiveType.kind === "enum";
|
|
3570
|
-
const arrayItemType = effectiveType.kind === "array" ? dereferenceType(ctx, effectiveType.items) : void 0;
|
|
3571
|
-
const isStringArray = arrayItemType?.kind === "primitive" && arrayItemType.primitiveKind === "string";
|
|
3572
|
-
const label = typeLabel(effectiveType);
|
|
3573
|
-
const ck = constraint.constraintKind;
|
|
3574
|
-
switch (ck) {
|
|
3575
|
-
case "minimum":
|
|
3576
|
-
case "maximum":
|
|
3577
|
-
case "exclusiveMinimum":
|
|
3578
|
-
case "exclusiveMaximum":
|
|
3579
|
-
case "multipleOf": {
|
|
3580
|
-
if (!isNumber) {
|
|
3581
|
-
addTypeMismatch(
|
|
3582
|
-
ctx,
|
|
3583
|
-
`Field "${fieldName}": constraint "${ck}" is only valid on number fields, but field type is "${label}"`,
|
|
3584
|
-
constraint.provenance
|
|
3585
|
-
);
|
|
3586
|
-
}
|
|
3587
|
-
break;
|
|
3588
|
-
}
|
|
3589
|
-
case "minLength":
|
|
3590
|
-
case "maxLength":
|
|
3591
|
-
case "pattern": {
|
|
3592
|
-
if (!isString && !isStringArray) {
|
|
3593
|
-
addTypeMismatch(
|
|
3594
|
-
ctx,
|
|
3595
|
-
`Field "${fieldName}": constraint "${ck}" is only valid on string fields or string array items, but field type is "${label}"`,
|
|
3596
|
-
constraint.provenance
|
|
3597
|
-
);
|
|
3598
|
-
}
|
|
3599
|
-
break;
|
|
3600
|
-
}
|
|
3601
|
-
case "minItems":
|
|
3602
|
-
case "maxItems":
|
|
3603
|
-
case "uniqueItems": {
|
|
3604
|
-
if (!isArray) {
|
|
3605
|
-
addTypeMismatch(
|
|
3606
|
-
ctx,
|
|
3607
|
-
`Field "${fieldName}": constraint "${ck}" is only valid on array fields, but field type is "${label}"`,
|
|
3608
|
-
constraint.provenance
|
|
3609
|
-
);
|
|
3610
|
-
}
|
|
3611
|
-
break;
|
|
3612
|
-
}
|
|
3613
|
-
case "allowedMembers": {
|
|
3614
|
-
if (!isEnum) {
|
|
3615
|
-
addTypeMismatch(
|
|
3616
|
-
ctx,
|
|
3617
|
-
`Field "${fieldName}": constraint "allowedMembers" is only valid on enum fields, but field type is "${label}"`,
|
|
3618
|
-
constraint.provenance
|
|
3619
|
-
);
|
|
3620
|
-
}
|
|
3621
|
-
break;
|
|
3622
|
-
}
|
|
3623
|
-
case "const": {
|
|
3624
|
-
const isPrimitiveConstType = effectiveType.kind === "primitive" && ["string", "number", "integer", "bigint", "boolean", "null"].includes(
|
|
3625
|
-
effectiveType.primitiveKind
|
|
3626
|
-
) || effectiveType.kind === "enum";
|
|
3627
|
-
if (!isPrimitiveConstType) {
|
|
3628
|
-
addTypeMismatch(
|
|
3629
|
-
ctx,
|
|
3630
|
-
`Field "${fieldName}": constraint "const" is only valid on primitive or enum fields, but field type is "${label}"`,
|
|
3631
|
-
constraint.provenance
|
|
3632
|
-
);
|
|
3633
|
-
break;
|
|
3634
|
-
}
|
|
3635
|
-
if (effectiveType.kind === "primitive") {
|
|
3636
|
-
const valueType = constraint.value === null ? "null" : Array.isArray(constraint.value) ? "array" : typeof constraint.value;
|
|
3637
|
-
const expectedValueType = effectiveType.primitiveKind === "integer" || effectiveType.primitiveKind === "bigint" ? "number" : effectiveType.primitiveKind;
|
|
3638
|
-
if (valueType !== expectedValueType) {
|
|
3639
|
-
addTypeMismatch(
|
|
3640
|
-
ctx,
|
|
3641
|
-
`Field "${fieldName}": @const value type "${valueType}" is incompatible with field type "${effectiveType.primitiveKind}"`,
|
|
3642
|
-
constraint.provenance
|
|
3643
|
-
);
|
|
3644
|
-
}
|
|
3645
|
-
break;
|
|
3646
|
-
}
|
|
3647
|
-
const memberValues = effectiveType.members.map((member) => member.value);
|
|
3648
|
-
if (!memberValues.some((member) => jsonValueEquals(member, constraint.value))) {
|
|
3649
|
-
addTypeMismatch(
|
|
3650
|
-
ctx,
|
|
3651
|
-
`Field "${fieldName}": @const value ${JSON.stringify(constraint.value)} is not one of the enum members`,
|
|
3652
|
-
constraint.provenance
|
|
3653
|
-
);
|
|
3654
|
-
}
|
|
3655
|
-
break;
|
|
3656
|
-
}
|
|
3657
|
-
case "custom": {
|
|
3658
|
-
checkCustomConstraint(ctx, fieldName, effectiveType, constraint);
|
|
3659
|
-
break;
|
|
3660
|
-
}
|
|
3661
|
-
default: {
|
|
3662
|
-
const _exhaustive = constraint;
|
|
3663
|
-
throw new Error(
|
|
3664
|
-
`Unhandled constraint kind: ${_exhaustive.constraintKind}`
|
|
3665
|
-
);
|
|
3666
|
-
}
|
|
3667
|
-
}
|
|
3668
|
-
}
|
|
3669
|
-
function checkTypeApplicability(ctx, fieldName, type, constraints) {
|
|
3670
|
-
for (const constraint of constraints) {
|
|
3671
|
-
if (constraint.path) {
|
|
3672
|
-
const resolution = resolvePathTargetType(ctx, type, constraint.path.segments);
|
|
3673
|
-
const targetFieldName = formatPathTargetFieldName(fieldName, constraint.path.segments);
|
|
3674
|
-
if (resolution.kind === "missing-property") {
|
|
3675
|
-
addUnknownPathTarget(
|
|
3676
|
-
ctx,
|
|
3677
|
-
`Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" references unknown path segment "${resolution.segment}"`,
|
|
3678
|
-
constraint.provenance
|
|
3679
|
-
);
|
|
3680
|
-
continue;
|
|
3681
|
-
}
|
|
3682
|
-
if (resolution.kind === "unresolvable") {
|
|
3683
|
-
addTypeMismatch(
|
|
3684
|
-
ctx,
|
|
3685
|
-
`Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" is invalid because type "${typeLabel(resolution.type)}" cannot be traversed`,
|
|
3686
|
-
constraint.provenance
|
|
3687
|
-
);
|
|
3688
|
-
continue;
|
|
3689
|
-
}
|
|
3690
|
-
checkConstraintOnType(ctx, targetFieldName, resolution.type, constraint);
|
|
3691
|
-
continue;
|
|
3692
|
-
}
|
|
3693
|
-
checkConstraintOnType(ctx, fieldName, type, constraint);
|
|
3694
|
-
}
|
|
3695
|
-
}
|
|
3696
|
-
function checkCustomConstraint(ctx, fieldName, type, constraint) {
|
|
3697
|
-
if (ctx.extensionRegistry === void 0) return;
|
|
3698
|
-
const registration = ctx.extensionRegistry.findConstraint(constraint.constraintId);
|
|
3699
|
-
if (registration === void 0) {
|
|
3700
|
-
addUnknownExtension(
|
|
3701
|
-
ctx,
|
|
3702
|
-
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not registered in the extension registry`,
|
|
3703
|
-
constraint.provenance
|
|
3704
|
-
);
|
|
3705
|
-
return;
|
|
3706
|
-
}
|
|
3707
|
-
const candidateTypes = collectCustomConstraintCandidateTypes(ctx, type);
|
|
3708
|
-
const normalizedTagName = constraint.provenance.tagName === void 0 ? void 0 : (0, import_core4.normalizeConstraintTagName)(constraint.provenance.tagName.replace(/^@/, ""));
|
|
3709
|
-
if (normalizedTagName !== void 0) {
|
|
3710
|
-
const tagRegistration = ctx.extensionRegistry.findConstraintTag(normalizedTagName);
|
|
3711
|
-
const extensionId = getExtensionIdFromConstraintId(constraint.constraintId);
|
|
3712
|
-
if (extensionId !== null && tagRegistration?.extensionId === extensionId && tagRegistration.registration.constraintName === registration.constraintName && !candidateTypes.some(
|
|
3713
|
-
(candidateType) => tagRegistration.registration.isApplicableToType?.(candidateType) !== false
|
|
3714
|
-
)) {
|
|
3715
|
-
addTypeMismatch(
|
|
3716
|
-
ctx,
|
|
3717
|
-
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
3718
|
-
constraint.provenance
|
|
3719
|
-
);
|
|
3720
|
-
return;
|
|
3721
|
-
}
|
|
3722
|
-
}
|
|
3723
|
-
if (registration.applicableTypes === null) {
|
|
3724
|
-
if (!candidateTypes.some((candidateType) => registration.isApplicableToType?.(candidateType) !== false)) {
|
|
3725
|
-
addTypeMismatch(
|
|
3726
|
-
ctx,
|
|
3727
|
-
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
3728
|
-
constraint.provenance
|
|
3729
|
-
);
|
|
3326
|
+
function validateFieldNode(ctx, field) {
|
|
3327
|
+
const analysis = (0, import_analysis2.analyzeConstraintTargets)(
|
|
3328
|
+
field.name,
|
|
3329
|
+
field.type,
|
|
3330
|
+
field.constraints,
|
|
3331
|
+
ctx.typeRegistry,
|
|
3332
|
+
ctx.extensionRegistry === void 0 ? void 0 : {
|
|
3333
|
+
extensionRegistry: ctx.extensionRegistry
|
|
3730
3334
|
}
|
|
3731
|
-
return;
|
|
3732
|
-
}
|
|
3733
|
-
const applicableTypes = registration.applicableTypes;
|
|
3734
|
-
const matchesApplicableType = candidateTypes.some(
|
|
3735
|
-
(candidateType) => applicableTypes.includes(candidateType.kind) && registration.isApplicableToType?.(candidateType) !== false
|
|
3736
3335
|
);
|
|
3737
|
-
|
|
3738
|
-
addTypeMismatch(
|
|
3739
|
-
ctx,
|
|
3740
|
-
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
3741
|
-
constraint.provenance
|
|
3742
|
-
);
|
|
3743
|
-
}
|
|
3744
|
-
}
|
|
3745
|
-
function validateFieldNode(ctx, field) {
|
|
3746
|
-
validateConstraints(ctx, field.name, field.type, [
|
|
3747
|
-
...collectReferencedTypeConstraints(ctx, field.type),
|
|
3748
|
-
...field.constraints
|
|
3749
|
-
]);
|
|
3336
|
+
ctx.diagnostics.push(...analysis.diagnostics);
|
|
3750
3337
|
if (field.type.kind === "object") {
|
|
3751
|
-
for (const
|
|
3752
|
-
validateObjectProperty(ctx, field.name,
|
|
3338
|
+
for (const property of field.type.properties) {
|
|
3339
|
+
validateObjectProperty(ctx, field.name, property);
|
|
3753
3340
|
}
|
|
3754
3341
|
}
|
|
3755
3342
|
}
|
|
3756
|
-
function validateObjectProperty(ctx, parentName,
|
|
3757
|
-
const qualifiedName = `${parentName}.${
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3343
|
+
function validateObjectProperty(ctx, parentName, property) {
|
|
3344
|
+
const qualifiedName = `${parentName}.${property.name}`;
|
|
3345
|
+
const analysis = (0, import_analysis2.analyzeConstraintTargets)(
|
|
3346
|
+
qualifiedName,
|
|
3347
|
+
property.type,
|
|
3348
|
+
property.constraints,
|
|
3349
|
+
ctx.typeRegistry,
|
|
3350
|
+
ctx.extensionRegistry === void 0 ? void 0 : {
|
|
3351
|
+
extensionRegistry: ctx.extensionRegistry
|
|
3352
|
+
}
|
|
3353
|
+
);
|
|
3354
|
+
ctx.diagnostics.push(...analysis.diagnostics);
|
|
3355
|
+
if (property.type.kind === "object") {
|
|
3356
|
+
for (const nestedProperty of property.type.properties) {
|
|
3357
|
+
validateObjectProperty(ctx, qualifiedName, nestedProperty);
|
|
3765
3358
|
}
|
|
3766
3359
|
}
|
|
3767
3360
|
}
|
|
3768
|
-
function validateConstraints(ctx, name, type, constraints) {
|
|
3769
|
-
checkNumericContradictions(ctx, name, constraints);
|
|
3770
|
-
checkLengthContradictions(ctx, name, constraints);
|
|
3771
|
-
checkAllowedMembersContradiction(ctx, name, constraints);
|
|
3772
|
-
checkConstContradictions(ctx, name, constraints);
|
|
3773
|
-
checkConstraintBroadening(ctx, name, constraints);
|
|
3774
|
-
checkCustomConstraintSemantics(ctx, name, constraints);
|
|
3775
|
-
checkTypeApplicability(ctx, name, type, constraints);
|
|
3776
|
-
}
|
|
3777
3361
|
function validateElement(ctx, element) {
|
|
3778
3362
|
switch (element.kind) {
|
|
3779
3363
|
case "field":
|
|
@@ -3790,8 +3374,8 @@ function validateElement(ctx, element) {
|
|
|
3790
3374
|
}
|
|
3791
3375
|
break;
|
|
3792
3376
|
default: {
|
|
3793
|
-
const
|
|
3794
|
-
throw new Error(`Unhandled element kind: ${
|
|
3377
|
+
const exhaustive = element;
|
|
3378
|
+
throw new Error(`Unhandled element kind: ${String(exhaustive)}`);
|
|
3795
3379
|
}
|
|
3796
3380
|
}
|
|
3797
3381
|
}
|
|
@@ -3806,14 +3390,14 @@ function validateIR(ir, options) {
|
|
|
3806
3390
|
}
|
|
3807
3391
|
return {
|
|
3808
3392
|
diagnostics: ctx.diagnostics,
|
|
3809
|
-
valid: ctx.diagnostics.every((
|
|
3393
|
+
valid: ctx.diagnostics.every((diagnostic) => diagnostic.severity !== "error")
|
|
3810
3394
|
};
|
|
3811
3395
|
}
|
|
3812
|
-
var
|
|
3396
|
+
var import_analysis2;
|
|
3813
3397
|
var init_constraint_validator = __esm({
|
|
3814
3398
|
"src/validate/constraint-validator.ts"() {
|
|
3815
3399
|
"use strict";
|
|
3816
|
-
|
|
3400
|
+
import_analysis2 = require("@formspec/analysis");
|
|
3817
3401
|
}
|
|
3818
3402
|
});
|
|
3819
3403
|
|
|
@@ -3827,6 +3411,12 @@ var init_validate = __esm({
|
|
|
3827
3411
|
|
|
3828
3412
|
// src/generators/class-schema.ts
|
|
3829
3413
|
function generateClassSchemas(analysis, source, options) {
|
|
3414
|
+
const errorDiagnostics = analysis.diagnostics?.filter(
|
|
3415
|
+
(diagnostic) => diagnostic.severity === "error"
|
|
3416
|
+
);
|
|
3417
|
+
if (errorDiagnostics !== void 0 && errorDiagnostics.length > 0) {
|
|
3418
|
+
throw new Error(formatValidationError(errorDiagnostics));
|
|
3419
|
+
}
|
|
3830
3420
|
const ir = canonicalizeTSDoc(analysis, source);
|
|
3831
3421
|
const validationResult = validateIR(ir, {
|
|
3832
3422
|
...options?.extensionRegistry !== void 0 && {
|