@formspec/build 0.1.0-alpha.17 → 0.1.0-alpha.19
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/README.md +74 -128
- package/dist/__tests__/class-schema.test.d.ts +2 -0
- package/dist/__tests__/class-schema.test.d.ts.map +1 -0
- package/dist/__tests__/date-extension.integration.test.d.ts +2 -0
- package/dist/__tests__/date-extension.integration.test.d.ts.map +1 -0
- package/dist/__tests__/fixtures/class-schema-regressions.d.ts +83 -0
- package/dist/__tests__/fixtures/class-schema-regressions.d.ts.map +1 -0
- package/dist/__tests__/fixtures/example-date-extension.d.ts +12 -0
- package/dist/__tests__/fixtures/example-date-extension.d.ts.map +1 -0
- package/dist/__tests__/fixtures/extension-forms.d.ts +7 -0
- package/dist/__tests__/fixtures/extension-forms.d.ts.map +1 -0
- package/dist/__tests__/fixtures/mixed-authoring-shipping-address.d.ts.map +1 -1
- package/dist/__tests__/fixtures/named-primitive-aliases.d.ts +15 -0
- package/dist/__tests__/fixtures/named-primitive-aliases.d.ts.map +1 -0
- package/dist/__tests__/fixtures/nested-array-path-constraints.d.ts +14 -0
- package/dist/__tests__/fixtures/nested-array-path-constraints.d.ts.map +1 -0
- package/dist/__tests__/fixtures/sample-forms.d.ts +10 -0
- package/dist/__tests__/fixtures/sample-forms.d.ts.map +1 -1
- package/dist/__tests__/generate-schemas.test.d.ts +2 -0
- package/dist/__tests__/generate-schemas.test.d.ts.map +1 -0
- package/dist/__tests__/parity/parity.test.d.ts +6 -2
- package/dist/__tests__/parity/parity.test.d.ts.map +1 -1
- package/dist/__tests__/parity/utils.d.ts +9 -4
- package/dist/__tests__/parity/utils.d.ts.map +1 -1
- package/dist/analyzer/class-analyzer.d.ts.map +1 -1
- package/dist/analyzer/jsdoc-constraints.d.ts.map +1 -1
- package/dist/analyzer/program.d.ts +15 -0
- package/dist/analyzer/program.d.ts.map +1 -1
- package/dist/analyzer/tsdoc-parser.d.ts +5 -0
- package/dist/analyzer/tsdoc-parser.d.ts.map +1 -1
- package/dist/browser.cjs +73 -10
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.js +73 -10
- package/dist/browser.js.map +1 -1
- package/dist/canonicalize/chain-dsl-canonicalizer.d.ts.map +1 -1
- package/dist/canonicalize/tsdoc-canonicalizer.d.ts.map +1 -1
- package/dist/cli.cjs +1147 -252
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1142 -248
- package/dist/cli.js.map +1 -1
- package/dist/extensions/registry.d.ts.map +1 -1
- package/dist/generators/class-schema.d.ts.map +1 -1
- package/dist/generators/method-schema.d.ts.map +1 -1
- package/dist/generators/mixed-authoring.d.ts.map +1 -1
- package/dist/index.cjs +1121 -239
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1121 -239
- package/dist/index.js.map +1 -1
- package/dist/internals.cjs +377 -195
- package/dist/internals.cjs.map +1 -1
- package/dist/internals.js +377 -195
- package/dist/internals.js.map +1 -1
- package/dist/json-schema/ir-generator.d.ts.map +1 -1
- package/dist/validate/constraint-validator.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/cli.cjs
CHANGED
|
@@ -45,6 +45,7 @@ function canonicalizeChainDSL(form) {
|
|
|
45
45
|
kind: "form-ir",
|
|
46
46
|
irVersion: import_core.IR_VERSION,
|
|
47
47
|
elements: canonicalizeElements(form.elements),
|
|
48
|
+
rootAnnotations: [],
|
|
48
49
|
typeRegistry: {},
|
|
49
50
|
provenance: CHAIN_DSL_PROVENANCE
|
|
50
51
|
};
|
|
@@ -362,6 +363,7 @@ function canonicalizeTSDoc(analysis, source) {
|
|
|
362
363
|
irVersion: import_core2.IR_VERSION,
|
|
363
364
|
elements,
|
|
364
365
|
typeRegistry: analysis.typeRegistry,
|
|
366
|
+
...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { rootAnnotations: analysis.annotations },
|
|
365
367
|
...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { annotations: analysis.annotations },
|
|
366
368
|
provenance
|
|
367
369
|
};
|
|
@@ -455,6 +457,9 @@ function generateJsonSchemaFromIR(ir, options) {
|
|
|
455
457
|
const ctx = makeContext(options);
|
|
456
458
|
for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
|
|
457
459
|
ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
|
|
460
|
+
if (typeDef.constraints && typeDef.constraints.length > 0) {
|
|
461
|
+
applyConstraints(ctx.defs[name], typeDef.constraints, ctx);
|
|
462
|
+
}
|
|
458
463
|
if (typeDef.annotations && typeDef.annotations.length > 0) {
|
|
459
464
|
applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
|
|
460
465
|
}
|
|
@@ -623,7 +628,9 @@ function generateTypeNode(type, ctx) {
|
|
|
623
628
|
}
|
|
624
629
|
}
|
|
625
630
|
function generatePrimitiveType(type) {
|
|
626
|
-
return {
|
|
631
|
+
return {
|
|
632
|
+
type: type.primitiveKind === "integer" || type.primitiveKind === "bigint" ? "integer" : type.primitiveKind
|
|
633
|
+
};
|
|
627
634
|
}
|
|
628
635
|
function generateEnumType(type) {
|
|
629
636
|
const hasDisplayNames = type.members.some((m) => m.displayName !== void 0);
|
|
@@ -796,7 +803,7 @@ function applyAnnotations(schema, annotations, ctx) {
|
|
|
796
803
|
case "deprecated":
|
|
797
804
|
schema.deprecated = true;
|
|
798
805
|
if (annotation.message !== void 0 && annotation.message !== "") {
|
|
799
|
-
schema[
|
|
806
|
+
schema[`${ctx.vendorPrefix}-deprecation-description`] = annotation.message;
|
|
800
807
|
}
|
|
801
808
|
break;
|
|
802
809
|
case "placeholder":
|
|
@@ -1305,84 +1312,6 @@ var init_schema2 = __esm({
|
|
|
1305
1312
|
}
|
|
1306
1313
|
});
|
|
1307
1314
|
|
|
1308
|
-
// src/analyzer/program.ts
|
|
1309
|
-
function createProgramContext(filePath) {
|
|
1310
|
-
const absolutePath = path.resolve(filePath);
|
|
1311
|
-
const fileDir = path.dirname(absolutePath);
|
|
1312
|
-
const configPath = ts.findConfigFile(fileDir, ts.sys.fileExists.bind(ts.sys), "tsconfig.json");
|
|
1313
|
-
let compilerOptions;
|
|
1314
|
-
let fileNames;
|
|
1315
|
-
if (configPath) {
|
|
1316
|
-
const configFile = ts.readConfigFile(configPath, ts.sys.readFile.bind(ts.sys));
|
|
1317
|
-
if (configFile.error) {
|
|
1318
|
-
throw new Error(
|
|
1319
|
-
`Error reading tsconfig.json: ${ts.flattenDiagnosticMessageText(configFile.error.messageText, "\n")}`
|
|
1320
|
-
);
|
|
1321
|
-
}
|
|
1322
|
-
const parsed = ts.parseJsonConfigFileContent(
|
|
1323
|
-
configFile.config,
|
|
1324
|
-
ts.sys,
|
|
1325
|
-
path.dirname(configPath)
|
|
1326
|
-
);
|
|
1327
|
-
if (parsed.errors.length > 0) {
|
|
1328
|
-
const errorMessages = parsed.errors.map((e) => ts.flattenDiagnosticMessageText(e.messageText, "\n")).join("\n");
|
|
1329
|
-
throw new Error(`Error parsing tsconfig.json: ${errorMessages}`);
|
|
1330
|
-
}
|
|
1331
|
-
compilerOptions = parsed.options;
|
|
1332
|
-
fileNames = parsed.fileNames.includes(absolutePath) ? parsed.fileNames : [...parsed.fileNames, absolutePath];
|
|
1333
|
-
} else {
|
|
1334
|
-
compilerOptions = {
|
|
1335
|
-
target: ts.ScriptTarget.ES2022,
|
|
1336
|
-
module: ts.ModuleKind.NodeNext,
|
|
1337
|
-
moduleResolution: ts.ModuleResolutionKind.NodeNext,
|
|
1338
|
-
strict: true,
|
|
1339
|
-
skipLibCheck: true,
|
|
1340
|
-
declaration: true
|
|
1341
|
-
};
|
|
1342
|
-
fileNames = [absolutePath];
|
|
1343
|
-
}
|
|
1344
|
-
const program = ts.createProgram(fileNames, compilerOptions);
|
|
1345
|
-
const sourceFile = program.getSourceFile(absolutePath);
|
|
1346
|
-
if (!sourceFile) {
|
|
1347
|
-
throw new Error(`Could not find source file: ${absolutePath}`);
|
|
1348
|
-
}
|
|
1349
|
-
return {
|
|
1350
|
-
program,
|
|
1351
|
-
checker: program.getTypeChecker(),
|
|
1352
|
-
sourceFile
|
|
1353
|
-
};
|
|
1354
|
-
}
|
|
1355
|
-
function findNodeByName(sourceFile, name, predicate, getName) {
|
|
1356
|
-
let result = null;
|
|
1357
|
-
function visit(node) {
|
|
1358
|
-
if (result) return;
|
|
1359
|
-
if (predicate(node) && getName(node) === name) {
|
|
1360
|
-
result = node;
|
|
1361
|
-
return;
|
|
1362
|
-
}
|
|
1363
|
-
ts.forEachChild(node, visit);
|
|
1364
|
-
}
|
|
1365
|
-
visit(sourceFile);
|
|
1366
|
-
return result;
|
|
1367
|
-
}
|
|
1368
|
-
function findClassByName(sourceFile, className) {
|
|
1369
|
-
return findNodeByName(sourceFile, className, ts.isClassDeclaration, (n) => n.name?.text);
|
|
1370
|
-
}
|
|
1371
|
-
function findInterfaceByName(sourceFile, interfaceName) {
|
|
1372
|
-
return findNodeByName(sourceFile, interfaceName, ts.isInterfaceDeclaration, (n) => n.name.text);
|
|
1373
|
-
}
|
|
1374
|
-
function findTypeAliasByName(sourceFile, aliasName) {
|
|
1375
|
-
return findNodeByName(sourceFile, aliasName, ts.isTypeAliasDeclaration, (n) => n.name.text);
|
|
1376
|
-
}
|
|
1377
|
-
var ts, path;
|
|
1378
|
-
var init_program = __esm({
|
|
1379
|
-
"src/analyzer/program.ts"() {
|
|
1380
|
-
"use strict";
|
|
1381
|
-
ts = __toESM(require("typescript"), 1);
|
|
1382
|
-
path = __toESM(require("path"), 1);
|
|
1383
|
-
}
|
|
1384
|
-
});
|
|
1385
|
-
|
|
1386
1315
|
// src/analyzer/json-utils.ts
|
|
1387
1316
|
function tryParseJson(text) {
|
|
1388
1317
|
try {
|
|
@@ -1455,10 +1384,10 @@ function parseTSDocTags(node, file = "", options) {
|
|
|
1455
1384
|
let placeholderProvenance;
|
|
1456
1385
|
const sourceFile = node.getSourceFile();
|
|
1457
1386
|
const sourceText = sourceFile.getFullText();
|
|
1458
|
-
const commentRanges =
|
|
1387
|
+
const commentRanges = ts.getLeadingCommentRanges(sourceText, node.getFullStart());
|
|
1459
1388
|
if (commentRanges) {
|
|
1460
1389
|
for (const range of commentRanges) {
|
|
1461
|
-
if (range.kind !==
|
|
1390
|
+
if (range.kind !== ts.SyntaxKind.MultiLineCommentTrivia) {
|
|
1462
1391
|
continue;
|
|
1463
1392
|
}
|
|
1464
1393
|
const commentText = sourceText.substring(range.pos, range.end);
|
|
@@ -1476,26 +1405,31 @@ function parseTSDocTags(node, file = "", options) {
|
|
|
1476
1405
|
const text2 = extractBlockText(block).trim();
|
|
1477
1406
|
if (text2 === "") continue;
|
|
1478
1407
|
const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
displayName
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1408
|
+
switch (tagName) {
|
|
1409
|
+
case "displayName":
|
|
1410
|
+
if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
|
|
1411
|
+
displayName = text2;
|
|
1412
|
+
displayNameProvenance = provenance2;
|
|
1413
|
+
}
|
|
1414
|
+
break;
|
|
1415
|
+
case "format":
|
|
1416
|
+
annotations.push({
|
|
1417
|
+
kind: "annotation",
|
|
1418
|
+
annotationKind: "format",
|
|
1419
|
+
value: text2,
|
|
1420
|
+
provenance: provenance2
|
|
1421
|
+
});
|
|
1422
|
+
break;
|
|
1423
|
+
case "description":
|
|
1493
1424
|
description = text2;
|
|
1494
1425
|
descriptionProvenance = provenance2;
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1426
|
+
break;
|
|
1427
|
+
case "placeholder":
|
|
1428
|
+
if (placeholder === void 0) {
|
|
1429
|
+
placeholder = text2;
|
|
1430
|
+
placeholderProvenance = provenance2;
|
|
1431
|
+
}
|
|
1432
|
+
break;
|
|
1499
1433
|
}
|
|
1500
1434
|
continue;
|
|
1501
1435
|
}
|
|
@@ -1525,6 +1459,13 @@ function parseTSDocTags(node, file = "", options) {
|
|
|
1525
1459
|
descriptionProvenance = provenanceForComment(range, sourceFile, file, "remarks");
|
|
1526
1460
|
}
|
|
1527
1461
|
}
|
|
1462
|
+
if (description === void 0) {
|
|
1463
|
+
const summary = extractPlainText(docComment.summarySection).trim();
|
|
1464
|
+
if (summary !== "") {
|
|
1465
|
+
description = summary;
|
|
1466
|
+
descriptionProvenance = provenanceForComment(range, sourceFile, file, "summary");
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1528
1469
|
}
|
|
1529
1470
|
}
|
|
1530
1471
|
if (displayName !== void 0 && displayNameProvenance !== void 0) {
|
|
@@ -1551,7 +1492,7 @@ function parseTSDocTags(node, file = "", options) {
|
|
|
1551
1492
|
provenance: placeholderProvenance
|
|
1552
1493
|
});
|
|
1553
1494
|
}
|
|
1554
|
-
const jsDocTagsAll =
|
|
1495
|
+
const jsDocTagsAll = ts.getJSDocTags(node);
|
|
1555
1496
|
for (const tag of jsDocTagsAll) {
|
|
1556
1497
|
const tagName = (0, import_core3.normalizeConstraintTagName)(tag.tagName.text);
|
|
1557
1498
|
if (!TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
@@ -1574,7 +1515,7 @@ function parseTSDocTags(node, file = "", options) {
|
|
|
1574
1515
|
function extractDisplayNameMetadata(node) {
|
|
1575
1516
|
let displayName;
|
|
1576
1517
|
const memberDisplayNames = /* @__PURE__ */ new Map();
|
|
1577
|
-
for (const tag of
|
|
1518
|
+
for (const tag of ts.getJSDocTags(node)) {
|
|
1578
1519
|
const tagName = (0, import_core3.normalizeConstraintTagName)(tag.tagName.text);
|
|
1579
1520
|
if (tagName !== "displayName") continue;
|
|
1580
1521
|
const commentText = getTagCommentText(tag);
|
|
@@ -1595,11 +1536,11 @@ function extractDisplayNameMetadata(node) {
|
|
|
1595
1536
|
}
|
|
1596
1537
|
function extractPathTarget(text) {
|
|
1597
1538
|
const trimmed = text.trimStart();
|
|
1598
|
-
const match = /^:([a-zA-Z_]\w*)
|
|
1599
|
-
if (!match?.[1]
|
|
1539
|
+
const match = /^:([a-zA-Z_]\w*)(?:\s+([\s\S]*))?$/.exec(trimmed);
|
|
1540
|
+
if (!match?.[1]) return null;
|
|
1600
1541
|
return {
|
|
1601
1542
|
path: { segments: [match[1]] },
|
|
1602
|
-
remainingText: match[2]
|
|
1543
|
+
remainingText: match[2] ?? ""
|
|
1603
1544
|
};
|
|
1604
1545
|
}
|
|
1605
1546
|
function extractBlockText(block) {
|
|
@@ -1862,13 +1803,13 @@ function getTagCommentText(tag) {
|
|
|
1862
1803
|
if (typeof tag.comment === "string") {
|
|
1863
1804
|
return tag.comment;
|
|
1864
1805
|
}
|
|
1865
|
-
return
|
|
1806
|
+
return ts.getTextOfJSDocComment(tag.comment);
|
|
1866
1807
|
}
|
|
1867
|
-
var
|
|
1808
|
+
var ts, import_tsdoc, import_core3, NUMERIC_CONSTRAINT_MAP, LENGTH_CONSTRAINT_MAP, TAGS_REQUIRING_RAW_TEXT, parserCache;
|
|
1868
1809
|
var init_tsdoc_parser = __esm({
|
|
1869
1810
|
"src/analyzer/tsdoc-parser.ts"() {
|
|
1870
1811
|
"use strict";
|
|
1871
|
-
|
|
1812
|
+
ts = __toESM(require("typescript"), 1);
|
|
1872
1813
|
import_tsdoc = require("@microsoft/tsdoc");
|
|
1873
1814
|
import_core3 = require("@formspec/core");
|
|
1874
1815
|
init_json_utils();
|
|
@@ -1902,18 +1843,18 @@ function extractJSDocAnnotationNodes(node, file = "", options) {
|
|
|
1902
1843
|
function extractDefaultValueAnnotation(initializer, file = "") {
|
|
1903
1844
|
if (!initializer) return null;
|
|
1904
1845
|
let value;
|
|
1905
|
-
if (
|
|
1846
|
+
if (ts2.isStringLiteral(initializer)) {
|
|
1906
1847
|
value = initializer.text;
|
|
1907
|
-
} else if (
|
|
1848
|
+
} else if (ts2.isNumericLiteral(initializer)) {
|
|
1908
1849
|
value = Number(initializer.text);
|
|
1909
|
-
} else if (initializer.kind ===
|
|
1850
|
+
} else if (initializer.kind === ts2.SyntaxKind.TrueKeyword) {
|
|
1910
1851
|
value = true;
|
|
1911
|
-
} else if (initializer.kind ===
|
|
1852
|
+
} else if (initializer.kind === ts2.SyntaxKind.FalseKeyword) {
|
|
1912
1853
|
value = false;
|
|
1913
|
-
} else if (initializer.kind ===
|
|
1854
|
+
} else if (initializer.kind === ts2.SyntaxKind.NullKeyword) {
|
|
1914
1855
|
value = null;
|
|
1915
|
-
} else if (
|
|
1916
|
-
if (initializer.operator ===
|
|
1856
|
+
} else if (ts2.isPrefixUnaryExpression(initializer)) {
|
|
1857
|
+
if (initializer.operator === ts2.SyntaxKind.MinusToken && ts2.isNumericLiteral(initializer.operand)) {
|
|
1917
1858
|
value = -Number(initializer.operand.text);
|
|
1918
1859
|
}
|
|
1919
1860
|
}
|
|
@@ -1932,21 +1873,21 @@ function extractDefaultValueAnnotation(initializer, file = "") {
|
|
|
1932
1873
|
}
|
|
1933
1874
|
};
|
|
1934
1875
|
}
|
|
1935
|
-
var
|
|
1876
|
+
var ts2;
|
|
1936
1877
|
var init_jsdoc_constraints = __esm({
|
|
1937
1878
|
"src/analyzer/jsdoc-constraints.ts"() {
|
|
1938
1879
|
"use strict";
|
|
1939
|
-
|
|
1880
|
+
ts2 = __toESM(require("typescript"), 1);
|
|
1940
1881
|
init_tsdoc_parser();
|
|
1941
1882
|
}
|
|
1942
1883
|
});
|
|
1943
1884
|
|
|
1944
1885
|
// src/analyzer/class-analyzer.ts
|
|
1945
1886
|
function isObjectType(type) {
|
|
1946
|
-
return !!(type.flags &
|
|
1887
|
+
return !!(type.flags & ts3.TypeFlags.Object);
|
|
1947
1888
|
}
|
|
1948
1889
|
function isTypeReference(type) {
|
|
1949
|
-
return !!(type.flags &
|
|
1890
|
+
return !!(type.flags & ts3.TypeFlags.Object) && !!(type.objectFlags & ts3.ObjectFlags.Reference);
|
|
1950
1891
|
}
|
|
1951
1892
|
function makeParseOptions(extensionRegistry, fieldType) {
|
|
1952
1893
|
if (extensionRegistry === void 0 && fieldType === void 0) {
|
|
@@ -1971,7 +1912,7 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
|
|
|
1971
1912
|
const instanceMethods = [];
|
|
1972
1913
|
const staticMethods = [];
|
|
1973
1914
|
for (const member of classDecl.members) {
|
|
1974
|
-
if (
|
|
1915
|
+
if (ts3.isPropertyDeclaration(member)) {
|
|
1975
1916
|
const fieldNode = analyzeFieldToIR(
|
|
1976
1917
|
member,
|
|
1977
1918
|
checker,
|
|
@@ -1984,10 +1925,10 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
|
|
|
1984
1925
|
fields.push(fieldNode);
|
|
1985
1926
|
fieldLayouts.push({});
|
|
1986
1927
|
}
|
|
1987
|
-
} else if (
|
|
1928
|
+
} else if (ts3.isMethodDeclaration(member)) {
|
|
1988
1929
|
const methodInfo = analyzeMethod(member, checker);
|
|
1989
1930
|
if (methodInfo) {
|
|
1990
|
-
const isStatic = member.modifiers?.some((m) => m.kind ===
|
|
1931
|
+
const isStatic = member.modifiers?.some((m) => m.kind === ts3.SyntaxKind.StaticKeyword);
|
|
1991
1932
|
if (isStatic) {
|
|
1992
1933
|
staticMethods.push(methodInfo);
|
|
1993
1934
|
} else {
|
|
@@ -2017,7 +1958,7 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
|
|
|
2017
1958
|
);
|
|
2018
1959
|
const visiting = /* @__PURE__ */ new Set();
|
|
2019
1960
|
for (const member of interfaceDecl.members) {
|
|
2020
|
-
if (
|
|
1961
|
+
if (ts3.isPropertySignature(member)) {
|
|
2021
1962
|
const fieldNode = analyzeInterfacePropertyToIR(
|
|
2022
1963
|
member,
|
|
2023
1964
|
checker,
|
|
@@ -2043,10 +1984,10 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
|
|
|
2043
1984
|
};
|
|
2044
1985
|
}
|
|
2045
1986
|
function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry) {
|
|
2046
|
-
if (!
|
|
1987
|
+
if (!ts3.isTypeLiteralNode(typeAlias.type)) {
|
|
2047
1988
|
const sourceFile = typeAlias.getSourceFile();
|
|
2048
1989
|
const { line } = sourceFile.getLineAndCharacterOfPosition(typeAlias.getStart());
|
|
2049
|
-
const kindDesc =
|
|
1990
|
+
const kindDesc = ts3.SyntaxKind[typeAlias.type.kind] ?? "unknown";
|
|
2050
1991
|
return {
|
|
2051
1992
|
ok: false,
|
|
2052
1993
|
error: `Type alias "${typeAlias.name.text}" at line ${String(line + 1)} is not an object type literal (found ${kindDesc})`
|
|
@@ -2062,7 +2003,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
|
|
|
2062
2003
|
);
|
|
2063
2004
|
const visiting = /* @__PURE__ */ new Set();
|
|
2064
2005
|
for (const member of typeAlias.type.members) {
|
|
2065
|
-
if (
|
|
2006
|
+
if (ts3.isPropertySignature(member)) {
|
|
2066
2007
|
const fieldNode = analyzeInterfacePropertyToIR(
|
|
2067
2008
|
member,
|
|
2068
2009
|
checker,
|
|
@@ -2090,7 +2031,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
|
|
|
2090
2031
|
};
|
|
2091
2032
|
}
|
|
2092
2033
|
function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2093
|
-
if (!
|
|
2034
|
+
if (!ts3.isIdentifier(prop.name)) {
|
|
2094
2035
|
return null;
|
|
2095
2036
|
}
|
|
2096
2037
|
const name = prop.name.text;
|
|
@@ -2107,12 +2048,14 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extension
|
|
|
2107
2048
|
extensionRegistry
|
|
2108
2049
|
);
|
|
2109
2050
|
const constraints = [];
|
|
2110
|
-
if (prop.type) {
|
|
2051
|
+
if (prop.type && !shouldEmitPrimitiveAliasDefinition(prop.type, checker)) {
|
|
2111
2052
|
constraints.push(
|
|
2112
2053
|
...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
|
|
2113
2054
|
);
|
|
2114
2055
|
}
|
|
2115
|
-
constraints.push(
|
|
2056
|
+
constraints.push(
|
|
2057
|
+
...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
2058
|
+
);
|
|
2116
2059
|
let annotations = [];
|
|
2117
2060
|
annotations.push(
|
|
2118
2061
|
...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
@@ -2133,7 +2076,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extension
|
|
|
2133
2076
|
};
|
|
2134
2077
|
}
|
|
2135
2078
|
function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2136
|
-
if (!
|
|
2079
|
+
if (!ts3.isIdentifier(prop.name)) {
|
|
2137
2080
|
return null;
|
|
2138
2081
|
}
|
|
2139
2082
|
const name = prop.name.text;
|
|
@@ -2150,12 +2093,14 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
|
|
|
2150
2093
|
extensionRegistry
|
|
2151
2094
|
);
|
|
2152
2095
|
const constraints = [];
|
|
2153
|
-
if (prop.type) {
|
|
2096
|
+
if (prop.type && !shouldEmitPrimitiveAliasDefinition(prop.type, checker)) {
|
|
2154
2097
|
constraints.push(
|
|
2155
2098
|
...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
|
|
2156
2099
|
);
|
|
2157
2100
|
}
|
|
2158
|
-
constraints.push(
|
|
2101
|
+
constraints.push(
|
|
2102
|
+
...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
2103
|
+
);
|
|
2159
2104
|
let annotations = [];
|
|
2160
2105
|
annotations.push(
|
|
2161
2106
|
...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
@@ -2244,7 +2189,7 @@ function resolveRegisteredCustomType(sourceNode, extensionRegistry, checker) {
|
|
|
2244
2189
|
return resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker);
|
|
2245
2190
|
}
|
|
2246
2191
|
function resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker) {
|
|
2247
|
-
if (
|
|
2192
|
+
if (ts3.isParenthesizedTypeNode(typeNode)) {
|
|
2248
2193
|
return resolveRegisteredCustomTypeFromTypeNode(typeNode.type, extensionRegistry, checker);
|
|
2249
2194
|
}
|
|
2250
2195
|
const typeName = getTypeNodeRegistrationName(typeNode);
|
|
@@ -2259,8 +2204,8 @@ function resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, ch
|
|
|
2259
2204
|
payload: null
|
|
2260
2205
|
};
|
|
2261
2206
|
}
|
|
2262
|
-
if (
|
|
2263
|
-
const aliasDecl = checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(
|
|
2207
|
+
if (ts3.isTypeReferenceNode(typeNode) && ts3.isIdentifier(typeNode.typeName)) {
|
|
2208
|
+
const aliasDecl = checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
2264
2209
|
if (aliasDecl !== void 0) {
|
|
2265
2210
|
return resolveRegisteredCustomTypeFromTypeNode(aliasDecl.type, extensionRegistry, checker);
|
|
2266
2211
|
}
|
|
@@ -2268,22 +2213,22 @@ function resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, ch
|
|
|
2268
2213
|
return null;
|
|
2269
2214
|
}
|
|
2270
2215
|
function extractTypeNodeFromSource(sourceNode) {
|
|
2271
|
-
if (
|
|
2216
|
+
if (ts3.isPropertyDeclaration(sourceNode) || ts3.isPropertySignature(sourceNode) || ts3.isParameter(sourceNode) || ts3.isTypeAliasDeclaration(sourceNode)) {
|
|
2272
2217
|
return sourceNode.type;
|
|
2273
2218
|
}
|
|
2274
|
-
if (
|
|
2219
|
+
if (ts3.isTypeNode(sourceNode)) {
|
|
2275
2220
|
return sourceNode;
|
|
2276
2221
|
}
|
|
2277
2222
|
return void 0;
|
|
2278
2223
|
}
|
|
2279
2224
|
function getTypeNodeRegistrationName(typeNode) {
|
|
2280
|
-
if (
|
|
2281
|
-
return
|
|
2225
|
+
if (ts3.isTypeReferenceNode(typeNode)) {
|
|
2226
|
+
return ts3.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : typeNode.typeName.right.text;
|
|
2282
2227
|
}
|
|
2283
|
-
if (
|
|
2228
|
+
if (ts3.isParenthesizedTypeNode(typeNode)) {
|
|
2284
2229
|
return getTypeNodeRegistrationName(typeNode.type);
|
|
2285
2230
|
}
|
|
2286
|
-
if (typeNode.kind ===
|
|
2231
|
+
if (typeNode.kind === ts3.SyntaxKind.BigIntKeyword || typeNode.kind === ts3.SyntaxKind.StringKeyword || typeNode.kind === ts3.SyntaxKind.NumberKeyword || typeNode.kind === ts3.SyntaxKind.BooleanKeyword) {
|
|
2287
2232
|
return typeNode.getText();
|
|
2288
2233
|
}
|
|
2289
2234
|
return null;
|
|
@@ -2293,19 +2238,34 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
|
|
|
2293
2238
|
if (customType) {
|
|
2294
2239
|
return customType;
|
|
2295
2240
|
}
|
|
2296
|
-
|
|
2241
|
+
const primitiveAlias = tryResolveNamedPrimitiveAlias(
|
|
2242
|
+
type,
|
|
2243
|
+
checker,
|
|
2244
|
+
file,
|
|
2245
|
+
typeRegistry,
|
|
2246
|
+
visiting,
|
|
2247
|
+
sourceNode,
|
|
2248
|
+
extensionRegistry
|
|
2249
|
+
);
|
|
2250
|
+
if (primitiveAlias) {
|
|
2251
|
+
return primitiveAlias;
|
|
2252
|
+
}
|
|
2253
|
+
if (type.flags & ts3.TypeFlags.String) {
|
|
2297
2254
|
return { kind: "primitive", primitiveKind: "string" };
|
|
2298
2255
|
}
|
|
2299
|
-
if (type.flags &
|
|
2256
|
+
if (type.flags & ts3.TypeFlags.Number) {
|
|
2300
2257
|
return { kind: "primitive", primitiveKind: "number" };
|
|
2301
2258
|
}
|
|
2302
|
-
if (type.flags &
|
|
2259
|
+
if (type.flags & (ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral)) {
|
|
2260
|
+
return { kind: "primitive", primitiveKind: "bigint" };
|
|
2261
|
+
}
|
|
2262
|
+
if (type.flags & ts3.TypeFlags.Boolean) {
|
|
2303
2263
|
return { kind: "primitive", primitiveKind: "boolean" };
|
|
2304
2264
|
}
|
|
2305
|
-
if (type.flags &
|
|
2265
|
+
if (type.flags & ts3.TypeFlags.Null) {
|
|
2306
2266
|
return { kind: "primitive", primitiveKind: "null" };
|
|
2307
2267
|
}
|
|
2308
|
-
if (type.flags &
|
|
2268
|
+
if (type.flags & ts3.TypeFlags.Undefined) {
|
|
2309
2269
|
return { kind: "primitive", primitiveKind: "null" };
|
|
2310
2270
|
}
|
|
2311
2271
|
if (type.isStringLiteral()) {
|
|
@@ -2347,6 +2307,75 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
|
|
|
2347
2307
|
}
|
|
2348
2308
|
return { kind: "primitive", primitiveKind: "string" };
|
|
2349
2309
|
}
|
|
2310
|
+
function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
2311
|
+
if (!(type.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null))) {
|
|
2312
|
+
return null;
|
|
2313
|
+
}
|
|
2314
|
+
const aliasDecl = type.aliasSymbol?.declarations?.find(ts3.isTypeAliasDeclaration) ?? getReferencedTypeAliasDeclaration(sourceNode, checker);
|
|
2315
|
+
if (!aliasDecl) {
|
|
2316
|
+
return null;
|
|
2317
|
+
}
|
|
2318
|
+
const aliasName = aliasDecl.name.text;
|
|
2319
|
+
if (!typeRegistry[aliasName]) {
|
|
2320
|
+
const aliasType = checker.getTypeFromTypeNode(aliasDecl.type);
|
|
2321
|
+
const constraints = [
|
|
2322
|
+
...extractJSDocConstraintNodes(aliasDecl, file, makeParseOptions(extensionRegistry)),
|
|
2323
|
+
...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, extensionRegistry)
|
|
2324
|
+
];
|
|
2325
|
+
const annotations = extractJSDocAnnotationNodes(
|
|
2326
|
+
aliasDecl,
|
|
2327
|
+
file,
|
|
2328
|
+
makeParseOptions(extensionRegistry)
|
|
2329
|
+
);
|
|
2330
|
+
typeRegistry[aliasName] = {
|
|
2331
|
+
name: aliasName,
|
|
2332
|
+
type: resolveAliasedPrimitiveTarget(
|
|
2333
|
+
aliasType,
|
|
2334
|
+
checker,
|
|
2335
|
+
file,
|
|
2336
|
+
typeRegistry,
|
|
2337
|
+
visiting,
|
|
2338
|
+
extensionRegistry
|
|
2339
|
+
),
|
|
2340
|
+
...constraints.length > 0 && { constraints },
|
|
2341
|
+
...annotations.length > 0 && { annotations },
|
|
2342
|
+
provenance: provenanceForDeclaration(aliasDecl, file)
|
|
2343
|
+
};
|
|
2344
|
+
}
|
|
2345
|
+
return { kind: "reference", name: aliasName, typeArguments: [] };
|
|
2346
|
+
}
|
|
2347
|
+
function getReferencedTypeAliasDeclaration(sourceNode, checker) {
|
|
2348
|
+
const typeNode = sourceNode && (ts3.isPropertyDeclaration(sourceNode) || ts3.isPropertySignature(sourceNode) || ts3.isParameter(sourceNode)) ? sourceNode.type : void 0;
|
|
2349
|
+
if (!typeNode || !ts3.isTypeReferenceNode(typeNode)) {
|
|
2350
|
+
return void 0;
|
|
2351
|
+
}
|
|
2352
|
+
return checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
2353
|
+
}
|
|
2354
|
+
function shouldEmitPrimitiveAliasDefinition(typeNode, checker) {
|
|
2355
|
+
if (!ts3.isTypeReferenceNode(typeNode)) {
|
|
2356
|
+
return false;
|
|
2357
|
+
}
|
|
2358
|
+
const aliasDecl = checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
2359
|
+
if (!aliasDecl) {
|
|
2360
|
+
return false;
|
|
2361
|
+
}
|
|
2362
|
+
const resolved = checker.getTypeFromTypeNode(aliasDecl.type);
|
|
2363
|
+
return !!(resolved.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null));
|
|
2364
|
+
}
|
|
2365
|
+
function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2366
|
+
const nestedAliasDecl = type.aliasSymbol?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
2367
|
+
if (nestedAliasDecl !== void 0) {
|
|
2368
|
+
return resolveAliasedPrimitiveTarget(
|
|
2369
|
+
checker.getTypeFromTypeNode(nestedAliasDecl.type),
|
|
2370
|
+
checker,
|
|
2371
|
+
file,
|
|
2372
|
+
typeRegistry,
|
|
2373
|
+
visiting,
|
|
2374
|
+
extensionRegistry
|
|
2375
|
+
);
|
|
2376
|
+
}
|
|
2377
|
+
return resolveTypeNode(type, checker, file, typeRegistry, visiting, void 0, extensionRegistry);
|
|
2378
|
+
}
|
|
2350
2379
|
function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
2351
2380
|
const typeName = getNamedTypeName(type);
|
|
2352
2381
|
const namedDecl = getNamedTypeDeclaration(type);
|
|
@@ -2359,13 +2388,13 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
2359
2388
|
(memberTypeNode) => !isNullishTypeNode(resolveAliasedTypeNode(memberTypeNode, checker))
|
|
2360
2389
|
);
|
|
2361
2390
|
const nonNullTypes = allTypes.filter(
|
|
2362
|
-
(memberType) => !(memberType.flags & (
|
|
2391
|
+
(memberType) => !(memberType.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined))
|
|
2363
2392
|
);
|
|
2364
2393
|
const nonNullMembers = nonNullTypes.map((memberType, index) => ({
|
|
2365
2394
|
memberType,
|
|
2366
2395
|
sourceNode: nonNullSourceNodes.length === nonNullTypes.length ? nonNullSourceNodes[index] : void 0
|
|
2367
2396
|
}));
|
|
2368
|
-
const hasNull = allTypes.some((t) => t.flags &
|
|
2397
|
+
const hasNull = allTypes.some((t) => t.flags & ts3.TypeFlags.Null);
|
|
2369
2398
|
const memberDisplayNames = /* @__PURE__ */ new Map();
|
|
2370
2399
|
if (namedDecl) {
|
|
2371
2400
|
for (const [value, label] of extractDisplayNameMetadata(namedDecl).memberDisplayNames) {
|
|
@@ -2394,7 +2423,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
2394
2423
|
const displayName = memberDisplayNames.get(String(value));
|
|
2395
2424
|
return displayName !== void 0 ? { value, displayName } : { value };
|
|
2396
2425
|
});
|
|
2397
|
-
const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags &
|
|
2426
|
+
const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts3.TypeFlags.BooleanLiteral);
|
|
2398
2427
|
if (isBooleanUnion2) {
|
|
2399
2428
|
const boolNode = { kind: "primitive", primitiveKind: "boolean" };
|
|
2400
2429
|
const result = hasNull ? {
|
|
@@ -2480,7 +2509,7 @@ function tryResolveRecordType(type, checker, file, typeRegistry, visiting, exten
|
|
|
2480
2509
|
if (type.getProperties().length > 0) {
|
|
2481
2510
|
return null;
|
|
2482
2511
|
}
|
|
2483
|
-
const indexInfo = checker.getIndexInfoOfType(type,
|
|
2512
|
+
const indexInfo = checker.getIndexInfoOfType(type, ts3.IndexKind.String);
|
|
2484
2513
|
if (!indexInfo) {
|
|
2485
2514
|
return null;
|
|
2486
2515
|
}
|
|
@@ -2591,7 +2620,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
2591
2620
|
const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
|
|
2592
2621
|
if (!declaration) continue;
|
|
2593
2622
|
const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
|
|
2594
|
-
const optional = !!(prop.flags &
|
|
2623
|
+
const optional = !!(prop.flags & ts3.SymbolFlags.Optional);
|
|
2595
2624
|
const propTypeNode = resolveTypeNode(
|
|
2596
2625
|
propType,
|
|
2597
2626
|
checker,
|
|
@@ -2636,11 +2665,11 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
2636
2665
|
for (const symbol of symbols) {
|
|
2637
2666
|
const declarations = symbol.declarations;
|
|
2638
2667
|
if (!declarations) continue;
|
|
2639
|
-
const classDecl = declarations.find(
|
|
2668
|
+
const classDecl = declarations.find(ts3.isClassDeclaration);
|
|
2640
2669
|
if (classDecl) {
|
|
2641
2670
|
const map = /* @__PURE__ */ new Map();
|
|
2642
2671
|
for (const member of classDecl.members) {
|
|
2643
|
-
if (
|
|
2672
|
+
if (ts3.isPropertyDeclaration(member) && ts3.isIdentifier(member.name)) {
|
|
2644
2673
|
const fieldNode = analyzeFieldToIR(
|
|
2645
2674
|
member,
|
|
2646
2675
|
checker,
|
|
@@ -2660,7 +2689,7 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
2660
2689
|
}
|
|
2661
2690
|
return map;
|
|
2662
2691
|
}
|
|
2663
|
-
const interfaceDecl = declarations.find(
|
|
2692
|
+
const interfaceDecl = declarations.find(ts3.isInterfaceDeclaration);
|
|
2664
2693
|
if (interfaceDecl) {
|
|
2665
2694
|
return buildFieldNodeInfoMap(
|
|
2666
2695
|
interfaceDecl.members,
|
|
@@ -2671,8 +2700,8 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
2671
2700
|
extensionRegistry
|
|
2672
2701
|
);
|
|
2673
2702
|
}
|
|
2674
|
-
const typeAliasDecl = declarations.find(
|
|
2675
|
-
if (typeAliasDecl &&
|
|
2703
|
+
const typeAliasDecl = declarations.find(ts3.isTypeAliasDeclaration);
|
|
2704
|
+
if (typeAliasDecl && ts3.isTypeLiteralNode(typeAliasDecl.type)) {
|
|
2676
2705
|
return buildFieldNodeInfoMap(
|
|
2677
2706
|
typeAliasDecl.type.members,
|
|
2678
2707
|
checker,
|
|
@@ -2691,10 +2720,10 @@ function extractArrayElementTypeNode(sourceNode, checker) {
|
|
|
2691
2720
|
return void 0;
|
|
2692
2721
|
}
|
|
2693
2722
|
const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
|
|
2694
|
-
if (
|
|
2723
|
+
if (ts3.isArrayTypeNode(resolvedTypeNode)) {
|
|
2695
2724
|
return resolvedTypeNode.elementType;
|
|
2696
2725
|
}
|
|
2697
|
-
if (
|
|
2726
|
+
if (ts3.isTypeReferenceNode(resolvedTypeNode) && ts3.isIdentifier(resolvedTypeNode.typeName) && resolvedTypeNode.typeName.text === "Array" && resolvedTypeNode.typeArguments?.[0]) {
|
|
2698
2727
|
return resolvedTypeNode.typeArguments[0];
|
|
2699
2728
|
}
|
|
2700
2729
|
return void 0;
|
|
@@ -2705,17 +2734,17 @@ function extractUnionMemberTypeNodes(sourceNode, checker) {
|
|
|
2705
2734
|
return [];
|
|
2706
2735
|
}
|
|
2707
2736
|
const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
|
|
2708
|
-
return
|
|
2737
|
+
return ts3.isUnionTypeNode(resolvedTypeNode) ? [...resolvedTypeNode.types] : [];
|
|
2709
2738
|
}
|
|
2710
2739
|
function resolveAliasedTypeNode(typeNode, checker, visited = /* @__PURE__ */ new Set()) {
|
|
2711
|
-
if (
|
|
2740
|
+
if (ts3.isParenthesizedTypeNode(typeNode)) {
|
|
2712
2741
|
return resolveAliasedTypeNode(typeNode.type, checker, visited);
|
|
2713
2742
|
}
|
|
2714
|
-
if (!
|
|
2743
|
+
if (!ts3.isTypeReferenceNode(typeNode) || !ts3.isIdentifier(typeNode.typeName)) {
|
|
2715
2744
|
return typeNode;
|
|
2716
2745
|
}
|
|
2717
2746
|
const symbol = checker.getSymbolAtLocation(typeNode.typeName);
|
|
2718
|
-
const aliasDecl = symbol?.declarations?.find(
|
|
2747
|
+
const aliasDecl = symbol?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
2719
2748
|
if (aliasDecl === void 0 || visited.has(aliasDecl)) {
|
|
2720
2749
|
return typeNode;
|
|
2721
2750
|
}
|
|
@@ -2723,15 +2752,15 @@ function resolveAliasedTypeNode(typeNode, checker, visited = /* @__PURE__ */ new
|
|
|
2723
2752
|
return resolveAliasedTypeNode(aliasDecl.type, checker, visited);
|
|
2724
2753
|
}
|
|
2725
2754
|
function isNullishTypeNode(typeNode) {
|
|
2726
|
-
if (typeNode.kind ===
|
|
2755
|
+
if (typeNode.kind === ts3.SyntaxKind.NullKeyword || typeNode.kind === ts3.SyntaxKind.UndefinedKeyword) {
|
|
2727
2756
|
return true;
|
|
2728
2757
|
}
|
|
2729
|
-
return
|
|
2758
|
+
return ts3.isLiteralTypeNode(typeNode) && (typeNode.literal.kind === ts3.SyntaxKind.NullKeyword || typeNode.literal.kind === ts3.SyntaxKind.UndefinedKeyword);
|
|
2730
2759
|
}
|
|
2731
2760
|
function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2732
2761
|
const map = /* @__PURE__ */ new Map();
|
|
2733
2762
|
for (const member of members) {
|
|
2734
|
-
if (
|
|
2763
|
+
if (ts3.isPropertySignature(member)) {
|
|
2735
2764
|
const fieldNode = analyzeInterfacePropertyToIR(
|
|
2736
2765
|
member,
|
|
2737
2766
|
checker,
|
|
@@ -2752,7 +2781,7 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, e
|
|
|
2752
2781
|
return map;
|
|
2753
2782
|
}
|
|
2754
2783
|
function extractTypeAliasConstraintNodes(typeNode, checker, file, extensionRegistry, depth = 0) {
|
|
2755
|
-
if (!
|
|
2784
|
+
if (!ts3.isTypeReferenceNode(typeNode)) return [];
|
|
2756
2785
|
if (depth >= MAX_ALIAS_CHAIN_DEPTH) {
|
|
2757
2786
|
const aliasName = typeNode.typeName.getText();
|
|
2758
2787
|
throw new Error(
|
|
@@ -2761,9 +2790,9 @@ function extractTypeAliasConstraintNodes(typeNode, checker, file, extensionRegis
|
|
|
2761
2790
|
}
|
|
2762
2791
|
const symbol = checker.getSymbolAtLocation(typeNode.typeName);
|
|
2763
2792
|
if (!symbol?.declarations) return [];
|
|
2764
|
-
const aliasDecl = symbol.declarations.find(
|
|
2793
|
+
const aliasDecl = symbol.declarations.find(ts3.isTypeAliasDeclaration);
|
|
2765
2794
|
if (!aliasDecl) return [];
|
|
2766
|
-
if (
|
|
2795
|
+
if (ts3.isTypeLiteralNode(aliasDecl.type)) return [];
|
|
2767
2796
|
const aliasFieldType = resolveTypeNode(
|
|
2768
2797
|
checker.getTypeAtLocation(aliasDecl.type),
|
|
2769
2798
|
checker,
|
|
@@ -2779,13 +2808,7 @@ function extractTypeAliasConstraintNodes(typeNode, checker, file, extensionRegis
|
|
|
2779
2808
|
makeParseOptions(extensionRegistry, aliasFieldType)
|
|
2780
2809
|
);
|
|
2781
2810
|
constraints.push(
|
|
2782
|
-
...extractTypeAliasConstraintNodes(
|
|
2783
|
-
aliasDecl.type,
|
|
2784
|
-
checker,
|
|
2785
|
-
file,
|
|
2786
|
-
extensionRegistry,
|
|
2787
|
-
depth + 1
|
|
2788
|
-
)
|
|
2811
|
+
...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, extensionRegistry, depth + 1)
|
|
2789
2812
|
);
|
|
2790
2813
|
return constraints;
|
|
2791
2814
|
}
|
|
@@ -2812,14 +2835,14 @@ function getNamedTypeName(type) {
|
|
|
2812
2835
|
const symbol = type.getSymbol();
|
|
2813
2836
|
if (symbol?.declarations) {
|
|
2814
2837
|
const decl = symbol.declarations[0];
|
|
2815
|
-
if (decl && (
|
|
2816
|
-
const name =
|
|
2838
|
+
if (decl && (ts3.isClassDeclaration(decl) || ts3.isInterfaceDeclaration(decl) || ts3.isTypeAliasDeclaration(decl))) {
|
|
2839
|
+
const name = ts3.isClassDeclaration(decl) ? decl.name?.text : decl.name.text;
|
|
2817
2840
|
if (name) return name;
|
|
2818
2841
|
}
|
|
2819
2842
|
}
|
|
2820
2843
|
const aliasSymbol = type.aliasSymbol;
|
|
2821
2844
|
if (aliasSymbol?.declarations) {
|
|
2822
|
-
const aliasDecl = aliasSymbol.declarations.find(
|
|
2845
|
+
const aliasDecl = aliasSymbol.declarations.find(ts3.isTypeAliasDeclaration);
|
|
2823
2846
|
if (aliasDecl) {
|
|
2824
2847
|
return aliasDecl.name.text;
|
|
2825
2848
|
}
|
|
@@ -2830,24 +2853,24 @@ function getNamedTypeDeclaration(type) {
|
|
|
2830
2853
|
const symbol = type.getSymbol();
|
|
2831
2854
|
if (symbol?.declarations) {
|
|
2832
2855
|
const decl = symbol.declarations[0];
|
|
2833
|
-
if (decl && (
|
|
2856
|
+
if (decl && (ts3.isClassDeclaration(decl) || ts3.isInterfaceDeclaration(decl) || ts3.isTypeAliasDeclaration(decl))) {
|
|
2834
2857
|
return decl;
|
|
2835
2858
|
}
|
|
2836
2859
|
}
|
|
2837
2860
|
const aliasSymbol = type.aliasSymbol;
|
|
2838
2861
|
if (aliasSymbol?.declarations) {
|
|
2839
|
-
return aliasSymbol.declarations.find(
|
|
2862
|
+
return aliasSymbol.declarations.find(ts3.isTypeAliasDeclaration);
|
|
2840
2863
|
}
|
|
2841
2864
|
return void 0;
|
|
2842
2865
|
}
|
|
2843
2866
|
function analyzeMethod(method, checker) {
|
|
2844
|
-
if (!
|
|
2867
|
+
if (!ts3.isIdentifier(method.name)) {
|
|
2845
2868
|
return null;
|
|
2846
2869
|
}
|
|
2847
2870
|
const name = method.name.text;
|
|
2848
2871
|
const parameters = [];
|
|
2849
2872
|
for (const param of method.parameters) {
|
|
2850
|
-
if (
|
|
2873
|
+
if (ts3.isIdentifier(param.name)) {
|
|
2851
2874
|
const paramInfo = analyzeParameter(param, checker);
|
|
2852
2875
|
parameters.push(paramInfo);
|
|
2853
2876
|
}
|
|
@@ -2858,7 +2881,7 @@ function analyzeMethod(method, checker) {
|
|
|
2858
2881
|
return { name, parameters, returnTypeNode, returnType };
|
|
2859
2882
|
}
|
|
2860
2883
|
function analyzeParameter(param, checker) {
|
|
2861
|
-
const name =
|
|
2884
|
+
const name = ts3.isIdentifier(param.name) ? param.name.text : "param";
|
|
2862
2885
|
const typeNode = param.type;
|
|
2863
2886
|
const type = checker.getTypeAtLocation(param);
|
|
2864
2887
|
const formSpecExportName = detectFormSpecReference(typeNode);
|
|
@@ -2867,24 +2890,24 @@ function analyzeParameter(param, checker) {
|
|
|
2867
2890
|
}
|
|
2868
2891
|
function detectFormSpecReference(typeNode) {
|
|
2869
2892
|
if (!typeNode) return null;
|
|
2870
|
-
if (!
|
|
2871
|
-
const typeName =
|
|
2893
|
+
if (!ts3.isTypeReferenceNode(typeNode)) return null;
|
|
2894
|
+
const typeName = ts3.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : ts3.isQualifiedName(typeNode.typeName) ? typeNode.typeName.right.text : null;
|
|
2872
2895
|
if (typeName !== "InferSchema" && typeName !== "InferFormSchema") return null;
|
|
2873
2896
|
const typeArg = typeNode.typeArguments?.[0];
|
|
2874
|
-
if (!typeArg || !
|
|
2875
|
-
if (
|
|
2897
|
+
if (!typeArg || !ts3.isTypeQueryNode(typeArg)) return null;
|
|
2898
|
+
if (ts3.isIdentifier(typeArg.exprName)) {
|
|
2876
2899
|
return typeArg.exprName.text;
|
|
2877
2900
|
}
|
|
2878
|
-
if (
|
|
2901
|
+
if (ts3.isQualifiedName(typeArg.exprName)) {
|
|
2879
2902
|
return typeArg.exprName.right.text;
|
|
2880
2903
|
}
|
|
2881
2904
|
return null;
|
|
2882
2905
|
}
|
|
2883
|
-
var
|
|
2906
|
+
var ts3, RESOLVING_TYPE_PLACEHOLDER, MAX_ALIAS_CHAIN_DEPTH;
|
|
2884
2907
|
var init_class_analyzer = __esm({
|
|
2885
2908
|
"src/analyzer/class-analyzer.ts"() {
|
|
2886
2909
|
"use strict";
|
|
2887
|
-
|
|
2910
|
+
ts3 = __toESM(require("typescript"), 1);
|
|
2888
2911
|
init_jsdoc_constraints();
|
|
2889
2912
|
init_tsdoc_parser();
|
|
2890
2913
|
RESOLVING_TYPE_PLACEHOLDER = {
|
|
@@ -2896,14 +2919,941 @@ var init_class_analyzer = __esm({
|
|
|
2896
2919
|
}
|
|
2897
2920
|
});
|
|
2898
2921
|
|
|
2922
|
+
// src/analyzer/program.ts
|
|
2923
|
+
function createProgramContext(filePath) {
|
|
2924
|
+
const absolutePath = path.resolve(filePath);
|
|
2925
|
+
const fileDir = path.dirname(absolutePath);
|
|
2926
|
+
const configPath = ts4.findConfigFile(fileDir, ts4.sys.fileExists.bind(ts4.sys), "tsconfig.json");
|
|
2927
|
+
let compilerOptions;
|
|
2928
|
+
let fileNames;
|
|
2929
|
+
if (configPath) {
|
|
2930
|
+
const configFile = ts4.readConfigFile(configPath, ts4.sys.readFile.bind(ts4.sys));
|
|
2931
|
+
if (configFile.error) {
|
|
2932
|
+
throw new Error(
|
|
2933
|
+
`Error reading tsconfig.json: ${ts4.flattenDiagnosticMessageText(configFile.error.messageText, "\n")}`
|
|
2934
|
+
);
|
|
2935
|
+
}
|
|
2936
|
+
const parsed = ts4.parseJsonConfigFileContent(
|
|
2937
|
+
configFile.config,
|
|
2938
|
+
ts4.sys,
|
|
2939
|
+
path.dirname(configPath)
|
|
2940
|
+
);
|
|
2941
|
+
if (parsed.errors.length > 0) {
|
|
2942
|
+
const errorMessages = parsed.errors.map((e) => ts4.flattenDiagnosticMessageText(e.messageText, "\n")).join("\n");
|
|
2943
|
+
throw new Error(`Error parsing tsconfig.json: ${errorMessages}`);
|
|
2944
|
+
}
|
|
2945
|
+
compilerOptions = parsed.options;
|
|
2946
|
+
fileNames = parsed.fileNames.includes(absolutePath) ? parsed.fileNames : [...parsed.fileNames, absolutePath];
|
|
2947
|
+
} else {
|
|
2948
|
+
compilerOptions = {
|
|
2949
|
+
target: ts4.ScriptTarget.ES2022,
|
|
2950
|
+
module: ts4.ModuleKind.NodeNext,
|
|
2951
|
+
moduleResolution: ts4.ModuleResolutionKind.NodeNext,
|
|
2952
|
+
strict: true,
|
|
2953
|
+
skipLibCheck: true,
|
|
2954
|
+
declaration: true
|
|
2955
|
+
};
|
|
2956
|
+
fileNames = [absolutePath];
|
|
2957
|
+
}
|
|
2958
|
+
const program = ts4.createProgram(fileNames, compilerOptions);
|
|
2959
|
+
const sourceFile = program.getSourceFile(absolutePath);
|
|
2960
|
+
if (!sourceFile) {
|
|
2961
|
+
throw new Error(`Could not find source file: ${absolutePath}`);
|
|
2962
|
+
}
|
|
2963
|
+
return {
|
|
2964
|
+
program,
|
|
2965
|
+
checker: program.getTypeChecker(),
|
|
2966
|
+
sourceFile
|
|
2967
|
+
};
|
|
2968
|
+
}
|
|
2969
|
+
function findNodeByName(sourceFile, name, predicate, getName) {
|
|
2970
|
+
let result = null;
|
|
2971
|
+
function visit(node) {
|
|
2972
|
+
if (result) return;
|
|
2973
|
+
if (predicate(node) && getName(node) === name) {
|
|
2974
|
+
result = node;
|
|
2975
|
+
return;
|
|
2976
|
+
}
|
|
2977
|
+
ts4.forEachChild(node, visit);
|
|
2978
|
+
}
|
|
2979
|
+
visit(sourceFile);
|
|
2980
|
+
return result;
|
|
2981
|
+
}
|
|
2982
|
+
function findClassByName(sourceFile, className) {
|
|
2983
|
+
return findNodeByName(sourceFile, className, ts4.isClassDeclaration, (n) => n.name?.text);
|
|
2984
|
+
}
|
|
2985
|
+
function findInterfaceByName(sourceFile, interfaceName) {
|
|
2986
|
+
return findNodeByName(sourceFile, interfaceName, ts4.isInterfaceDeclaration, (n) => n.name.text);
|
|
2987
|
+
}
|
|
2988
|
+
function findTypeAliasByName(sourceFile, aliasName) {
|
|
2989
|
+
return findNodeByName(sourceFile, aliasName, ts4.isTypeAliasDeclaration, (n) => n.name.text);
|
|
2990
|
+
}
|
|
2991
|
+
function analyzeNamedTypeToIR(filePath, typeName, extensionRegistry) {
|
|
2992
|
+
const ctx = createProgramContext(filePath);
|
|
2993
|
+
const classDecl = findClassByName(ctx.sourceFile, typeName);
|
|
2994
|
+
if (classDecl !== null) {
|
|
2995
|
+
return analyzeClassToIR(classDecl, ctx.checker, filePath, extensionRegistry);
|
|
2996
|
+
}
|
|
2997
|
+
const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
|
|
2998
|
+
if (interfaceDecl !== null) {
|
|
2999
|
+
return analyzeInterfaceToIR(interfaceDecl, ctx.checker, filePath, extensionRegistry);
|
|
3000
|
+
}
|
|
3001
|
+
const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
|
|
3002
|
+
if (typeAlias !== null) {
|
|
3003
|
+
const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, filePath, extensionRegistry);
|
|
3004
|
+
if (result.ok) {
|
|
3005
|
+
return result.analysis;
|
|
3006
|
+
}
|
|
3007
|
+
throw new Error(result.error);
|
|
3008
|
+
}
|
|
3009
|
+
throw new Error(
|
|
3010
|
+
`Type "${typeName}" not found as a class, interface, or type alias in ${filePath}`
|
|
3011
|
+
);
|
|
3012
|
+
}
|
|
3013
|
+
var ts4, path;
|
|
3014
|
+
var init_program = __esm({
|
|
3015
|
+
"src/analyzer/program.ts"() {
|
|
3016
|
+
"use strict";
|
|
3017
|
+
ts4 = __toESM(require("typescript"), 1);
|
|
3018
|
+
path = __toESM(require("path"), 1);
|
|
3019
|
+
init_class_analyzer();
|
|
3020
|
+
}
|
|
3021
|
+
});
|
|
3022
|
+
|
|
3023
|
+
// src/validate/constraint-validator.ts
|
|
3024
|
+
function addContradiction(ctx, message, primary, related) {
|
|
3025
|
+
ctx.diagnostics.push({
|
|
3026
|
+
code: "CONTRADICTING_CONSTRAINTS",
|
|
3027
|
+
message,
|
|
3028
|
+
severity: "error",
|
|
3029
|
+
primaryLocation: primary,
|
|
3030
|
+
relatedLocations: [related]
|
|
3031
|
+
});
|
|
3032
|
+
}
|
|
3033
|
+
function addTypeMismatch(ctx, message, primary) {
|
|
3034
|
+
ctx.diagnostics.push({
|
|
3035
|
+
code: "TYPE_MISMATCH",
|
|
3036
|
+
message,
|
|
3037
|
+
severity: "error",
|
|
3038
|
+
primaryLocation: primary,
|
|
3039
|
+
relatedLocations: []
|
|
3040
|
+
});
|
|
3041
|
+
}
|
|
3042
|
+
function addUnknownExtension(ctx, message, primary) {
|
|
3043
|
+
ctx.diagnostics.push({
|
|
3044
|
+
code: "UNKNOWN_EXTENSION",
|
|
3045
|
+
message,
|
|
3046
|
+
severity: "warning",
|
|
3047
|
+
primaryLocation: primary,
|
|
3048
|
+
relatedLocations: []
|
|
3049
|
+
});
|
|
3050
|
+
}
|
|
3051
|
+
function addUnknownPathTarget(ctx, message, primary) {
|
|
3052
|
+
ctx.diagnostics.push({
|
|
3053
|
+
code: "UNKNOWN_PATH_TARGET",
|
|
3054
|
+
message,
|
|
3055
|
+
severity: "error",
|
|
3056
|
+
primaryLocation: primary,
|
|
3057
|
+
relatedLocations: []
|
|
3058
|
+
});
|
|
3059
|
+
}
|
|
3060
|
+
function addConstraintBroadening(ctx, message, primary, related) {
|
|
3061
|
+
ctx.diagnostics.push({
|
|
3062
|
+
code: "CONSTRAINT_BROADENING",
|
|
3063
|
+
message,
|
|
3064
|
+
severity: "error",
|
|
3065
|
+
primaryLocation: primary,
|
|
3066
|
+
relatedLocations: [related]
|
|
3067
|
+
});
|
|
3068
|
+
}
|
|
3069
|
+
function getExtensionIdFromConstraintId(constraintId) {
|
|
3070
|
+
const separator = constraintId.lastIndexOf("/");
|
|
3071
|
+
if (separator <= 0) {
|
|
3072
|
+
return null;
|
|
3073
|
+
}
|
|
3074
|
+
return constraintId.slice(0, separator);
|
|
3075
|
+
}
|
|
3076
|
+
function findNumeric(constraints, constraintKind) {
|
|
3077
|
+
return constraints.find((c) => c.constraintKind === constraintKind);
|
|
3078
|
+
}
|
|
3079
|
+
function findLength(constraints, constraintKind) {
|
|
3080
|
+
return constraints.find((c) => c.constraintKind === constraintKind);
|
|
3081
|
+
}
|
|
3082
|
+
function findAllowedMembers(constraints) {
|
|
3083
|
+
return constraints.filter(
|
|
3084
|
+
(c) => c.constraintKind === "allowedMembers"
|
|
3085
|
+
);
|
|
3086
|
+
}
|
|
3087
|
+
function findConstConstraints(constraints) {
|
|
3088
|
+
return constraints.filter(
|
|
3089
|
+
(c) => c.constraintKind === "const"
|
|
3090
|
+
);
|
|
3091
|
+
}
|
|
3092
|
+
function jsonValueEquals(left, right) {
|
|
3093
|
+
if (left === right) {
|
|
3094
|
+
return true;
|
|
3095
|
+
}
|
|
3096
|
+
if (Array.isArray(left) || Array.isArray(right)) {
|
|
3097
|
+
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
|
|
3098
|
+
return false;
|
|
3099
|
+
}
|
|
3100
|
+
return left.every((item, index) => jsonValueEquals(item, right[index]));
|
|
3101
|
+
}
|
|
3102
|
+
if (isJsonObject(left) || isJsonObject(right)) {
|
|
3103
|
+
if (!isJsonObject(left) || !isJsonObject(right)) {
|
|
3104
|
+
return false;
|
|
3105
|
+
}
|
|
3106
|
+
const leftKeys = Object.keys(left).sort();
|
|
3107
|
+
const rightKeys = Object.keys(right).sort();
|
|
3108
|
+
if (leftKeys.length !== rightKeys.length) {
|
|
3109
|
+
return false;
|
|
3110
|
+
}
|
|
3111
|
+
return leftKeys.every((key, index) => {
|
|
3112
|
+
const rightKey = rightKeys[index];
|
|
3113
|
+
if (rightKey !== key) {
|
|
3114
|
+
return false;
|
|
3115
|
+
}
|
|
3116
|
+
const leftValue = left[key];
|
|
3117
|
+
const rightValue = right[rightKey];
|
|
3118
|
+
return leftValue !== void 0 && rightValue !== void 0 && jsonValueEquals(leftValue, rightValue);
|
|
3119
|
+
});
|
|
3120
|
+
}
|
|
3121
|
+
return false;
|
|
3122
|
+
}
|
|
3123
|
+
function isJsonObject(value) {
|
|
3124
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3125
|
+
}
|
|
3126
|
+
function isOrderedBoundConstraint(constraint) {
|
|
3127
|
+
return constraint.constraintKind === "minimum" || constraint.constraintKind === "exclusiveMinimum" || constraint.constraintKind === "minLength" || constraint.constraintKind === "minItems" || constraint.constraintKind === "maximum" || constraint.constraintKind === "exclusiveMaximum" || constraint.constraintKind === "maxLength" || constraint.constraintKind === "maxItems";
|
|
3128
|
+
}
|
|
3129
|
+
function pathKey(constraint) {
|
|
3130
|
+
return constraint.path?.segments.join(".") ?? "";
|
|
3131
|
+
}
|
|
3132
|
+
function orderedBoundFamily(kind) {
|
|
3133
|
+
switch (kind) {
|
|
3134
|
+
case "minimum":
|
|
3135
|
+
case "exclusiveMinimum":
|
|
3136
|
+
return "numeric-lower";
|
|
3137
|
+
case "maximum":
|
|
3138
|
+
case "exclusiveMaximum":
|
|
3139
|
+
return "numeric-upper";
|
|
3140
|
+
case "minLength":
|
|
3141
|
+
return "minLength";
|
|
3142
|
+
case "minItems":
|
|
3143
|
+
return "minItems";
|
|
3144
|
+
case "maxLength":
|
|
3145
|
+
return "maxLength";
|
|
3146
|
+
case "maxItems":
|
|
3147
|
+
return "maxItems";
|
|
3148
|
+
default: {
|
|
3149
|
+
const _exhaustive = kind;
|
|
3150
|
+
return _exhaustive;
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
}
|
|
3154
|
+
function isNumericLowerKind(kind) {
|
|
3155
|
+
return kind === "minimum" || kind === "exclusiveMinimum";
|
|
3156
|
+
}
|
|
3157
|
+
function isNumericUpperKind(kind) {
|
|
3158
|
+
return kind === "maximum" || kind === "exclusiveMaximum";
|
|
3159
|
+
}
|
|
3160
|
+
function describeConstraintTag(constraint) {
|
|
3161
|
+
return `@${constraint.constraintKind}`;
|
|
3162
|
+
}
|
|
3163
|
+
function compareConstraintStrength(current, previous) {
|
|
3164
|
+
const family = orderedBoundFamily(current.constraintKind);
|
|
3165
|
+
if (family === "numeric-lower") {
|
|
3166
|
+
if (!isNumericLowerKind(current.constraintKind) || !isNumericLowerKind(previous.constraintKind)) {
|
|
3167
|
+
throw new Error("numeric-lower family received non-numeric lower-bound constraint");
|
|
3168
|
+
}
|
|
3169
|
+
if (current.value !== previous.value) {
|
|
3170
|
+
return current.value > previous.value ? 1 : -1;
|
|
3171
|
+
}
|
|
3172
|
+
if (current.constraintKind === "exclusiveMinimum" && previous.constraintKind === "minimum") {
|
|
3173
|
+
return 1;
|
|
3174
|
+
}
|
|
3175
|
+
if (current.constraintKind === "minimum" && previous.constraintKind === "exclusiveMinimum") {
|
|
3176
|
+
return -1;
|
|
3177
|
+
}
|
|
3178
|
+
return 0;
|
|
3179
|
+
}
|
|
3180
|
+
if (family === "numeric-upper") {
|
|
3181
|
+
if (!isNumericUpperKind(current.constraintKind) || !isNumericUpperKind(previous.constraintKind)) {
|
|
3182
|
+
throw new Error("numeric-upper family received non-numeric upper-bound constraint");
|
|
3183
|
+
}
|
|
3184
|
+
if (current.value !== previous.value) {
|
|
3185
|
+
return current.value < previous.value ? 1 : -1;
|
|
3186
|
+
}
|
|
3187
|
+
if (current.constraintKind === "exclusiveMaximum" && previous.constraintKind === "maximum") {
|
|
3188
|
+
return 1;
|
|
3189
|
+
}
|
|
3190
|
+
if (current.constraintKind === "maximum" && previous.constraintKind === "exclusiveMaximum") {
|
|
3191
|
+
return -1;
|
|
3192
|
+
}
|
|
3193
|
+
return 0;
|
|
3194
|
+
}
|
|
3195
|
+
switch (family) {
|
|
3196
|
+
case "minLength":
|
|
3197
|
+
case "minItems":
|
|
3198
|
+
if (current.value === previous.value) {
|
|
3199
|
+
return 0;
|
|
3200
|
+
}
|
|
3201
|
+
return current.value > previous.value ? 1 : -1;
|
|
3202
|
+
case "maxLength":
|
|
3203
|
+
case "maxItems":
|
|
3204
|
+
if (current.value === previous.value) {
|
|
3205
|
+
return 0;
|
|
3206
|
+
}
|
|
3207
|
+
return current.value < previous.value ? 1 : -1;
|
|
3208
|
+
default: {
|
|
3209
|
+
const _exhaustive = family;
|
|
3210
|
+
return _exhaustive;
|
|
3211
|
+
}
|
|
3212
|
+
}
|
|
3213
|
+
}
|
|
3214
|
+
function checkConstraintBroadening(ctx, fieldName, constraints) {
|
|
3215
|
+
const strongestByKey = /* @__PURE__ */ new Map();
|
|
3216
|
+
for (const constraint of constraints) {
|
|
3217
|
+
if (!isOrderedBoundConstraint(constraint)) {
|
|
3218
|
+
continue;
|
|
3219
|
+
}
|
|
3220
|
+
const key = `${orderedBoundFamily(constraint.constraintKind)}:${pathKey(constraint)}`;
|
|
3221
|
+
const previous = strongestByKey.get(key);
|
|
3222
|
+
if (previous === void 0) {
|
|
3223
|
+
strongestByKey.set(key, constraint);
|
|
3224
|
+
continue;
|
|
3225
|
+
}
|
|
3226
|
+
const strength = compareConstraintStrength(constraint, previous);
|
|
3227
|
+
if (strength < 0) {
|
|
3228
|
+
const displayFieldName = formatPathTargetFieldName(
|
|
3229
|
+
fieldName,
|
|
3230
|
+
constraint.path?.segments ?? []
|
|
3231
|
+
);
|
|
3232
|
+
addConstraintBroadening(
|
|
3233
|
+
ctx,
|
|
3234
|
+
`Field "${displayFieldName}": ${describeConstraintTag(constraint)} (${String(constraint.value)}) is broader than earlier ${describeConstraintTag(previous)} (${String(previous.value)}). Constraints can only narrow.`,
|
|
3235
|
+
constraint.provenance,
|
|
3236
|
+
previous.provenance
|
|
3237
|
+
);
|
|
3238
|
+
continue;
|
|
3239
|
+
}
|
|
3240
|
+
if (strength <= 0) {
|
|
3241
|
+
continue;
|
|
3242
|
+
}
|
|
3243
|
+
strongestByKey.set(key, constraint);
|
|
3244
|
+
}
|
|
3245
|
+
}
|
|
3246
|
+
function compareCustomConstraintStrength(current, previous) {
|
|
3247
|
+
const order = current.comparePayloads(current.constraint.payload, previous.constraint.payload);
|
|
3248
|
+
const equalPayloadTiebreaker = order === 0 ? compareSemanticInclusivity(current.role.inclusive, previous.role.inclusive) : order;
|
|
3249
|
+
switch (current.role.bound) {
|
|
3250
|
+
case "lower":
|
|
3251
|
+
return equalPayloadTiebreaker;
|
|
3252
|
+
case "upper":
|
|
3253
|
+
return equalPayloadTiebreaker === 0 ? 0 : -equalPayloadTiebreaker;
|
|
3254
|
+
case "exact":
|
|
3255
|
+
return order === 0 ? 0 : Number.NaN;
|
|
3256
|
+
default: {
|
|
3257
|
+
const _exhaustive = current.role.bound;
|
|
3258
|
+
return _exhaustive;
|
|
3259
|
+
}
|
|
3260
|
+
}
|
|
3261
|
+
}
|
|
3262
|
+
function compareSemanticInclusivity(currentInclusive, previousInclusive) {
|
|
3263
|
+
if (currentInclusive === previousInclusive) {
|
|
3264
|
+
return 0;
|
|
3265
|
+
}
|
|
3266
|
+
return currentInclusive ? -1 : 1;
|
|
3267
|
+
}
|
|
3268
|
+
function customConstraintsContradict(lower, upper) {
|
|
3269
|
+
const order = lower.comparePayloads(lower.constraint.payload, upper.constraint.payload);
|
|
3270
|
+
if (order > 0) {
|
|
3271
|
+
return true;
|
|
3272
|
+
}
|
|
3273
|
+
if (order < 0) {
|
|
3274
|
+
return false;
|
|
3275
|
+
}
|
|
3276
|
+
return !lower.role.inclusive || !upper.role.inclusive;
|
|
3277
|
+
}
|
|
3278
|
+
function describeCustomConstraintTag(constraint) {
|
|
3279
|
+
return constraint.provenance.tagName ?? constraint.constraintId;
|
|
3280
|
+
}
|
|
3281
|
+
function checkCustomConstraintSemantics(ctx, fieldName, constraints) {
|
|
3282
|
+
if (ctx.extensionRegistry === void 0) {
|
|
3283
|
+
return;
|
|
3284
|
+
}
|
|
3285
|
+
const strongestByKey = /* @__PURE__ */ new Map();
|
|
3286
|
+
const lowerByFamily = /* @__PURE__ */ new Map();
|
|
3287
|
+
const upperByFamily = /* @__PURE__ */ new Map();
|
|
3288
|
+
for (const constraint of constraints) {
|
|
3289
|
+
if (constraint.constraintKind !== "custom") {
|
|
3290
|
+
continue;
|
|
3291
|
+
}
|
|
3292
|
+
const registration = ctx.extensionRegistry.findConstraint(constraint.constraintId);
|
|
3293
|
+
if (registration?.comparePayloads === void 0 || registration.semanticRole === void 0) {
|
|
3294
|
+
continue;
|
|
3295
|
+
}
|
|
3296
|
+
const entry = {
|
|
3297
|
+
constraint,
|
|
3298
|
+
comparePayloads: registration.comparePayloads,
|
|
3299
|
+
role: registration.semanticRole
|
|
3300
|
+
};
|
|
3301
|
+
const familyKey = `${registration.semanticRole.family}:${pathKey(constraint)}`;
|
|
3302
|
+
const boundKey = `${familyKey}:${registration.semanticRole.bound}`;
|
|
3303
|
+
const previous = strongestByKey.get(boundKey);
|
|
3304
|
+
if (previous !== void 0) {
|
|
3305
|
+
const strength = compareCustomConstraintStrength(entry, previous);
|
|
3306
|
+
if (Number.isNaN(strength)) {
|
|
3307
|
+
addContradiction(
|
|
3308
|
+
ctx,
|
|
3309
|
+
`Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} conflicts with ${describeCustomConstraintTag(previous.constraint)}`,
|
|
3310
|
+
constraint.provenance,
|
|
3311
|
+
previous.constraint.provenance
|
|
3312
|
+
);
|
|
3313
|
+
continue;
|
|
3314
|
+
}
|
|
3315
|
+
if (strength < 0) {
|
|
3316
|
+
addConstraintBroadening(
|
|
3317
|
+
ctx,
|
|
3318
|
+
`Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} is broader than earlier ${describeCustomConstraintTag(previous.constraint)}. Constraints can only narrow.`,
|
|
3319
|
+
constraint.provenance,
|
|
3320
|
+
previous.constraint.provenance
|
|
3321
|
+
);
|
|
3322
|
+
continue;
|
|
3323
|
+
}
|
|
3324
|
+
if (strength > 0) {
|
|
3325
|
+
strongestByKey.set(boundKey, entry);
|
|
3326
|
+
}
|
|
3327
|
+
} else {
|
|
3328
|
+
strongestByKey.set(boundKey, entry);
|
|
3329
|
+
}
|
|
3330
|
+
if (registration.semanticRole.bound === "lower") {
|
|
3331
|
+
lowerByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
|
|
3332
|
+
} else if (registration.semanticRole.bound === "upper") {
|
|
3333
|
+
upperByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
|
|
3334
|
+
}
|
|
3335
|
+
}
|
|
3336
|
+
for (const [familyKey, lower] of lowerByFamily) {
|
|
3337
|
+
const upper = upperByFamily.get(familyKey);
|
|
3338
|
+
if (upper === void 0) {
|
|
3339
|
+
continue;
|
|
3340
|
+
}
|
|
3341
|
+
if (!customConstraintsContradict(lower, upper)) {
|
|
3342
|
+
continue;
|
|
3343
|
+
}
|
|
3344
|
+
addContradiction(
|
|
3345
|
+
ctx,
|
|
3346
|
+
`Field "${formatPathTargetFieldName(fieldName, lower.constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(lower.constraint)} contradicts ${describeCustomConstraintTag(upper.constraint)}`,
|
|
3347
|
+
lower.constraint.provenance,
|
|
3348
|
+
upper.constraint.provenance
|
|
3349
|
+
);
|
|
3350
|
+
}
|
|
3351
|
+
}
|
|
3352
|
+
function checkNumericContradictions(ctx, fieldName, constraints) {
|
|
3353
|
+
const min = findNumeric(constraints, "minimum");
|
|
3354
|
+
const max = findNumeric(constraints, "maximum");
|
|
3355
|
+
const exMin = findNumeric(constraints, "exclusiveMinimum");
|
|
3356
|
+
const exMax = findNumeric(constraints, "exclusiveMaximum");
|
|
3357
|
+
if (min !== void 0 && max !== void 0 && min.value > max.value) {
|
|
3358
|
+
addContradiction(
|
|
3359
|
+
ctx,
|
|
3360
|
+
`Field "${fieldName}": minimum (${String(min.value)}) is greater than maximum (${String(max.value)})`,
|
|
3361
|
+
min.provenance,
|
|
3362
|
+
max.provenance
|
|
3363
|
+
);
|
|
3364
|
+
}
|
|
3365
|
+
if (exMin !== void 0 && max !== void 0 && exMin.value >= max.value) {
|
|
3366
|
+
addContradiction(
|
|
3367
|
+
ctx,
|
|
3368
|
+
`Field "${fieldName}": exclusiveMinimum (${String(exMin.value)}) is greater than or equal to maximum (${String(max.value)})`,
|
|
3369
|
+
exMin.provenance,
|
|
3370
|
+
max.provenance
|
|
3371
|
+
);
|
|
3372
|
+
}
|
|
3373
|
+
if (min !== void 0 && exMax !== void 0 && min.value >= exMax.value) {
|
|
3374
|
+
addContradiction(
|
|
3375
|
+
ctx,
|
|
3376
|
+
`Field "${fieldName}": minimum (${String(min.value)}) is greater than or equal to exclusiveMaximum (${String(exMax.value)})`,
|
|
3377
|
+
min.provenance,
|
|
3378
|
+
exMax.provenance
|
|
3379
|
+
);
|
|
3380
|
+
}
|
|
3381
|
+
if (exMin !== void 0 && exMax !== void 0 && exMin.value >= exMax.value) {
|
|
3382
|
+
addContradiction(
|
|
3383
|
+
ctx,
|
|
3384
|
+
`Field "${fieldName}": exclusiveMinimum (${String(exMin.value)}) is greater than or equal to exclusiveMaximum (${String(exMax.value)})`,
|
|
3385
|
+
exMin.provenance,
|
|
3386
|
+
exMax.provenance
|
|
3387
|
+
);
|
|
3388
|
+
}
|
|
3389
|
+
}
|
|
3390
|
+
function checkLengthContradictions(ctx, fieldName, constraints) {
|
|
3391
|
+
const minLen = findLength(constraints, "minLength");
|
|
3392
|
+
const maxLen = findLength(constraints, "maxLength");
|
|
3393
|
+
if (minLen !== void 0 && maxLen !== void 0 && minLen.value > maxLen.value) {
|
|
3394
|
+
addContradiction(
|
|
3395
|
+
ctx,
|
|
3396
|
+
`Field "${fieldName}": minLength (${String(minLen.value)}) is greater than maxLength (${String(maxLen.value)})`,
|
|
3397
|
+
minLen.provenance,
|
|
3398
|
+
maxLen.provenance
|
|
3399
|
+
);
|
|
3400
|
+
}
|
|
3401
|
+
const minItems = findLength(constraints, "minItems");
|
|
3402
|
+
const maxItems = findLength(constraints, "maxItems");
|
|
3403
|
+
if (minItems !== void 0 && maxItems !== void 0 && minItems.value > maxItems.value) {
|
|
3404
|
+
addContradiction(
|
|
3405
|
+
ctx,
|
|
3406
|
+
`Field "${fieldName}": minItems (${String(minItems.value)}) is greater than maxItems (${String(maxItems.value)})`,
|
|
3407
|
+
minItems.provenance,
|
|
3408
|
+
maxItems.provenance
|
|
3409
|
+
);
|
|
3410
|
+
}
|
|
3411
|
+
}
|
|
3412
|
+
function checkAllowedMembersContradiction(ctx, fieldName, constraints) {
|
|
3413
|
+
const members = findAllowedMembers(constraints);
|
|
3414
|
+
if (members.length < 2) return;
|
|
3415
|
+
const firstSet = new Set(members[0]?.members ?? []);
|
|
3416
|
+
for (let i = 1; i < members.length; i++) {
|
|
3417
|
+
const current = members[i];
|
|
3418
|
+
if (current === void 0) continue;
|
|
3419
|
+
for (const m of firstSet) {
|
|
3420
|
+
if (!current.members.includes(m)) {
|
|
3421
|
+
firstSet.delete(m);
|
|
3422
|
+
}
|
|
3423
|
+
}
|
|
3424
|
+
}
|
|
3425
|
+
if (firstSet.size === 0) {
|
|
3426
|
+
const first = members[0];
|
|
3427
|
+
const second = members[1];
|
|
3428
|
+
if (first !== void 0 && second !== void 0) {
|
|
3429
|
+
addContradiction(
|
|
3430
|
+
ctx,
|
|
3431
|
+
`Field "${fieldName}": allowedMembers constraints have an empty intersection (no valid values remain)`,
|
|
3432
|
+
first.provenance,
|
|
3433
|
+
second.provenance
|
|
3434
|
+
);
|
|
3435
|
+
}
|
|
3436
|
+
}
|
|
3437
|
+
}
|
|
3438
|
+
function checkConstContradictions(ctx, fieldName, constraints) {
|
|
3439
|
+
const constConstraints = findConstConstraints(constraints);
|
|
3440
|
+
if (constConstraints.length < 2) return;
|
|
3441
|
+
const first = constConstraints[0];
|
|
3442
|
+
if (first === void 0) return;
|
|
3443
|
+
for (let i = 1; i < constConstraints.length; i++) {
|
|
3444
|
+
const current = constConstraints[i];
|
|
3445
|
+
if (current === void 0) continue;
|
|
3446
|
+
if (jsonValueEquals(first.value, current.value)) {
|
|
3447
|
+
continue;
|
|
3448
|
+
}
|
|
3449
|
+
addContradiction(
|
|
3450
|
+
ctx,
|
|
3451
|
+
`Field "${fieldName}": conflicting @const constraints require both ${JSON.stringify(first.value)} and ${JSON.stringify(current.value)}`,
|
|
3452
|
+
first.provenance,
|
|
3453
|
+
current.provenance
|
|
3454
|
+
);
|
|
3455
|
+
}
|
|
3456
|
+
}
|
|
3457
|
+
function typeLabel(type) {
|
|
3458
|
+
switch (type.kind) {
|
|
3459
|
+
case "primitive":
|
|
3460
|
+
return type.primitiveKind;
|
|
3461
|
+
case "enum":
|
|
3462
|
+
return "enum";
|
|
3463
|
+
case "array":
|
|
3464
|
+
return "array";
|
|
3465
|
+
case "object":
|
|
3466
|
+
return "object";
|
|
3467
|
+
case "record":
|
|
3468
|
+
return "record";
|
|
3469
|
+
case "union":
|
|
3470
|
+
return "union";
|
|
3471
|
+
case "reference":
|
|
3472
|
+
return `reference(${type.name})`;
|
|
3473
|
+
case "dynamic":
|
|
3474
|
+
return `dynamic(${type.dynamicKind})`;
|
|
3475
|
+
case "custom":
|
|
3476
|
+
return `custom(${type.typeId})`;
|
|
3477
|
+
default: {
|
|
3478
|
+
const _exhaustive = type;
|
|
3479
|
+
return String(_exhaustive);
|
|
3480
|
+
}
|
|
3481
|
+
}
|
|
3482
|
+
}
|
|
3483
|
+
function dereferenceType(ctx, type) {
|
|
3484
|
+
let current = type;
|
|
3485
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3486
|
+
while (current.kind === "reference") {
|
|
3487
|
+
if (seen.has(current.name)) {
|
|
3488
|
+
return current;
|
|
3489
|
+
}
|
|
3490
|
+
seen.add(current.name);
|
|
3491
|
+
const definition = ctx.typeRegistry[current.name];
|
|
3492
|
+
if (definition === void 0) {
|
|
3493
|
+
return current;
|
|
3494
|
+
}
|
|
3495
|
+
current = definition.type;
|
|
3496
|
+
}
|
|
3497
|
+
return current;
|
|
3498
|
+
}
|
|
3499
|
+
function collectReferencedTypeConstraints(ctx, type) {
|
|
3500
|
+
const collected = [];
|
|
3501
|
+
let current = type;
|
|
3502
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3503
|
+
while (current.kind === "reference") {
|
|
3504
|
+
if (seen.has(current.name)) {
|
|
3505
|
+
break;
|
|
3506
|
+
}
|
|
3507
|
+
seen.add(current.name);
|
|
3508
|
+
const definition = ctx.typeRegistry[current.name];
|
|
3509
|
+
if (definition === void 0) {
|
|
3510
|
+
break;
|
|
3511
|
+
}
|
|
3512
|
+
if (definition.constraints !== void 0) {
|
|
3513
|
+
collected.push(...definition.constraints);
|
|
3514
|
+
}
|
|
3515
|
+
current = definition.type;
|
|
3516
|
+
}
|
|
3517
|
+
return collected;
|
|
3518
|
+
}
|
|
3519
|
+
function resolvePathTargetType(ctx, type, segments) {
|
|
3520
|
+
const effectiveType = dereferenceType(ctx, type);
|
|
3521
|
+
if (segments.length === 0) {
|
|
3522
|
+
return { kind: "resolved", type: effectiveType };
|
|
3523
|
+
}
|
|
3524
|
+
if (effectiveType.kind === "array") {
|
|
3525
|
+
return resolvePathTargetType(ctx, effectiveType.items, segments);
|
|
3526
|
+
}
|
|
3527
|
+
if (effectiveType.kind === "object") {
|
|
3528
|
+
const [segment, ...rest] = segments;
|
|
3529
|
+
if (segment === void 0) {
|
|
3530
|
+
throw new Error("Invariant violation: object path traversal requires a segment");
|
|
3531
|
+
}
|
|
3532
|
+
const property = effectiveType.properties.find((prop) => prop.name === segment);
|
|
3533
|
+
if (property === void 0) {
|
|
3534
|
+
return { kind: "missing-property", segment };
|
|
3535
|
+
}
|
|
3536
|
+
return resolvePathTargetType(ctx, property.type, rest);
|
|
3537
|
+
}
|
|
3538
|
+
return { kind: "unresolvable", type: effectiveType };
|
|
3539
|
+
}
|
|
3540
|
+
function isNullType(type) {
|
|
3541
|
+
return type.kind === "primitive" && type.primitiveKind === "null";
|
|
3542
|
+
}
|
|
3543
|
+
function collectCustomConstraintCandidateTypes(ctx, type) {
|
|
3544
|
+
const effectiveType = dereferenceType(ctx, type);
|
|
3545
|
+
const candidates = [effectiveType];
|
|
3546
|
+
if (effectiveType.kind === "array") {
|
|
3547
|
+
candidates.push(...collectCustomConstraintCandidateTypes(ctx, effectiveType.items));
|
|
3548
|
+
}
|
|
3549
|
+
if (effectiveType.kind === "union") {
|
|
3550
|
+
const memberTypes = effectiveType.members.map((member) => dereferenceType(ctx, member));
|
|
3551
|
+
const nonNullMembers = memberTypes.filter((member) => !isNullType(member));
|
|
3552
|
+
if (nonNullMembers.length === 1 && nonNullMembers.length < memberTypes.length) {
|
|
3553
|
+
const [nullableMember] = nonNullMembers;
|
|
3554
|
+
if (nullableMember !== void 0) {
|
|
3555
|
+
candidates.push(...collectCustomConstraintCandidateTypes(ctx, nullableMember));
|
|
3556
|
+
}
|
|
3557
|
+
}
|
|
3558
|
+
}
|
|
3559
|
+
return candidates;
|
|
3560
|
+
}
|
|
3561
|
+
function formatPathTargetFieldName(fieldName, path4) {
|
|
3562
|
+
return path4.length === 0 ? fieldName : `${fieldName}.${path4.join(".")}`;
|
|
3563
|
+
}
|
|
3564
|
+
function checkConstraintOnType(ctx, fieldName, type, constraint) {
|
|
3565
|
+
const effectiveType = dereferenceType(ctx, type);
|
|
3566
|
+
const isNumber = effectiveType.kind === "primitive" && ["number", "integer", "bigint"].includes(effectiveType.primitiveKind);
|
|
3567
|
+
const isString = effectiveType.kind === "primitive" && effectiveType.primitiveKind === "string";
|
|
3568
|
+
const isArray = effectiveType.kind === "array";
|
|
3569
|
+
const isEnum = effectiveType.kind === "enum";
|
|
3570
|
+
const arrayItemType = effectiveType.kind === "array" ? dereferenceType(ctx, effectiveType.items) : void 0;
|
|
3571
|
+
const isStringArray = arrayItemType?.kind === "primitive" && arrayItemType.primitiveKind === "string";
|
|
3572
|
+
const label = typeLabel(effectiveType);
|
|
3573
|
+
const ck = constraint.constraintKind;
|
|
3574
|
+
switch (ck) {
|
|
3575
|
+
case "minimum":
|
|
3576
|
+
case "maximum":
|
|
3577
|
+
case "exclusiveMinimum":
|
|
3578
|
+
case "exclusiveMaximum":
|
|
3579
|
+
case "multipleOf": {
|
|
3580
|
+
if (!isNumber) {
|
|
3581
|
+
addTypeMismatch(
|
|
3582
|
+
ctx,
|
|
3583
|
+
`Field "${fieldName}": constraint "${ck}" is only valid on number fields, but field type is "${label}"`,
|
|
3584
|
+
constraint.provenance
|
|
3585
|
+
);
|
|
3586
|
+
}
|
|
3587
|
+
break;
|
|
3588
|
+
}
|
|
3589
|
+
case "minLength":
|
|
3590
|
+
case "maxLength":
|
|
3591
|
+
case "pattern": {
|
|
3592
|
+
if (!isString && !isStringArray) {
|
|
3593
|
+
addTypeMismatch(
|
|
3594
|
+
ctx,
|
|
3595
|
+
`Field "${fieldName}": constraint "${ck}" is only valid on string fields or string array items, but field type is "${label}"`,
|
|
3596
|
+
constraint.provenance
|
|
3597
|
+
);
|
|
3598
|
+
}
|
|
3599
|
+
break;
|
|
3600
|
+
}
|
|
3601
|
+
case "minItems":
|
|
3602
|
+
case "maxItems":
|
|
3603
|
+
case "uniqueItems": {
|
|
3604
|
+
if (!isArray) {
|
|
3605
|
+
addTypeMismatch(
|
|
3606
|
+
ctx,
|
|
3607
|
+
`Field "${fieldName}": constraint "${ck}" is only valid on array fields, but field type is "${label}"`,
|
|
3608
|
+
constraint.provenance
|
|
3609
|
+
);
|
|
3610
|
+
}
|
|
3611
|
+
break;
|
|
3612
|
+
}
|
|
3613
|
+
case "allowedMembers": {
|
|
3614
|
+
if (!isEnum) {
|
|
3615
|
+
addTypeMismatch(
|
|
3616
|
+
ctx,
|
|
3617
|
+
`Field "${fieldName}": constraint "allowedMembers" is only valid on enum fields, but field type is "${label}"`,
|
|
3618
|
+
constraint.provenance
|
|
3619
|
+
);
|
|
3620
|
+
}
|
|
3621
|
+
break;
|
|
3622
|
+
}
|
|
3623
|
+
case "const": {
|
|
3624
|
+
const isPrimitiveConstType = effectiveType.kind === "primitive" && ["string", "number", "integer", "bigint", "boolean", "null"].includes(
|
|
3625
|
+
effectiveType.primitiveKind
|
|
3626
|
+
) || effectiveType.kind === "enum";
|
|
3627
|
+
if (!isPrimitiveConstType) {
|
|
3628
|
+
addTypeMismatch(
|
|
3629
|
+
ctx,
|
|
3630
|
+
`Field "${fieldName}": constraint "const" is only valid on primitive or enum fields, but field type is "${label}"`,
|
|
3631
|
+
constraint.provenance
|
|
3632
|
+
);
|
|
3633
|
+
break;
|
|
3634
|
+
}
|
|
3635
|
+
if (effectiveType.kind === "primitive") {
|
|
3636
|
+
const valueType = constraint.value === null ? "null" : Array.isArray(constraint.value) ? "array" : typeof constraint.value;
|
|
3637
|
+
const expectedValueType = effectiveType.primitiveKind === "integer" || effectiveType.primitiveKind === "bigint" ? "number" : effectiveType.primitiveKind;
|
|
3638
|
+
if (valueType !== expectedValueType) {
|
|
3639
|
+
addTypeMismatch(
|
|
3640
|
+
ctx,
|
|
3641
|
+
`Field "${fieldName}": @const value type "${valueType}" is incompatible with field type "${effectiveType.primitiveKind}"`,
|
|
3642
|
+
constraint.provenance
|
|
3643
|
+
);
|
|
3644
|
+
}
|
|
3645
|
+
break;
|
|
3646
|
+
}
|
|
3647
|
+
const memberValues = effectiveType.members.map((member) => member.value);
|
|
3648
|
+
if (!memberValues.some((member) => jsonValueEquals(member, constraint.value))) {
|
|
3649
|
+
addTypeMismatch(
|
|
3650
|
+
ctx,
|
|
3651
|
+
`Field "${fieldName}": @const value ${JSON.stringify(constraint.value)} is not one of the enum members`,
|
|
3652
|
+
constraint.provenance
|
|
3653
|
+
);
|
|
3654
|
+
}
|
|
3655
|
+
break;
|
|
3656
|
+
}
|
|
3657
|
+
case "custom": {
|
|
3658
|
+
checkCustomConstraint(ctx, fieldName, effectiveType, constraint);
|
|
3659
|
+
break;
|
|
3660
|
+
}
|
|
3661
|
+
default: {
|
|
3662
|
+
const _exhaustive = constraint;
|
|
3663
|
+
throw new Error(
|
|
3664
|
+
`Unhandled constraint kind: ${_exhaustive.constraintKind}`
|
|
3665
|
+
);
|
|
3666
|
+
}
|
|
3667
|
+
}
|
|
3668
|
+
}
|
|
3669
|
+
function checkTypeApplicability(ctx, fieldName, type, constraints) {
|
|
3670
|
+
for (const constraint of constraints) {
|
|
3671
|
+
if (constraint.path) {
|
|
3672
|
+
const resolution = resolvePathTargetType(ctx, type, constraint.path.segments);
|
|
3673
|
+
const targetFieldName = formatPathTargetFieldName(fieldName, constraint.path.segments);
|
|
3674
|
+
if (resolution.kind === "missing-property") {
|
|
3675
|
+
addUnknownPathTarget(
|
|
3676
|
+
ctx,
|
|
3677
|
+
`Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" references unknown path segment "${resolution.segment}"`,
|
|
3678
|
+
constraint.provenance
|
|
3679
|
+
);
|
|
3680
|
+
continue;
|
|
3681
|
+
}
|
|
3682
|
+
if (resolution.kind === "unresolvable") {
|
|
3683
|
+
addTypeMismatch(
|
|
3684
|
+
ctx,
|
|
3685
|
+
`Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" is invalid because type "${typeLabel(resolution.type)}" cannot be traversed`,
|
|
3686
|
+
constraint.provenance
|
|
3687
|
+
);
|
|
3688
|
+
continue;
|
|
3689
|
+
}
|
|
3690
|
+
checkConstraintOnType(ctx, targetFieldName, resolution.type, constraint);
|
|
3691
|
+
continue;
|
|
3692
|
+
}
|
|
3693
|
+
checkConstraintOnType(ctx, fieldName, type, constraint);
|
|
3694
|
+
}
|
|
3695
|
+
}
|
|
3696
|
+
function checkCustomConstraint(ctx, fieldName, type, constraint) {
|
|
3697
|
+
if (ctx.extensionRegistry === void 0) return;
|
|
3698
|
+
const registration = ctx.extensionRegistry.findConstraint(constraint.constraintId);
|
|
3699
|
+
if (registration === void 0) {
|
|
3700
|
+
addUnknownExtension(
|
|
3701
|
+
ctx,
|
|
3702
|
+
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not registered in the extension registry`,
|
|
3703
|
+
constraint.provenance
|
|
3704
|
+
);
|
|
3705
|
+
return;
|
|
3706
|
+
}
|
|
3707
|
+
const candidateTypes = collectCustomConstraintCandidateTypes(ctx, type);
|
|
3708
|
+
const normalizedTagName = constraint.provenance.tagName === void 0 ? void 0 : (0, import_core4.normalizeConstraintTagName)(constraint.provenance.tagName.replace(/^@/, ""));
|
|
3709
|
+
if (normalizedTagName !== void 0) {
|
|
3710
|
+
const tagRegistration = ctx.extensionRegistry.findConstraintTag(normalizedTagName);
|
|
3711
|
+
const extensionId = getExtensionIdFromConstraintId(constraint.constraintId);
|
|
3712
|
+
if (extensionId !== null && tagRegistration?.extensionId === extensionId && tagRegistration.registration.constraintName === registration.constraintName && !candidateTypes.some(
|
|
3713
|
+
(candidateType) => tagRegistration.registration.isApplicableToType?.(candidateType) !== false
|
|
3714
|
+
)) {
|
|
3715
|
+
addTypeMismatch(
|
|
3716
|
+
ctx,
|
|
3717
|
+
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
3718
|
+
constraint.provenance
|
|
3719
|
+
);
|
|
3720
|
+
return;
|
|
3721
|
+
}
|
|
3722
|
+
}
|
|
3723
|
+
if (registration.applicableTypes === null) {
|
|
3724
|
+
if (!candidateTypes.some((candidateType) => registration.isApplicableToType?.(candidateType) !== false)) {
|
|
3725
|
+
addTypeMismatch(
|
|
3726
|
+
ctx,
|
|
3727
|
+
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
3728
|
+
constraint.provenance
|
|
3729
|
+
);
|
|
3730
|
+
}
|
|
3731
|
+
return;
|
|
3732
|
+
}
|
|
3733
|
+
const applicableTypes = registration.applicableTypes;
|
|
3734
|
+
const matchesApplicableType = candidateTypes.some(
|
|
3735
|
+
(candidateType) => applicableTypes.includes(candidateType.kind) && registration.isApplicableToType?.(candidateType) !== false
|
|
3736
|
+
);
|
|
3737
|
+
if (!matchesApplicableType) {
|
|
3738
|
+
addTypeMismatch(
|
|
3739
|
+
ctx,
|
|
3740
|
+
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
3741
|
+
constraint.provenance
|
|
3742
|
+
);
|
|
3743
|
+
}
|
|
3744
|
+
}
|
|
3745
|
+
function validateFieldNode(ctx, field) {
|
|
3746
|
+
validateConstraints(ctx, field.name, field.type, [
|
|
3747
|
+
...collectReferencedTypeConstraints(ctx, field.type),
|
|
3748
|
+
...field.constraints
|
|
3749
|
+
]);
|
|
3750
|
+
if (field.type.kind === "object") {
|
|
3751
|
+
for (const prop of field.type.properties) {
|
|
3752
|
+
validateObjectProperty(ctx, field.name, prop);
|
|
3753
|
+
}
|
|
3754
|
+
}
|
|
3755
|
+
}
|
|
3756
|
+
function validateObjectProperty(ctx, parentName, prop) {
|
|
3757
|
+
const qualifiedName = `${parentName}.${prop.name}`;
|
|
3758
|
+
validateConstraints(ctx, qualifiedName, prop.type, [
|
|
3759
|
+
...collectReferencedTypeConstraints(ctx, prop.type),
|
|
3760
|
+
...prop.constraints
|
|
3761
|
+
]);
|
|
3762
|
+
if (prop.type.kind === "object") {
|
|
3763
|
+
for (const nestedProp of prop.type.properties) {
|
|
3764
|
+
validateObjectProperty(ctx, qualifiedName, nestedProp);
|
|
3765
|
+
}
|
|
3766
|
+
}
|
|
3767
|
+
}
|
|
3768
|
+
function validateConstraints(ctx, name, type, constraints) {
|
|
3769
|
+
checkNumericContradictions(ctx, name, constraints);
|
|
3770
|
+
checkLengthContradictions(ctx, name, constraints);
|
|
3771
|
+
checkAllowedMembersContradiction(ctx, name, constraints);
|
|
3772
|
+
checkConstContradictions(ctx, name, constraints);
|
|
3773
|
+
checkConstraintBroadening(ctx, name, constraints);
|
|
3774
|
+
checkCustomConstraintSemantics(ctx, name, constraints);
|
|
3775
|
+
checkTypeApplicability(ctx, name, type, constraints);
|
|
3776
|
+
}
|
|
3777
|
+
function validateElement(ctx, element) {
|
|
3778
|
+
switch (element.kind) {
|
|
3779
|
+
case "field":
|
|
3780
|
+
validateFieldNode(ctx, element);
|
|
3781
|
+
break;
|
|
3782
|
+
case "group":
|
|
3783
|
+
for (const child of element.elements) {
|
|
3784
|
+
validateElement(ctx, child);
|
|
3785
|
+
}
|
|
3786
|
+
break;
|
|
3787
|
+
case "conditional":
|
|
3788
|
+
for (const child of element.elements) {
|
|
3789
|
+
validateElement(ctx, child);
|
|
3790
|
+
}
|
|
3791
|
+
break;
|
|
3792
|
+
default: {
|
|
3793
|
+
const _exhaustive = element;
|
|
3794
|
+
throw new Error(`Unhandled element kind: ${_exhaustive.kind}`);
|
|
3795
|
+
}
|
|
3796
|
+
}
|
|
3797
|
+
}
|
|
3798
|
+
function validateIR(ir, options) {
|
|
3799
|
+
const ctx = {
|
|
3800
|
+
diagnostics: [],
|
|
3801
|
+
extensionRegistry: options?.extensionRegistry,
|
|
3802
|
+
typeRegistry: ir.typeRegistry
|
|
3803
|
+
};
|
|
3804
|
+
for (const element of ir.elements) {
|
|
3805
|
+
validateElement(ctx, element);
|
|
3806
|
+
}
|
|
3807
|
+
return {
|
|
3808
|
+
diagnostics: ctx.diagnostics,
|
|
3809
|
+
valid: ctx.diagnostics.every((d) => d.severity !== "error")
|
|
3810
|
+
};
|
|
3811
|
+
}
|
|
3812
|
+
var import_core4;
|
|
3813
|
+
var init_constraint_validator = __esm({
|
|
3814
|
+
"src/validate/constraint-validator.ts"() {
|
|
3815
|
+
"use strict";
|
|
3816
|
+
import_core4 = require("@formspec/core");
|
|
3817
|
+
}
|
|
3818
|
+
});
|
|
3819
|
+
|
|
3820
|
+
// src/validate/index.ts
|
|
3821
|
+
var init_validate = __esm({
|
|
3822
|
+
"src/validate/index.ts"() {
|
|
3823
|
+
"use strict";
|
|
3824
|
+
init_constraint_validator();
|
|
3825
|
+
}
|
|
3826
|
+
});
|
|
3827
|
+
|
|
2899
3828
|
// src/generators/class-schema.ts
|
|
2900
3829
|
function generateClassSchemas(analysis, source, options) {
|
|
2901
3830
|
const ir = canonicalizeTSDoc(analysis, source);
|
|
3831
|
+
const validationResult = validateIR(ir, {
|
|
3832
|
+
...options?.extensionRegistry !== void 0 && {
|
|
3833
|
+
extensionRegistry: options.extensionRegistry
|
|
3834
|
+
},
|
|
3835
|
+
...options?.vendorPrefix !== void 0 && { vendorPrefix: options.vendorPrefix }
|
|
3836
|
+
});
|
|
3837
|
+
if (!validationResult.valid) {
|
|
3838
|
+
throw new Error(formatValidationError(validationResult.diagnostics));
|
|
3839
|
+
}
|
|
2902
3840
|
return {
|
|
2903
3841
|
jsonSchema: generateJsonSchemaFromIR(ir, options),
|
|
2904
3842
|
uiSchema: generateUiSchemaFromIR(ir)
|
|
2905
3843
|
};
|
|
2906
3844
|
}
|
|
3845
|
+
function formatValidationError(diagnostics) {
|
|
3846
|
+
const lines = diagnostics.map((diagnostic) => {
|
|
3847
|
+
const primary = formatLocation(diagnostic.primaryLocation);
|
|
3848
|
+
const related = diagnostic.relatedLocations.length > 0 ? ` [related: ${diagnostic.relatedLocations.map(formatLocation).join(", ")}]` : "";
|
|
3849
|
+
return `${diagnostic.code}: ${diagnostic.message} (${primary})${related}`;
|
|
3850
|
+
});
|
|
3851
|
+
return `FormSpec validation failed:
|
|
3852
|
+
${lines.map((line) => `- ${line}`).join("\n")}`;
|
|
3853
|
+
}
|
|
3854
|
+
function formatLocation(location) {
|
|
3855
|
+
return `${location.file}:${String(location.line)}:${String(location.column)}`;
|
|
3856
|
+
}
|
|
2907
3857
|
function generateSchemasFromClass(options) {
|
|
2908
3858
|
const ctx = createProgramContext(options.filePath);
|
|
2909
3859
|
const classDecl = findClassByName(ctx.sourceFile, options.className);
|
|
@@ -2926,44 +3876,12 @@ function generateSchemasFromClass(options) {
|
|
|
2926
3876
|
);
|
|
2927
3877
|
}
|
|
2928
3878
|
function generateSchemas(options) {
|
|
2929
|
-
const
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
const analysis = analyzeClassToIR(
|
|
2934
|
-
classDecl,
|
|
2935
|
-
ctx.checker,
|
|
2936
|
-
options.filePath,
|
|
2937
|
-
options.extensionRegistry
|
|
2938
|
-
);
|
|
2939
|
-
return generateClassSchemas(analysis, source, options);
|
|
2940
|
-
}
|
|
2941
|
-
const interfaceDecl = findInterfaceByName(ctx.sourceFile, options.typeName);
|
|
2942
|
-
if (interfaceDecl) {
|
|
2943
|
-
const analysis = analyzeInterfaceToIR(
|
|
2944
|
-
interfaceDecl,
|
|
2945
|
-
ctx.checker,
|
|
2946
|
-
options.filePath,
|
|
2947
|
-
options.extensionRegistry
|
|
2948
|
-
);
|
|
2949
|
-
return generateClassSchemas(analysis, source, options);
|
|
2950
|
-
}
|
|
2951
|
-
const typeAlias = findTypeAliasByName(ctx.sourceFile, options.typeName);
|
|
2952
|
-
if (typeAlias) {
|
|
2953
|
-
const result = analyzeTypeAliasToIR(
|
|
2954
|
-
typeAlias,
|
|
2955
|
-
ctx.checker,
|
|
2956
|
-
options.filePath,
|
|
2957
|
-
options.extensionRegistry
|
|
2958
|
-
);
|
|
2959
|
-
if (result.ok) {
|
|
2960
|
-
return generateClassSchemas(result.analysis, source, options);
|
|
2961
|
-
}
|
|
2962
|
-
throw new Error(result.error);
|
|
2963
|
-
}
|
|
2964
|
-
throw new Error(
|
|
2965
|
-
`Type "${options.typeName}" not found as a class, interface, or type alias in ${options.filePath}`
|
|
3879
|
+
const analysis = analyzeNamedTypeToIR(
|
|
3880
|
+
options.filePath,
|
|
3881
|
+
options.typeName,
|
|
3882
|
+
options.extensionRegistry
|
|
2966
3883
|
);
|
|
3884
|
+
return generateClassSchemas(analysis, { file: options.filePath }, options);
|
|
2967
3885
|
}
|
|
2968
3886
|
var init_class_schema = __esm({
|
|
2969
3887
|
"src/generators/class-schema.ts"() {
|
|
@@ -2973,13 +3891,14 @@ var init_class_schema = __esm({
|
|
|
2973
3891
|
init_canonicalize();
|
|
2974
3892
|
init_ir_generator();
|
|
2975
3893
|
init_ir_generator2();
|
|
3894
|
+
init_validate();
|
|
2976
3895
|
}
|
|
2977
3896
|
});
|
|
2978
3897
|
|
|
2979
3898
|
// src/generators/mixed-authoring.ts
|
|
2980
3899
|
function buildMixedAuthoringSchemas(options) {
|
|
2981
3900
|
const { filePath, typeName, overlays, ...schemaOptions } = options;
|
|
2982
|
-
const analysis =
|
|
3901
|
+
const analysis = analyzeNamedTypeToIR(filePath, typeName, schemaOptions.extensionRegistry);
|
|
2983
3902
|
const composedAnalysis = composeAnalysisWithOverlays(analysis, overlays);
|
|
2984
3903
|
const ir = canonicalizeTSDoc(composedAnalysis, { file: filePath });
|
|
2985
3904
|
return {
|
|
@@ -2987,29 +3906,6 @@ function buildMixedAuthoringSchemas(options) {
|
|
|
2987
3906
|
uiSchema: generateUiSchemaFromIR(ir)
|
|
2988
3907
|
};
|
|
2989
3908
|
}
|
|
2990
|
-
function analyzeNamedType(filePath, typeName, extensionRegistry) {
|
|
2991
|
-
const ctx = createProgramContext(filePath);
|
|
2992
|
-
const source = { file: filePath };
|
|
2993
|
-
const classDecl = findClassByName(ctx.sourceFile, typeName);
|
|
2994
|
-
if (classDecl !== null) {
|
|
2995
|
-
return analyzeClassToIR(classDecl, ctx.checker, source.file, extensionRegistry);
|
|
2996
|
-
}
|
|
2997
|
-
const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
|
|
2998
|
-
if (interfaceDecl !== null) {
|
|
2999
|
-
return analyzeInterfaceToIR(interfaceDecl, ctx.checker, source.file, extensionRegistry);
|
|
3000
|
-
}
|
|
3001
|
-
const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
|
|
3002
|
-
if (typeAlias !== null) {
|
|
3003
|
-
const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, source.file, extensionRegistry);
|
|
3004
|
-
if (result.ok) {
|
|
3005
|
-
return result.analysis;
|
|
3006
|
-
}
|
|
3007
|
-
throw new Error(result.error);
|
|
3008
|
-
}
|
|
3009
|
-
throw new Error(
|
|
3010
|
-
`Type "${typeName}" not found as a class, interface, or type alias in ${filePath}`
|
|
3011
|
-
);
|
|
3012
|
-
}
|
|
3013
3909
|
function composeAnalysisWithOverlays(analysis, overlays) {
|
|
3014
3910
|
const overlayIR = canonicalizeChainDSL(overlays);
|
|
3015
3911
|
const overlayFields = collectOverlayFields(overlayIR.elements);
|
|
@@ -3181,7 +4077,6 @@ var init_mixed_authoring = __esm({
|
|
|
3181
4077
|
init_ir_generator2();
|
|
3182
4078
|
init_canonicalize();
|
|
3183
4079
|
init_program();
|
|
3184
|
-
init_class_analyzer();
|
|
3185
4080
|
}
|
|
3186
4081
|
});
|
|
3187
4082
|
|