@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.js
CHANGED
|
@@ -1219,6 +1219,19 @@ import * as ts2 from "typescript";
|
|
|
1219
1219
|
|
|
1220
1220
|
// src/analyzer/tsdoc-parser.ts
|
|
1221
1221
|
import * as ts from "typescript";
|
|
1222
|
+
import {
|
|
1223
|
+
checkSyntheticTagApplication,
|
|
1224
|
+
extractPathTarget as extractSharedPathTarget,
|
|
1225
|
+
getTagDefinition,
|
|
1226
|
+
hasTypeSemanticCapability,
|
|
1227
|
+
parseConstraintTagValue,
|
|
1228
|
+
parseDefaultValueTagValue,
|
|
1229
|
+
resolveDeclarationPlacement,
|
|
1230
|
+
resolvePathTargetType,
|
|
1231
|
+
sliceCommentSpan,
|
|
1232
|
+
parseCommentBlock,
|
|
1233
|
+
parseTagSyntax
|
|
1234
|
+
} from "@formspec/analysis";
|
|
1222
1235
|
import {
|
|
1223
1236
|
TSDocParser,
|
|
1224
1237
|
TSDocConfiguration,
|
|
@@ -1233,30 +1246,6 @@ import {
|
|
|
1233
1246
|
normalizeConstraintTagName,
|
|
1234
1247
|
isBuiltinConstraintName
|
|
1235
1248
|
} from "@formspec/core";
|
|
1236
|
-
|
|
1237
|
-
// src/analyzer/json-utils.ts
|
|
1238
|
-
function tryParseJson(text) {
|
|
1239
|
-
try {
|
|
1240
|
-
return JSON.parse(text);
|
|
1241
|
-
} catch {
|
|
1242
|
-
return null;
|
|
1243
|
-
}
|
|
1244
|
-
}
|
|
1245
|
-
|
|
1246
|
-
// src/analyzer/tsdoc-parser.ts
|
|
1247
|
-
var NUMERIC_CONSTRAINT_MAP = {
|
|
1248
|
-
minimum: "minimum",
|
|
1249
|
-
maximum: "maximum",
|
|
1250
|
-
exclusiveMinimum: "exclusiveMinimum",
|
|
1251
|
-
exclusiveMaximum: "exclusiveMaximum",
|
|
1252
|
-
multipleOf: "multipleOf"
|
|
1253
|
-
};
|
|
1254
|
-
var LENGTH_CONSTRAINT_MAP = {
|
|
1255
|
-
minLength: "minLength",
|
|
1256
|
-
maxLength: "maxLength",
|
|
1257
|
-
minItems: "minItems",
|
|
1258
|
-
maxItems: "maxItems"
|
|
1259
|
-
};
|
|
1260
1249
|
var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions", "defaultValue"]);
|
|
1261
1250
|
function createFormSpecTSDocConfig(extensionTagNames = []) {
|
|
1262
1251
|
const config = new TSDocConfiguration();
|
|
@@ -1289,7 +1278,294 @@ function createFormSpecTSDocConfig(extensionTagNames = []) {
|
|
|
1289
1278
|
}
|
|
1290
1279
|
return config;
|
|
1291
1280
|
}
|
|
1281
|
+
function sharedCommentSyntaxOptions(options, offset) {
|
|
1282
|
+
const extensions = options?.extensionRegistry?.extensions;
|
|
1283
|
+
return {
|
|
1284
|
+
...offset !== void 0 ? { offset } : {},
|
|
1285
|
+
...extensions !== void 0 ? { extensions } : {}
|
|
1286
|
+
};
|
|
1287
|
+
}
|
|
1288
|
+
function sharedTagValueOptions(options) {
|
|
1289
|
+
return {
|
|
1290
|
+
...options?.extensionRegistry !== void 0 ? { registry: options.extensionRegistry } : {},
|
|
1291
|
+
...options?.fieldType !== void 0 ? { fieldType: options.fieldType } : {}
|
|
1292
|
+
};
|
|
1293
|
+
}
|
|
1294
|
+
var SYNTHETIC_TYPE_FORMAT_FLAGS = ts.TypeFormatFlags.NoTruncation | ts.TypeFormatFlags.UseAliasDefinedOutsideCurrentScope;
|
|
1295
|
+
function buildSupportingDeclarations(sourceFile) {
|
|
1296
|
+
return sourceFile.statements.filter(
|
|
1297
|
+
(statement) => !ts.isImportDeclaration(statement) && !ts.isImportEqualsDeclaration(statement) && !(ts.isExportDeclaration(statement) && statement.moduleSpecifier !== void 0)
|
|
1298
|
+
).map((statement) => statement.getText(sourceFile));
|
|
1299
|
+
}
|
|
1300
|
+
function renderSyntheticArgumentExpression(valueKind, argumentText) {
|
|
1301
|
+
const trimmed = argumentText.trim();
|
|
1302
|
+
if (trimmed === "") {
|
|
1303
|
+
return null;
|
|
1304
|
+
}
|
|
1305
|
+
switch (valueKind) {
|
|
1306
|
+
case "number":
|
|
1307
|
+
case "integer":
|
|
1308
|
+
case "signedInteger":
|
|
1309
|
+
return Number.isFinite(Number(trimmed)) ? trimmed : JSON.stringify(trimmed);
|
|
1310
|
+
case "string":
|
|
1311
|
+
return JSON.stringify(argumentText);
|
|
1312
|
+
case "json":
|
|
1313
|
+
try {
|
|
1314
|
+
JSON.parse(trimmed);
|
|
1315
|
+
return `(${trimmed})`;
|
|
1316
|
+
} catch {
|
|
1317
|
+
return JSON.stringify(trimmed);
|
|
1318
|
+
}
|
|
1319
|
+
case "boolean":
|
|
1320
|
+
return trimmed === "true" || trimmed === "false" ? trimmed : JSON.stringify(trimmed);
|
|
1321
|
+
case "condition":
|
|
1322
|
+
return "undefined as unknown as FormSpecCondition";
|
|
1323
|
+
case null:
|
|
1324
|
+
return null;
|
|
1325
|
+
default: {
|
|
1326
|
+
return String(valueKind);
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
function getArrayElementType(type, checker) {
|
|
1331
|
+
if (!checker.isArrayType(type)) {
|
|
1332
|
+
return null;
|
|
1333
|
+
}
|
|
1334
|
+
return checker.getTypeArguments(type)[0] ?? null;
|
|
1335
|
+
}
|
|
1336
|
+
function supportsConstraintCapability(type, checker, capability) {
|
|
1337
|
+
if (capability === void 0) {
|
|
1338
|
+
return true;
|
|
1339
|
+
}
|
|
1340
|
+
if (hasTypeSemanticCapability(type, checker, capability)) {
|
|
1341
|
+
return true;
|
|
1342
|
+
}
|
|
1343
|
+
if (capability === "string-like") {
|
|
1344
|
+
const itemType = getArrayElementType(type, checker);
|
|
1345
|
+
return itemType !== null && hasTypeSemanticCapability(itemType, checker, capability);
|
|
1346
|
+
}
|
|
1347
|
+
return false;
|
|
1348
|
+
}
|
|
1349
|
+
function makeDiagnostic(code, message, provenance) {
|
|
1350
|
+
return {
|
|
1351
|
+
code,
|
|
1352
|
+
message,
|
|
1353
|
+
severity: "error",
|
|
1354
|
+
primaryLocation: provenance,
|
|
1355
|
+
relatedLocations: []
|
|
1356
|
+
};
|
|
1357
|
+
}
|
|
1358
|
+
function placementLabel(placement) {
|
|
1359
|
+
switch (placement) {
|
|
1360
|
+
case "class":
|
|
1361
|
+
return "class declarations";
|
|
1362
|
+
case "class-field":
|
|
1363
|
+
return "class fields";
|
|
1364
|
+
case "class-method":
|
|
1365
|
+
return "class methods";
|
|
1366
|
+
case "interface":
|
|
1367
|
+
return "interface declarations";
|
|
1368
|
+
case "interface-field":
|
|
1369
|
+
return "interface fields";
|
|
1370
|
+
case "type-alias":
|
|
1371
|
+
return "type aliases";
|
|
1372
|
+
case "type-alias-field":
|
|
1373
|
+
return "type-alias properties";
|
|
1374
|
+
case "variable":
|
|
1375
|
+
return "variables";
|
|
1376
|
+
case "function":
|
|
1377
|
+
return "functions";
|
|
1378
|
+
case "function-parameter":
|
|
1379
|
+
return "function parameters";
|
|
1380
|
+
case "method-parameter":
|
|
1381
|
+
return "method parameters";
|
|
1382
|
+
default: {
|
|
1383
|
+
const exhaustive = placement;
|
|
1384
|
+
return String(exhaustive);
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
function capabilityLabel(capability) {
|
|
1389
|
+
switch (capability) {
|
|
1390
|
+
case "numeric-comparable":
|
|
1391
|
+
return "number";
|
|
1392
|
+
case "string-like":
|
|
1393
|
+
return "string";
|
|
1394
|
+
case "array-like":
|
|
1395
|
+
return "array";
|
|
1396
|
+
case "enum-member-addressable":
|
|
1397
|
+
return "enum";
|
|
1398
|
+
case "json-like":
|
|
1399
|
+
return "JSON-compatible";
|
|
1400
|
+
case "object-like":
|
|
1401
|
+
return "object";
|
|
1402
|
+
case "condition-like":
|
|
1403
|
+
return "conditional";
|
|
1404
|
+
case void 0:
|
|
1405
|
+
return "compatible";
|
|
1406
|
+
default:
|
|
1407
|
+
return capability;
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
function getBroadenedCustomTypeId(fieldType) {
|
|
1411
|
+
if (fieldType?.kind === "custom") {
|
|
1412
|
+
return fieldType.typeId;
|
|
1413
|
+
}
|
|
1414
|
+
if (fieldType?.kind !== "union") {
|
|
1415
|
+
return void 0;
|
|
1416
|
+
}
|
|
1417
|
+
const customMembers = fieldType.members.filter(
|
|
1418
|
+
(member) => member.kind === "custom"
|
|
1419
|
+
);
|
|
1420
|
+
if (customMembers.length !== 1) {
|
|
1421
|
+
return void 0;
|
|
1422
|
+
}
|
|
1423
|
+
const nonCustomMembers = fieldType.members.filter((member) => member.kind !== "custom");
|
|
1424
|
+
const allOtherMembersAreNull = nonCustomMembers.every(
|
|
1425
|
+
(member) => member.kind === "primitive" && member.primitiveKind === "null"
|
|
1426
|
+
);
|
|
1427
|
+
const customMember = customMembers[0];
|
|
1428
|
+
return allOtherMembersAreNull && customMember !== void 0 ? customMember.typeId : void 0;
|
|
1429
|
+
}
|
|
1430
|
+
function hasBuiltinConstraintBroadening(tagName, options) {
|
|
1431
|
+
const broadenedTypeId = getBroadenedCustomTypeId(options?.fieldType);
|
|
1432
|
+
return broadenedTypeId !== void 0 && options?.extensionRegistry?.findBuiltinConstraintBroadening(broadenedTypeId, tagName) !== void 0;
|
|
1433
|
+
}
|
|
1434
|
+
function buildCompilerBackedConstraintDiagnostics(node, sourceFile, tagName, parsedTag, provenance, supportingDeclarations, options) {
|
|
1435
|
+
if (!isBuiltinConstraintName(tagName)) {
|
|
1436
|
+
return [];
|
|
1437
|
+
}
|
|
1438
|
+
const checker = options?.checker;
|
|
1439
|
+
const subjectType = options?.subjectType;
|
|
1440
|
+
if (checker === void 0 || subjectType === void 0) {
|
|
1441
|
+
return [];
|
|
1442
|
+
}
|
|
1443
|
+
const placement = resolveDeclarationPlacement(node);
|
|
1444
|
+
if (placement === null) {
|
|
1445
|
+
return [];
|
|
1446
|
+
}
|
|
1447
|
+
const definition = getTagDefinition(tagName, options?.extensionRegistry?.extensions);
|
|
1448
|
+
if (definition === null) {
|
|
1449
|
+
return [];
|
|
1450
|
+
}
|
|
1451
|
+
if (!definition.placements.includes(placement)) {
|
|
1452
|
+
return [
|
|
1453
|
+
makeDiagnostic(
|
|
1454
|
+
"INVALID_TAG_PLACEMENT",
|
|
1455
|
+
`Tag "@${tagName}" is not allowed on ${placementLabel(placement)}.`,
|
|
1456
|
+
provenance
|
|
1457
|
+
)
|
|
1458
|
+
];
|
|
1459
|
+
}
|
|
1460
|
+
const target = parsedTag?.target ?? null;
|
|
1461
|
+
const hasBroadening = target === null && hasBuiltinConstraintBroadening(tagName, options);
|
|
1462
|
+
if (target !== null) {
|
|
1463
|
+
if (target.kind !== "path") {
|
|
1464
|
+
return [
|
|
1465
|
+
makeDiagnostic(
|
|
1466
|
+
"UNSUPPORTED_TARGETING_SYNTAX",
|
|
1467
|
+
`Tag "@${tagName}" does not support ${target.kind} targeting syntax.`,
|
|
1468
|
+
provenance
|
|
1469
|
+
)
|
|
1470
|
+
];
|
|
1471
|
+
}
|
|
1472
|
+
if (!target.valid || target.path === null) {
|
|
1473
|
+
return [
|
|
1474
|
+
makeDiagnostic(
|
|
1475
|
+
"UNSUPPORTED_TARGETING_SYNTAX",
|
|
1476
|
+
`Tag "@${tagName}" has invalid path targeting syntax.`,
|
|
1477
|
+
provenance
|
|
1478
|
+
)
|
|
1479
|
+
];
|
|
1480
|
+
}
|
|
1481
|
+
const resolution = resolvePathTargetType(subjectType, checker, target.path.segments);
|
|
1482
|
+
if (resolution.kind === "missing-property") {
|
|
1483
|
+
return [
|
|
1484
|
+
makeDiagnostic(
|
|
1485
|
+
"UNKNOWN_PATH_TARGET",
|
|
1486
|
+
`Target "${target.rawText}": path-targeted constraint "${tagName}" references unknown path segment "${resolution.segment}"`,
|
|
1487
|
+
provenance
|
|
1488
|
+
)
|
|
1489
|
+
];
|
|
1490
|
+
}
|
|
1491
|
+
if (resolution.kind === "unresolvable") {
|
|
1492
|
+
const actualType = checker.typeToString(resolution.type, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
|
|
1493
|
+
return [
|
|
1494
|
+
makeDiagnostic(
|
|
1495
|
+
"TYPE_MISMATCH",
|
|
1496
|
+
`Target "${target.rawText}": path-targeted constraint "${tagName}" is invalid because type "${actualType}" cannot be traversed`,
|
|
1497
|
+
provenance
|
|
1498
|
+
)
|
|
1499
|
+
];
|
|
1500
|
+
}
|
|
1501
|
+
const requiredCapability = definition.capabilities[0];
|
|
1502
|
+
if (requiredCapability !== void 0 && !supportsConstraintCapability(resolution.type, checker, requiredCapability)) {
|
|
1503
|
+
const actualType = checker.typeToString(resolution.type, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
|
|
1504
|
+
return [
|
|
1505
|
+
makeDiagnostic(
|
|
1506
|
+
"TYPE_MISMATCH",
|
|
1507
|
+
`Target "${target.rawText}": constraint "${tagName}" is only valid on ${capabilityLabel(requiredCapability)} targets, but field type is "${actualType}"`,
|
|
1508
|
+
provenance
|
|
1509
|
+
)
|
|
1510
|
+
];
|
|
1511
|
+
}
|
|
1512
|
+
} else if (!hasBroadening) {
|
|
1513
|
+
const requiredCapability = definition.capabilities[0];
|
|
1514
|
+
if (requiredCapability !== void 0 && !supportsConstraintCapability(subjectType, checker, requiredCapability)) {
|
|
1515
|
+
const actualType = checker.typeToString(subjectType, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
|
|
1516
|
+
return [
|
|
1517
|
+
makeDiagnostic(
|
|
1518
|
+
"TYPE_MISMATCH",
|
|
1519
|
+
`Target "${node.getText(sourceFile)}": constraint "${tagName}" is only valid on ${capabilityLabel(requiredCapability)} targets, but field type is "${actualType}"`,
|
|
1520
|
+
provenance
|
|
1521
|
+
)
|
|
1522
|
+
];
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
const argumentExpression = renderSyntheticArgumentExpression(
|
|
1526
|
+
definition.valueKind,
|
|
1527
|
+
parsedTag?.argumentText ?? ""
|
|
1528
|
+
);
|
|
1529
|
+
if (definition.requiresArgument && argumentExpression === null) {
|
|
1530
|
+
return [];
|
|
1531
|
+
}
|
|
1532
|
+
if (hasBroadening) {
|
|
1533
|
+
return [];
|
|
1534
|
+
}
|
|
1535
|
+
const subjectTypeText = checker.typeToString(subjectType, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
|
|
1536
|
+
const hostType = options?.hostType ?? subjectType;
|
|
1537
|
+
const hostTypeText = checker.typeToString(hostType, node, SYNTHETIC_TYPE_FORMAT_FLAGS);
|
|
1538
|
+
const result = checkSyntheticTagApplication({
|
|
1539
|
+
tagName,
|
|
1540
|
+
placement,
|
|
1541
|
+
hostType: hostTypeText,
|
|
1542
|
+
subjectType: subjectTypeText,
|
|
1543
|
+
...target?.kind === "path" ? { target: { kind: "path", text: target.rawText } } : {},
|
|
1544
|
+
...argumentExpression !== null ? { argumentExpression } : {},
|
|
1545
|
+
supportingDeclarations,
|
|
1546
|
+
...options?.extensionRegistry !== void 0 ? {
|
|
1547
|
+
extensions: options.extensionRegistry.extensions.map((extension) => ({
|
|
1548
|
+
extensionId: extension.extensionId,
|
|
1549
|
+
...extension.constraintTags !== void 0 ? {
|
|
1550
|
+
constraintTags: extension.constraintTags.map((tag) => ({ tagName: tag.tagName }))
|
|
1551
|
+
} : {}
|
|
1552
|
+
}))
|
|
1553
|
+
} : {}
|
|
1554
|
+
});
|
|
1555
|
+
if (result.diagnostics.length === 0) {
|
|
1556
|
+
return [];
|
|
1557
|
+
}
|
|
1558
|
+
const expectedLabel = definition.valueKind === null ? "compatible argument" : capabilityLabel(definition.valueKind);
|
|
1559
|
+
return [
|
|
1560
|
+
makeDiagnostic(
|
|
1561
|
+
"TYPE_MISMATCH",
|
|
1562
|
+
`Tag "@${tagName}" received an invalid argument for ${expectedLabel}.`,
|
|
1563
|
+
provenance
|
|
1564
|
+
)
|
|
1565
|
+
];
|
|
1566
|
+
}
|
|
1292
1567
|
var parserCache = /* @__PURE__ */ new Map();
|
|
1568
|
+
var parseResultCache = /* @__PURE__ */ new Map();
|
|
1293
1569
|
function getParser(options) {
|
|
1294
1570
|
const extensionTagNames = [
|
|
1295
1571
|
...options?.extensionRegistry?.extensions.flatMap(
|
|
@@ -1305,18 +1581,54 @@ function getParser(options) {
|
|
|
1305
1581
|
parserCache.set(cacheKey, parser);
|
|
1306
1582
|
return parser;
|
|
1307
1583
|
}
|
|
1584
|
+
function getExtensionRegistryCacheKey(registry) {
|
|
1585
|
+
if (registry === void 0) {
|
|
1586
|
+
return "";
|
|
1587
|
+
}
|
|
1588
|
+
return registry.extensions.map(
|
|
1589
|
+
(extension) => JSON.stringify({
|
|
1590
|
+
extensionId: extension.extensionId,
|
|
1591
|
+
typeNames: extension.types?.map((type) => type.typeName) ?? [],
|
|
1592
|
+
constraintTags: extension.constraintTags?.map((tag) => tag.tagName) ?? []
|
|
1593
|
+
})
|
|
1594
|
+
).join("|");
|
|
1595
|
+
}
|
|
1596
|
+
function getParseCacheKey(node, file, options) {
|
|
1597
|
+
const sourceFile = node.getSourceFile();
|
|
1598
|
+
const checker = options?.checker;
|
|
1599
|
+
return JSON.stringify({
|
|
1600
|
+
file,
|
|
1601
|
+
sourceFile: sourceFile.fileName,
|
|
1602
|
+
sourceText: sourceFile.text,
|
|
1603
|
+
start: node.getFullStart(),
|
|
1604
|
+
end: node.getEnd(),
|
|
1605
|
+
fieldType: options?.fieldType ?? null,
|
|
1606
|
+
subjectType: checker !== void 0 && options?.subjectType !== void 0 ? checker.typeToString(options.subjectType, node, SYNTHETIC_TYPE_FORMAT_FLAGS) : null,
|
|
1607
|
+
hostType: checker !== void 0 && options?.hostType !== void 0 ? checker.typeToString(options.hostType, node, SYNTHETIC_TYPE_FORMAT_FLAGS) : null,
|
|
1608
|
+
extensions: getExtensionRegistryCacheKey(options?.extensionRegistry)
|
|
1609
|
+
});
|
|
1610
|
+
}
|
|
1308
1611
|
function parseTSDocTags(node, file = "", options) {
|
|
1612
|
+
const cacheKey = getParseCacheKey(node, file, options);
|
|
1613
|
+
const cached = parseResultCache.get(cacheKey);
|
|
1614
|
+
if (cached !== void 0) {
|
|
1615
|
+
return cached;
|
|
1616
|
+
}
|
|
1309
1617
|
const constraints = [];
|
|
1310
1618
|
const annotations = [];
|
|
1619
|
+
const diagnostics = [];
|
|
1311
1620
|
let displayName;
|
|
1312
1621
|
let description;
|
|
1313
1622
|
let placeholder;
|
|
1314
1623
|
let displayNameProvenance;
|
|
1315
1624
|
let descriptionProvenance;
|
|
1316
1625
|
let placeholderProvenance;
|
|
1626
|
+
const rawTextTags = [];
|
|
1317
1627
|
const sourceFile = node.getSourceFile();
|
|
1318
1628
|
const sourceText = sourceFile.getFullText();
|
|
1629
|
+
const supportingDeclarations = buildSupportingDeclarations(sourceFile);
|
|
1319
1630
|
const commentRanges = ts.getLeadingCommentRanges(sourceText, node.getFullStart());
|
|
1631
|
+
const rawTextFallbacks = collectRawTextFallbacks(node, file);
|
|
1320
1632
|
if (commentRanges) {
|
|
1321
1633
|
for (const range of commentRanges) {
|
|
1322
1634
|
if (range.kind !== ts.SyntaxKind.MultiLineCommentTrivia) {
|
|
@@ -1331,12 +1643,33 @@ function parseTSDocTags(node, file = "", options) {
|
|
|
1331
1643
|
TextRange.fromStringRange(sourceText, range.pos, range.end)
|
|
1332
1644
|
);
|
|
1333
1645
|
const docComment = parserContext.docComment;
|
|
1646
|
+
const parsedComment = parseCommentBlock(
|
|
1647
|
+
commentText,
|
|
1648
|
+
sharedCommentSyntaxOptions(options, range.pos)
|
|
1649
|
+
);
|
|
1650
|
+
let parsedTagCursor = 0;
|
|
1651
|
+
const nextParsedTag = (normalizedTagName) => {
|
|
1652
|
+
while (parsedTagCursor < parsedComment.tags.length) {
|
|
1653
|
+
const candidate = parsedComment.tags[parsedTagCursor];
|
|
1654
|
+
parsedTagCursor += 1;
|
|
1655
|
+
if (candidate?.normalizedTagName === normalizedTagName) {
|
|
1656
|
+
return candidate;
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
return null;
|
|
1660
|
+
};
|
|
1661
|
+
for (const parsedTag of parsedComment.tags) {
|
|
1662
|
+
if (TAGS_REQUIRING_RAW_TEXT.has(parsedTag.normalizedTagName)) {
|
|
1663
|
+
rawTextTags.push({ tag: parsedTag, commentText, commentOffset: range.pos });
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1334
1666
|
for (const block of docComment.customBlocks) {
|
|
1335
1667
|
const tagName = normalizeConstraintTagName(block.blockTag.tagName.substring(1));
|
|
1668
|
+
const parsedTag = nextParsedTag(tagName);
|
|
1336
1669
|
if (tagName === "displayName" || tagName === "description" || tagName === "format" || tagName === "placeholder") {
|
|
1337
|
-
const text2 =
|
|
1670
|
+
const text2 = getBestBlockPayloadText(parsedTag, commentText, range.pos, block);
|
|
1338
1671
|
if (text2 === "") continue;
|
|
1339
|
-
const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
|
|
1672
|
+
const provenance2 = parsedTag !== null ? provenanceForParsedTag(parsedTag, sourceFile, file) : provenanceForComment(range, sourceFile, file, tagName);
|
|
1340
1673
|
switch (tagName) {
|
|
1341
1674
|
case "displayName":
|
|
1342
1675
|
if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
|
|
@@ -1366,11 +1699,29 @@ function parseTSDocTags(node, file = "", options) {
|
|
|
1366
1699
|
continue;
|
|
1367
1700
|
}
|
|
1368
1701
|
if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
1369
|
-
const text =
|
|
1702
|
+
const text = getBestBlockPayloadText(parsedTag, commentText, range.pos, block);
|
|
1370
1703
|
const expectedType = isBuiltinConstraintName(tagName) ? BUILTIN_CONSTRAINT_DEFINITIONS[tagName] : void 0;
|
|
1371
1704
|
if (text === "" && expectedType !== "boolean") continue;
|
|
1372
|
-
const provenance = provenanceForComment(range, sourceFile, file, tagName);
|
|
1373
|
-
const
|
|
1705
|
+
const provenance = parsedTag !== null ? provenanceForParsedTag(parsedTag, sourceFile, file) : provenanceForComment(range, sourceFile, file, tagName);
|
|
1706
|
+
const compilerDiagnostics = buildCompilerBackedConstraintDiagnostics(
|
|
1707
|
+
node,
|
|
1708
|
+
sourceFile,
|
|
1709
|
+
tagName,
|
|
1710
|
+
parsedTag,
|
|
1711
|
+
provenance,
|
|
1712
|
+
supportingDeclarations,
|
|
1713
|
+
options
|
|
1714
|
+
);
|
|
1715
|
+
if (compilerDiagnostics.length > 0) {
|
|
1716
|
+
diagnostics.push(...compilerDiagnostics);
|
|
1717
|
+
continue;
|
|
1718
|
+
}
|
|
1719
|
+
const constraintNode = parseConstraintTagValue(
|
|
1720
|
+
tagName,
|
|
1721
|
+
text,
|
|
1722
|
+
provenance,
|
|
1723
|
+
sharedTagValueOptions(options)
|
|
1724
|
+
);
|
|
1374
1725
|
if (constraintNode) {
|
|
1375
1726
|
constraints.push(constraintNode);
|
|
1376
1727
|
}
|
|
@@ -1424,57 +1775,114 @@ function parseTSDocTags(node, file = "", options) {
|
|
|
1424
1775
|
provenance: placeholderProvenance
|
|
1425
1776
|
});
|
|
1426
1777
|
}
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
const
|
|
1437
|
-
|
|
1438
|
-
|
|
1778
|
+
if (rawTextTags.length > 0) {
|
|
1779
|
+
for (const rawTextTag of rawTextTags) {
|
|
1780
|
+
const fallbackQueue = rawTextFallbacks.get(rawTextTag.tag.normalizedTagName);
|
|
1781
|
+
const fallback = fallbackQueue?.shift();
|
|
1782
|
+
const text = choosePreferredPayloadText(
|
|
1783
|
+
getSharedPayloadText(rawTextTag.tag, rawTextTag.commentText, rawTextTag.commentOffset),
|
|
1784
|
+
fallback?.text ?? ""
|
|
1785
|
+
);
|
|
1786
|
+
if (text === "") continue;
|
|
1787
|
+
const provenance = provenanceForParsedTag(rawTextTag.tag, sourceFile, file);
|
|
1788
|
+
if (rawTextTag.tag.normalizedTagName === "defaultValue") {
|
|
1789
|
+
const defaultValueNode = parseDefaultValueTagValue(text, provenance);
|
|
1790
|
+
annotations.push(defaultValueNode);
|
|
1791
|
+
continue;
|
|
1792
|
+
}
|
|
1793
|
+
const compilerDiagnostics = buildCompilerBackedConstraintDiagnostics(
|
|
1794
|
+
node,
|
|
1795
|
+
sourceFile,
|
|
1796
|
+
rawTextTag.tag.normalizedTagName,
|
|
1797
|
+
rawTextTag.tag,
|
|
1798
|
+
provenance,
|
|
1799
|
+
supportingDeclarations,
|
|
1800
|
+
options
|
|
1801
|
+
);
|
|
1802
|
+
if (compilerDiagnostics.length > 0) {
|
|
1803
|
+
diagnostics.push(...compilerDiagnostics);
|
|
1804
|
+
continue;
|
|
1805
|
+
}
|
|
1806
|
+
const constraintNode = parseConstraintTagValue(
|
|
1807
|
+
rawTextTag.tag.normalizedTagName,
|
|
1808
|
+
text,
|
|
1809
|
+
provenance,
|
|
1810
|
+
sharedTagValueOptions(options)
|
|
1811
|
+
);
|
|
1812
|
+
if (constraintNode) {
|
|
1813
|
+
constraints.push(constraintNode);
|
|
1814
|
+
}
|
|
1439
1815
|
}
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1816
|
+
}
|
|
1817
|
+
for (const [tagName, fallbacks] of rawTextFallbacks) {
|
|
1818
|
+
for (const fallback of fallbacks) {
|
|
1819
|
+
const text = fallback.text.trim();
|
|
1820
|
+
if (text === "") continue;
|
|
1821
|
+
const provenance = fallback.provenance;
|
|
1822
|
+
if (tagName === "defaultValue") {
|
|
1823
|
+
const defaultValueNode = parseDefaultValueTagValue(text, provenance);
|
|
1824
|
+
annotations.push(defaultValueNode);
|
|
1825
|
+
continue;
|
|
1826
|
+
}
|
|
1827
|
+
const compilerDiagnostics = buildCompilerBackedConstraintDiagnostics(
|
|
1828
|
+
node,
|
|
1829
|
+
sourceFile,
|
|
1830
|
+
tagName,
|
|
1831
|
+
null,
|
|
1832
|
+
provenance,
|
|
1833
|
+
supportingDeclarations,
|
|
1834
|
+
options
|
|
1835
|
+
);
|
|
1836
|
+
if (compilerDiagnostics.length > 0) {
|
|
1837
|
+
diagnostics.push(...compilerDiagnostics);
|
|
1838
|
+
continue;
|
|
1839
|
+
}
|
|
1840
|
+
const constraintNode = parseConstraintTagValue(
|
|
1841
|
+
tagName,
|
|
1842
|
+
text,
|
|
1843
|
+
provenance,
|
|
1844
|
+
sharedTagValueOptions(options)
|
|
1845
|
+
);
|
|
1846
|
+
if (constraintNode) {
|
|
1847
|
+
constraints.push(constraintNode);
|
|
1848
|
+
}
|
|
1443
1849
|
}
|
|
1444
1850
|
}
|
|
1445
|
-
|
|
1851
|
+
const result = { constraints, annotations, diagnostics };
|
|
1852
|
+
parseResultCache.set(cacheKey, result);
|
|
1853
|
+
return result;
|
|
1446
1854
|
}
|
|
1447
1855
|
function extractDisplayNameMetadata(node) {
|
|
1448
1856
|
let displayName;
|
|
1449
1857
|
const memberDisplayNames = /* @__PURE__ */ new Map();
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1858
|
+
const sourceFile = node.getSourceFile();
|
|
1859
|
+
const sourceText = sourceFile.getFullText();
|
|
1860
|
+
const commentRanges = ts.getLeadingCommentRanges(sourceText, node.getFullStart());
|
|
1861
|
+
if (commentRanges) {
|
|
1862
|
+
for (const range of commentRanges) {
|
|
1863
|
+
if (range.kind !== ts.SyntaxKind.MultiLineCommentTrivia) continue;
|
|
1864
|
+
const commentText = sourceText.substring(range.pos, range.end);
|
|
1865
|
+
if (!commentText.startsWith("/**")) continue;
|
|
1866
|
+
const parsed = parseCommentBlock(commentText);
|
|
1867
|
+
for (const tag of parsed.tags) {
|
|
1868
|
+
if (tag.normalizedTagName !== "displayName") {
|
|
1869
|
+
continue;
|
|
1870
|
+
}
|
|
1871
|
+
if (tag.target !== null && tag.argumentText !== "") {
|
|
1872
|
+
memberDisplayNames.set(tag.target.rawText, tag.argumentText);
|
|
1873
|
+
continue;
|
|
1874
|
+
}
|
|
1875
|
+
if (tag.argumentText !== "") {
|
|
1876
|
+
displayName ??= tag.argumentText;
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1461
1879
|
}
|
|
1462
|
-
displayName ??= text;
|
|
1463
1880
|
}
|
|
1464
1881
|
return {
|
|
1465
1882
|
...displayName !== void 0 && { displayName },
|
|
1466
1883
|
memberDisplayNames
|
|
1467
1884
|
};
|
|
1468
1885
|
}
|
|
1469
|
-
function extractPathTarget(text) {
|
|
1470
|
-
const trimmed = text.trimStart();
|
|
1471
|
-
const match = /^:([a-zA-Z_]\w*)(?:\s+([\s\S]*))?$/.exec(trimmed);
|
|
1472
|
-
if (!match?.[1]) return null;
|
|
1473
|
-
return {
|
|
1474
|
-
path: { segments: [match[1]] },
|
|
1475
|
-
remainingText: match[2] ?? ""
|
|
1476
|
-
};
|
|
1477
|
-
}
|
|
1478
1886
|
function extractBlockText(block) {
|
|
1479
1887
|
return extractPlainText(block.content);
|
|
1480
1888
|
}
|
|
@@ -1493,258 +1901,100 @@ function extractPlainText(node) {
|
|
|
1493
1901
|
}
|
|
1494
1902
|
return result;
|
|
1495
1903
|
}
|
|
1496
|
-
function
|
|
1497
|
-
const
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
if (
|
|
1502
|
-
|
|
1904
|
+
function choosePreferredPayloadText(primary, fallback) {
|
|
1905
|
+
const preferred = primary.trim();
|
|
1906
|
+
const alternate = fallback.trim();
|
|
1907
|
+
if (preferred === "") return alternate;
|
|
1908
|
+
if (alternate === "") return preferred;
|
|
1909
|
+
if (alternate.includes("\n")) return alternate;
|
|
1910
|
+
if (alternate.length > preferred.length && alternate.startsWith(preferred)) {
|
|
1911
|
+
return alternate;
|
|
1503
1912
|
}
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
const value = Number(effectiveText);
|
|
1510
|
-
if (Number.isNaN(value)) {
|
|
1511
|
-
return null;
|
|
1512
|
-
}
|
|
1513
|
-
const numericKind = NUMERIC_CONSTRAINT_MAP[tagName];
|
|
1514
|
-
if (numericKind) {
|
|
1515
|
-
return {
|
|
1516
|
-
kind: "constraint",
|
|
1517
|
-
constraintKind: numericKind,
|
|
1518
|
-
value,
|
|
1519
|
-
...path3 && { path: path3 },
|
|
1520
|
-
provenance
|
|
1521
|
-
};
|
|
1522
|
-
}
|
|
1523
|
-
const lengthKind = LENGTH_CONSTRAINT_MAP[tagName];
|
|
1524
|
-
if (lengthKind) {
|
|
1525
|
-
return {
|
|
1526
|
-
kind: "constraint",
|
|
1527
|
-
constraintKind: lengthKind,
|
|
1528
|
-
value,
|
|
1529
|
-
...path3 && { path: path3 },
|
|
1530
|
-
provenance
|
|
1531
|
-
};
|
|
1532
|
-
}
|
|
1533
|
-
return null;
|
|
1534
|
-
}
|
|
1535
|
-
if (expectedType === "boolean") {
|
|
1536
|
-
const trimmed = effectiveText.trim();
|
|
1537
|
-
if (trimmed !== "" && trimmed !== "true") {
|
|
1538
|
-
return null;
|
|
1539
|
-
}
|
|
1540
|
-
if (tagName === "uniqueItems") {
|
|
1541
|
-
return {
|
|
1542
|
-
kind: "constraint",
|
|
1543
|
-
constraintKind: "uniqueItems",
|
|
1544
|
-
value: true,
|
|
1545
|
-
...path3 && { path: path3 },
|
|
1546
|
-
provenance
|
|
1547
|
-
};
|
|
1548
|
-
}
|
|
1549
|
-
return null;
|
|
1913
|
+
return preferred;
|
|
1914
|
+
}
|
|
1915
|
+
function getSharedPayloadText(tag, commentText, commentOffset) {
|
|
1916
|
+
if (tag.payloadSpan === null) {
|
|
1917
|
+
return "";
|
|
1550
1918
|
}
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
}
|
|
1574
|
-
const parsed = tryParseJson(effectiveText);
|
|
1575
|
-
if (!Array.isArray(parsed)) {
|
|
1576
|
-
return null;
|
|
1577
|
-
}
|
|
1578
|
-
const members = [];
|
|
1579
|
-
for (const item of parsed) {
|
|
1580
|
-
if (typeof item === "string" || typeof item === "number") {
|
|
1581
|
-
members.push(item);
|
|
1582
|
-
} else if (typeof item === "object" && item !== null && "id" in item) {
|
|
1583
|
-
const id = item["id"];
|
|
1584
|
-
if (typeof id === "string" || typeof id === "number") {
|
|
1585
|
-
members.push(id);
|
|
1586
|
-
}
|
|
1587
|
-
}
|
|
1588
|
-
}
|
|
1589
|
-
return {
|
|
1590
|
-
kind: "constraint",
|
|
1591
|
-
constraintKind: "allowedMembers",
|
|
1592
|
-
members,
|
|
1593
|
-
...path3 && { path: path3 },
|
|
1594
|
-
provenance
|
|
1595
|
-
};
|
|
1919
|
+
return sliceCommentSpan(commentText, tag.payloadSpan, {
|
|
1920
|
+
offset: commentOffset
|
|
1921
|
+
}).trim();
|
|
1922
|
+
}
|
|
1923
|
+
function getBestBlockPayloadText(tag, commentText, commentOffset, block) {
|
|
1924
|
+
const sharedText = tag === null ? "" : getSharedPayloadText(tag, commentText, commentOffset);
|
|
1925
|
+
const blockText = extractBlockText(block).replace(/\s+/g, " ").trim();
|
|
1926
|
+
return choosePreferredPayloadText(sharedText, blockText);
|
|
1927
|
+
}
|
|
1928
|
+
function collectRawTextFallbacks(node, file) {
|
|
1929
|
+
const fallbacks = /* @__PURE__ */ new Map();
|
|
1930
|
+
for (const tag of ts.getJSDocTags(node)) {
|
|
1931
|
+
const tagName = normalizeConstraintTagName(tag.tagName.text);
|
|
1932
|
+
if (!TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
1933
|
+
const commentText = getTagCommentText(tag)?.trim() ?? "";
|
|
1934
|
+
if (commentText === "") continue;
|
|
1935
|
+
const entries = fallbacks.get(tagName) ?? [];
|
|
1936
|
+
entries.push({
|
|
1937
|
+
text: commentText,
|
|
1938
|
+
provenance: provenanceForJSDocTag(tag, file)
|
|
1939
|
+
});
|
|
1940
|
+
fallbacks.set(tagName, entries);
|
|
1596
1941
|
}
|
|
1942
|
+
return fallbacks;
|
|
1943
|
+
}
|
|
1944
|
+
function isMemberTargetDisplayName(text) {
|
|
1945
|
+
return parseTagSyntax("displayName", text).target !== null;
|
|
1946
|
+
}
|
|
1947
|
+
function provenanceForComment(range, sourceFile, file, tagName) {
|
|
1948
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(range.pos);
|
|
1597
1949
|
return {
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1950
|
+
surface: "tsdoc",
|
|
1951
|
+
file,
|
|
1952
|
+
line: line + 1,
|
|
1953
|
+
column: character,
|
|
1954
|
+
tagName: "@" + tagName
|
|
1603
1955
|
};
|
|
1604
1956
|
}
|
|
1605
|
-
function
|
|
1606
|
-
const
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1957
|
+
function provenanceForParsedTag(tag, sourceFile, file) {
|
|
1958
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(tag.tagNameSpan.start);
|
|
1959
|
+
return {
|
|
1960
|
+
surface: "tsdoc",
|
|
1961
|
+
file,
|
|
1962
|
+
line: line + 1,
|
|
1963
|
+
column: character,
|
|
1964
|
+
tagName: "@" + tag.normalizedTagName
|
|
1965
|
+
};
|
|
1966
|
+
}
|
|
1967
|
+
function provenanceForJSDocTag(tag, file) {
|
|
1968
|
+
const sourceFile = tag.getSourceFile();
|
|
1969
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(tag.getStart());
|
|
1970
|
+
return {
|
|
1971
|
+
surface: "tsdoc",
|
|
1972
|
+
file,
|
|
1973
|
+
line: line + 1,
|
|
1974
|
+
column: character,
|
|
1975
|
+
tagName: "@" + tag.tagName.text
|
|
1976
|
+
};
|
|
1977
|
+
}
|
|
1978
|
+
function getTagCommentText(tag) {
|
|
1979
|
+
if (tag.comment === void 0) {
|
|
1980
|
+
return void 0;
|
|
1612
1981
|
}
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
return makeCustomConstraintNode(
|
|
1616
|
-
directTag.extensionId,
|
|
1617
|
-
directTag.registration.constraintName,
|
|
1618
|
-
directTag.registration.parseValue(effectiveText),
|
|
1619
|
-
provenance,
|
|
1620
|
-
path3,
|
|
1621
|
-
registry
|
|
1622
|
-
);
|
|
1623
|
-
}
|
|
1624
|
-
if (!isBuiltinConstraintName(tagName)) {
|
|
1625
|
-
return null;
|
|
1626
|
-
}
|
|
1627
|
-
const broadenedTypeId = getBroadenedCustomTypeId(options?.fieldType);
|
|
1628
|
-
if (broadenedTypeId === void 0) {
|
|
1629
|
-
return null;
|
|
1630
|
-
}
|
|
1631
|
-
const broadened = registry.findBuiltinConstraintBroadening(broadenedTypeId, tagName);
|
|
1632
|
-
if (broadened === void 0) {
|
|
1633
|
-
return null;
|
|
1634
|
-
}
|
|
1635
|
-
return makeCustomConstraintNode(
|
|
1636
|
-
broadened.extensionId,
|
|
1637
|
-
broadened.registration.constraintName,
|
|
1638
|
-
broadened.registration.parseValue(effectiveText),
|
|
1639
|
-
provenance,
|
|
1640
|
-
path3,
|
|
1641
|
-
registry
|
|
1642
|
-
);
|
|
1643
|
-
}
|
|
1644
|
-
function getBroadenedCustomTypeId(fieldType) {
|
|
1645
|
-
if (fieldType?.kind === "custom") {
|
|
1646
|
-
return fieldType.typeId;
|
|
1647
|
-
}
|
|
1648
|
-
if (fieldType?.kind !== "union") {
|
|
1649
|
-
return void 0;
|
|
1650
|
-
}
|
|
1651
|
-
const customMembers = fieldType.members.filter(
|
|
1652
|
-
(member) => member.kind === "custom"
|
|
1653
|
-
);
|
|
1654
|
-
if (customMembers.length !== 1) {
|
|
1655
|
-
return void 0;
|
|
1656
|
-
}
|
|
1657
|
-
const nonCustomMembers = fieldType.members.filter((member) => member.kind !== "custom");
|
|
1658
|
-
const allOtherMembersAreNull = nonCustomMembers.every(
|
|
1659
|
-
(member) => member.kind === "primitive" && member.primitiveKind === "null"
|
|
1660
|
-
);
|
|
1661
|
-
const customMember = customMembers[0];
|
|
1662
|
-
return allOtherMembersAreNull && customMember !== void 0 ? customMember.typeId : void 0;
|
|
1663
|
-
}
|
|
1664
|
-
function makeCustomConstraintNode(extensionId, constraintName, payload, provenance, path3, registry) {
|
|
1665
|
-
const constraintId = `${extensionId}/${constraintName}`;
|
|
1666
|
-
const registration = registry.findConstraint(constraintId);
|
|
1667
|
-
if (registration === void 0) {
|
|
1668
|
-
throw new Error(
|
|
1669
|
-
`Custom TSDoc tag resolved to unregistered constraint "${constraintId}". Register the constraint before using its tag.`
|
|
1670
|
-
);
|
|
1671
|
-
}
|
|
1672
|
-
return {
|
|
1673
|
-
kind: "constraint",
|
|
1674
|
-
constraintKind: "custom",
|
|
1675
|
-
constraintId,
|
|
1676
|
-
payload,
|
|
1677
|
-
compositionRule: registration.compositionRule,
|
|
1678
|
-
...path3 && { path: path3 },
|
|
1679
|
-
provenance
|
|
1680
|
-
};
|
|
1681
|
-
}
|
|
1682
|
-
function parseDefaultValueValue(text, provenance) {
|
|
1683
|
-
const trimmed = text.trim();
|
|
1684
|
-
let value;
|
|
1685
|
-
if (trimmed === "null") {
|
|
1686
|
-
value = null;
|
|
1687
|
-
} else if (trimmed === "true") {
|
|
1688
|
-
value = true;
|
|
1689
|
-
} else if (trimmed === "false") {
|
|
1690
|
-
value = false;
|
|
1691
|
-
} else {
|
|
1692
|
-
const parsed = tryParseJson(trimmed);
|
|
1693
|
-
value = parsed !== null ? parsed : trimmed;
|
|
1694
|
-
}
|
|
1695
|
-
return {
|
|
1696
|
-
kind: "annotation",
|
|
1697
|
-
annotationKind: "defaultValue",
|
|
1698
|
-
value,
|
|
1699
|
-
provenance
|
|
1700
|
-
};
|
|
1701
|
-
}
|
|
1702
|
-
function isMemberTargetDisplayName(text) {
|
|
1703
|
-
return parseMemberTargetDisplayName(text) !== null;
|
|
1704
|
-
}
|
|
1705
|
-
function parseMemberTargetDisplayName(text) {
|
|
1706
|
-
const match = /^:([^\s]+)\s+([\s\S]+)$/.exec(text);
|
|
1707
|
-
if (!match?.[1] || !match[2]) return null;
|
|
1708
|
-
return { target: match[1], label: match[2].trim() };
|
|
1709
|
-
}
|
|
1710
|
-
function provenanceForComment(range, sourceFile, file, tagName) {
|
|
1711
|
-
const { line, character } = sourceFile.getLineAndCharacterOfPosition(range.pos);
|
|
1712
|
-
return {
|
|
1713
|
-
surface: "tsdoc",
|
|
1714
|
-
file,
|
|
1715
|
-
line: line + 1,
|
|
1716
|
-
column: character,
|
|
1717
|
-
tagName: "@" + tagName
|
|
1718
|
-
};
|
|
1719
|
-
}
|
|
1720
|
-
function provenanceForJSDocTag(tag, file) {
|
|
1721
|
-
const sourceFile = tag.getSourceFile();
|
|
1722
|
-
const { line, character } = sourceFile.getLineAndCharacterOfPosition(tag.getStart());
|
|
1723
|
-
return {
|
|
1724
|
-
surface: "tsdoc",
|
|
1725
|
-
file,
|
|
1726
|
-
line: line + 1,
|
|
1727
|
-
column: character,
|
|
1728
|
-
tagName: "@" + tag.tagName.text
|
|
1729
|
-
};
|
|
1730
|
-
}
|
|
1731
|
-
function getTagCommentText(tag) {
|
|
1732
|
-
if (tag.comment === void 0) {
|
|
1733
|
-
return void 0;
|
|
1734
|
-
}
|
|
1735
|
-
if (typeof tag.comment === "string") {
|
|
1736
|
-
return tag.comment;
|
|
1982
|
+
if (typeof tag.comment === "string") {
|
|
1983
|
+
return tag.comment;
|
|
1737
1984
|
}
|
|
1738
1985
|
return ts.getTextOfJSDocComment(tag.comment);
|
|
1739
1986
|
}
|
|
1740
1987
|
|
|
1741
1988
|
// src/analyzer/jsdoc-constraints.ts
|
|
1989
|
+
function extractJSDocParseResult(node, file = "", options) {
|
|
1990
|
+
return parseTSDocTags(node, file, options);
|
|
1991
|
+
}
|
|
1742
1992
|
function extractJSDocConstraintNodes(node, file = "", options) {
|
|
1743
|
-
const result =
|
|
1993
|
+
const result = extractJSDocParseResult(node, file, options);
|
|
1744
1994
|
return [...result.constraints];
|
|
1745
1995
|
}
|
|
1746
1996
|
function extractJSDocAnnotationNodes(node, file = "", options) {
|
|
1747
|
-
const result =
|
|
1997
|
+
const result = extractJSDocParseResult(node, file, options);
|
|
1748
1998
|
return [...result.annotations];
|
|
1749
1999
|
}
|
|
1750
2000
|
function extractDefaultValueAnnotation(initializer, file = "") {
|
|
@@ -1793,13 +2043,16 @@ var RESOLVING_TYPE_PLACEHOLDER = {
|
|
|
1793
2043
|
properties: [],
|
|
1794
2044
|
additionalProperties: true
|
|
1795
2045
|
};
|
|
1796
|
-
function makeParseOptions(extensionRegistry, fieldType) {
|
|
1797
|
-
if (extensionRegistry === void 0 && fieldType === void 0) {
|
|
2046
|
+
function makeParseOptions(extensionRegistry, fieldType, checker, subjectType, hostType) {
|
|
2047
|
+
if (extensionRegistry === void 0 && fieldType === void 0 && checker === void 0 && subjectType === void 0 && hostType === void 0) {
|
|
1798
2048
|
return void 0;
|
|
1799
2049
|
}
|
|
1800
2050
|
return {
|
|
1801
2051
|
...extensionRegistry !== void 0 && { extensionRegistry },
|
|
1802
|
-
...fieldType !== void 0 && { fieldType }
|
|
2052
|
+
...fieldType !== void 0 && { fieldType },
|
|
2053
|
+
...checker !== void 0 && { checker },
|
|
2054
|
+
...subjectType !== void 0 && { subjectType },
|
|
2055
|
+
...hostType !== void 0 && { hostType }
|
|
1803
2056
|
};
|
|
1804
2057
|
}
|
|
1805
2058
|
function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
|
|
@@ -1807,11 +2060,15 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
|
|
|
1807
2060
|
const fields = [];
|
|
1808
2061
|
const fieldLayouts = [];
|
|
1809
2062
|
const typeRegistry = {};
|
|
1810
|
-
const
|
|
2063
|
+
const diagnostics = [];
|
|
2064
|
+
const classType = checker.getTypeAtLocation(classDecl);
|
|
2065
|
+
const classDoc = extractJSDocParseResult(
|
|
1811
2066
|
classDecl,
|
|
1812
2067
|
file,
|
|
1813
|
-
makeParseOptions(extensionRegistry)
|
|
2068
|
+
makeParseOptions(extensionRegistry, void 0, checker, classType, classType)
|
|
1814
2069
|
);
|
|
2070
|
+
const annotations = [...classDoc.annotations];
|
|
2071
|
+
diagnostics.push(...classDoc.diagnostics);
|
|
1815
2072
|
const visiting = /* @__PURE__ */ new Set();
|
|
1816
2073
|
const instanceMethods = [];
|
|
1817
2074
|
const staticMethods = [];
|
|
@@ -1823,6 +2080,8 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
|
|
|
1823
2080
|
file,
|
|
1824
2081
|
typeRegistry,
|
|
1825
2082
|
visiting,
|
|
2083
|
+
diagnostics,
|
|
2084
|
+
classType,
|
|
1826
2085
|
extensionRegistry
|
|
1827
2086
|
);
|
|
1828
2087
|
if (fieldNode) {
|
|
@@ -1847,6 +2106,7 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
|
|
|
1847
2106
|
fieldLayouts,
|
|
1848
2107
|
typeRegistry,
|
|
1849
2108
|
...annotations.length > 0 && { annotations },
|
|
2109
|
+
...diagnostics.length > 0 && { diagnostics },
|
|
1850
2110
|
instanceMethods,
|
|
1851
2111
|
staticMethods
|
|
1852
2112
|
};
|
|
@@ -1855,11 +2115,15 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
|
|
|
1855
2115
|
const name = interfaceDecl.name.text;
|
|
1856
2116
|
const fields = [];
|
|
1857
2117
|
const typeRegistry = {};
|
|
1858
|
-
const
|
|
2118
|
+
const diagnostics = [];
|
|
2119
|
+
const interfaceType = checker.getTypeAtLocation(interfaceDecl);
|
|
2120
|
+
const interfaceDoc = extractJSDocParseResult(
|
|
1859
2121
|
interfaceDecl,
|
|
1860
2122
|
file,
|
|
1861
|
-
makeParseOptions(extensionRegistry)
|
|
2123
|
+
makeParseOptions(extensionRegistry, void 0, checker, interfaceType, interfaceType)
|
|
1862
2124
|
);
|
|
2125
|
+
const annotations = [...interfaceDoc.annotations];
|
|
2126
|
+
diagnostics.push(...interfaceDoc.diagnostics);
|
|
1863
2127
|
const visiting = /* @__PURE__ */ new Set();
|
|
1864
2128
|
for (const member of interfaceDecl.members) {
|
|
1865
2129
|
if (ts3.isPropertySignature(member)) {
|
|
@@ -1869,6 +2133,8 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
|
|
|
1869
2133
|
file,
|
|
1870
2134
|
typeRegistry,
|
|
1871
2135
|
visiting,
|
|
2136
|
+
diagnostics,
|
|
2137
|
+
interfaceType,
|
|
1872
2138
|
extensionRegistry
|
|
1873
2139
|
);
|
|
1874
2140
|
if (fieldNode) {
|
|
@@ -1883,6 +2149,7 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
|
|
|
1883
2149
|
fieldLayouts,
|
|
1884
2150
|
typeRegistry,
|
|
1885
2151
|
...annotations.length > 0 && { annotations },
|
|
2152
|
+
...diagnostics.length > 0 && { diagnostics },
|
|
1886
2153
|
instanceMethods: [],
|
|
1887
2154
|
staticMethods: []
|
|
1888
2155
|
};
|
|
@@ -1900,11 +2167,15 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
|
|
|
1900
2167
|
const name = typeAlias.name.text;
|
|
1901
2168
|
const fields = [];
|
|
1902
2169
|
const typeRegistry = {};
|
|
1903
|
-
const
|
|
2170
|
+
const diagnostics = [];
|
|
2171
|
+
const aliasType = checker.getTypeAtLocation(typeAlias);
|
|
2172
|
+
const typeAliasDoc = extractJSDocParseResult(
|
|
1904
2173
|
typeAlias,
|
|
1905
2174
|
file,
|
|
1906
|
-
makeParseOptions(extensionRegistry)
|
|
2175
|
+
makeParseOptions(extensionRegistry, void 0, checker, aliasType, aliasType)
|
|
1907
2176
|
);
|
|
2177
|
+
const annotations = [...typeAliasDoc.annotations];
|
|
2178
|
+
diagnostics.push(...typeAliasDoc.diagnostics);
|
|
1908
2179
|
const visiting = /* @__PURE__ */ new Set();
|
|
1909
2180
|
for (const member of typeAlias.type.members) {
|
|
1910
2181
|
if (ts3.isPropertySignature(member)) {
|
|
@@ -1914,6 +2185,8 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
|
|
|
1914
2185
|
file,
|
|
1915
2186
|
typeRegistry,
|
|
1916
2187
|
visiting,
|
|
2188
|
+
diagnostics,
|
|
2189
|
+
aliasType,
|
|
1917
2190
|
extensionRegistry
|
|
1918
2191
|
);
|
|
1919
2192
|
if (fieldNode) {
|
|
@@ -1929,12 +2202,13 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
|
|
|
1929
2202
|
fieldLayouts: fields.map(() => ({})),
|
|
1930
2203
|
typeRegistry,
|
|
1931
2204
|
...annotations.length > 0 && { annotations },
|
|
2205
|
+
...diagnostics.length > 0 && { diagnostics },
|
|
1932
2206
|
instanceMethods: [],
|
|
1933
2207
|
staticMethods: []
|
|
1934
2208
|
}
|
|
1935
2209
|
};
|
|
1936
2210
|
}
|
|
1937
|
-
function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2211
|
+
function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, extensionRegistry) {
|
|
1938
2212
|
if (!ts3.isIdentifier(prop.name)) {
|
|
1939
2213
|
return null;
|
|
1940
2214
|
}
|
|
@@ -1949,7 +2223,8 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extension
|
|
|
1949
2223
|
typeRegistry,
|
|
1950
2224
|
visiting,
|
|
1951
2225
|
prop,
|
|
1952
|
-
extensionRegistry
|
|
2226
|
+
extensionRegistry,
|
|
2227
|
+
diagnostics
|
|
1953
2228
|
);
|
|
1954
2229
|
const constraints = [];
|
|
1955
2230
|
if (prop.type && !shouldEmitPrimitiveAliasDefinition(prop.type, checker)) {
|
|
@@ -1957,13 +2232,15 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extension
|
|
|
1957
2232
|
...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
|
|
1958
2233
|
);
|
|
1959
2234
|
}
|
|
1960
|
-
|
|
1961
|
-
|
|
2235
|
+
const docResult = extractJSDocParseResult(
|
|
2236
|
+
prop,
|
|
2237
|
+
file,
|
|
2238
|
+
makeParseOptions(extensionRegistry, type, checker, tsType, hostType)
|
|
1962
2239
|
);
|
|
2240
|
+
constraints.push(...docResult.constraints);
|
|
2241
|
+
diagnostics.push(...docResult.diagnostics);
|
|
1963
2242
|
let annotations = [];
|
|
1964
|
-
annotations.push(
|
|
1965
|
-
...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
1966
|
-
);
|
|
2243
|
+
annotations.push(...docResult.annotations);
|
|
1967
2244
|
const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
|
|
1968
2245
|
if (defaultAnnotation && !annotations.some((a) => a.annotationKind === "defaultValue")) {
|
|
1969
2246
|
annotations.push(defaultAnnotation);
|
|
@@ -1979,7 +2256,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extension
|
|
|
1979
2256
|
provenance
|
|
1980
2257
|
};
|
|
1981
2258
|
}
|
|
1982
|
-
function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2259
|
+
function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, extensionRegistry) {
|
|
1983
2260
|
if (!ts3.isIdentifier(prop.name)) {
|
|
1984
2261
|
return null;
|
|
1985
2262
|
}
|
|
@@ -1994,7 +2271,8 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
|
|
|
1994
2271
|
typeRegistry,
|
|
1995
2272
|
visiting,
|
|
1996
2273
|
prop,
|
|
1997
|
-
extensionRegistry
|
|
2274
|
+
extensionRegistry,
|
|
2275
|
+
diagnostics
|
|
1998
2276
|
);
|
|
1999
2277
|
const constraints = [];
|
|
2000
2278
|
if (prop.type && !shouldEmitPrimitiveAliasDefinition(prop.type, checker)) {
|
|
@@ -2002,13 +2280,15 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
|
|
|
2002
2280
|
...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
|
|
2003
2281
|
);
|
|
2004
2282
|
}
|
|
2005
|
-
|
|
2006
|
-
|
|
2283
|
+
const docResult = extractJSDocParseResult(
|
|
2284
|
+
prop,
|
|
2285
|
+
file,
|
|
2286
|
+
makeParseOptions(extensionRegistry, type, checker, tsType, hostType)
|
|
2007
2287
|
);
|
|
2288
|
+
constraints.push(...docResult.constraints);
|
|
2289
|
+
diagnostics.push(...docResult.diagnostics);
|
|
2008
2290
|
let annotations = [];
|
|
2009
|
-
annotations.push(
|
|
2010
|
-
...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
2011
|
-
);
|
|
2291
|
+
annotations.push(...docResult.annotations);
|
|
2012
2292
|
({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
|
|
2013
2293
|
return {
|
|
2014
2294
|
kind: "field",
|
|
@@ -2137,7 +2417,7 @@ function getTypeNodeRegistrationName(typeNode) {
|
|
|
2137
2417
|
}
|
|
2138
2418
|
return null;
|
|
2139
2419
|
}
|
|
2140
|
-
function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
2420
|
+
function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
|
|
2141
2421
|
const customType = resolveRegisteredCustomType(sourceNode, extensionRegistry, checker);
|
|
2142
2422
|
if (customType) {
|
|
2143
2423
|
return customType;
|
|
@@ -2149,7 +2429,8 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
|
|
|
2149
2429
|
typeRegistry,
|
|
2150
2430
|
visiting,
|
|
2151
2431
|
sourceNode,
|
|
2152
|
-
extensionRegistry
|
|
2432
|
+
extensionRegistry,
|
|
2433
|
+
diagnostics
|
|
2153
2434
|
);
|
|
2154
2435
|
if (primitiveAlias) {
|
|
2155
2436
|
return primitiveAlias;
|
|
@@ -2192,7 +2473,8 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
|
|
|
2192
2473
|
typeRegistry,
|
|
2193
2474
|
visiting,
|
|
2194
2475
|
sourceNode,
|
|
2195
|
-
extensionRegistry
|
|
2476
|
+
extensionRegistry,
|
|
2477
|
+
diagnostics
|
|
2196
2478
|
);
|
|
2197
2479
|
}
|
|
2198
2480
|
if (checker.isArrayType(type)) {
|
|
@@ -2203,15 +2485,24 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
|
|
|
2203
2485
|
typeRegistry,
|
|
2204
2486
|
visiting,
|
|
2205
2487
|
sourceNode,
|
|
2206
|
-
extensionRegistry
|
|
2488
|
+
extensionRegistry,
|
|
2489
|
+
diagnostics
|
|
2207
2490
|
);
|
|
2208
2491
|
}
|
|
2209
2492
|
if (isObjectType(type)) {
|
|
2210
|
-
return resolveObjectType(
|
|
2493
|
+
return resolveObjectType(
|
|
2494
|
+
type,
|
|
2495
|
+
checker,
|
|
2496
|
+
file,
|
|
2497
|
+
typeRegistry,
|
|
2498
|
+
visiting,
|
|
2499
|
+
extensionRegistry,
|
|
2500
|
+
diagnostics
|
|
2501
|
+
);
|
|
2211
2502
|
}
|
|
2212
2503
|
return { kind: "primitive", primitiveKind: "string" };
|
|
2213
2504
|
}
|
|
2214
|
-
function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
2505
|
+
function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
|
|
2215
2506
|
if (!(type.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null))) {
|
|
2216
2507
|
return null;
|
|
2217
2508
|
}
|
|
@@ -2239,7 +2530,8 @@ function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiti
|
|
|
2239
2530
|
file,
|
|
2240
2531
|
typeRegistry,
|
|
2241
2532
|
visiting,
|
|
2242
|
-
extensionRegistry
|
|
2533
|
+
extensionRegistry,
|
|
2534
|
+
diagnostics
|
|
2243
2535
|
),
|
|
2244
2536
|
...constraints.length > 0 && { constraints },
|
|
2245
2537
|
...annotations.length > 0 && { annotations },
|
|
@@ -2266,7 +2558,7 @@ function shouldEmitPrimitiveAliasDefinition(typeNode, checker) {
|
|
|
2266
2558
|
const resolved = checker.getTypeFromTypeNode(aliasDecl.type);
|
|
2267
2559
|
return !!(resolved.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null));
|
|
2268
2560
|
}
|
|
2269
|
-
function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2561
|
+
function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
|
|
2270
2562
|
const nestedAliasDecl = type.aliasSymbol?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
2271
2563
|
if (nestedAliasDecl !== void 0) {
|
|
2272
2564
|
return resolveAliasedPrimitiveTarget(
|
|
@@ -2275,12 +2567,22 @@ function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiti
|
|
|
2275
2567
|
file,
|
|
2276
2568
|
typeRegistry,
|
|
2277
2569
|
visiting,
|
|
2278
|
-
extensionRegistry
|
|
2570
|
+
extensionRegistry,
|
|
2571
|
+
diagnostics
|
|
2279
2572
|
);
|
|
2280
2573
|
}
|
|
2281
|
-
return resolveTypeNode(
|
|
2574
|
+
return resolveTypeNode(
|
|
2575
|
+
type,
|
|
2576
|
+
checker,
|
|
2577
|
+
file,
|
|
2578
|
+
typeRegistry,
|
|
2579
|
+
visiting,
|
|
2580
|
+
void 0,
|
|
2581
|
+
extensionRegistry,
|
|
2582
|
+
diagnostics
|
|
2583
|
+
);
|
|
2282
2584
|
}
|
|
2283
|
-
function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
2585
|
+
function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
|
|
2284
2586
|
const typeName = getNamedTypeName(type);
|
|
2285
2587
|
const namedDecl = getNamedTypeDeclaration(type);
|
|
2286
2588
|
if (typeName && typeName in typeRegistry) {
|
|
@@ -2370,7 +2672,8 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
2370
2672
|
typeRegistry,
|
|
2371
2673
|
visiting,
|
|
2372
2674
|
nonNullMembers[0].sourceNode ?? sourceNode,
|
|
2373
|
-
extensionRegistry
|
|
2675
|
+
extensionRegistry,
|
|
2676
|
+
diagnostics
|
|
2374
2677
|
);
|
|
2375
2678
|
const result = hasNull ? {
|
|
2376
2679
|
kind: "union",
|
|
@@ -2386,7 +2689,8 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
2386
2689
|
typeRegistry,
|
|
2387
2690
|
visiting,
|
|
2388
2691
|
memberSourceNode ?? sourceNode,
|
|
2389
|
-
extensionRegistry
|
|
2692
|
+
extensionRegistry,
|
|
2693
|
+
diagnostics
|
|
2390
2694
|
)
|
|
2391
2695
|
);
|
|
2392
2696
|
if (hasNull) {
|
|
@@ -2394,7 +2698,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
2394
2698
|
}
|
|
2395
2699
|
return registerNamed({ kind: "union", members });
|
|
2396
2700
|
}
|
|
2397
|
-
function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
2701
|
+
function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
|
|
2398
2702
|
const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
|
|
2399
2703
|
const elementType = typeArgs?.[0];
|
|
2400
2704
|
const elementSourceNode = extractArrayElementTypeNode(sourceNode, checker);
|
|
@@ -2405,11 +2709,12 @@ function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
2405
2709
|
typeRegistry,
|
|
2406
2710
|
visiting,
|
|
2407
2711
|
elementSourceNode,
|
|
2408
|
-
extensionRegistry
|
|
2712
|
+
extensionRegistry,
|
|
2713
|
+
diagnostics
|
|
2409
2714
|
) : { kind: "primitive", primitiveKind: "string" };
|
|
2410
2715
|
return { kind: "array", items };
|
|
2411
2716
|
}
|
|
2412
|
-
function tryResolveRecordType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2717
|
+
function tryResolveRecordType(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
|
|
2413
2718
|
if (type.getProperties().length > 0) {
|
|
2414
2719
|
return null;
|
|
2415
2720
|
}
|
|
@@ -2424,7 +2729,8 @@ function tryResolveRecordType(type, checker, file, typeRegistry, visiting, exten
|
|
|
2424
2729
|
typeRegistry,
|
|
2425
2730
|
visiting,
|
|
2426
2731
|
void 0,
|
|
2427
|
-
extensionRegistry
|
|
2732
|
+
extensionRegistry,
|
|
2733
|
+
diagnostics
|
|
2428
2734
|
);
|
|
2429
2735
|
return { kind: "record", valueType };
|
|
2430
2736
|
}
|
|
@@ -2453,7 +2759,7 @@ function typeNodeContainsReference(type, targetName) {
|
|
|
2453
2759
|
}
|
|
2454
2760
|
}
|
|
2455
2761
|
}
|
|
2456
|
-
function resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2762
|
+
function resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
|
|
2457
2763
|
const typeName = getNamedTypeName(type);
|
|
2458
2764
|
const namedTypeName = typeName ?? void 0;
|
|
2459
2765
|
const namedDecl = getNamedTypeDeclaration(type);
|
|
@@ -2490,7 +2796,8 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
2490
2796
|
file,
|
|
2491
2797
|
typeRegistry,
|
|
2492
2798
|
visiting,
|
|
2493
|
-
extensionRegistry
|
|
2799
|
+
extensionRegistry,
|
|
2800
|
+
diagnostics
|
|
2494
2801
|
);
|
|
2495
2802
|
if (recordNode) {
|
|
2496
2803
|
visiting.delete(type);
|
|
@@ -2518,6 +2825,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
2518
2825
|
file,
|
|
2519
2826
|
typeRegistry,
|
|
2520
2827
|
visiting,
|
|
2828
|
+
diagnostics ?? [],
|
|
2521
2829
|
extensionRegistry
|
|
2522
2830
|
);
|
|
2523
2831
|
for (const prop of type.getProperties()) {
|
|
@@ -2532,7 +2840,8 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
2532
2840
|
typeRegistry,
|
|
2533
2841
|
visiting,
|
|
2534
2842
|
declaration,
|
|
2535
|
-
extensionRegistry
|
|
2843
|
+
extensionRegistry,
|
|
2844
|
+
diagnostics
|
|
2536
2845
|
);
|
|
2537
2846
|
const fieldNodeInfo = fieldInfoMap?.get(prop.name);
|
|
2538
2847
|
properties.push({
|
|
@@ -2562,7 +2871,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
2562
2871
|
}
|
|
2563
2872
|
return objectNode;
|
|
2564
2873
|
}
|
|
2565
|
-
function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2874
|
+
function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, diagnostics, extensionRegistry) {
|
|
2566
2875
|
const symbols = [type.getSymbol(), type.aliasSymbol].filter(
|
|
2567
2876
|
(s) => s?.declarations != null && s.declarations.length > 0
|
|
2568
2877
|
);
|
|
@@ -2572,6 +2881,7 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
2572
2881
|
const classDecl = declarations.find(ts3.isClassDeclaration);
|
|
2573
2882
|
if (classDecl) {
|
|
2574
2883
|
const map = /* @__PURE__ */ new Map();
|
|
2884
|
+
const hostType = checker.getTypeAtLocation(classDecl);
|
|
2575
2885
|
for (const member of classDecl.members) {
|
|
2576
2886
|
if (ts3.isPropertyDeclaration(member) && ts3.isIdentifier(member.name)) {
|
|
2577
2887
|
const fieldNode = analyzeFieldToIR(
|
|
@@ -2580,6 +2890,8 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
2580
2890
|
file,
|
|
2581
2891
|
typeRegistry,
|
|
2582
2892
|
visiting,
|
|
2893
|
+
diagnostics,
|
|
2894
|
+
hostType,
|
|
2583
2895
|
extensionRegistry
|
|
2584
2896
|
);
|
|
2585
2897
|
if (fieldNode) {
|
|
@@ -2601,6 +2913,8 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
2601
2913
|
file,
|
|
2602
2914
|
typeRegistry,
|
|
2603
2915
|
visiting,
|
|
2916
|
+
checker.getTypeAtLocation(interfaceDecl),
|
|
2917
|
+
diagnostics,
|
|
2604
2918
|
extensionRegistry
|
|
2605
2919
|
);
|
|
2606
2920
|
}
|
|
@@ -2612,6 +2926,8 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
2612
2926
|
file,
|
|
2613
2927
|
typeRegistry,
|
|
2614
2928
|
visiting,
|
|
2929
|
+
checker.getTypeAtLocation(typeAliasDecl),
|
|
2930
|
+
diagnostics,
|
|
2615
2931
|
extensionRegistry
|
|
2616
2932
|
);
|
|
2617
2933
|
}
|
|
@@ -2661,7 +2977,7 @@ function isNullishTypeNode(typeNode) {
|
|
|
2661
2977
|
}
|
|
2662
2978
|
return ts3.isLiteralTypeNode(typeNode) && (typeNode.literal.kind === ts3.SyntaxKind.NullKeyword || typeNode.literal.kind === ts3.SyntaxKind.UndefinedKeyword);
|
|
2663
2979
|
}
|
|
2664
|
-
function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2980
|
+
function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, hostType, diagnostics, extensionRegistry) {
|
|
2665
2981
|
const map = /* @__PURE__ */ new Map();
|
|
2666
2982
|
for (const member of members) {
|
|
2667
2983
|
if (ts3.isPropertySignature(member)) {
|
|
@@ -2671,6 +2987,8 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, e
|
|
|
2671
2987
|
file,
|
|
2672
2988
|
typeRegistry,
|
|
2673
2989
|
visiting,
|
|
2990
|
+
diagnostics,
|
|
2991
|
+
hostType,
|
|
2674
2992
|
extensionRegistry
|
|
2675
2993
|
);
|
|
2676
2994
|
if (fieldNode) {
|
|
@@ -2902,760 +3220,44 @@ function analyzeNamedTypeToIR(filePath, typeName, extensionRegistry) {
|
|
|
2902
3220
|
}
|
|
2903
3221
|
|
|
2904
3222
|
// src/validate/constraint-validator.ts
|
|
2905
|
-
import {
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
ctx.diagnostics.push({
|
|
2917
|
-
code: "TYPE_MISMATCH",
|
|
2918
|
-
message,
|
|
2919
|
-
severity: "error",
|
|
2920
|
-
primaryLocation: primary,
|
|
2921
|
-
relatedLocations: []
|
|
2922
|
-
});
|
|
2923
|
-
}
|
|
2924
|
-
function addUnknownExtension(ctx, message, primary) {
|
|
2925
|
-
ctx.diagnostics.push({
|
|
2926
|
-
code: "UNKNOWN_EXTENSION",
|
|
2927
|
-
message,
|
|
2928
|
-
severity: "warning",
|
|
2929
|
-
primaryLocation: primary,
|
|
2930
|
-
relatedLocations: []
|
|
2931
|
-
});
|
|
2932
|
-
}
|
|
2933
|
-
function addUnknownPathTarget(ctx, message, primary) {
|
|
2934
|
-
ctx.diagnostics.push({
|
|
2935
|
-
code: "UNKNOWN_PATH_TARGET",
|
|
2936
|
-
message,
|
|
2937
|
-
severity: "error",
|
|
2938
|
-
primaryLocation: primary,
|
|
2939
|
-
relatedLocations: []
|
|
2940
|
-
});
|
|
2941
|
-
}
|
|
2942
|
-
function addConstraintBroadening(ctx, message, primary, related) {
|
|
2943
|
-
ctx.diagnostics.push({
|
|
2944
|
-
code: "CONSTRAINT_BROADENING",
|
|
2945
|
-
message,
|
|
2946
|
-
severity: "error",
|
|
2947
|
-
primaryLocation: primary,
|
|
2948
|
-
relatedLocations: [related]
|
|
2949
|
-
});
|
|
2950
|
-
}
|
|
2951
|
-
function getExtensionIdFromConstraintId(constraintId) {
|
|
2952
|
-
const separator = constraintId.lastIndexOf("/");
|
|
2953
|
-
if (separator <= 0) {
|
|
2954
|
-
return null;
|
|
2955
|
-
}
|
|
2956
|
-
return constraintId.slice(0, separator);
|
|
2957
|
-
}
|
|
2958
|
-
function findNumeric(constraints, constraintKind) {
|
|
2959
|
-
return constraints.find((c) => c.constraintKind === constraintKind);
|
|
2960
|
-
}
|
|
2961
|
-
function findLength(constraints, constraintKind) {
|
|
2962
|
-
return constraints.find((c) => c.constraintKind === constraintKind);
|
|
2963
|
-
}
|
|
2964
|
-
function findAllowedMembers(constraints) {
|
|
2965
|
-
return constraints.filter(
|
|
2966
|
-
(c) => c.constraintKind === "allowedMembers"
|
|
2967
|
-
);
|
|
2968
|
-
}
|
|
2969
|
-
function findConstConstraints(constraints) {
|
|
2970
|
-
return constraints.filter(
|
|
2971
|
-
(c) => c.constraintKind === "const"
|
|
2972
|
-
);
|
|
2973
|
-
}
|
|
2974
|
-
function jsonValueEquals(left, right) {
|
|
2975
|
-
if (left === right) {
|
|
2976
|
-
return true;
|
|
2977
|
-
}
|
|
2978
|
-
if (Array.isArray(left) || Array.isArray(right)) {
|
|
2979
|
-
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
|
|
2980
|
-
return false;
|
|
2981
|
-
}
|
|
2982
|
-
return left.every((item, index) => jsonValueEquals(item, right[index]));
|
|
2983
|
-
}
|
|
2984
|
-
if (isJsonObject(left) || isJsonObject(right)) {
|
|
2985
|
-
if (!isJsonObject(left) || !isJsonObject(right)) {
|
|
2986
|
-
return false;
|
|
2987
|
-
}
|
|
2988
|
-
const leftKeys = Object.keys(left).sort();
|
|
2989
|
-
const rightKeys = Object.keys(right).sort();
|
|
2990
|
-
if (leftKeys.length !== rightKeys.length) {
|
|
2991
|
-
return false;
|
|
2992
|
-
}
|
|
2993
|
-
return leftKeys.every((key, index) => {
|
|
2994
|
-
const rightKey = rightKeys[index];
|
|
2995
|
-
if (rightKey !== key) {
|
|
2996
|
-
return false;
|
|
2997
|
-
}
|
|
2998
|
-
const leftValue = left[key];
|
|
2999
|
-
const rightValue = right[rightKey];
|
|
3000
|
-
return leftValue !== void 0 && rightValue !== void 0 && jsonValueEquals(leftValue, rightValue);
|
|
3001
|
-
});
|
|
3002
|
-
}
|
|
3003
|
-
return false;
|
|
3004
|
-
}
|
|
3005
|
-
function isJsonObject(value) {
|
|
3006
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3007
|
-
}
|
|
3008
|
-
function isOrderedBoundConstraint(constraint) {
|
|
3009
|
-
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";
|
|
3010
|
-
}
|
|
3011
|
-
function pathKey(constraint) {
|
|
3012
|
-
return constraint.path?.segments.join(".") ?? "";
|
|
3013
|
-
}
|
|
3014
|
-
function orderedBoundFamily(kind) {
|
|
3015
|
-
switch (kind) {
|
|
3016
|
-
case "minimum":
|
|
3017
|
-
case "exclusiveMinimum":
|
|
3018
|
-
return "numeric-lower";
|
|
3019
|
-
case "maximum":
|
|
3020
|
-
case "exclusiveMaximum":
|
|
3021
|
-
return "numeric-upper";
|
|
3022
|
-
case "minLength":
|
|
3023
|
-
return "minLength";
|
|
3024
|
-
case "minItems":
|
|
3025
|
-
return "minItems";
|
|
3026
|
-
case "maxLength":
|
|
3027
|
-
return "maxLength";
|
|
3028
|
-
case "maxItems":
|
|
3029
|
-
return "maxItems";
|
|
3030
|
-
default: {
|
|
3031
|
-
const _exhaustive = kind;
|
|
3032
|
-
return _exhaustive;
|
|
3033
|
-
}
|
|
3034
|
-
}
|
|
3035
|
-
}
|
|
3036
|
-
function isNumericLowerKind(kind) {
|
|
3037
|
-
return kind === "minimum" || kind === "exclusiveMinimum";
|
|
3038
|
-
}
|
|
3039
|
-
function isNumericUpperKind(kind) {
|
|
3040
|
-
return kind === "maximum" || kind === "exclusiveMaximum";
|
|
3041
|
-
}
|
|
3042
|
-
function describeConstraintTag(constraint) {
|
|
3043
|
-
return `@${constraint.constraintKind}`;
|
|
3044
|
-
}
|
|
3045
|
-
function compareConstraintStrength(current, previous) {
|
|
3046
|
-
const family = orderedBoundFamily(current.constraintKind);
|
|
3047
|
-
if (family === "numeric-lower") {
|
|
3048
|
-
if (!isNumericLowerKind(current.constraintKind) || !isNumericLowerKind(previous.constraintKind)) {
|
|
3049
|
-
throw new Error("numeric-lower family received non-numeric lower-bound constraint");
|
|
3050
|
-
}
|
|
3051
|
-
if (current.value !== previous.value) {
|
|
3052
|
-
return current.value > previous.value ? 1 : -1;
|
|
3053
|
-
}
|
|
3054
|
-
if (current.constraintKind === "exclusiveMinimum" && previous.constraintKind === "minimum") {
|
|
3055
|
-
return 1;
|
|
3056
|
-
}
|
|
3057
|
-
if (current.constraintKind === "minimum" && previous.constraintKind === "exclusiveMinimum") {
|
|
3058
|
-
return -1;
|
|
3059
|
-
}
|
|
3060
|
-
return 0;
|
|
3061
|
-
}
|
|
3062
|
-
if (family === "numeric-upper") {
|
|
3063
|
-
if (!isNumericUpperKind(current.constraintKind) || !isNumericUpperKind(previous.constraintKind)) {
|
|
3064
|
-
throw new Error("numeric-upper family received non-numeric upper-bound constraint");
|
|
3065
|
-
}
|
|
3066
|
-
if (current.value !== previous.value) {
|
|
3067
|
-
return current.value < previous.value ? 1 : -1;
|
|
3068
|
-
}
|
|
3069
|
-
if (current.constraintKind === "exclusiveMaximum" && previous.constraintKind === "maximum") {
|
|
3070
|
-
return 1;
|
|
3071
|
-
}
|
|
3072
|
-
if (current.constraintKind === "maximum" && previous.constraintKind === "exclusiveMaximum") {
|
|
3073
|
-
return -1;
|
|
3074
|
-
}
|
|
3075
|
-
return 0;
|
|
3076
|
-
}
|
|
3077
|
-
switch (family) {
|
|
3078
|
-
case "minLength":
|
|
3079
|
-
case "minItems":
|
|
3080
|
-
if (current.value === previous.value) {
|
|
3081
|
-
return 0;
|
|
3082
|
-
}
|
|
3083
|
-
return current.value > previous.value ? 1 : -1;
|
|
3084
|
-
case "maxLength":
|
|
3085
|
-
case "maxItems":
|
|
3086
|
-
if (current.value === previous.value) {
|
|
3087
|
-
return 0;
|
|
3088
|
-
}
|
|
3089
|
-
return current.value < previous.value ? 1 : -1;
|
|
3090
|
-
default: {
|
|
3091
|
-
const _exhaustive = family;
|
|
3092
|
-
return _exhaustive;
|
|
3093
|
-
}
|
|
3094
|
-
}
|
|
3095
|
-
}
|
|
3096
|
-
function checkConstraintBroadening(ctx, fieldName, constraints) {
|
|
3097
|
-
const strongestByKey = /* @__PURE__ */ new Map();
|
|
3098
|
-
for (const constraint of constraints) {
|
|
3099
|
-
if (!isOrderedBoundConstraint(constraint)) {
|
|
3100
|
-
continue;
|
|
3101
|
-
}
|
|
3102
|
-
const key = `${orderedBoundFamily(constraint.constraintKind)}:${pathKey(constraint)}`;
|
|
3103
|
-
const previous = strongestByKey.get(key);
|
|
3104
|
-
if (previous === void 0) {
|
|
3105
|
-
strongestByKey.set(key, constraint);
|
|
3106
|
-
continue;
|
|
3107
|
-
}
|
|
3108
|
-
const strength = compareConstraintStrength(constraint, previous);
|
|
3109
|
-
if (strength < 0) {
|
|
3110
|
-
const displayFieldName = formatPathTargetFieldName(
|
|
3111
|
-
fieldName,
|
|
3112
|
-
constraint.path?.segments ?? []
|
|
3113
|
-
);
|
|
3114
|
-
addConstraintBroadening(
|
|
3115
|
-
ctx,
|
|
3116
|
-
`Field "${displayFieldName}": ${describeConstraintTag(constraint)} (${String(constraint.value)}) is broader than earlier ${describeConstraintTag(previous)} (${String(previous.value)}). Constraints can only narrow.`,
|
|
3117
|
-
constraint.provenance,
|
|
3118
|
-
previous.provenance
|
|
3119
|
-
);
|
|
3120
|
-
continue;
|
|
3121
|
-
}
|
|
3122
|
-
if (strength <= 0) {
|
|
3123
|
-
continue;
|
|
3124
|
-
}
|
|
3125
|
-
strongestByKey.set(key, constraint);
|
|
3126
|
-
}
|
|
3127
|
-
}
|
|
3128
|
-
function compareCustomConstraintStrength(current, previous) {
|
|
3129
|
-
const order = current.comparePayloads(current.constraint.payload, previous.constraint.payload);
|
|
3130
|
-
const equalPayloadTiebreaker = order === 0 ? compareSemanticInclusivity(current.role.inclusive, previous.role.inclusive) : order;
|
|
3131
|
-
switch (current.role.bound) {
|
|
3132
|
-
case "lower":
|
|
3133
|
-
return equalPayloadTiebreaker;
|
|
3134
|
-
case "upper":
|
|
3135
|
-
return equalPayloadTiebreaker === 0 ? 0 : -equalPayloadTiebreaker;
|
|
3136
|
-
case "exact":
|
|
3137
|
-
return order === 0 ? 0 : Number.NaN;
|
|
3138
|
-
default: {
|
|
3139
|
-
const _exhaustive = current.role.bound;
|
|
3140
|
-
return _exhaustive;
|
|
3141
|
-
}
|
|
3142
|
-
}
|
|
3143
|
-
}
|
|
3144
|
-
function compareSemanticInclusivity(currentInclusive, previousInclusive) {
|
|
3145
|
-
if (currentInclusive === previousInclusive) {
|
|
3146
|
-
return 0;
|
|
3147
|
-
}
|
|
3148
|
-
return currentInclusive ? -1 : 1;
|
|
3149
|
-
}
|
|
3150
|
-
function customConstraintsContradict(lower, upper) {
|
|
3151
|
-
const order = lower.comparePayloads(lower.constraint.payload, upper.constraint.payload);
|
|
3152
|
-
if (order > 0) {
|
|
3153
|
-
return true;
|
|
3154
|
-
}
|
|
3155
|
-
if (order < 0) {
|
|
3156
|
-
return false;
|
|
3157
|
-
}
|
|
3158
|
-
return !lower.role.inclusive || !upper.role.inclusive;
|
|
3159
|
-
}
|
|
3160
|
-
function describeCustomConstraintTag(constraint) {
|
|
3161
|
-
return constraint.provenance.tagName ?? constraint.constraintId;
|
|
3162
|
-
}
|
|
3163
|
-
function checkCustomConstraintSemantics(ctx, fieldName, constraints) {
|
|
3164
|
-
if (ctx.extensionRegistry === void 0) {
|
|
3165
|
-
return;
|
|
3166
|
-
}
|
|
3167
|
-
const strongestByKey = /* @__PURE__ */ new Map();
|
|
3168
|
-
const lowerByFamily = /* @__PURE__ */ new Map();
|
|
3169
|
-
const upperByFamily = /* @__PURE__ */ new Map();
|
|
3170
|
-
for (const constraint of constraints) {
|
|
3171
|
-
if (constraint.constraintKind !== "custom") {
|
|
3172
|
-
continue;
|
|
3173
|
-
}
|
|
3174
|
-
const registration = ctx.extensionRegistry.findConstraint(constraint.constraintId);
|
|
3175
|
-
if (registration?.comparePayloads === void 0 || registration.semanticRole === void 0) {
|
|
3176
|
-
continue;
|
|
3177
|
-
}
|
|
3178
|
-
const entry = {
|
|
3179
|
-
constraint,
|
|
3180
|
-
comparePayloads: registration.comparePayloads,
|
|
3181
|
-
role: registration.semanticRole
|
|
3182
|
-
};
|
|
3183
|
-
const familyKey = `${registration.semanticRole.family}:${pathKey(constraint)}`;
|
|
3184
|
-
const boundKey = `${familyKey}:${registration.semanticRole.bound}`;
|
|
3185
|
-
const previous = strongestByKey.get(boundKey);
|
|
3186
|
-
if (previous !== void 0) {
|
|
3187
|
-
const strength = compareCustomConstraintStrength(entry, previous);
|
|
3188
|
-
if (Number.isNaN(strength)) {
|
|
3189
|
-
addContradiction(
|
|
3190
|
-
ctx,
|
|
3191
|
-
`Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} conflicts with ${describeCustomConstraintTag(previous.constraint)}`,
|
|
3192
|
-
constraint.provenance,
|
|
3193
|
-
previous.constraint.provenance
|
|
3194
|
-
);
|
|
3195
|
-
continue;
|
|
3196
|
-
}
|
|
3197
|
-
if (strength < 0) {
|
|
3198
|
-
addConstraintBroadening(
|
|
3199
|
-
ctx,
|
|
3200
|
-
`Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} is broader than earlier ${describeCustomConstraintTag(previous.constraint)}. Constraints can only narrow.`,
|
|
3201
|
-
constraint.provenance,
|
|
3202
|
-
previous.constraint.provenance
|
|
3203
|
-
);
|
|
3204
|
-
continue;
|
|
3205
|
-
}
|
|
3206
|
-
if (strength > 0) {
|
|
3207
|
-
strongestByKey.set(boundKey, entry);
|
|
3208
|
-
}
|
|
3209
|
-
} else {
|
|
3210
|
-
strongestByKey.set(boundKey, entry);
|
|
3211
|
-
}
|
|
3212
|
-
if (registration.semanticRole.bound === "lower") {
|
|
3213
|
-
lowerByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
|
|
3214
|
-
} else if (registration.semanticRole.bound === "upper") {
|
|
3215
|
-
upperByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
|
|
3216
|
-
}
|
|
3217
|
-
}
|
|
3218
|
-
for (const [familyKey, lower] of lowerByFamily) {
|
|
3219
|
-
const upper = upperByFamily.get(familyKey);
|
|
3220
|
-
if (upper === void 0) {
|
|
3221
|
-
continue;
|
|
3222
|
-
}
|
|
3223
|
-
if (!customConstraintsContradict(lower, upper)) {
|
|
3224
|
-
continue;
|
|
3225
|
-
}
|
|
3226
|
-
addContradiction(
|
|
3227
|
-
ctx,
|
|
3228
|
-
`Field "${formatPathTargetFieldName(fieldName, lower.constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(lower.constraint)} contradicts ${describeCustomConstraintTag(upper.constraint)}`,
|
|
3229
|
-
lower.constraint.provenance,
|
|
3230
|
-
upper.constraint.provenance
|
|
3231
|
-
);
|
|
3232
|
-
}
|
|
3233
|
-
}
|
|
3234
|
-
function checkNumericContradictions(ctx, fieldName, constraints) {
|
|
3235
|
-
const min = findNumeric(constraints, "minimum");
|
|
3236
|
-
const max = findNumeric(constraints, "maximum");
|
|
3237
|
-
const exMin = findNumeric(constraints, "exclusiveMinimum");
|
|
3238
|
-
const exMax = findNumeric(constraints, "exclusiveMaximum");
|
|
3239
|
-
if (min !== void 0 && max !== void 0 && min.value > max.value) {
|
|
3240
|
-
addContradiction(
|
|
3241
|
-
ctx,
|
|
3242
|
-
`Field "${fieldName}": minimum (${String(min.value)}) is greater than maximum (${String(max.value)})`,
|
|
3243
|
-
min.provenance,
|
|
3244
|
-
max.provenance
|
|
3245
|
-
);
|
|
3246
|
-
}
|
|
3247
|
-
if (exMin !== void 0 && max !== void 0 && exMin.value >= max.value) {
|
|
3248
|
-
addContradiction(
|
|
3249
|
-
ctx,
|
|
3250
|
-
`Field "${fieldName}": exclusiveMinimum (${String(exMin.value)}) is greater than or equal to maximum (${String(max.value)})`,
|
|
3251
|
-
exMin.provenance,
|
|
3252
|
-
max.provenance
|
|
3253
|
-
);
|
|
3254
|
-
}
|
|
3255
|
-
if (min !== void 0 && exMax !== void 0 && min.value >= exMax.value) {
|
|
3256
|
-
addContradiction(
|
|
3257
|
-
ctx,
|
|
3258
|
-
`Field "${fieldName}": minimum (${String(min.value)}) is greater than or equal to exclusiveMaximum (${String(exMax.value)})`,
|
|
3259
|
-
min.provenance,
|
|
3260
|
-
exMax.provenance
|
|
3261
|
-
);
|
|
3262
|
-
}
|
|
3263
|
-
if (exMin !== void 0 && exMax !== void 0 && exMin.value >= exMax.value) {
|
|
3264
|
-
addContradiction(
|
|
3265
|
-
ctx,
|
|
3266
|
-
`Field "${fieldName}": exclusiveMinimum (${String(exMin.value)}) is greater than or equal to exclusiveMaximum (${String(exMax.value)})`,
|
|
3267
|
-
exMin.provenance,
|
|
3268
|
-
exMax.provenance
|
|
3269
|
-
);
|
|
3270
|
-
}
|
|
3271
|
-
}
|
|
3272
|
-
function checkLengthContradictions(ctx, fieldName, constraints) {
|
|
3273
|
-
const minLen = findLength(constraints, "minLength");
|
|
3274
|
-
const maxLen = findLength(constraints, "maxLength");
|
|
3275
|
-
if (minLen !== void 0 && maxLen !== void 0 && minLen.value > maxLen.value) {
|
|
3276
|
-
addContradiction(
|
|
3277
|
-
ctx,
|
|
3278
|
-
`Field "${fieldName}": minLength (${String(minLen.value)}) is greater than maxLength (${String(maxLen.value)})`,
|
|
3279
|
-
minLen.provenance,
|
|
3280
|
-
maxLen.provenance
|
|
3281
|
-
);
|
|
3282
|
-
}
|
|
3283
|
-
const minItems = findLength(constraints, "minItems");
|
|
3284
|
-
const maxItems = findLength(constraints, "maxItems");
|
|
3285
|
-
if (minItems !== void 0 && maxItems !== void 0 && minItems.value > maxItems.value) {
|
|
3286
|
-
addContradiction(
|
|
3287
|
-
ctx,
|
|
3288
|
-
`Field "${fieldName}": minItems (${String(minItems.value)}) is greater than maxItems (${String(maxItems.value)})`,
|
|
3289
|
-
minItems.provenance,
|
|
3290
|
-
maxItems.provenance
|
|
3291
|
-
);
|
|
3292
|
-
}
|
|
3293
|
-
}
|
|
3294
|
-
function checkAllowedMembersContradiction(ctx, fieldName, constraints) {
|
|
3295
|
-
const members = findAllowedMembers(constraints);
|
|
3296
|
-
if (members.length < 2) return;
|
|
3297
|
-
const firstSet = new Set(members[0]?.members ?? []);
|
|
3298
|
-
for (let i = 1; i < members.length; i++) {
|
|
3299
|
-
const current = members[i];
|
|
3300
|
-
if (current === void 0) continue;
|
|
3301
|
-
for (const m of firstSet) {
|
|
3302
|
-
if (!current.members.includes(m)) {
|
|
3303
|
-
firstSet.delete(m);
|
|
3304
|
-
}
|
|
3305
|
-
}
|
|
3306
|
-
}
|
|
3307
|
-
if (firstSet.size === 0) {
|
|
3308
|
-
const first = members[0];
|
|
3309
|
-
const second = members[1];
|
|
3310
|
-
if (first !== void 0 && second !== void 0) {
|
|
3311
|
-
addContradiction(
|
|
3312
|
-
ctx,
|
|
3313
|
-
`Field "${fieldName}": allowedMembers constraints have an empty intersection (no valid values remain)`,
|
|
3314
|
-
first.provenance,
|
|
3315
|
-
second.provenance
|
|
3316
|
-
);
|
|
3317
|
-
}
|
|
3318
|
-
}
|
|
3319
|
-
}
|
|
3320
|
-
function checkConstContradictions(ctx, fieldName, constraints) {
|
|
3321
|
-
const constConstraints = findConstConstraints(constraints);
|
|
3322
|
-
if (constConstraints.length < 2) return;
|
|
3323
|
-
const first = constConstraints[0];
|
|
3324
|
-
if (first === void 0) return;
|
|
3325
|
-
for (let i = 1; i < constConstraints.length; i++) {
|
|
3326
|
-
const current = constConstraints[i];
|
|
3327
|
-
if (current === void 0) continue;
|
|
3328
|
-
if (jsonValueEquals(first.value, current.value)) {
|
|
3329
|
-
continue;
|
|
3330
|
-
}
|
|
3331
|
-
addContradiction(
|
|
3332
|
-
ctx,
|
|
3333
|
-
`Field "${fieldName}": conflicting @const constraints require both ${JSON.stringify(first.value)} and ${JSON.stringify(current.value)}`,
|
|
3334
|
-
first.provenance,
|
|
3335
|
-
current.provenance
|
|
3336
|
-
);
|
|
3337
|
-
}
|
|
3338
|
-
}
|
|
3339
|
-
function typeLabel(type) {
|
|
3340
|
-
switch (type.kind) {
|
|
3341
|
-
case "primitive":
|
|
3342
|
-
return type.primitiveKind;
|
|
3343
|
-
case "enum":
|
|
3344
|
-
return "enum";
|
|
3345
|
-
case "array":
|
|
3346
|
-
return "array";
|
|
3347
|
-
case "object":
|
|
3348
|
-
return "object";
|
|
3349
|
-
case "record":
|
|
3350
|
-
return "record";
|
|
3351
|
-
case "union":
|
|
3352
|
-
return "union";
|
|
3353
|
-
case "reference":
|
|
3354
|
-
return `reference(${type.name})`;
|
|
3355
|
-
case "dynamic":
|
|
3356
|
-
return `dynamic(${type.dynamicKind})`;
|
|
3357
|
-
case "custom":
|
|
3358
|
-
return `custom(${type.typeId})`;
|
|
3359
|
-
default: {
|
|
3360
|
-
const _exhaustive = type;
|
|
3361
|
-
return String(_exhaustive);
|
|
3362
|
-
}
|
|
3363
|
-
}
|
|
3364
|
-
}
|
|
3365
|
-
function dereferenceType(ctx, type) {
|
|
3366
|
-
let current = type;
|
|
3367
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3368
|
-
while (current.kind === "reference") {
|
|
3369
|
-
if (seen.has(current.name)) {
|
|
3370
|
-
return current;
|
|
3371
|
-
}
|
|
3372
|
-
seen.add(current.name);
|
|
3373
|
-
const definition = ctx.typeRegistry[current.name];
|
|
3374
|
-
if (definition === void 0) {
|
|
3375
|
-
return current;
|
|
3376
|
-
}
|
|
3377
|
-
current = definition.type;
|
|
3378
|
-
}
|
|
3379
|
-
return current;
|
|
3380
|
-
}
|
|
3381
|
-
function collectReferencedTypeConstraints(ctx, type) {
|
|
3382
|
-
const collected = [];
|
|
3383
|
-
let current = type;
|
|
3384
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3385
|
-
while (current.kind === "reference") {
|
|
3386
|
-
if (seen.has(current.name)) {
|
|
3387
|
-
break;
|
|
3388
|
-
}
|
|
3389
|
-
seen.add(current.name);
|
|
3390
|
-
const definition = ctx.typeRegistry[current.name];
|
|
3391
|
-
if (definition === void 0) {
|
|
3392
|
-
break;
|
|
3393
|
-
}
|
|
3394
|
-
if (definition.constraints !== void 0) {
|
|
3395
|
-
collected.push(...definition.constraints);
|
|
3396
|
-
}
|
|
3397
|
-
current = definition.type;
|
|
3398
|
-
}
|
|
3399
|
-
return collected;
|
|
3400
|
-
}
|
|
3401
|
-
function resolvePathTargetType(ctx, type, segments) {
|
|
3402
|
-
const effectiveType = dereferenceType(ctx, type);
|
|
3403
|
-
if (segments.length === 0) {
|
|
3404
|
-
return { kind: "resolved", type: effectiveType };
|
|
3405
|
-
}
|
|
3406
|
-
if (effectiveType.kind === "array") {
|
|
3407
|
-
return resolvePathTargetType(ctx, effectiveType.items, segments);
|
|
3408
|
-
}
|
|
3409
|
-
if (effectiveType.kind === "object") {
|
|
3410
|
-
const [segment, ...rest] = segments;
|
|
3411
|
-
if (segment === void 0) {
|
|
3412
|
-
throw new Error("Invariant violation: object path traversal requires a segment");
|
|
3413
|
-
}
|
|
3414
|
-
const property = effectiveType.properties.find((prop) => prop.name === segment);
|
|
3415
|
-
if (property === void 0) {
|
|
3416
|
-
return { kind: "missing-property", segment };
|
|
3417
|
-
}
|
|
3418
|
-
return resolvePathTargetType(ctx, property.type, rest);
|
|
3419
|
-
}
|
|
3420
|
-
return { kind: "unresolvable", type: effectiveType };
|
|
3421
|
-
}
|
|
3422
|
-
function isNullType(type) {
|
|
3423
|
-
return type.kind === "primitive" && type.primitiveKind === "null";
|
|
3424
|
-
}
|
|
3425
|
-
function collectCustomConstraintCandidateTypes(ctx, type) {
|
|
3426
|
-
const effectiveType = dereferenceType(ctx, type);
|
|
3427
|
-
const candidates = [effectiveType];
|
|
3428
|
-
if (effectiveType.kind === "array") {
|
|
3429
|
-
candidates.push(...collectCustomConstraintCandidateTypes(ctx, effectiveType.items));
|
|
3430
|
-
}
|
|
3431
|
-
if (effectiveType.kind === "union") {
|
|
3432
|
-
const memberTypes = effectiveType.members.map((member) => dereferenceType(ctx, member));
|
|
3433
|
-
const nonNullMembers = memberTypes.filter((member) => !isNullType(member));
|
|
3434
|
-
if (nonNullMembers.length === 1 && nonNullMembers.length < memberTypes.length) {
|
|
3435
|
-
const [nullableMember] = nonNullMembers;
|
|
3436
|
-
if (nullableMember !== void 0) {
|
|
3437
|
-
candidates.push(...collectCustomConstraintCandidateTypes(ctx, nullableMember));
|
|
3438
|
-
}
|
|
3439
|
-
}
|
|
3440
|
-
}
|
|
3441
|
-
return candidates;
|
|
3442
|
-
}
|
|
3443
|
-
function formatPathTargetFieldName(fieldName, path3) {
|
|
3444
|
-
return path3.length === 0 ? fieldName : `${fieldName}.${path3.join(".")}`;
|
|
3445
|
-
}
|
|
3446
|
-
function checkConstraintOnType(ctx, fieldName, type, constraint) {
|
|
3447
|
-
const effectiveType = dereferenceType(ctx, type);
|
|
3448
|
-
const isNumber = effectiveType.kind === "primitive" && ["number", "integer", "bigint"].includes(effectiveType.primitiveKind);
|
|
3449
|
-
const isString = effectiveType.kind === "primitive" && effectiveType.primitiveKind === "string";
|
|
3450
|
-
const isArray = effectiveType.kind === "array";
|
|
3451
|
-
const isEnum = effectiveType.kind === "enum";
|
|
3452
|
-
const arrayItemType = effectiveType.kind === "array" ? dereferenceType(ctx, effectiveType.items) : void 0;
|
|
3453
|
-
const isStringArray = arrayItemType?.kind === "primitive" && arrayItemType.primitiveKind === "string";
|
|
3454
|
-
const label = typeLabel(effectiveType);
|
|
3455
|
-
const ck = constraint.constraintKind;
|
|
3456
|
-
switch (ck) {
|
|
3457
|
-
case "minimum":
|
|
3458
|
-
case "maximum":
|
|
3459
|
-
case "exclusiveMinimum":
|
|
3460
|
-
case "exclusiveMaximum":
|
|
3461
|
-
case "multipleOf": {
|
|
3462
|
-
if (!isNumber) {
|
|
3463
|
-
addTypeMismatch(
|
|
3464
|
-
ctx,
|
|
3465
|
-
`Field "${fieldName}": constraint "${ck}" is only valid on number fields, but field type is "${label}"`,
|
|
3466
|
-
constraint.provenance
|
|
3467
|
-
);
|
|
3468
|
-
}
|
|
3469
|
-
break;
|
|
3470
|
-
}
|
|
3471
|
-
case "minLength":
|
|
3472
|
-
case "maxLength":
|
|
3473
|
-
case "pattern": {
|
|
3474
|
-
if (!isString && !isStringArray) {
|
|
3475
|
-
addTypeMismatch(
|
|
3476
|
-
ctx,
|
|
3477
|
-
`Field "${fieldName}": constraint "${ck}" is only valid on string fields or string array items, but field type is "${label}"`,
|
|
3478
|
-
constraint.provenance
|
|
3479
|
-
);
|
|
3480
|
-
}
|
|
3481
|
-
break;
|
|
3482
|
-
}
|
|
3483
|
-
case "minItems":
|
|
3484
|
-
case "maxItems":
|
|
3485
|
-
case "uniqueItems": {
|
|
3486
|
-
if (!isArray) {
|
|
3487
|
-
addTypeMismatch(
|
|
3488
|
-
ctx,
|
|
3489
|
-
`Field "${fieldName}": constraint "${ck}" is only valid on array fields, but field type is "${label}"`,
|
|
3490
|
-
constraint.provenance
|
|
3491
|
-
);
|
|
3492
|
-
}
|
|
3493
|
-
break;
|
|
3494
|
-
}
|
|
3495
|
-
case "allowedMembers": {
|
|
3496
|
-
if (!isEnum) {
|
|
3497
|
-
addTypeMismatch(
|
|
3498
|
-
ctx,
|
|
3499
|
-
`Field "${fieldName}": constraint "allowedMembers" is only valid on enum fields, but field type is "${label}"`,
|
|
3500
|
-
constraint.provenance
|
|
3501
|
-
);
|
|
3502
|
-
}
|
|
3503
|
-
break;
|
|
3504
|
-
}
|
|
3505
|
-
case "const": {
|
|
3506
|
-
const isPrimitiveConstType = effectiveType.kind === "primitive" && ["string", "number", "integer", "bigint", "boolean", "null"].includes(
|
|
3507
|
-
effectiveType.primitiveKind
|
|
3508
|
-
) || effectiveType.kind === "enum";
|
|
3509
|
-
if (!isPrimitiveConstType) {
|
|
3510
|
-
addTypeMismatch(
|
|
3511
|
-
ctx,
|
|
3512
|
-
`Field "${fieldName}": constraint "const" is only valid on primitive or enum fields, but field type is "${label}"`,
|
|
3513
|
-
constraint.provenance
|
|
3514
|
-
);
|
|
3515
|
-
break;
|
|
3516
|
-
}
|
|
3517
|
-
if (effectiveType.kind === "primitive") {
|
|
3518
|
-
const valueType = constraint.value === null ? "null" : Array.isArray(constraint.value) ? "array" : typeof constraint.value;
|
|
3519
|
-
const expectedValueType = effectiveType.primitiveKind === "integer" || effectiveType.primitiveKind === "bigint" ? "number" : effectiveType.primitiveKind;
|
|
3520
|
-
if (valueType !== expectedValueType) {
|
|
3521
|
-
addTypeMismatch(
|
|
3522
|
-
ctx,
|
|
3523
|
-
`Field "${fieldName}": @const value type "${valueType}" is incompatible with field type "${effectiveType.primitiveKind}"`,
|
|
3524
|
-
constraint.provenance
|
|
3525
|
-
);
|
|
3526
|
-
}
|
|
3527
|
-
break;
|
|
3528
|
-
}
|
|
3529
|
-
const memberValues = effectiveType.members.map((member) => member.value);
|
|
3530
|
-
if (!memberValues.some((member) => jsonValueEquals(member, constraint.value))) {
|
|
3531
|
-
addTypeMismatch(
|
|
3532
|
-
ctx,
|
|
3533
|
-
`Field "${fieldName}": @const value ${JSON.stringify(constraint.value)} is not one of the enum members`,
|
|
3534
|
-
constraint.provenance
|
|
3535
|
-
);
|
|
3536
|
-
}
|
|
3537
|
-
break;
|
|
3538
|
-
}
|
|
3539
|
-
case "custom": {
|
|
3540
|
-
checkCustomConstraint(ctx, fieldName, effectiveType, constraint);
|
|
3541
|
-
break;
|
|
3542
|
-
}
|
|
3543
|
-
default: {
|
|
3544
|
-
const _exhaustive = constraint;
|
|
3545
|
-
throw new Error(
|
|
3546
|
-
`Unhandled constraint kind: ${_exhaustive.constraintKind}`
|
|
3547
|
-
);
|
|
3548
|
-
}
|
|
3549
|
-
}
|
|
3550
|
-
}
|
|
3551
|
-
function checkTypeApplicability(ctx, fieldName, type, constraints) {
|
|
3552
|
-
for (const constraint of constraints) {
|
|
3553
|
-
if (constraint.path) {
|
|
3554
|
-
const resolution = resolvePathTargetType(ctx, type, constraint.path.segments);
|
|
3555
|
-
const targetFieldName = formatPathTargetFieldName(fieldName, constraint.path.segments);
|
|
3556
|
-
if (resolution.kind === "missing-property") {
|
|
3557
|
-
addUnknownPathTarget(
|
|
3558
|
-
ctx,
|
|
3559
|
-
`Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" references unknown path segment "${resolution.segment}"`,
|
|
3560
|
-
constraint.provenance
|
|
3561
|
-
);
|
|
3562
|
-
continue;
|
|
3563
|
-
}
|
|
3564
|
-
if (resolution.kind === "unresolvable") {
|
|
3565
|
-
addTypeMismatch(
|
|
3566
|
-
ctx,
|
|
3567
|
-
`Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" is invalid because type "${typeLabel(resolution.type)}" cannot be traversed`,
|
|
3568
|
-
constraint.provenance
|
|
3569
|
-
);
|
|
3570
|
-
continue;
|
|
3571
|
-
}
|
|
3572
|
-
checkConstraintOnType(ctx, targetFieldName, resolution.type, constraint);
|
|
3573
|
-
continue;
|
|
3574
|
-
}
|
|
3575
|
-
checkConstraintOnType(ctx, fieldName, type, constraint);
|
|
3576
|
-
}
|
|
3577
|
-
}
|
|
3578
|
-
function checkCustomConstraint(ctx, fieldName, type, constraint) {
|
|
3579
|
-
if (ctx.extensionRegistry === void 0) return;
|
|
3580
|
-
const registration = ctx.extensionRegistry.findConstraint(constraint.constraintId);
|
|
3581
|
-
if (registration === void 0) {
|
|
3582
|
-
addUnknownExtension(
|
|
3583
|
-
ctx,
|
|
3584
|
-
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not registered in the extension registry`,
|
|
3585
|
-
constraint.provenance
|
|
3586
|
-
);
|
|
3587
|
-
return;
|
|
3588
|
-
}
|
|
3589
|
-
const candidateTypes = collectCustomConstraintCandidateTypes(ctx, type);
|
|
3590
|
-
const normalizedTagName = constraint.provenance.tagName === void 0 ? void 0 : normalizeConstraintTagName2(constraint.provenance.tagName.replace(/^@/, ""));
|
|
3591
|
-
if (normalizedTagName !== void 0) {
|
|
3592
|
-
const tagRegistration = ctx.extensionRegistry.findConstraintTag(normalizedTagName);
|
|
3593
|
-
const extensionId = getExtensionIdFromConstraintId(constraint.constraintId);
|
|
3594
|
-
if (extensionId !== null && tagRegistration?.extensionId === extensionId && tagRegistration.registration.constraintName === registration.constraintName && !candidateTypes.some(
|
|
3595
|
-
(candidateType) => tagRegistration.registration.isApplicableToType?.(candidateType) !== false
|
|
3596
|
-
)) {
|
|
3597
|
-
addTypeMismatch(
|
|
3598
|
-
ctx,
|
|
3599
|
-
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
3600
|
-
constraint.provenance
|
|
3601
|
-
);
|
|
3602
|
-
return;
|
|
3603
|
-
}
|
|
3604
|
-
}
|
|
3605
|
-
if (registration.applicableTypes === null) {
|
|
3606
|
-
if (!candidateTypes.some((candidateType) => registration.isApplicableToType?.(candidateType) !== false)) {
|
|
3607
|
-
addTypeMismatch(
|
|
3608
|
-
ctx,
|
|
3609
|
-
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
3610
|
-
constraint.provenance
|
|
3611
|
-
);
|
|
3223
|
+
import {
|
|
3224
|
+
analyzeConstraintTargets
|
|
3225
|
+
} from "@formspec/analysis";
|
|
3226
|
+
function validateFieldNode(ctx, field) {
|
|
3227
|
+
const analysis = analyzeConstraintTargets(
|
|
3228
|
+
field.name,
|
|
3229
|
+
field.type,
|
|
3230
|
+
field.constraints,
|
|
3231
|
+
ctx.typeRegistry,
|
|
3232
|
+
ctx.extensionRegistry === void 0 ? void 0 : {
|
|
3233
|
+
extensionRegistry: ctx.extensionRegistry
|
|
3612
3234
|
}
|
|
3613
|
-
return;
|
|
3614
|
-
}
|
|
3615
|
-
const applicableTypes = registration.applicableTypes;
|
|
3616
|
-
const matchesApplicableType = candidateTypes.some(
|
|
3617
|
-
(candidateType) => applicableTypes.includes(candidateType.kind) && registration.isApplicableToType?.(candidateType) !== false
|
|
3618
3235
|
);
|
|
3619
|
-
|
|
3620
|
-
addTypeMismatch(
|
|
3621
|
-
ctx,
|
|
3622
|
-
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
3623
|
-
constraint.provenance
|
|
3624
|
-
);
|
|
3625
|
-
}
|
|
3626
|
-
}
|
|
3627
|
-
function validateFieldNode(ctx, field) {
|
|
3628
|
-
validateConstraints(ctx, field.name, field.type, [
|
|
3629
|
-
...collectReferencedTypeConstraints(ctx, field.type),
|
|
3630
|
-
...field.constraints
|
|
3631
|
-
]);
|
|
3236
|
+
ctx.diagnostics.push(...analysis.diagnostics);
|
|
3632
3237
|
if (field.type.kind === "object") {
|
|
3633
|
-
for (const
|
|
3634
|
-
validateObjectProperty(ctx, field.name,
|
|
3238
|
+
for (const property of field.type.properties) {
|
|
3239
|
+
validateObjectProperty(ctx, field.name, property);
|
|
3635
3240
|
}
|
|
3636
3241
|
}
|
|
3637
3242
|
}
|
|
3638
|
-
function validateObjectProperty(ctx, parentName,
|
|
3639
|
-
const qualifiedName = `${parentName}.${
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3243
|
+
function validateObjectProperty(ctx, parentName, property) {
|
|
3244
|
+
const qualifiedName = `${parentName}.${property.name}`;
|
|
3245
|
+
const analysis = analyzeConstraintTargets(
|
|
3246
|
+
qualifiedName,
|
|
3247
|
+
property.type,
|
|
3248
|
+
property.constraints,
|
|
3249
|
+
ctx.typeRegistry,
|
|
3250
|
+
ctx.extensionRegistry === void 0 ? void 0 : {
|
|
3251
|
+
extensionRegistry: ctx.extensionRegistry
|
|
3252
|
+
}
|
|
3253
|
+
);
|
|
3254
|
+
ctx.diagnostics.push(...analysis.diagnostics);
|
|
3255
|
+
if (property.type.kind === "object") {
|
|
3256
|
+
for (const nestedProperty of property.type.properties) {
|
|
3257
|
+
validateObjectProperty(ctx, qualifiedName, nestedProperty);
|
|
3647
3258
|
}
|
|
3648
3259
|
}
|
|
3649
3260
|
}
|
|
3650
|
-
function validateConstraints(ctx, name, type, constraints) {
|
|
3651
|
-
checkNumericContradictions(ctx, name, constraints);
|
|
3652
|
-
checkLengthContradictions(ctx, name, constraints);
|
|
3653
|
-
checkAllowedMembersContradiction(ctx, name, constraints);
|
|
3654
|
-
checkConstContradictions(ctx, name, constraints);
|
|
3655
|
-
checkConstraintBroadening(ctx, name, constraints);
|
|
3656
|
-
checkCustomConstraintSemantics(ctx, name, constraints);
|
|
3657
|
-
checkTypeApplicability(ctx, name, type, constraints);
|
|
3658
|
-
}
|
|
3659
3261
|
function validateElement(ctx, element) {
|
|
3660
3262
|
switch (element.kind) {
|
|
3661
3263
|
case "field":
|
|
@@ -3672,8 +3274,8 @@ function validateElement(ctx, element) {
|
|
|
3672
3274
|
}
|
|
3673
3275
|
break;
|
|
3674
3276
|
default: {
|
|
3675
|
-
const
|
|
3676
|
-
throw new Error(`Unhandled element kind: ${
|
|
3277
|
+
const exhaustive = element;
|
|
3278
|
+
throw new Error(`Unhandled element kind: ${String(exhaustive)}`);
|
|
3677
3279
|
}
|
|
3678
3280
|
}
|
|
3679
3281
|
}
|
|
@@ -3688,12 +3290,18 @@ function validateIR(ir, options) {
|
|
|
3688
3290
|
}
|
|
3689
3291
|
return {
|
|
3690
3292
|
diagnostics: ctx.diagnostics,
|
|
3691
|
-
valid: ctx.diagnostics.every((
|
|
3293
|
+
valid: ctx.diagnostics.every((diagnostic) => diagnostic.severity !== "error")
|
|
3692
3294
|
};
|
|
3693
3295
|
}
|
|
3694
3296
|
|
|
3695
3297
|
// src/generators/class-schema.ts
|
|
3696
3298
|
function generateClassSchemas(analysis, source, options) {
|
|
3299
|
+
const errorDiagnostics = analysis.diagnostics?.filter(
|
|
3300
|
+
(diagnostic) => diagnostic.severity === "error"
|
|
3301
|
+
);
|
|
3302
|
+
if (errorDiagnostics !== void 0 && errorDiagnostics.length > 0) {
|
|
3303
|
+
throw new Error(formatValidationError(errorDiagnostics));
|
|
3304
|
+
}
|
|
3697
3305
|
const ir = canonicalizeTSDoc(analysis, source);
|
|
3698
3306
|
const validationResult = validateIR(ir, {
|
|
3699
3307
|
...options?.extensionRegistry !== void 0 && {
|