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