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