@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.js
CHANGED
|
@@ -25,6 +25,7 @@ function canonicalizeChainDSL(form) {
|
|
|
25
25
|
kind: "form-ir",
|
|
26
26
|
irVersion: IR_VERSION,
|
|
27
27
|
elements: canonicalizeElements(form.elements),
|
|
28
|
+
rootAnnotations: [],
|
|
28
29
|
typeRegistry: {},
|
|
29
30
|
provenance: CHAIN_DSL_PROVENANCE
|
|
30
31
|
};
|
|
@@ -342,6 +343,7 @@ function canonicalizeTSDoc(analysis, source) {
|
|
|
342
343
|
irVersion: IR_VERSION2,
|
|
343
344
|
elements,
|
|
344
345
|
typeRegistry: analysis.typeRegistry,
|
|
346
|
+
...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { rootAnnotations: analysis.annotations },
|
|
345
347
|
...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { annotations: analysis.annotations },
|
|
346
348
|
provenance
|
|
347
349
|
};
|
|
@@ -433,6 +435,9 @@ function generateJsonSchemaFromIR(ir, options) {
|
|
|
433
435
|
const ctx = makeContext(options);
|
|
434
436
|
for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
|
|
435
437
|
ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
|
|
438
|
+
if (typeDef.constraints && typeDef.constraints.length > 0) {
|
|
439
|
+
applyConstraints(ctx.defs[name], typeDef.constraints, ctx);
|
|
440
|
+
}
|
|
436
441
|
if (typeDef.annotations && typeDef.annotations.length > 0) {
|
|
437
442
|
applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
|
|
438
443
|
}
|
|
@@ -601,7 +606,9 @@ function generateTypeNode(type, ctx) {
|
|
|
601
606
|
}
|
|
602
607
|
}
|
|
603
608
|
function generatePrimitiveType(type) {
|
|
604
|
-
return {
|
|
609
|
+
return {
|
|
610
|
+
type: type.primitiveKind === "integer" || type.primitiveKind === "bigint" ? "integer" : type.primitiveKind
|
|
611
|
+
};
|
|
605
612
|
}
|
|
606
613
|
function generateEnumType(type) {
|
|
607
614
|
const hasDisplayNames = type.members.some((m) => m.displayName !== void 0);
|
|
@@ -774,7 +781,7 @@ function applyAnnotations(schema, annotations, ctx) {
|
|
|
774
781
|
case "deprecated":
|
|
775
782
|
schema.deprecated = true;
|
|
776
783
|
if (annotation.message !== void 0 && annotation.message !== "") {
|
|
777
|
-
schema[
|
|
784
|
+
schema[`${ctx.vendorPrefix}-deprecation-description`] = annotation.message;
|
|
778
785
|
}
|
|
779
786
|
break;
|
|
780
787
|
case "placeholder":
|
|
@@ -1282,83 +1289,6 @@ var init_schema2 = __esm({
|
|
|
1282
1289
|
}
|
|
1283
1290
|
});
|
|
1284
1291
|
|
|
1285
|
-
// src/analyzer/program.ts
|
|
1286
|
-
import * as ts from "typescript";
|
|
1287
|
-
import * as path from "path";
|
|
1288
|
-
function createProgramContext(filePath) {
|
|
1289
|
-
const absolutePath = path.resolve(filePath);
|
|
1290
|
-
const fileDir = path.dirname(absolutePath);
|
|
1291
|
-
const configPath = ts.findConfigFile(fileDir, ts.sys.fileExists.bind(ts.sys), "tsconfig.json");
|
|
1292
|
-
let compilerOptions;
|
|
1293
|
-
let fileNames;
|
|
1294
|
-
if (configPath) {
|
|
1295
|
-
const configFile = ts.readConfigFile(configPath, ts.sys.readFile.bind(ts.sys));
|
|
1296
|
-
if (configFile.error) {
|
|
1297
|
-
throw new Error(
|
|
1298
|
-
`Error reading tsconfig.json: ${ts.flattenDiagnosticMessageText(configFile.error.messageText, "\n")}`
|
|
1299
|
-
);
|
|
1300
|
-
}
|
|
1301
|
-
const parsed = ts.parseJsonConfigFileContent(
|
|
1302
|
-
configFile.config,
|
|
1303
|
-
ts.sys,
|
|
1304
|
-
path.dirname(configPath)
|
|
1305
|
-
);
|
|
1306
|
-
if (parsed.errors.length > 0) {
|
|
1307
|
-
const errorMessages = parsed.errors.map((e) => ts.flattenDiagnosticMessageText(e.messageText, "\n")).join("\n");
|
|
1308
|
-
throw new Error(`Error parsing tsconfig.json: ${errorMessages}`);
|
|
1309
|
-
}
|
|
1310
|
-
compilerOptions = parsed.options;
|
|
1311
|
-
fileNames = parsed.fileNames.includes(absolutePath) ? parsed.fileNames : [...parsed.fileNames, absolutePath];
|
|
1312
|
-
} else {
|
|
1313
|
-
compilerOptions = {
|
|
1314
|
-
target: ts.ScriptTarget.ES2022,
|
|
1315
|
-
module: ts.ModuleKind.NodeNext,
|
|
1316
|
-
moduleResolution: ts.ModuleResolutionKind.NodeNext,
|
|
1317
|
-
strict: true,
|
|
1318
|
-
skipLibCheck: true,
|
|
1319
|
-
declaration: true
|
|
1320
|
-
};
|
|
1321
|
-
fileNames = [absolutePath];
|
|
1322
|
-
}
|
|
1323
|
-
const program = ts.createProgram(fileNames, compilerOptions);
|
|
1324
|
-
const sourceFile = program.getSourceFile(absolutePath);
|
|
1325
|
-
if (!sourceFile) {
|
|
1326
|
-
throw new Error(`Could not find source file: ${absolutePath}`);
|
|
1327
|
-
}
|
|
1328
|
-
return {
|
|
1329
|
-
program,
|
|
1330
|
-
checker: program.getTypeChecker(),
|
|
1331
|
-
sourceFile
|
|
1332
|
-
};
|
|
1333
|
-
}
|
|
1334
|
-
function findNodeByName(sourceFile, name, predicate, getName) {
|
|
1335
|
-
let result = null;
|
|
1336
|
-
function visit(node) {
|
|
1337
|
-
if (result) return;
|
|
1338
|
-
if (predicate(node) && getName(node) === name) {
|
|
1339
|
-
result = node;
|
|
1340
|
-
return;
|
|
1341
|
-
}
|
|
1342
|
-
ts.forEachChild(node, visit);
|
|
1343
|
-
}
|
|
1344
|
-
visit(sourceFile);
|
|
1345
|
-
return result;
|
|
1346
|
-
}
|
|
1347
|
-
function findClassByName(sourceFile, className) {
|
|
1348
|
-
return findNodeByName(sourceFile, className, ts.isClassDeclaration, (n) => n.name?.text);
|
|
1349
|
-
}
|
|
1350
|
-
function findInterfaceByName(sourceFile, interfaceName) {
|
|
1351
|
-
return findNodeByName(sourceFile, interfaceName, ts.isInterfaceDeclaration, (n) => n.name.text);
|
|
1352
|
-
}
|
|
1353
|
-
function findTypeAliasByName(sourceFile, aliasName) {
|
|
1354
|
-
return findNodeByName(sourceFile, aliasName, ts.isTypeAliasDeclaration, (n) => n.name.text);
|
|
1355
|
-
}
|
|
1356
|
-
var init_program = __esm({
|
|
1357
|
-
"src/analyzer/program.ts"() {
|
|
1358
|
-
"use strict";
|
|
1359
|
-
}
|
|
1360
|
-
});
|
|
1361
|
-
|
|
1362
1292
|
// src/analyzer/json-utils.ts
|
|
1363
1293
|
function tryParseJson(text) {
|
|
1364
1294
|
try {
|
|
@@ -1374,7 +1304,7 @@ var init_json_utils = __esm({
|
|
|
1374
1304
|
});
|
|
1375
1305
|
|
|
1376
1306
|
// src/analyzer/tsdoc-parser.ts
|
|
1377
|
-
import * as
|
|
1307
|
+
import * as ts from "typescript";
|
|
1378
1308
|
import {
|
|
1379
1309
|
TSDocParser,
|
|
1380
1310
|
TSDocConfiguration,
|
|
@@ -1446,10 +1376,10 @@ function parseTSDocTags(node, file = "", options) {
|
|
|
1446
1376
|
let placeholderProvenance;
|
|
1447
1377
|
const sourceFile = node.getSourceFile();
|
|
1448
1378
|
const sourceText = sourceFile.getFullText();
|
|
1449
|
-
const commentRanges =
|
|
1379
|
+
const commentRanges = ts.getLeadingCommentRanges(sourceText, node.getFullStart());
|
|
1450
1380
|
if (commentRanges) {
|
|
1451
1381
|
for (const range of commentRanges) {
|
|
1452
|
-
if (range.kind !==
|
|
1382
|
+
if (range.kind !== ts.SyntaxKind.MultiLineCommentTrivia) {
|
|
1453
1383
|
continue;
|
|
1454
1384
|
}
|
|
1455
1385
|
const commentText = sourceText.substring(range.pos, range.end);
|
|
@@ -1467,26 +1397,31 @@ function parseTSDocTags(node, file = "", options) {
|
|
|
1467
1397
|
const text2 = extractBlockText(block).trim();
|
|
1468
1398
|
if (text2 === "") continue;
|
|
1469
1399
|
const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
displayName
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1400
|
+
switch (tagName) {
|
|
1401
|
+
case "displayName":
|
|
1402
|
+
if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
|
|
1403
|
+
displayName = text2;
|
|
1404
|
+
displayNameProvenance = provenance2;
|
|
1405
|
+
}
|
|
1406
|
+
break;
|
|
1407
|
+
case "format":
|
|
1408
|
+
annotations.push({
|
|
1409
|
+
kind: "annotation",
|
|
1410
|
+
annotationKind: "format",
|
|
1411
|
+
value: text2,
|
|
1412
|
+
provenance: provenance2
|
|
1413
|
+
});
|
|
1414
|
+
break;
|
|
1415
|
+
case "description":
|
|
1484
1416
|
description = text2;
|
|
1485
1417
|
descriptionProvenance = provenance2;
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1418
|
+
break;
|
|
1419
|
+
case "placeholder":
|
|
1420
|
+
if (placeholder === void 0) {
|
|
1421
|
+
placeholder = text2;
|
|
1422
|
+
placeholderProvenance = provenance2;
|
|
1423
|
+
}
|
|
1424
|
+
break;
|
|
1490
1425
|
}
|
|
1491
1426
|
continue;
|
|
1492
1427
|
}
|
|
@@ -1516,6 +1451,13 @@ function parseTSDocTags(node, file = "", options) {
|
|
|
1516
1451
|
descriptionProvenance = provenanceForComment(range, sourceFile, file, "remarks");
|
|
1517
1452
|
}
|
|
1518
1453
|
}
|
|
1454
|
+
if (description === void 0) {
|
|
1455
|
+
const summary = extractPlainText(docComment.summarySection).trim();
|
|
1456
|
+
if (summary !== "") {
|
|
1457
|
+
description = summary;
|
|
1458
|
+
descriptionProvenance = provenanceForComment(range, sourceFile, file, "summary");
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1519
1461
|
}
|
|
1520
1462
|
}
|
|
1521
1463
|
if (displayName !== void 0 && displayNameProvenance !== void 0) {
|
|
@@ -1542,7 +1484,7 @@ function parseTSDocTags(node, file = "", options) {
|
|
|
1542
1484
|
provenance: placeholderProvenance
|
|
1543
1485
|
});
|
|
1544
1486
|
}
|
|
1545
|
-
const jsDocTagsAll =
|
|
1487
|
+
const jsDocTagsAll = ts.getJSDocTags(node);
|
|
1546
1488
|
for (const tag of jsDocTagsAll) {
|
|
1547
1489
|
const tagName = normalizeConstraintTagName(tag.tagName.text);
|
|
1548
1490
|
if (!TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
@@ -1565,7 +1507,7 @@ function parseTSDocTags(node, file = "", options) {
|
|
|
1565
1507
|
function extractDisplayNameMetadata(node) {
|
|
1566
1508
|
let displayName;
|
|
1567
1509
|
const memberDisplayNames = /* @__PURE__ */ new Map();
|
|
1568
|
-
for (const tag of
|
|
1510
|
+
for (const tag of ts.getJSDocTags(node)) {
|
|
1569
1511
|
const tagName = normalizeConstraintTagName(tag.tagName.text);
|
|
1570
1512
|
if (tagName !== "displayName") continue;
|
|
1571
1513
|
const commentText = getTagCommentText(tag);
|
|
@@ -1586,11 +1528,11 @@ function extractDisplayNameMetadata(node) {
|
|
|
1586
1528
|
}
|
|
1587
1529
|
function extractPathTarget(text) {
|
|
1588
1530
|
const trimmed = text.trimStart();
|
|
1589
|
-
const match = /^:([a-zA-Z_]\w*)
|
|
1590
|
-
if (!match?.[1]
|
|
1531
|
+
const match = /^:([a-zA-Z_]\w*)(?:\s+([\s\S]*))?$/.exec(trimmed);
|
|
1532
|
+
if (!match?.[1]) return null;
|
|
1591
1533
|
return {
|
|
1592
1534
|
path: { segments: [match[1]] },
|
|
1593
|
-
remainingText: match[2]
|
|
1535
|
+
remainingText: match[2] ?? ""
|
|
1594
1536
|
};
|
|
1595
1537
|
}
|
|
1596
1538
|
function extractBlockText(block) {
|
|
@@ -1853,7 +1795,7 @@ function getTagCommentText(tag) {
|
|
|
1853
1795
|
if (typeof tag.comment === "string") {
|
|
1854
1796
|
return tag.comment;
|
|
1855
1797
|
}
|
|
1856
|
-
return
|
|
1798
|
+
return ts.getTextOfJSDocComment(tag.comment);
|
|
1857
1799
|
}
|
|
1858
1800
|
var NUMERIC_CONSTRAINT_MAP, LENGTH_CONSTRAINT_MAP, TAGS_REQUIRING_RAW_TEXT, parserCache;
|
|
1859
1801
|
var init_tsdoc_parser = __esm({
|
|
@@ -1879,7 +1821,7 @@ var init_tsdoc_parser = __esm({
|
|
|
1879
1821
|
});
|
|
1880
1822
|
|
|
1881
1823
|
// src/analyzer/jsdoc-constraints.ts
|
|
1882
|
-
import * as
|
|
1824
|
+
import * as ts2 from "typescript";
|
|
1883
1825
|
function extractJSDocConstraintNodes(node, file = "", options) {
|
|
1884
1826
|
const result = parseTSDocTags(node, file, options);
|
|
1885
1827
|
return [...result.constraints];
|
|
@@ -1891,18 +1833,18 @@ function extractJSDocAnnotationNodes(node, file = "", options) {
|
|
|
1891
1833
|
function extractDefaultValueAnnotation(initializer, file = "") {
|
|
1892
1834
|
if (!initializer) return null;
|
|
1893
1835
|
let value;
|
|
1894
|
-
if (
|
|
1836
|
+
if (ts2.isStringLiteral(initializer)) {
|
|
1895
1837
|
value = initializer.text;
|
|
1896
|
-
} else if (
|
|
1838
|
+
} else if (ts2.isNumericLiteral(initializer)) {
|
|
1897
1839
|
value = Number(initializer.text);
|
|
1898
|
-
} else if (initializer.kind ===
|
|
1840
|
+
} else if (initializer.kind === ts2.SyntaxKind.TrueKeyword) {
|
|
1899
1841
|
value = true;
|
|
1900
|
-
} else if (initializer.kind ===
|
|
1842
|
+
} else if (initializer.kind === ts2.SyntaxKind.FalseKeyword) {
|
|
1901
1843
|
value = false;
|
|
1902
|
-
} else if (initializer.kind ===
|
|
1844
|
+
} else if (initializer.kind === ts2.SyntaxKind.NullKeyword) {
|
|
1903
1845
|
value = null;
|
|
1904
|
-
} else if (
|
|
1905
|
-
if (initializer.operator ===
|
|
1846
|
+
} else if (ts2.isPrefixUnaryExpression(initializer)) {
|
|
1847
|
+
if (initializer.operator === ts2.SyntaxKind.MinusToken && ts2.isNumericLiteral(initializer.operand)) {
|
|
1906
1848
|
value = -Number(initializer.operand.text);
|
|
1907
1849
|
}
|
|
1908
1850
|
}
|
|
@@ -1929,12 +1871,12 @@ var init_jsdoc_constraints = __esm({
|
|
|
1929
1871
|
});
|
|
1930
1872
|
|
|
1931
1873
|
// src/analyzer/class-analyzer.ts
|
|
1932
|
-
import * as
|
|
1874
|
+
import * as ts3 from "typescript";
|
|
1933
1875
|
function isObjectType(type) {
|
|
1934
|
-
return !!(type.flags &
|
|
1876
|
+
return !!(type.flags & ts3.TypeFlags.Object);
|
|
1935
1877
|
}
|
|
1936
1878
|
function isTypeReference(type) {
|
|
1937
|
-
return !!(type.flags &
|
|
1879
|
+
return !!(type.flags & ts3.TypeFlags.Object) && !!(type.objectFlags & ts3.ObjectFlags.Reference);
|
|
1938
1880
|
}
|
|
1939
1881
|
function makeParseOptions(extensionRegistry, fieldType) {
|
|
1940
1882
|
if (extensionRegistry === void 0 && fieldType === void 0) {
|
|
@@ -1959,7 +1901,7 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
|
|
|
1959
1901
|
const instanceMethods = [];
|
|
1960
1902
|
const staticMethods = [];
|
|
1961
1903
|
for (const member of classDecl.members) {
|
|
1962
|
-
if (
|
|
1904
|
+
if (ts3.isPropertyDeclaration(member)) {
|
|
1963
1905
|
const fieldNode = analyzeFieldToIR(
|
|
1964
1906
|
member,
|
|
1965
1907
|
checker,
|
|
@@ -1972,10 +1914,10 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
|
|
|
1972
1914
|
fields.push(fieldNode);
|
|
1973
1915
|
fieldLayouts.push({});
|
|
1974
1916
|
}
|
|
1975
|
-
} else if (
|
|
1917
|
+
} else if (ts3.isMethodDeclaration(member)) {
|
|
1976
1918
|
const methodInfo = analyzeMethod(member, checker);
|
|
1977
1919
|
if (methodInfo) {
|
|
1978
|
-
const isStatic = member.modifiers?.some((m) => m.kind ===
|
|
1920
|
+
const isStatic = member.modifiers?.some((m) => m.kind === ts3.SyntaxKind.StaticKeyword);
|
|
1979
1921
|
if (isStatic) {
|
|
1980
1922
|
staticMethods.push(methodInfo);
|
|
1981
1923
|
} else {
|
|
@@ -2005,7 +1947,7 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
|
|
|
2005
1947
|
);
|
|
2006
1948
|
const visiting = /* @__PURE__ */ new Set();
|
|
2007
1949
|
for (const member of interfaceDecl.members) {
|
|
2008
|
-
if (
|
|
1950
|
+
if (ts3.isPropertySignature(member)) {
|
|
2009
1951
|
const fieldNode = analyzeInterfacePropertyToIR(
|
|
2010
1952
|
member,
|
|
2011
1953
|
checker,
|
|
@@ -2031,10 +1973,10 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
|
|
|
2031
1973
|
};
|
|
2032
1974
|
}
|
|
2033
1975
|
function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry) {
|
|
2034
|
-
if (!
|
|
1976
|
+
if (!ts3.isTypeLiteralNode(typeAlias.type)) {
|
|
2035
1977
|
const sourceFile = typeAlias.getSourceFile();
|
|
2036
1978
|
const { line } = sourceFile.getLineAndCharacterOfPosition(typeAlias.getStart());
|
|
2037
|
-
const kindDesc =
|
|
1979
|
+
const kindDesc = ts3.SyntaxKind[typeAlias.type.kind] ?? "unknown";
|
|
2038
1980
|
return {
|
|
2039
1981
|
ok: false,
|
|
2040
1982
|
error: `Type alias "${typeAlias.name.text}" at line ${String(line + 1)} is not an object type literal (found ${kindDesc})`
|
|
@@ -2050,7 +1992,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
|
|
|
2050
1992
|
);
|
|
2051
1993
|
const visiting = /* @__PURE__ */ new Set();
|
|
2052
1994
|
for (const member of typeAlias.type.members) {
|
|
2053
|
-
if (
|
|
1995
|
+
if (ts3.isPropertySignature(member)) {
|
|
2054
1996
|
const fieldNode = analyzeInterfacePropertyToIR(
|
|
2055
1997
|
member,
|
|
2056
1998
|
checker,
|
|
@@ -2078,7 +2020,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
|
|
|
2078
2020
|
};
|
|
2079
2021
|
}
|
|
2080
2022
|
function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2081
|
-
if (!
|
|
2023
|
+
if (!ts3.isIdentifier(prop.name)) {
|
|
2082
2024
|
return null;
|
|
2083
2025
|
}
|
|
2084
2026
|
const name = prop.name.text;
|
|
@@ -2095,12 +2037,14 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extension
|
|
|
2095
2037
|
extensionRegistry
|
|
2096
2038
|
);
|
|
2097
2039
|
const constraints = [];
|
|
2098
|
-
if (prop.type) {
|
|
2040
|
+
if (prop.type && !shouldEmitPrimitiveAliasDefinition(prop.type, checker)) {
|
|
2099
2041
|
constraints.push(
|
|
2100
2042
|
...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
|
|
2101
2043
|
);
|
|
2102
2044
|
}
|
|
2103
|
-
constraints.push(
|
|
2045
|
+
constraints.push(
|
|
2046
|
+
...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
2047
|
+
);
|
|
2104
2048
|
let annotations = [];
|
|
2105
2049
|
annotations.push(
|
|
2106
2050
|
...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
@@ -2121,7 +2065,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extension
|
|
|
2121
2065
|
};
|
|
2122
2066
|
}
|
|
2123
2067
|
function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2124
|
-
if (!
|
|
2068
|
+
if (!ts3.isIdentifier(prop.name)) {
|
|
2125
2069
|
return null;
|
|
2126
2070
|
}
|
|
2127
2071
|
const name = prop.name.text;
|
|
@@ -2138,12 +2082,14 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
|
|
|
2138
2082
|
extensionRegistry
|
|
2139
2083
|
);
|
|
2140
2084
|
const constraints = [];
|
|
2141
|
-
if (prop.type) {
|
|
2085
|
+
if (prop.type && !shouldEmitPrimitiveAliasDefinition(prop.type, checker)) {
|
|
2142
2086
|
constraints.push(
|
|
2143
2087
|
...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
|
|
2144
2088
|
);
|
|
2145
2089
|
}
|
|
2146
|
-
constraints.push(
|
|
2090
|
+
constraints.push(
|
|
2091
|
+
...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
2092
|
+
);
|
|
2147
2093
|
let annotations = [];
|
|
2148
2094
|
annotations.push(
|
|
2149
2095
|
...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
@@ -2232,7 +2178,7 @@ function resolveRegisteredCustomType(sourceNode, extensionRegistry, checker) {
|
|
|
2232
2178
|
return resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker);
|
|
2233
2179
|
}
|
|
2234
2180
|
function resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker) {
|
|
2235
|
-
if (
|
|
2181
|
+
if (ts3.isParenthesizedTypeNode(typeNode)) {
|
|
2236
2182
|
return resolveRegisteredCustomTypeFromTypeNode(typeNode.type, extensionRegistry, checker);
|
|
2237
2183
|
}
|
|
2238
2184
|
const typeName = getTypeNodeRegistrationName(typeNode);
|
|
@@ -2247,8 +2193,8 @@ function resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, ch
|
|
|
2247
2193
|
payload: null
|
|
2248
2194
|
};
|
|
2249
2195
|
}
|
|
2250
|
-
if (
|
|
2251
|
-
const aliasDecl = checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(
|
|
2196
|
+
if (ts3.isTypeReferenceNode(typeNode) && ts3.isIdentifier(typeNode.typeName)) {
|
|
2197
|
+
const aliasDecl = checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
2252
2198
|
if (aliasDecl !== void 0) {
|
|
2253
2199
|
return resolveRegisteredCustomTypeFromTypeNode(aliasDecl.type, extensionRegistry, checker);
|
|
2254
2200
|
}
|
|
@@ -2256,22 +2202,22 @@ function resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, ch
|
|
|
2256
2202
|
return null;
|
|
2257
2203
|
}
|
|
2258
2204
|
function extractTypeNodeFromSource(sourceNode) {
|
|
2259
|
-
if (
|
|
2205
|
+
if (ts3.isPropertyDeclaration(sourceNode) || ts3.isPropertySignature(sourceNode) || ts3.isParameter(sourceNode) || ts3.isTypeAliasDeclaration(sourceNode)) {
|
|
2260
2206
|
return sourceNode.type;
|
|
2261
2207
|
}
|
|
2262
|
-
if (
|
|
2208
|
+
if (ts3.isTypeNode(sourceNode)) {
|
|
2263
2209
|
return sourceNode;
|
|
2264
2210
|
}
|
|
2265
2211
|
return void 0;
|
|
2266
2212
|
}
|
|
2267
2213
|
function getTypeNodeRegistrationName(typeNode) {
|
|
2268
|
-
if (
|
|
2269
|
-
return
|
|
2214
|
+
if (ts3.isTypeReferenceNode(typeNode)) {
|
|
2215
|
+
return ts3.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : typeNode.typeName.right.text;
|
|
2270
2216
|
}
|
|
2271
|
-
if (
|
|
2217
|
+
if (ts3.isParenthesizedTypeNode(typeNode)) {
|
|
2272
2218
|
return getTypeNodeRegistrationName(typeNode.type);
|
|
2273
2219
|
}
|
|
2274
|
-
if (typeNode.kind ===
|
|
2220
|
+
if (typeNode.kind === ts3.SyntaxKind.BigIntKeyword || typeNode.kind === ts3.SyntaxKind.StringKeyword || typeNode.kind === ts3.SyntaxKind.NumberKeyword || typeNode.kind === ts3.SyntaxKind.BooleanKeyword) {
|
|
2275
2221
|
return typeNode.getText();
|
|
2276
2222
|
}
|
|
2277
2223
|
return null;
|
|
@@ -2281,19 +2227,34 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
|
|
|
2281
2227
|
if (customType) {
|
|
2282
2228
|
return customType;
|
|
2283
2229
|
}
|
|
2284
|
-
|
|
2230
|
+
const primitiveAlias = tryResolveNamedPrimitiveAlias(
|
|
2231
|
+
type,
|
|
2232
|
+
checker,
|
|
2233
|
+
file,
|
|
2234
|
+
typeRegistry,
|
|
2235
|
+
visiting,
|
|
2236
|
+
sourceNode,
|
|
2237
|
+
extensionRegistry
|
|
2238
|
+
);
|
|
2239
|
+
if (primitiveAlias) {
|
|
2240
|
+
return primitiveAlias;
|
|
2241
|
+
}
|
|
2242
|
+
if (type.flags & ts3.TypeFlags.String) {
|
|
2285
2243
|
return { kind: "primitive", primitiveKind: "string" };
|
|
2286
2244
|
}
|
|
2287
|
-
if (type.flags &
|
|
2245
|
+
if (type.flags & ts3.TypeFlags.Number) {
|
|
2288
2246
|
return { kind: "primitive", primitiveKind: "number" };
|
|
2289
2247
|
}
|
|
2290
|
-
if (type.flags &
|
|
2248
|
+
if (type.flags & (ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral)) {
|
|
2249
|
+
return { kind: "primitive", primitiveKind: "bigint" };
|
|
2250
|
+
}
|
|
2251
|
+
if (type.flags & ts3.TypeFlags.Boolean) {
|
|
2291
2252
|
return { kind: "primitive", primitiveKind: "boolean" };
|
|
2292
2253
|
}
|
|
2293
|
-
if (type.flags &
|
|
2254
|
+
if (type.flags & ts3.TypeFlags.Null) {
|
|
2294
2255
|
return { kind: "primitive", primitiveKind: "null" };
|
|
2295
2256
|
}
|
|
2296
|
-
if (type.flags &
|
|
2257
|
+
if (type.flags & ts3.TypeFlags.Undefined) {
|
|
2297
2258
|
return { kind: "primitive", primitiveKind: "null" };
|
|
2298
2259
|
}
|
|
2299
2260
|
if (type.isStringLiteral()) {
|
|
@@ -2335,6 +2296,75 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
|
|
|
2335
2296
|
}
|
|
2336
2297
|
return { kind: "primitive", primitiveKind: "string" };
|
|
2337
2298
|
}
|
|
2299
|
+
function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
2300
|
+
if (!(type.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null))) {
|
|
2301
|
+
return null;
|
|
2302
|
+
}
|
|
2303
|
+
const aliasDecl = type.aliasSymbol?.declarations?.find(ts3.isTypeAliasDeclaration) ?? getReferencedTypeAliasDeclaration(sourceNode, checker);
|
|
2304
|
+
if (!aliasDecl) {
|
|
2305
|
+
return null;
|
|
2306
|
+
}
|
|
2307
|
+
const aliasName = aliasDecl.name.text;
|
|
2308
|
+
if (!typeRegistry[aliasName]) {
|
|
2309
|
+
const aliasType = checker.getTypeFromTypeNode(aliasDecl.type);
|
|
2310
|
+
const constraints = [
|
|
2311
|
+
...extractJSDocConstraintNodes(aliasDecl, file, makeParseOptions(extensionRegistry)),
|
|
2312
|
+
...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, extensionRegistry)
|
|
2313
|
+
];
|
|
2314
|
+
const annotations = extractJSDocAnnotationNodes(
|
|
2315
|
+
aliasDecl,
|
|
2316
|
+
file,
|
|
2317
|
+
makeParseOptions(extensionRegistry)
|
|
2318
|
+
);
|
|
2319
|
+
typeRegistry[aliasName] = {
|
|
2320
|
+
name: aliasName,
|
|
2321
|
+
type: resolveAliasedPrimitiveTarget(
|
|
2322
|
+
aliasType,
|
|
2323
|
+
checker,
|
|
2324
|
+
file,
|
|
2325
|
+
typeRegistry,
|
|
2326
|
+
visiting,
|
|
2327
|
+
extensionRegistry
|
|
2328
|
+
),
|
|
2329
|
+
...constraints.length > 0 && { constraints },
|
|
2330
|
+
...annotations.length > 0 && { annotations },
|
|
2331
|
+
provenance: provenanceForDeclaration(aliasDecl, file)
|
|
2332
|
+
};
|
|
2333
|
+
}
|
|
2334
|
+
return { kind: "reference", name: aliasName, typeArguments: [] };
|
|
2335
|
+
}
|
|
2336
|
+
function getReferencedTypeAliasDeclaration(sourceNode, checker) {
|
|
2337
|
+
const typeNode = sourceNode && (ts3.isPropertyDeclaration(sourceNode) || ts3.isPropertySignature(sourceNode) || ts3.isParameter(sourceNode)) ? sourceNode.type : void 0;
|
|
2338
|
+
if (!typeNode || !ts3.isTypeReferenceNode(typeNode)) {
|
|
2339
|
+
return void 0;
|
|
2340
|
+
}
|
|
2341
|
+
return checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
2342
|
+
}
|
|
2343
|
+
function shouldEmitPrimitiveAliasDefinition(typeNode, checker) {
|
|
2344
|
+
if (!ts3.isTypeReferenceNode(typeNode)) {
|
|
2345
|
+
return false;
|
|
2346
|
+
}
|
|
2347
|
+
const aliasDecl = checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
2348
|
+
if (!aliasDecl) {
|
|
2349
|
+
return false;
|
|
2350
|
+
}
|
|
2351
|
+
const resolved = checker.getTypeFromTypeNode(aliasDecl.type);
|
|
2352
|
+
return !!(resolved.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null));
|
|
2353
|
+
}
|
|
2354
|
+
function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2355
|
+
const nestedAliasDecl = type.aliasSymbol?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
2356
|
+
if (nestedAliasDecl !== void 0) {
|
|
2357
|
+
return resolveAliasedPrimitiveTarget(
|
|
2358
|
+
checker.getTypeFromTypeNode(nestedAliasDecl.type),
|
|
2359
|
+
checker,
|
|
2360
|
+
file,
|
|
2361
|
+
typeRegistry,
|
|
2362
|
+
visiting,
|
|
2363
|
+
extensionRegistry
|
|
2364
|
+
);
|
|
2365
|
+
}
|
|
2366
|
+
return resolveTypeNode(type, checker, file, typeRegistry, visiting, void 0, extensionRegistry);
|
|
2367
|
+
}
|
|
2338
2368
|
function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
2339
2369
|
const typeName = getNamedTypeName(type);
|
|
2340
2370
|
const namedDecl = getNamedTypeDeclaration(type);
|
|
@@ -2347,13 +2377,13 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
2347
2377
|
(memberTypeNode) => !isNullishTypeNode(resolveAliasedTypeNode(memberTypeNode, checker))
|
|
2348
2378
|
);
|
|
2349
2379
|
const nonNullTypes = allTypes.filter(
|
|
2350
|
-
(memberType) => !(memberType.flags & (
|
|
2380
|
+
(memberType) => !(memberType.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined))
|
|
2351
2381
|
);
|
|
2352
2382
|
const nonNullMembers = nonNullTypes.map((memberType, index) => ({
|
|
2353
2383
|
memberType,
|
|
2354
2384
|
sourceNode: nonNullSourceNodes.length === nonNullTypes.length ? nonNullSourceNodes[index] : void 0
|
|
2355
2385
|
}));
|
|
2356
|
-
const hasNull = allTypes.some((t) => t.flags &
|
|
2386
|
+
const hasNull = allTypes.some((t) => t.flags & ts3.TypeFlags.Null);
|
|
2357
2387
|
const memberDisplayNames = /* @__PURE__ */ new Map();
|
|
2358
2388
|
if (namedDecl) {
|
|
2359
2389
|
for (const [value, label] of extractDisplayNameMetadata(namedDecl).memberDisplayNames) {
|
|
@@ -2382,7 +2412,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
2382
2412
|
const displayName = memberDisplayNames.get(String(value));
|
|
2383
2413
|
return displayName !== void 0 ? { value, displayName } : { value };
|
|
2384
2414
|
});
|
|
2385
|
-
const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags &
|
|
2415
|
+
const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts3.TypeFlags.BooleanLiteral);
|
|
2386
2416
|
if (isBooleanUnion2) {
|
|
2387
2417
|
const boolNode = { kind: "primitive", primitiveKind: "boolean" };
|
|
2388
2418
|
const result = hasNull ? {
|
|
@@ -2468,7 +2498,7 @@ function tryResolveRecordType(type, checker, file, typeRegistry, visiting, exten
|
|
|
2468
2498
|
if (type.getProperties().length > 0) {
|
|
2469
2499
|
return null;
|
|
2470
2500
|
}
|
|
2471
|
-
const indexInfo = checker.getIndexInfoOfType(type,
|
|
2501
|
+
const indexInfo = checker.getIndexInfoOfType(type, ts3.IndexKind.String);
|
|
2472
2502
|
if (!indexInfo) {
|
|
2473
2503
|
return null;
|
|
2474
2504
|
}
|
|
@@ -2579,7 +2609,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
2579
2609
|
const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
|
|
2580
2610
|
if (!declaration) continue;
|
|
2581
2611
|
const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
|
|
2582
|
-
const optional = !!(prop.flags &
|
|
2612
|
+
const optional = !!(prop.flags & ts3.SymbolFlags.Optional);
|
|
2583
2613
|
const propTypeNode = resolveTypeNode(
|
|
2584
2614
|
propType,
|
|
2585
2615
|
checker,
|
|
@@ -2624,11 +2654,11 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
2624
2654
|
for (const symbol of symbols) {
|
|
2625
2655
|
const declarations = symbol.declarations;
|
|
2626
2656
|
if (!declarations) continue;
|
|
2627
|
-
const classDecl = declarations.find(
|
|
2657
|
+
const classDecl = declarations.find(ts3.isClassDeclaration);
|
|
2628
2658
|
if (classDecl) {
|
|
2629
2659
|
const map = /* @__PURE__ */ new Map();
|
|
2630
2660
|
for (const member of classDecl.members) {
|
|
2631
|
-
if (
|
|
2661
|
+
if (ts3.isPropertyDeclaration(member) && ts3.isIdentifier(member.name)) {
|
|
2632
2662
|
const fieldNode = analyzeFieldToIR(
|
|
2633
2663
|
member,
|
|
2634
2664
|
checker,
|
|
@@ -2648,7 +2678,7 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
2648
2678
|
}
|
|
2649
2679
|
return map;
|
|
2650
2680
|
}
|
|
2651
|
-
const interfaceDecl = declarations.find(
|
|
2681
|
+
const interfaceDecl = declarations.find(ts3.isInterfaceDeclaration);
|
|
2652
2682
|
if (interfaceDecl) {
|
|
2653
2683
|
return buildFieldNodeInfoMap(
|
|
2654
2684
|
interfaceDecl.members,
|
|
@@ -2659,8 +2689,8 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
2659
2689
|
extensionRegistry
|
|
2660
2690
|
);
|
|
2661
2691
|
}
|
|
2662
|
-
const typeAliasDecl = declarations.find(
|
|
2663
|
-
if (typeAliasDecl &&
|
|
2692
|
+
const typeAliasDecl = declarations.find(ts3.isTypeAliasDeclaration);
|
|
2693
|
+
if (typeAliasDecl && ts3.isTypeLiteralNode(typeAliasDecl.type)) {
|
|
2664
2694
|
return buildFieldNodeInfoMap(
|
|
2665
2695
|
typeAliasDecl.type.members,
|
|
2666
2696
|
checker,
|
|
@@ -2679,10 +2709,10 @@ function extractArrayElementTypeNode(sourceNode, checker) {
|
|
|
2679
2709
|
return void 0;
|
|
2680
2710
|
}
|
|
2681
2711
|
const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
|
|
2682
|
-
if (
|
|
2712
|
+
if (ts3.isArrayTypeNode(resolvedTypeNode)) {
|
|
2683
2713
|
return resolvedTypeNode.elementType;
|
|
2684
2714
|
}
|
|
2685
|
-
if (
|
|
2715
|
+
if (ts3.isTypeReferenceNode(resolvedTypeNode) && ts3.isIdentifier(resolvedTypeNode.typeName) && resolvedTypeNode.typeName.text === "Array" && resolvedTypeNode.typeArguments?.[0]) {
|
|
2686
2716
|
return resolvedTypeNode.typeArguments[0];
|
|
2687
2717
|
}
|
|
2688
2718
|
return void 0;
|
|
@@ -2693,17 +2723,17 @@ function extractUnionMemberTypeNodes(sourceNode, checker) {
|
|
|
2693
2723
|
return [];
|
|
2694
2724
|
}
|
|
2695
2725
|
const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
|
|
2696
|
-
return
|
|
2726
|
+
return ts3.isUnionTypeNode(resolvedTypeNode) ? [...resolvedTypeNode.types] : [];
|
|
2697
2727
|
}
|
|
2698
2728
|
function resolveAliasedTypeNode(typeNode, checker, visited = /* @__PURE__ */ new Set()) {
|
|
2699
|
-
if (
|
|
2729
|
+
if (ts3.isParenthesizedTypeNode(typeNode)) {
|
|
2700
2730
|
return resolveAliasedTypeNode(typeNode.type, checker, visited);
|
|
2701
2731
|
}
|
|
2702
|
-
if (!
|
|
2732
|
+
if (!ts3.isTypeReferenceNode(typeNode) || !ts3.isIdentifier(typeNode.typeName)) {
|
|
2703
2733
|
return typeNode;
|
|
2704
2734
|
}
|
|
2705
2735
|
const symbol = checker.getSymbolAtLocation(typeNode.typeName);
|
|
2706
|
-
const aliasDecl = symbol?.declarations?.find(
|
|
2736
|
+
const aliasDecl = symbol?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
2707
2737
|
if (aliasDecl === void 0 || visited.has(aliasDecl)) {
|
|
2708
2738
|
return typeNode;
|
|
2709
2739
|
}
|
|
@@ -2711,15 +2741,15 @@ function resolveAliasedTypeNode(typeNode, checker, visited = /* @__PURE__ */ new
|
|
|
2711
2741
|
return resolveAliasedTypeNode(aliasDecl.type, checker, visited);
|
|
2712
2742
|
}
|
|
2713
2743
|
function isNullishTypeNode(typeNode) {
|
|
2714
|
-
if (typeNode.kind ===
|
|
2744
|
+
if (typeNode.kind === ts3.SyntaxKind.NullKeyword || typeNode.kind === ts3.SyntaxKind.UndefinedKeyword) {
|
|
2715
2745
|
return true;
|
|
2716
2746
|
}
|
|
2717
|
-
return
|
|
2747
|
+
return ts3.isLiteralTypeNode(typeNode) && (typeNode.literal.kind === ts3.SyntaxKind.NullKeyword || typeNode.literal.kind === ts3.SyntaxKind.UndefinedKeyword);
|
|
2718
2748
|
}
|
|
2719
2749
|
function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2720
2750
|
const map = /* @__PURE__ */ new Map();
|
|
2721
2751
|
for (const member of members) {
|
|
2722
|
-
if (
|
|
2752
|
+
if (ts3.isPropertySignature(member)) {
|
|
2723
2753
|
const fieldNode = analyzeInterfacePropertyToIR(
|
|
2724
2754
|
member,
|
|
2725
2755
|
checker,
|
|
@@ -2740,7 +2770,7 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, e
|
|
|
2740
2770
|
return map;
|
|
2741
2771
|
}
|
|
2742
2772
|
function extractTypeAliasConstraintNodes(typeNode, checker, file, extensionRegistry, depth = 0) {
|
|
2743
|
-
if (!
|
|
2773
|
+
if (!ts3.isTypeReferenceNode(typeNode)) return [];
|
|
2744
2774
|
if (depth >= MAX_ALIAS_CHAIN_DEPTH) {
|
|
2745
2775
|
const aliasName = typeNode.typeName.getText();
|
|
2746
2776
|
throw new Error(
|
|
@@ -2749,9 +2779,9 @@ function extractTypeAliasConstraintNodes(typeNode, checker, file, extensionRegis
|
|
|
2749
2779
|
}
|
|
2750
2780
|
const symbol = checker.getSymbolAtLocation(typeNode.typeName);
|
|
2751
2781
|
if (!symbol?.declarations) return [];
|
|
2752
|
-
const aliasDecl = symbol.declarations.find(
|
|
2782
|
+
const aliasDecl = symbol.declarations.find(ts3.isTypeAliasDeclaration);
|
|
2753
2783
|
if (!aliasDecl) return [];
|
|
2754
|
-
if (
|
|
2784
|
+
if (ts3.isTypeLiteralNode(aliasDecl.type)) return [];
|
|
2755
2785
|
const aliasFieldType = resolveTypeNode(
|
|
2756
2786
|
checker.getTypeAtLocation(aliasDecl.type),
|
|
2757
2787
|
checker,
|
|
@@ -2767,13 +2797,7 @@ function extractTypeAliasConstraintNodes(typeNode, checker, file, extensionRegis
|
|
|
2767
2797
|
makeParseOptions(extensionRegistry, aliasFieldType)
|
|
2768
2798
|
);
|
|
2769
2799
|
constraints.push(
|
|
2770
|
-
...extractTypeAliasConstraintNodes(
|
|
2771
|
-
aliasDecl.type,
|
|
2772
|
-
checker,
|
|
2773
|
-
file,
|
|
2774
|
-
extensionRegistry,
|
|
2775
|
-
depth + 1
|
|
2776
|
-
)
|
|
2800
|
+
...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, extensionRegistry, depth + 1)
|
|
2777
2801
|
);
|
|
2778
2802
|
return constraints;
|
|
2779
2803
|
}
|
|
@@ -2800,14 +2824,14 @@ function getNamedTypeName(type) {
|
|
|
2800
2824
|
const symbol = type.getSymbol();
|
|
2801
2825
|
if (symbol?.declarations) {
|
|
2802
2826
|
const decl = symbol.declarations[0];
|
|
2803
|
-
if (decl && (
|
|
2804
|
-
const name =
|
|
2827
|
+
if (decl && (ts3.isClassDeclaration(decl) || ts3.isInterfaceDeclaration(decl) || ts3.isTypeAliasDeclaration(decl))) {
|
|
2828
|
+
const name = ts3.isClassDeclaration(decl) ? decl.name?.text : decl.name.text;
|
|
2805
2829
|
if (name) return name;
|
|
2806
2830
|
}
|
|
2807
2831
|
}
|
|
2808
2832
|
const aliasSymbol = type.aliasSymbol;
|
|
2809
2833
|
if (aliasSymbol?.declarations) {
|
|
2810
|
-
const aliasDecl = aliasSymbol.declarations.find(
|
|
2834
|
+
const aliasDecl = aliasSymbol.declarations.find(ts3.isTypeAliasDeclaration);
|
|
2811
2835
|
if (aliasDecl) {
|
|
2812
2836
|
return aliasDecl.name.text;
|
|
2813
2837
|
}
|
|
@@ -2818,24 +2842,24 @@ function getNamedTypeDeclaration(type) {
|
|
|
2818
2842
|
const symbol = type.getSymbol();
|
|
2819
2843
|
if (symbol?.declarations) {
|
|
2820
2844
|
const decl = symbol.declarations[0];
|
|
2821
|
-
if (decl && (
|
|
2845
|
+
if (decl && (ts3.isClassDeclaration(decl) || ts3.isInterfaceDeclaration(decl) || ts3.isTypeAliasDeclaration(decl))) {
|
|
2822
2846
|
return decl;
|
|
2823
2847
|
}
|
|
2824
2848
|
}
|
|
2825
2849
|
const aliasSymbol = type.aliasSymbol;
|
|
2826
2850
|
if (aliasSymbol?.declarations) {
|
|
2827
|
-
return aliasSymbol.declarations.find(
|
|
2851
|
+
return aliasSymbol.declarations.find(ts3.isTypeAliasDeclaration);
|
|
2828
2852
|
}
|
|
2829
2853
|
return void 0;
|
|
2830
2854
|
}
|
|
2831
2855
|
function analyzeMethod(method, checker) {
|
|
2832
|
-
if (!
|
|
2856
|
+
if (!ts3.isIdentifier(method.name)) {
|
|
2833
2857
|
return null;
|
|
2834
2858
|
}
|
|
2835
2859
|
const name = method.name.text;
|
|
2836
2860
|
const parameters = [];
|
|
2837
2861
|
for (const param of method.parameters) {
|
|
2838
|
-
if (
|
|
2862
|
+
if (ts3.isIdentifier(param.name)) {
|
|
2839
2863
|
const paramInfo = analyzeParameter(param, checker);
|
|
2840
2864
|
parameters.push(paramInfo);
|
|
2841
2865
|
}
|
|
@@ -2846,7 +2870,7 @@ function analyzeMethod(method, checker) {
|
|
|
2846
2870
|
return { name, parameters, returnTypeNode, returnType };
|
|
2847
2871
|
}
|
|
2848
2872
|
function analyzeParameter(param, checker) {
|
|
2849
|
-
const name =
|
|
2873
|
+
const name = ts3.isIdentifier(param.name) ? param.name.text : "param";
|
|
2850
2874
|
const typeNode = param.type;
|
|
2851
2875
|
const type = checker.getTypeAtLocation(param);
|
|
2852
2876
|
const formSpecExportName = detectFormSpecReference(typeNode);
|
|
@@ -2855,15 +2879,15 @@ function analyzeParameter(param, checker) {
|
|
|
2855
2879
|
}
|
|
2856
2880
|
function detectFormSpecReference(typeNode) {
|
|
2857
2881
|
if (!typeNode) return null;
|
|
2858
|
-
if (!
|
|
2859
|
-
const typeName =
|
|
2882
|
+
if (!ts3.isTypeReferenceNode(typeNode)) return null;
|
|
2883
|
+
const typeName = ts3.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : ts3.isQualifiedName(typeNode.typeName) ? typeNode.typeName.right.text : null;
|
|
2860
2884
|
if (typeName !== "InferSchema" && typeName !== "InferFormSchema") return null;
|
|
2861
2885
|
const typeArg = typeNode.typeArguments?.[0];
|
|
2862
|
-
if (!typeArg || !
|
|
2863
|
-
if (
|
|
2886
|
+
if (!typeArg || !ts3.isTypeQueryNode(typeArg)) return null;
|
|
2887
|
+
if (ts3.isIdentifier(typeArg.exprName)) {
|
|
2864
2888
|
return typeArg.exprName.text;
|
|
2865
2889
|
}
|
|
2866
|
-
if (
|
|
2890
|
+
if (ts3.isQualifiedName(typeArg.exprName)) {
|
|
2867
2891
|
return typeArg.exprName.right.text;
|
|
2868
2892
|
}
|
|
2869
2893
|
return null;
|
|
@@ -2883,14 +2907,939 @@ var init_class_analyzer = __esm({
|
|
|
2883
2907
|
}
|
|
2884
2908
|
});
|
|
2885
2909
|
|
|
2910
|
+
// src/analyzer/program.ts
|
|
2911
|
+
import * as ts4 from "typescript";
|
|
2912
|
+
import * as path from "path";
|
|
2913
|
+
function createProgramContext(filePath) {
|
|
2914
|
+
const absolutePath = path.resolve(filePath);
|
|
2915
|
+
const fileDir = path.dirname(absolutePath);
|
|
2916
|
+
const configPath = ts4.findConfigFile(fileDir, ts4.sys.fileExists.bind(ts4.sys), "tsconfig.json");
|
|
2917
|
+
let compilerOptions;
|
|
2918
|
+
let fileNames;
|
|
2919
|
+
if (configPath) {
|
|
2920
|
+
const configFile = ts4.readConfigFile(configPath, ts4.sys.readFile.bind(ts4.sys));
|
|
2921
|
+
if (configFile.error) {
|
|
2922
|
+
throw new Error(
|
|
2923
|
+
`Error reading tsconfig.json: ${ts4.flattenDiagnosticMessageText(configFile.error.messageText, "\n")}`
|
|
2924
|
+
);
|
|
2925
|
+
}
|
|
2926
|
+
const parsed = ts4.parseJsonConfigFileContent(
|
|
2927
|
+
configFile.config,
|
|
2928
|
+
ts4.sys,
|
|
2929
|
+
path.dirname(configPath)
|
|
2930
|
+
);
|
|
2931
|
+
if (parsed.errors.length > 0) {
|
|
2932
|
+
const errorMessages = parsed.errors.map((e) => ts4.flattenDiagnosticMessageText(e.messageText, "\n")).join("\n");
|
|
2933
|
+
throw new Error(`Error parsing tsconfig.json: ${errorMessages}`);
|
|
2934
|
+
}
|
|
2935
|
+
compilerOptions = parsed.options;
|
|
2936
|
+
fileNames = parsed.fileNames.includes(absolutePath) ? parsed.fileNames : [...parsed.fileNames, absolutePath];
|
|
2937
|
+
} else {
|
|
2938
|
+
compilerOptions = {
|
|
2939
|
+
target: ts4.ScriptTarget.ES2022,
|
|
2940
|
+
module: ts4.ModuleKind.NodeNext,
|
|
2941
|
+
moduleResolution: ts4.ModuleResolutionKind.NodeNext,
|
|
2942
|
+
strict: true,
|
|
2943
|
+
skipLibCheck: true,
|
|
2944
|
+
declaration: true
|
|
2945
|
+
};
|
|
2946
|
+
fileNames = [absolutePath];
|
|
2947
|
+
}
|
|
2948
|
+
const program = ts4.createProgram(fileNames, compilerOptions);
|
|
2949
|
+
const sourceFile = program.getSourceFile(absolutePath);
|
|
2950
|
+
if (!sourceFile) {
|
|
2951
|
+
throw new Error(`Could not find source file: ${absolutePath}`);
|
|
2952
|
+
}
|
|
2953
|
+
return {
|
|
2954
|
+
program,
|
|
2955
|
+
checker: program.getTypeChecker(),
|
|
2956
|
+
sourceFile
|
|
2957
|
+
};
|
|
2958
|
+
}
|
|
2959
|
+
function findNodeByName(sourceFile, name, predicate, getName) {
|
|
2960
|
+
let result = null;
|
|
2961
|
+
function visit(node) {
|
|
2962
|
+
if (result) return;
|
|
2963
|
+
if (predicate(node) && getName(node) === name) {
|
|
2964
|
+
result = node;
|
|
2965
|
+
return;
|
|
2966
|
+
}
|
|
2967
|
+
ts4.forEachChild(node, visit);
|
|
2968
|
+
}
|
|
2969
|
+
visit(sourceFile);
|
|
2970
|
+
return result;
|
|
2971
|
+
}
|
|
2972
|
+
function findClassByName(sourceFile, className) {
|
|
2973
|
+
return findNodeByName(sourceFile, className, ts4.isClassDeclaration, (n) => n.name?.text);
|
|
2974
|
+
}
|
|
2975
|
+
function findInterfaceByName(sourceFile, interfaceName) {
|
|
2976
|
+
return findNodeByName(sourceFile, interfaceName, ts4.isInterfaceDeclaration, (n) => n.name.text);
|
|
2977
|
+
}
|
|
2978
|
+
function findTypeAliasByName(sourceFile, aliasName) {
|
|
2979
|
+
return findNodeByName(sourceFile, aliasName, ts4.isTypeAliasDeclaration, (n) => n.name.text);
|
|
2980
|
+
}
|
|
2981
|
+
function analyzeNamedTypeToIR(filePath, typeName, extensionRegistry) {
|
|
2982
|
+
const ctx = createProgramContext(filePath);
|
|
2983
|
+
const classDecl = findClassByName(ctx.sourceFile, typeName);
|
|
2984
|
+
if (classDecl !== null) {
|
|
2985
|
+
return analyzeClassToIR(classDecl, ctx.checker, filePath, extensionRegistry);
|
|
2986
|
+
}
|
|
2987
|
+
const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
|
|
2988
|
+
if (interfaceDecl !== null) {
|
|
2989
|
+
return analyzeInterfaceToIR(interfaceDecl, ctx.checker, filePath, extensionRegistry);
|
|
2990
|
+
}
|
|
2991
|
+
const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
|
|
2992
|
+
if (typeAlias !== null) {
|
|
2993
|
+
const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, filePath, extensionRegistry);
|
|
2994
|
+
if (result.ok) {
|
|
2995
|
+
return result.analysis;
|
|
2996
|
+
}
|
|
2997
|
+
throw new Error(result.error);
|
|
2998
|
+
}
|
|
2999
|
+
throw new Error(
|
|
3000
|
+
`Type "${typeName}" not found as a class, interface, or type alias in ${filePath}`
|
|
3001
|
+
);
|
|
3002
|
+
}
|
|
3003
|
+
var init_program = __esm({
|
|
3004
|
+
"src/analyzer/program.ts"() {
|
|
3005
|
+
"use strict";
|
|
3006
|
+
init_class_analyzer();
|
|
3007
|
+
}
|
|
3008
|
+
});
|
|
3009
|
+
|
|
3010
|
+
// src/validate/constraint-validator.ts
|
|
3011
|
+
import { normalizeConstraintTagName as normalizeConstraintTagName2 } from "@formspec/core";
|
|
3012
|
+
function addContradiction(ctx, message, primary, related) {
|
|
3013
|
+
ctx.diagnostics.push({
|
|
3014
|
+
code: "CONTRADICTING_CONSTRAINTS",
|
|
3015
|
+
message,
|
|
3016
|
+
severity: "error",
|
|
3017
|
+
primaryLocation: primary,
|
|
3018
|
+
relatedLocations: [related]
|
|
3019
|
+
});
|
|
3020
|
+
}
|
|
3021
|
+
function addTypeMismatch(ctx, message, primary) {
|
|
3022
|
+
ctx.diagnostics.push({
|
|
3023
|
+
code: "TYPE_MISMATCH",
|
|
3024
|
+
message,
|
|
3025
|
+
severity: "error",
|
|
3026
|
+
primaryLocation: primary,
|
|
3027
|
+
relatedLocations: []
|
|
3028
|
+
});
|
|
3029
|
+
}
|
|
3030
|
+
function addUnknownExtension(ctx, message, primary) {
|
|
3031
|
+
ctx.diagnostics.push({
|
|
3032
|
+
code: "UNKNOWN_EXTENSION",
|
|
3033
|
+
message,
|
|
3034
|
+
severity: "warning",
|
|
3035
|
+
primaryLocation: primary,
|
|
3036
|
+
relatedLocations: []
|
|
3037
|
+
});
|
|
3038
|
+
}
|
|
3039
|
+
function addUnknownPathTarget(ctx, message, primary) {
|
|
3040
|
+
ctx.diagnostics.push({
|
|
3041
|
+
code: "UNKNOWN_PATH_TARGET",
|
|
3042
|
+
message,
|
|
3043
|
+
severity: "error",
|
|
3044
|
+
primaryLocation: primary,
|
|
3045
|
+
relatedLocations: []
|
|
3046
|
+
});
|
|
3047
|
+
}
|
|
3048
|
+
function addConstraintBroadening(ctx, message, primary, related) {
|
|
3049
|
+
ctx.diagnostics.push({
|
|
3050
|
+
code: "CONSTRAINT_BROADENING",
|
|
3051
|
+
message,
|
|
3052
|
+
severity: "error",
|
|
3053
|
+
primaryLocation: primary,
|
|
3054
|
+
relatedLocations: [related]
|
|
3055
|
+
});
|
|
3056
|
+
}
|
|
3057
|
+
function getExtensionIdFromConstraintId(constraintId) {
|
|
3058
|
+
const separator = constraintId.lastIndexOf("/");
|
|
3059
|
+
if (separator <= 0) {
|
|
3060
|
+
return null;
|
|
3061
|
+
}
|
|
3062
|
+
return constraintId.slice(0, separator);
|
|
3063
|
+
}
|
|
3064
|
+
function findNumeric(constraints, constraintKind) {
|
|
3065
|
+
return constraints.find((c) => c.constraintKind === constraintKind);
|
|
3066
|
+
}
|
|
3067
|
+
function findLength(constraints, constraintKind) {
|
|
3068
|
+
return constraints.find((c) => c.constraintKind === constraintKind);
|
|
3069
|
+
}
|
|
3070
|
+
function findAllowedMembers(constraints) {
|
|
3071
|
+
return constraints.filter(
|
|
3072
|
+
(c) => c.constraintKind === "allowedMembers"
|
|
3073
|
+
);
|
|
3074
|
+
}
|
|
3075
|
+
function findConstConstraints(constraints) {
|
|
3076
|
+
return constraints.filter(
|
|
3077
|
+
(c) => c.constraintKind === "const"
|
|
3078
|
+
);
|
|
3079
|
+
}
|
|
3080
|
+
function jsonValueEquals(left, right) {
|
|
3081
|
+
if (left === right) {
|
|
3082
|
+
return true;
|
|
3083
|
+
}
|
|
3084
|
+
if (Array.isArray(left) || Array.isArray(right)) {
|
|
3085
|
+
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
|
|
3086
|
+
return false;
|
|
3087
|
+
}
|
|
3088
|
+
return left.every((item, index) => jsonValueEquals(item, right[index]));
|
|
3089
|
+
}
|
|
3090
|
+
if (isJsonObject(left) || isJsonObject(right)) {
|
|
3091
|
+
if (!isJsonObject(left) || !isJsonObject(right)) {
|
|
3092
|
+
return false;
|
|
3093
|
+
}
|
|
3094
|
+
const leftKeys = Object.keys(left).sort();
|
|
3095
|
+
const rightKeys = Object.keys(right).sort();
|
|
3096
|
+
if (leftKeys.length !== rightKeys.length) {
|
|
3097
|
+
return false;
|
|
3098
|
+
}
|
|
3099
|
+
return leftKeys.every((key, index) => {
|
|
3100
|
+
const rightKey = rightKeys[index];
|
|
3101
|
+
if (rightKey !== key) {
|
|
3102
|
+
return false;
|
|
3103
|
+
}
|
|
3104
|
+
const leftValue = left[key];
|
|
3105
|
+
const rightValue = right[rightKey];
|
|
3106
|
+
return leftValue !== void 0 && rightValue !== void 0 && jsonValueEquals(leftValue, rightValue);
|
|
3107
|
+
});
|
|
3108
|
+
}
|
|
3109
|
+
return false;
|
|
3110
|
+
}
|
|
3111
|
+
function isJsonObject(value) {
|
|
3112
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3113
|
+
}
|
|
3114
|
+
function isOrderedBoundConstraint(constraint) {
|
|
3115
|
+
return constraint.constraintKind === "minimum" || constraint.constraintKind === "exclusiveMinimum" || constraint.constraintKind === "minLength" || constraint.constraintKind === "minItems" || constraint.constraintKind === "maximum" || constraint.constraintKind === "exclusiveMaximum" || constraint.constraintKind === "maxLength" || constraint.constraintKind === "maxItems";
|
|
3116
|
+
}
|
|
3117
|
+
function pathKey(constraint) {
|
|
3118
|
+
return constraint.path?.segments.join(".") ?? "";
|
|
3119
|
+
}
|
|
3120
|
+
function orderedBoundFamily(kind) {
|
|
3121
|
+
switch (kind) {
|
|
3122
|
+
case "minimum":
|
|
3123
|
+
case "exclusiveMinimum":
|
|
3124
|
+
return "numeric-lower";
|
|
3125
|
+
case "maximum":
|
|
3126
|
+
case "exclusiveMaximum":
|
|
3127
|
+
return "numeric-upper";
|
|
3128
|
+
case "minLength":
|
|
3129
|
+
return "minLength";
|
|
3130
|
+
case "minItems":
|
|
3131
|
+
return "minItems";
|
|
3132
|
+
case "maxLength":
|
|
3133
|
+
return "maxLength";
|
|
3134
|
+
case "maxItems":
|
|
3135
|
+
return "maxItems";
|
|
3136
|
+
default: {
|
|
3137
|
+
const _exhaustive = kind;
|
|
3138
|
+
return _exhaustive;
|
|
3139
|
+
}
|
|
3140
|
+
}
|
|
3141
|
+
}
|
|
3142
|
+
function isNumericLowerKind(kind) {
|
|
3143
|
+
return kind === "minimum" || kind === "exclusiveMinimum";
|
|
3144
|
+
}
|
|
3145
|
+
function isNumericUpperKind(kind) {
|
|
3146
|
+
return kind === "maximum" || kind === "exclusiveMaximum";
|
|
3147
|
+
}
|
|
3148
|
+
function describeConstraintTag(constraint) {
|
|
3149
|
+
return `@${constraint.constraintKind}`;
|
|
3150
|
+
}
|
|
3151
|
+
function compareConstraintStrength(current, previous) {
|
|
3152
|
+
const family = orderedBoundFamily(current.constraintKind);
|
|
3153
|
+
if (family === "numeric-lower") {
|
|
3154
|
+
if (!isNumericLowerKind(current.constraintKind) || !isNumericLowerKind(previous.constraintKind)) {
|
|
3155
|
+
throw new Error("numeric-lower family received non-numeric lower-bound constraint");
|
|
3156
|
+
}
|
|
3157
|
+
if (current.value !== previous.value) {
|
|
3158
|
+
return current.value > previous.value ? 1 : -1;
|
|
3159
|
+
}
|
|
3160
|
+
if (current.constraintKind === "exclusiveMinimum" && previous.constraintKind === "minimum") {
|
|
3161
|
+
return 1;
|
|
3162
|
+
}
|
|
3163
|
+
if (current.constraintKind === "minimum" && previous.constraintKind === "exclusiveMinimum") {
|
|
3164
|
+
return -1;
|
|
3165
|
+
}
|
|
3166
|
+
return 0;
|
|
3167
|
+
}
|
|
3168
|
+
if (family === "numeric-upper") {
|
|
3169
|
+
if (!isNumericUpperKind(current.constraintKind) || !isNumericUpperKind(previous.constraintKind)) {
|
|
3170
|
+
throw new Error("numeric-upper family received non-numeric upper-bound constraint");
|
|
3171
|
+
}
|
|
3172
|
+
if (current.value !== previous.value) {
|
|
3173
|
+
return current.value < previous.value ? 1 : -1;
|
|
3174
|
+
}
|
|
3175
|
+
if (current.constraintKind === "exclusiveMaximum" && previous.constraintKind === "maximum") {
|
|
3176
|
+
return 1;
|
|
3177
|
+
}
|
|
3178
|
+
if (current.constraintKind === "maximum" && previous.constraintKind === "exclusiveMaximum") {
|
|
3179
|
+
return -1;
|
|
3180
|
+
}
|
|
3181
|
+
return 0;
|
|
3182
|
+
}
|
|
3183
|
+
switch (family) {
|
|
3184
|
+
case "minLength":
|
|
3185
|
+
case "minItems":
|
|
3186
|
+
if (current.value === previous.value) {
|
|
3187
|
+
return 0;
|
|
3188
|
+
}
|
|
3189
|
+
return current.value > previous.value ? 1 : -1;
|
|
3190
|
+
case "maxLength":
|
|
3191
|
+
case "maxItems":
|
|
3192
|
+
if (current.value === previous.value) {
|
|
3193
|
+
return 0;
|
|
3194
|
+
}
|
|
3195
|
+
return current.value < previous.value ? 1 : -1;
|
|
3196
|
+
default: {
|
|
3197
|
+
const _exhaustive = family;
|
|
3198
|
+
return _exhaustive;
|
|
3199
|
+
}
|
|
3200
|
+
}
|
|
3201
|
+
}
|
|
3202
|
+
function checkConstraintBroadening(ctx, fieldName, constraints) {
|
|
3203
|
+
const strongestByKey = /* @__PURE__ */ new Map();
|
|
3204
|
+
for (const constraint of constraints) {
|
|
3205
|
+
if (!isOrderedBoundConstraint(constraint)) {
|
|
3206
|
+
continue;
|
|
3207
|
+
}
|
|
3208
|
+
const key = `${orderedBoundFamily(constraint.constraintKind)}:${pathKey(constraint)}`;
|
|
3209
|
+
const previous = strongestByKey.get(key);
|
|
3210
|
+
if (previous === void 0) {
|
|
3211
|
+
strongestByKey.set(key, constraint);
|
|
3212
|
+
continue;
|
|
3213
|
+
}
|
|
3214
|
+
const strength = compareConstraintStrength(constraint, previous);
|
|
3215
|
+
if (strength < 0) {
|
|
3216
|
+
const displayFieldName = formatPathTargetFieldName(
|
|
3217
|
+
fieldName,
|
|
3218
|
+
constraint.path?.segments ?? []
|
|
3219
|
+
);
|
|
3220
|
+
addConstraintBroadening(
|
|
3221
|
+
ctx,
|
|
3222
|
+
`Field "${displayFieldName}": ${describeConstraintTag(constraint)} (${String(constraint.value)}) is broader than earlier ${describeConstraintTag(previous)} (${String(previous.value)}). Constraints can only narrow.`,
|
|
3223
|
+
constraint.provenance,
|
|
3224
|
+
previous.provenance
|
|
3225
|
+
);
|
|
3226
|
+
continue;
|
|
3227
|
+
}
|
|
3228
|
+
if (strength <= 0) {
|
|
3229
|
+
continue;
|
|
3230
|
+
}
|
|
3231
|
+
strongestByKey.set(key, constraint);
|
|
3232
|
+
}
|
|
3233
|
+
}
|
|
3234
|
+
function compareCustomConstraintStrength(current, previous) {
|
|
3235
|
+
const order = current.comparePayloads(current.constraint.payload, previous.constraint.payload);
|
|
3236
|
+
const equalPayloadTiebreaker = order === 0 ? compareSemanticInclusivity(current.role.inclusive, previous.role.inclusive) : order;
|
|
3237
|
+
switch (current.role.bound) {
|
|
3238
|
+
case "lower":
|
|
3239
|
+
return equalPayloadTiebreaker;
|
|
3240
|
+
case "upper":
|
|
3241
|
+
return equalPayloadTiebreaker === 0 ? 0 : -equalPayloadTiebreaker;
|
|
3242
|
+
case "exact":
|
|
3243
|
+
return order === 0 ? 0 : Number.NaN;
|
|
3244
|
+
default: {
|
|
3245
|
+
const _exhaustive = current.role.bound;
|
|
3246
|
+
return _exhaustive;
|
|
3247
|
+
}
|
|
3248
|
+
}
|
|
3249
|
+
}
|
|
3250
|
+
function compareSemanticInclusivity(currentInclusive, previousInclusive) {
|
|
3251
|
+
if (currentInclusive === previousInclusive) {
|
|
3252
|
+
return 0;
|
|
3253
|
+
}
|
|
3254
|
+
return currentInclusive ? -1 : 1;
|
|
3255
|
+
}
|
|
3256
|
+
function customConstraintsContradict(lower, upper) {
|
|
3257
|
+
const order = lower.comparePayloads(lower.constraint.payload, upper.constraint.payload);
|
|
3258
|
+
if (order > 0) {
|
|
3259
|
+
return true;
|
|
3260
|
+
}
|
|
3261
|
+
if (order < 0) {
|
|
3262
|
+
return false;
|
|
3263
|
+
}
|
|
3264
|
+
return !lower.role.inclusive || !upper.role.inclusive;
|
|
3265
|
+
}
|
|
3266
|
+
function describeCustomConstraintTag(constraint) {
|
|
3267
|
+
return constraint.provenance.tagName ?? constraint.constraintId;
|
|
3268
|
+
}
|
|
3269
|
+
function checkCustomConstraintSemantics(ctx, fieldName, constraints) {
|
|
3270
|
+
if (ctx.extensionRegistry === void 0) {
|
|
3271
|
+
return;
|
|
3272
|
+
}
|
|
3273
|
+
const strongestByKey = /* @__PURE__ */ new Map();
|
|
3274
|
+
const lowerByFamily = /* @__PURE__ */ new Map();
|
|
3275
|
+
const upperByFamily = /* @__PURE__ */ new Map();
|
|
3276
|
+
for (const constraint of constraints) {
|
|
3277
|
+
if (constraint.constraintKind !== "custom") {
|
|
3278
|
+
continue;
|
|
3279
|
+
}
|
|
3280
|
+
const registration = ctx.extensionRegistry.findConstraint(constraint.constraintId);
|
|
3281
|
+
if (registration?.comparePayloads === void 0 || registration.semanticRole === void 0) {
|
|
3282
|
+
continue;
|
|
3283
|
+
}
|
|
3284
|
+
const entry = {
|
|
3285
|
+
constraint,
|
|
3286
|
+
comparePayloads: registration.comparePayloads,
|
|
3287
|
+
role: registration.semanticRole
|
|
3288
|
+
};
|
|
3289
|
+
const familyKey = `${registration.semanticRole.family}:${pathKey(constraint)}`;
|
|
3290
|
+
const boundKey = `${familyKey}:${registration.semanticRole.bound}`;
|
|
3291
|
+
const previous = strongestByKey.get(boundKey);
|
|
3292
|
+
if (previous !== void 0) {
|
|
3293
|
+
const strength = compareCustomConstraintStrength(entry, previous);
|
|
3294
|
+
if (Number.isNaN(strength)) {
|
|
3295
|
+
addContradiction(
|
|
3296
|
+
ctx,
|
|
3297
|
+
`Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} conflicts with ${describeCustomConstraintTag(previous.constraint)}`,
|
|
3298
|
+
constraint.provenance,
|
|
3299
|
+
previous.constraint.provenance
|
|
3300
|
+
);
|
|
3301
|
+
continue;
|
|
3302
|
+
}
|
|
3303
|
+
if (strength < 0) {
|
|
3304
|
+
addConstraintBroadening(
|
|
3305
|
+
ctx,
|
|
3306
|
+
`Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} is broader than earlier ${describeCustomConstraintTag(previous.constraint)}. Constraints can only narrow.`,
|
|
3307
|
+
constraint.provenance,
|
|
3308
|
+
previous.constraint.provenance
|
|
3309
|
+
);
|
|
3310
|
+
continue;
|
|
3311
|
+
}
|
|
3312
|
+
if (strength > 0) {
|
|
3313
|
+
strongestByKey.set(boundKey, entry);
|
|
3314
|
+
}
|
|
3315
|
+
} else {
|
|
3316
|
+
strongestByKey.set(boundKey, entry);
|
|
3317
|
+
}
|
|
3318
|
+
if (registration.semanticRole.bound === "lower") {
|
|
3319
|
+
lowerByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
|
|
3320
|
+
} else if (registration.semanticRole.bound === "upper") {
|
|
3321
|
+
upperByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
|
|
3322
|
+
}
|
|
3323
|
+
}
|
|
3324
|
+
for (const [familyKey, lower] of lowerByFamily) {
|
|
3325
|
+
const upper = upperByFamily.get(familyKey);
|
|
3326
|
+
if (upper === void 0) {
|
|
3327
|
+
continue;
|
|
3328
|
+
}
|
|
3329
|
+
if (!customConstraintsContradict(lower, upper)) {
|
|
3330
|
+
continue;
|
|
3331
|
+
}
|
|
3332
|
+
addContradiction(
|
|
3333
|
+
ctx,
|
|
3334
|
+
`Field "${formatPathTargetFieldName(fieldName, lower.constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(lower.constraint)} contradicts ${describeCustomConstraintTag(upper.constraint)}`,
|
|
3335
|
+
lower.constraint.provenance,
|
|
3336
|
+
upper.constraint.provenance
|
|
3337
|
+
);
|
|
3338
|
+
}
|
|
3339
|
+
}
|
|
3340
|
+
function checkNumericContradictions(ctx, fieldName, constraints) {
|
|
3341
|
+
const min = findNumeric(constraints, "minimum");
|
|
3342
|
+
const max = findNumeric(constraints, "maximum");
|
|
3343
|
+
const exMin = findNumeric(constraints, "exclusiveMinimum");
|
|
3344
|
+
const exMax = findNumeric(constraints, "exclusiveMaximum");
|
|
3345
|
+
if (min !== void 0 && max !== void 0 && min.value > max.value) {
|
|
3346
|
+
addContradiction(
|
|
3347
|
+
ctx,
|
|
3348
|
+
`Field "${fieldName}": minimum (${String(min.value)}) is greater than maximum (${String(max.value)})`,
|
|
3349
|
+
min.provenance,
|
|
3350
|
+
max.provenance
|
|
3351
|
+
);
|
|
3352
|
+
}
|
|
3353
|
+
if (exMin !== void 0 && max !== void 0 && exMin.value >= max.value) {
|
|
3354
|
+
addContradiction(
|
|
3355
|
+
ctx,
|
|
3356
|
+
`Field "${fieldName}": exclusiveMinimum (${String(exMin.value)}) is greater than or equal to maximum (${String(max.value)})`,
|
|
3357
|
+
exMin.provenance,
|
|
3358
|
+
max.provenance
|
|
3359
|
+
);
|
|
3360
|
+
}
|
|
3361
|
+
if (min !== void 0 && exMax !== void 0 && min.value >= exMax.value) {
|
|
3362
|
+
addContradiction(
|
|
3363
|
+
ctx,
|
|
3364
|
+
`Field "${fieldName}": minimum (${String(min.value)}) is greater than or equal to exclusiveMaximum (${String(exMax.value)})`,
|
|
3365
|
+
min.provenance,
|
|
3366
|
+
exMax.provenance
|
|
3367
|
+
);
|
|
3368
|
+
}
|
|
3369
|
+
if (exMin !== void 0 && exMax !== void 0 && exMin.value >= exMax.value) {
|
|
3370
|
+
addContradiction(
|
|
3371
|
+
ctx,
|
|
3372
|
+
`Field "${fieldName}": exclusiveMinimum (${String(exMin.value)}) is greater than or equal to exclusiveMaximum (${String(exMax.value)})`,
|
|
3373
|
+
exMin.provenance,
|
|
3374
|
+
exMax.provenance
|
|
3375
|
+
);
|
|
3376
|
+
}
|
|
3377
|
+
}
|
|
3378
|
+
function checkLengthContradictions(ctx, fieldName, constraints) {
|
|
3379
|
+
const minLen = findLength(constraints, "minLength");
|
|
3380
|
+
const maxLen = findLength(constraints, "maxLength");
|
|
3381
|
+
if (minLen !== void 0 && maxLen !== void 0 && minLen.value > maxLen.value) {
|
|
3382
|
+
addContradiction(
|
|
3383
|
+
ctx,
|
|
3384
|
+
`Field "${fieldName}": minLength (${String(minLen.value)}) is greater than maxLength (${String(maxLen.value)})`,
|
|
3385
|
+
minLen.provenance,
|
|
3386
|
+
maxLen.provenance
|
|
3387
|
+
);
|
|
3388
|
+
}
|
|
3389
|
+
const minItems = findLength(constraints, "minItems");
|
|
3390
|
+
const maxItems = findLength(constraints, "maxItems");
|
|
3391
|
+
if (minItems !== void 0 && maxItems !== void 0 && minItems.value > maxItems.value) {
|
|
3392
|
+
addContradiction(
|
|
3393
|
+
ctx,
|
|
3394
|
+
`Field "${fieldName}": minItems (${String(minItems.value)}) is greater than maxItems (${String(maxItems.value)})`,
|
|
3395
|
+
minItems.provenance,
|
|
3396
|
+
maxItems.provenance
|
|
3397
|
+
);
|
|
3398
|
+
}
|
|
3399
|
+
}
|
|
3400
|
+
function checkAllowedMembersContradiction(ctx, fieldName, constraints) {
|
|
3401
|
+
const members = findAllowedMembers(constraints);
|
|
3402
|
+
if (members.length < 2) return;
|
|
3403
|
+
const firstSet = new Set(members[0]?.members ?? []);
|
|
3404
|
+
for (let i = 1; i < members.length; i++) {
|
|
3405
|
+
const current = members[i];
|
|
3406
|
+
if (current === void 0) continue;
|
|
3407
|
+
for (const m of firstSet) {
|
|
3408
|
+
if (!current.members.includes(m)) {
|
|
3409
|
+
firstSet.delete(m);
|
|
3410
|
+
}
|
|
3411
|
+
}
|
|
3412
|
+
}
|
|
3413
|
+
if (firstSet.size === 0) {
|
|
3414
|
+
const first = members[0];
|
|
3415
|
+
const second = members[1];
|
|
3416
|
+
if (first !== void 0 && second !== void 0) {
|
|
3417
|
+
addContradiction(
|
|
3418
|
+
ctx,
|
|
3419
|
+
`Field "${fieldName}": allowedMembers constraints have an empty intersection (no valid values remain)`,
|
|
3420
|
+
first.provenance,
|
|
3421
|
+
second.provenance
|
|
3422
|
+
);
|
|
3423
|
+
}
|
|
3424
|
+
}
|
|
3425
|
+
}
|
|
3426
|
+
function checkConstContradictions(ctx, fieldName, constraints) {
|
|
3427
|
+
const constConstraints = findConstConstraints(constraints);
|
|
3428
|
+
if (constConstraints.length < 2) return;
|
|
3429
|
+
const first = constConstraints[0];
|
|
3430
|
+
if (first === void 0) return;
|
|
3431
|
+
for (let i = 1; i < constConstraints.length; i++) {
|
|
3432
|
+
const current = constConstraints[i];
|
|
3433
|
+
if (current === void 0) continue;
|
|
3434
|
+
if (jsonValueEquals(first.value, current.value)) {
|
|
3435
|
+
continue;
|
|
3436
|
+
}
|
|
3437
|
+
addContradiction(
|
|
3438
|
+
ctx,
|
|
3439
|
+
`Field "${fieldName}": conflicting @const constraints require both ${JSON.stringify(first.value)} and ${JSON.stringify(current.value)}`,
|
|
3440
|
+
first.provenance,
|
|
3441
|
+
current.provenance
|
|
3442
|
+
);
|
|
3443
|
+
}
|
|
3444
|
+
}
|
|
3445
|
+
function typeLabel(type) {
|
|
3446
|
+
switch (type.kind) {
|
|
3447
|
+
case "primitive":
|
|
3448
|
+
return type.primitiveKind;
|
|
3449
|
+
case "enum":
|
|
3450
|
+
return "enum";
|
|
3451
|
+
case "array":
|
|
3452
|
+
return "array";
|
|
3453
|
+
case "object":
|
|
3454
|
+
return "object";
|
|
3455
|
+
case "record":
|
|
3456
|
+
return "record";
|
|
3457
|
+
case "union":
|
|
3458
|
+
return "union";
|
|
3459
|
+
case "reference":
|
|
3460
|
+
return `reference(${type.name})`;
|
|
3461
|
+
case "dynamic":
|
|
3462
|
+
return `dynamic(${type.dynamicKind})`;
|
|
3463
|
+
case "custom":
|
|
3464
|
+
return `custom(${type.typeId})`;
|
|
3465
|
+
default: {
|
|
3466
|
+
const _exhaustive = type;
|
|
3467
|
+
return String(_exhaustive);
|
|
3468
|
+
}
|
|
3469
|
+
}
|
|
3470
|
+
}
|
|
3471
|
+
function dereferenceType(ctx, type) {
|
|
3472
|
+
let current = type;
|
|
3473
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3474
|
+
while (current.kind === "reference") {
|
|
3475
|
+
if (seen.has(current.name)) {
|
|
3476
|
+
return current;
|
|
3477
|
+
}
|
|
3478
|
+
seen.add(current.name);
|
|
3479
|
+
const definition = ctx.typeRegistry[current.name];
|
|
3480
|
+
if (definition === void 0) {
|
|
3481
|
+
return current;
|
|
3482
|
+
}
|
|
3483
|
+
current = definition.type;
|
|
3484
|
+
}
|
|
3485
|
+
return current;
|
|
3486
|
+
}
|
|
3487
|
+
function collectReferencedTypeConstraints(ctx, type) {
|
|
3488
|
+
const collected = [];
|
|
3489
|
+
let current = type;
|
|
3490
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3491
|
+
while (current.kind === "reference") {
|
|
3492
|
+
if (seen.has(current.name)) {
|
|
3493
|
+
break;
|
|
3494
|
+
}
|
|
3495
|
+
seen.add(current.name);
|
|
3496
|
+
const definition = ctx.typeRegistry[current.name];
|
|
3497
|
+
if (definition === void 0) {
|
|
3498
|
+
break;
|
|
3499
|
+
}
|
|
3500
|
+
if (definition.constraints !== void 0) {
|
|
3501
|
+
collected.push(...definition.constraints);
|
|
3502
|
+
}
|
|
3503
|
+
current = definition.type;
|
|
3504
|
+
}
|
|
3505
|
+
return collected;
|
|
3506
|
+
}
|
|
3507
|
+
function resolvePathTargetType(ctx, type, segments) {
|
|
3508
|
+
const effectiveType = dereferenceType(ctx, type);
|
|
3509
|
+
if (segments.length === 0) {
|
|
3510
|
+
return { kind: "resolved", type: effectiveType };
|
|
3511
|
+
}
|
|
3512
|
+
if (effectiveType.kind === "array") {
|
|
3513
|
+
return resolvePathTargetType(ctx, effectiveType.items, segments);
|
|
3514
|
+
}
|
|
3515
|
+
if (effectiveType.kind === "object") {
|
|
3516
|
+
const [segment, ...rest] = segments;
|
|
3517
|
+
if (segment === void 0) {
|
|
3518
|
+
throw new Error("Invariant violation: object path traversal requires a segment");
|
|
3519
|
+
}
|
|
3520
|
+
const property = effectiveType.properties.find((prop) => prop.name === segment);
|
|
3521
|
+
if (property === void 0) {
|
|
3522
|
+
return { kind: "missing-property", segment };
|
|
3523
|
+
}
|
|
3524
|
+
return resolvePathTargetType(ctx, property.type, rest);
|
|
3525
|
+
}
|
|
3526
|
+
return { kind: "unresolvable", type: effectiveType };
|
|
3527
|
+
}
|
|
3528
|
+
function isNullType(type) {
|
|
3529
|
+
return type.kind === "primitive" && type.primitiveKind === "null";
|
|
3530
|
+
}
|
|
3531
|
+
function collectCustomConstraintCandidateTypes(ctx, type) {
|
|
3532
|
+
const effectiveType = dereferenceType(ctx, type);
|
|
3533
|
+
const candidates = [effectiveType];
|
|
3534
|
+
if (effectiveType.kind === "array") {
|
|
3535
|
+
candidates.push(...collectCustomConstraintCandidateTypes(ctx, effectiveType.items));
|
|
3536
|
+
}
|
|
3537
|
+
if (effectiveType.kind === "union") {
|
|
3538
|
+
const memberTypes = effectiveType.members.map((member) => dereferenceType(ctx, member));
|
|
3539
|
+
const nonNullMembers = memberTypes.filter((member) => !isNullType(member));
|
|
3540
|
+
if (nonNullMembers.length === 1 && nonNullMembers.length < memberTypes.length) {
|
|
3541
|
+
const [nullableMember] = nonNullMembers;
|
|
3542
|
+
if (nullableMember !== void 0) {
|
|
3543
|
+
candidates.push(...collectCustomConstraintCandidateTypes(ctx, nullableMember));
|
|
3544
|
+
}
|
|
3545
|
+
}
|
|
3546
|
+
}
|
|
3547
|
+
return candidates;
|
|
3548
|
+
}
|
|
3549
|
+
function formatPathTargetFieldName(fieldName, path4) {
|
|
3550
|
+
return path4.length === 0 ? fieldName : `${fieldName}.${path4.join(".")}`;
|
|
3551
|
+
}
|
|
3552
|
+
function checkConstraintOnType(ctx, fieldName, type, constraint) {
|
|
3553
|
+
const effectiveType = dereferenceType(ctx, type);
|
|
3554
|
+
const isNumber = effectiveType.kind === "primitive" && ["number", "integer", "bigint"].includes(effectiveType.primitiveKind);
|
|
3555
|
+
const isString = effectiveType.kind === "primitive" && effectiveType.primitiveKind === "string";
|
|
3556
|
+
const isArray = effectiveType.kind === "array";
|
|
3557
|
+
const isEnum = effectiveType.kind === "enum";
|
|
3558
|
+
const arrayItemType = effectiveType.kind === "array" ? dereferenceType(ctx, effectiveType.items) : void 0;
|
|
3559
|
+
const isStringArray = arrayItemType?.kind === "primitive" && arrayItemType.primitiveKind === "string";
|
|
3560
|
+
const label = typeLabel(effectiveType);
|
|
3561
|
+
const ck = constraint.constraintKind;
|
|
3562
|
+
switch (ck) {
|
|
3563
|
+
case "minimum":
|
|
3564
|
+
case "maximum":
|
|
3565
|
+
case "exclusiveMinimum":
|
|
3566
|
+
case "exclusiveMaximum":
|
|
3567
|
+
case "multipleOf": {
|
|
3568
|
+
if (!isNumber) {
|
|
3569
|
+
addTypeMismatch(
|
|
3570
|
+
ctx,
|
|
3571
|
+
`Field "${fieldName}": constraint "${ck}" is only valid on number fields, but field type is "${label}"`,
|
|
3572
|
+
constraint.provenance
|
|
3573
|
+
);
|
|
3574
|
+
}
|
|
3575
|
+
break;
|
|
3576
|
+
}
|
|
3577
|
+
case "minLength":
|
|
3578
|
+
case "maxLength":
|
|
3579
|
+
case "pattern": {
|
|
3580
|
+
if (!isString && !isStringArray) {
|
|
3581
|
+
addTypeMismatch(
|
|
3582
|
+
ctx,
|
|
3583
|
+
`Field "${fieldName}": constraint "${ck}" is only valid on string fields or string array items, but field type is "${label}"`,
|
|
3584
|
+
constraint.provenance
|
|
3585
|
+
);
|
|
3586
|
+
}
|
|
3587
|
+
break;
|
|
3588
|
+
}
|
|
3589
|
+
case "minItems":
|
|
3590
|
+
case "maxItems":
|
|
3591
|
+
case "uniqueItems": {
|
|
3592
|
+
if (!isArray) {
|
|
3593
|
+
addTypeMismatch(
|
|
3594
|
+
ctx,
|
|
3595
|
+
`Field "${fieldName}": constraint "${ck}" is only valid on array fields, but field type is "${label}"`,
|
|
3596
|
+
constraint.provenance
|
|
3597
|
+
);
|
|
3598
|
+
}
|
|
3599
|
+
break;
|
|
3600
|
+
}
|
|
3601
|
+
case "allowedMembers": {
|
|
3602
|
+
if (!isEnum) {
|
|
3603
|
+
addTypeMismatch(
|
|
3604
|
+
ctx,
|
|
3605
|
+
`Field "${fieldName}": constraint "allowedMembers" is only valid on enum fields, but field type is "${label}"`,
|
|
3606
|
+
constraint.provenance
|
|
3607
|
+
);
|
|
3608
|
+
}
|
|
3609
|
+
break;
|
|
3610
|
+
}
|
|
3611
|
+
case "const": {
|
|
3612
|
+
const isPrimitiveConstType = effectiveType.kind === "primitive" && ["string", "number", "integer", "bigint", "boolean", "null"].includes(
|
|
3613
|
+
effectiveType.primitiveKind
|
|
3614
|
+
) || effectiveType.kind === "enum";
|
|
3615
|
+
if (!isPrimitiveConstType) {
|
|
3616
|
+
addTypeMismatch(
|
|
3617
|
+
ctx,
|
|
3618
|
+
`Field "${fieldName}": constraint "const" is only valid on primitive or enum fields, but field type is "${label}"`,
|
|
3619
|
+
constraint.provenance
|
|
3620
|
+
);
|
|
3621
|
+
break;
|
|
3622
|
+
}
|
|
3623
|
+
if (effectiveType.kind === "primitive") {
|
|
3624
|
+
const valueType = constraint.value === null ? "null" : Array.isArray(constraint.value) ? "array" : typeof constraint.value;
|
|
3625
|
+
const expectedValueType = effectiveType.primitiveKind === "integer" || effectiveType.primitiveKind === "bigint" ? "number" : effectiveType.primitiveKind;
|
|
3626
|
+
if (valueType !== expectedValueType) {
|
|
3627
|
+
addTypeMismatch(
|
|
3628
|
+
ctx,
|
|
3629
|
+
`Field "${fieldName}": @const value type "${valueType}" is incompatible with field type "${effectiveType.primitiveKind}"`,
|
|
3630
|
+
constraint.provenance
|
|
3631
|
+
);
|
|
3632
|
+
}
|
|
3633
|
+
break;
|
|
3634
|
+
}
|
|
3635
|
+
const memberValues = effectiveType.members.map((member) => member.value);
|
|
3636
|
+
if (!memberValues.some((member) => jsonValueEquals(member, constraint.value))) {
|
|
3637
|
+
addTypeMismatch(
|
|
3638
|
+
ctx,
|
|
3639
|
+
`Field "${fieldName}": @const value ${JSON.stringify(constraint.value)} is not one of the enum members`,
|
|
3640
|
+
constraint.provenance
|
|
3641
|
+
);
|
|
3642
|
+
}
|
|
3643
|
+
break;
|
|
3644
|
+
}
|
|
3645
|
+
case "custom": {
|
|
3646
|
+
checkCustomConstraint(ctx, fieldName, effectiveType, constraint);
|
|
3647
|
+
break;
|
|
3648
|
+
}
|
|
3649
|
+
default: {
|
|
3650
|
+
const _exhaustive = constraint;
|
|
3651
|
+
throw new Error(
|
|
3652
|
+
`Unhandled constraint kind: ${_exhaustive.constraintKind}`
|
|
3653
|
+
);
|
|
3654
|
+
}
|
|
3655
|
+
}
|
|
3656
|
+
}
|
|
3657
|
+
function checkTypeApplicability(ctx, fieldName, type, constraints) {
|
|
3658
|
+
for (const constraint of constraints) {
|
|
3659
|
+
if (constraint.path) {
|
|
3660
|
+
const resolution = resolvePathTargetType(ctx, type, constraint.path.segments);
|
|
3661
|
+
const targetFieldName = formatPathTargetFieldName(fieldName, constraint.path.segments);
|
|
3662
|
+
if (resolution.kind === "missing-property") {
|
|
3663
|
+
addUnknownPathTarget(
|
|
3664
|
+
ctx,
|
|
3665
|
+
`Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" references unknown path segment "${resolution.segment}"`,
|
|
3666
|
+
constraint.provenance
|
|
3667
|
+
);
|
|
3668
|
+
continue;
|
|
3669
|
+
}
|
|
3670
|
+
if (resolution.kind === "unresolvable") {
|
|
3671
|
+
addTypeMismatch(
|
|
3672
|
+
ctx,
|
|
3673
|
+
`Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" is invalid because type "${typeLabel(resolution.type)}" cannot be traversed`,
|
|
3674
|
+
constraint.provenance
|
|
3675
|
+
);
|
|
3676
|
+
continue;
|
|
3677
|
+
}
|
|
3678
|
+
checkConstraintOnType(ctx, targetFieldName, resolution.type, constraint);
|
|
3679
|
+
continue;
|
|
3680
|
+
}
|
|
3681
|
+
checkConstraintOnType(ctx, fieldName, type, constraint);
|
|
3682
|
+
}
|
|
3683
|
+
}
|
|
3684
|
+
function checkCustomConstraint(ctx, fieldName, type, constraint) {
|
|
3685
|
+
if (ctx.extensionRegistry === void 0) return;
|
|
3686
|
+
const registration = ctx.extensionRegistry.findConstraint(constraint.constraintId);
|
|
3687
|
+
if (registration === void 0) {
|
|
3688
|
+
addUnknownExtension(
|
|
3689
|
+
ctx,
|
|
3690
|
+
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not registered in the extension registry`,
|
|
3691
|
+
constraint.provenance
|
|
3692
|
+
);
|
|
3693
|
+
return;
|
|
3694
|
+
}
|
|
3695
|
+
const candidateTypes = collectCustomConstraintCandidateTypes(ctx, type);
|
|
3696
|
+
const normalizedTagName = constraint.provenance.tagName === void 0 ? void 0 : normalizeConstraintTagName2(constraint.provenance.tagName.replace(/^@/, ""));
|
|
3697
|
+
if (normalizedTagName !== void 0) {
|
|
3698
|
+
const tagRegistration = ctx.extensionRegistry.findConstraintTag(normalizedTagName);
|
|
3699
|
+
const extensionId = getExtensionIdFromConstraintId(constraint.constraintId);
|
|
3700
|
+
if (extensionId !== null && tagRegistration?.extensionId === extensionId && tagRegistration.registration.constraintName === registration.constraintName && !candidateTypes.some(
|
|
3701
|
+
(candidateType) => tagRegistration.registration.isApplicableToType?.(candidateType) !== false
|
|
3702
|
+
)) {
|
|
3703
|
+
addTypeMismatch(
|
|
3704
|
+
ctx,
|
|
3705
|
+
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
3706
|
+
constraint.provenance
|
|
3707
|
+
);
|
|
3708
|
+
return;
|
|
3709
|
+
}
|
|
3710
|
+
}
|
|
3711
|
+
if (registration.applicableTypes === null) {
|
|
3712
|
+
if (!candidateTypes.some((candidateType) => registration.isApplicableToType?.(candidateType) !== false)) {
|
|
3713
|
+
addTypeMismatch(
|
|
3714
|
+
ctx,
|
|
3715
|
+
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
3716
|
+
constraint.provenance
|
|
3717
|
+
);
|
|
3718
|
+
}
|
|
3719
|
+
return;
|
|
3720
|
+
}
|
|
3721
|
+
const applicableTypes = registration.applicableTypes;
|
|
3722
|
+
const matchesApplicableType = candidateTypes.some(
|
|
3723
|
+
(candidateType) => applicableTypes.includes(candidateType.kind) && registration.isApplicableToType?.(candidateType) !== false
|
|
3724
|
+
);
|
|
3725
|
+
if (!matchesApplicableType) {
|
|
3726
|
+
addTypeMismatch(
|
|
3727
|
+
ctx,
|
|
3728
|
+
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
3729
|
+
constraint.provenance
|
|
3730
|
+
);
|
|
3731
|
+
}
|
|
3732
|
+
}
|
|
3733
|
+
function validateFieldNode(ctx, field) {
|
|
3734
|
+
validateConstraints(ctx, field.name, field.type, [
|
|
3735
|
+
...collectReferencedTypeConstraints(ctx, field.type),
|
|
3736
|
+
...field.constraints
|
|
3737
|
+
]);
|
|
3738
|
+
if (field.type.kind === "object") {
|
|
3739
|
+
for (const prop of field.type.properties) {
|
|
3740
|
+
validateObjectProperty(ctx, field.name, prop);
|
|
3741
|
+
}
|
|
3742
|
+
}
|
|
3743
|
+
}
|
|
3744
|
+
function validateObjectProperty(ctx, parentName, prop) {
|
|
3745
|
+
const qualifiedName = `${parentName}.${prop.name}`;
|
|
3746
|
+
validateConstraints(ctx, qualifiedName, prop.type, [
|
|
3747
|
+
...collectReferencedTypeConstraints(ctx, prop.type),
|
|
3748
|
+
...prop.constraints
|
|
3749
|
+
]);
|
|
3750
|
+
if (prop.type.kind === "object") {
|
|
3751
|
+
for (const nestedProp of prop.type.properties) {
|
|
3752
|
+
validateObjectProperty(ctx, qualifiedName, nestedProp);
|
|
3753
|
+
}
|
|
3754
|
+
}
|
|
3755
|
+
}
|
|
3756
|
+
function validateConstraints(ctx, name, type, constraints) {
|
|
3757
|
+
checkNumericContradictions(ctx, name, constraints);
|
|
3758
|
+
checkLengthContradictions(ctx, name, constraints);
|
|
3759
|
+
checkAllowedMembersContradiction(ctx, name, constraints);
|
|
3760
|
+
checkConstContradictions(ctx, name, constraints);
|
|
3761
|
+
checkConstraintBroadening(ctx, name, constraints);
|
|
3762
|
+
checkCustomConstraintSemantics(ctx, name, constraints);
|
|
3763
|
+
checkTypeApplicability(ctx, name, type, constraints);
|
|
3764
|
+
}
|
|
3765
|
+
function validateElement(ctx, element) {
|
|
3766
|
+
switch (element.kind) {
|
|
3767
|
+
case "field":
|
|
3768
|
+
validateFieldNode(ctx, element);
|
|
3769
|
+
break;
|
|
3770
|
+
case "group":
|
|
3771
|
+
for (const child of element.elements) {
|
|
3772
|
+
validateElement(ctx, child);
|
|
3773
|
+
}
|
|
3774
|
+
break;
|
|
3775
|
+
case "conditional":
|
|
3776
|
+
for (const child of element.elements) {
|
|
3777
|
+
validateElement(ctx, child);
|
|
3778
|
+
}
|
|
3779
|
+
break;
|
|
3780
|
+
default: {
|
|
3781
|
+
const _exhaustive = element;
|
|
3782
|
+
throw new Error(`Unhandled element kind: ${_exhaustive.kind}`);
|
|
3783
|
+
}
|
|
3784
|
+
}
|
|
3785
|
+
}
|
|
3786
|
+
function validateIR(ir, options) {
|
|
3787
|
+
const ctx = {
|
|
3788
|
+
diagnostics: [],
|
|
3789
|
+
extensionRegistry: options?.extensionRegistry,
|
|
3790
|
+
typeRegistry: ir.typeRegistry
|
|
3791
|
+
};
|
|
3792
|
+
for (const element of ir.elements) {
|
|
3793
|
+
validateElement(ctx, element);
|
|
3794
|
+
}
|
|
3795
|
+
return {
|
|
3796
|
+
diagnostics: ctx.diagnostics,
|
|
3797
|
+
valid: ctx.diagnostics.every((d) => d.severity !== "error")
|
|
3798
|
+
};
|
|
3799
|
+
}
|
|
3800
|
+
var init_constraint_validator = __esm({
|
|
3801
|
+
"src/validate/constraint-validator.ts"() {
|
|
3802
|
+
"use strict";
|
|
3803
|
+
}
|
|
3804
|
+
});
|
|
3805
|
+
|
|
3806
|
+
// src/validate/index.ts
|
|
3807
|
+
var init_validate = __esm({
|
|
3808
|
+
"src/validate/index.ts"() {
|
|
3809
|
+
"use strict";
|
|
3810
|
+
init_constraint_validator();
|
|
3811
|
+
}
|
|
3812
|
+
});
|
|
3813
|
+
|
|
2886
3814
|
// src/generators/class-schema.ts
|
|
2887
3815
|
function generateClassSchemas(analysis, source, options) {
|
|
2888
3816
|
const ir = canonicalizeTSDoc(analysis, source);
|
|
3817
|
+
const validationResult = validateIR(ir, {
|
|
3818
|
+
...options?.extensionRegistry !== void 0 && {
|
|
3819
|
+
extensionRegistry: options.extensionRegistry
|
|
3820
|
+
},
|
|
3821
|
+
...options?.vendorPrefix !== void 0 && { vendorPrefix: options.vendorPrefix }
|
|
3822
|
+
});
|
|
3823
|
+
if (!validationResult.valid) {
|
|
3824
|
+
throw new Error(formatValidationError(validationResult.diagnostics));
|
|
3825
|
+
}
|
|
2889
3826
|
return {
|
|
2890
3827
|
jsonSchema: generateJsonSchemaFromIR(ir, options),
|
|
2891
3828
|
uiSchema: generateUiSchemaFromIR(ir)
|
|
2892
3829
|
};
|
|
2893
3830
|
}
|
|
3831
|
+
function formatValidationError(diagnostics) {
|
|
3832
|
+
const lines = diagnostics.map((diagnostic) => {
|
|
3833
|
+
const primary = formatLocation(diagnostic.primaryLocation);
|
|
3834
|
+
const related = diagnostic.relatedLocations.length > 0 ? ` [related: ${diagnostic.relatedLocations.map(formatLocation).join(", ")}]` : "";
|
|
3835
|
+
return `${diagnostic.code}: ${diagnostic.message} (${primary})${related}`;
|
|
3836
|
+
});
|
|
3837
|
+
return `FormSpec validation failed:
|
|
3838
|
+
${lines.map((line) => `- ${line}`).join("\n")}`;
|
|
3839
|
+
}
|
|
3840
|
+
function formatLocation(location) {
|
|
3841
|
+
return `${location.file}:${String(location.line)}:${String(location.column)}`;
|
|
3842
|
+
}
|
|
2894
3843
|
function generateSchemasFromClass(options) {
|
|
2895
3844
|
const ctx = createProgramContext(options.filePath);
|
|
2896
3845
|
const classDecl = findClassByName(ctx.sourceFile, options.className);
|
|
@@ -2913,44 +3862,12 @@ function generateSchemasFromClass(options) {
|
|
|
2913
3862
|
);
|
|
2914
3863
|
}
|
|
2915
3864
|
function generateSchemas(options) {
|
|
2916
|
-
const
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
const analysis = analyzeClassToIR(
|
|
2921
|
-
classDecl,
|
|
2922
|
-
ctx.checker,
|
|
2923
|
-
options.filePath,
|
|
2924
|
-
options.extensionRegistry
|
|
2925
|
-
);
|
|
2926
|
-
return generateClassSchemas(analysis, source, options);
|
|
2927
|
-
}
|
|
2928
|
-
const interfaceDecl = findInterfaceByName(ctx.sourceFile, options.typeName);
|
|
2929
|
-
if (interfaceDecl) {
|
|
2930
|
-
const analysis = analyzeInterfaceToIR(
|
|
2931
|
-
interfaceDecl,
|
|
2932
|
-
ctx.checker,
|
|
2933
|
-
options.filePath,
|
|
2934
|
-
options.extensionRegistry
|
|
2935
|
-
);
|
|
2936
|
-
return generateClassSchemas(analysis, source, options);
|
|
2937
|
-
}
|
|
2938
|
-
const typeAlias = findTypeAliasByName(ctx.sourceFile, options.typeName);
|
|
2939
|
-
if (typeAlias) {
|
|
2940
|
-
const result = analyzeTypeAliasToIR(
|
|
2941
|
-
typeAlias,
|
|
2942
|
-
ctx.checker,
|
|
2943
|
-
options.filePath,
|
|
2944
|
-
options.extensionRegistry
|
|
2945
|
-
);
|
|
2946
|
-
if (result.ok) {
|
|
2947
|
-
return generateClassSchemas(result.analysis, source, options);
|
|
2948
|
-
}
|
|
2949
|
-
throw new Error(result.error);
|
|
2950
|
-
}
|
|
2951
|
-
throw new Error(
|
|
2952
|
-
`Type "${options.typeName}" not found as a class, interface, or type alias in ${options.filePath}`
|
|
3865
|
+
const analysis = analyzeNamedTypeToIR(
|
|
3866
|
+
options.filePath,
|
|
3867
|
+
options.typeName,
|
|
3868
|
+
options.extensionRegistry
|
|
2953
3869
|
);
|
|
3870
|
+
return generateClassSchemas(analysis, { file: options.filePath }, options);
|
|
2954
3871
|
}
|
|
2955
3872
|
var init_class_schema = __esm({
|
|
2956
3873
|
"src/generators/class-schema.ts"() {
|
|
@@ -2960,13 +3877,14 @@ var init_class_schema = __esm({
|
|
|
2960
3877
|
init_canonicalize();
|
|
2961
3878
|
init_ir_generator();
|
|
2962
3879
|
init_ir_generator2();
|
|
3880
|
+
init_validate();
|
|
2963
3881
|
}
|
|
2964
3882
|
});
|
|
2965
3883
|
|
|
2966
3884
|
// src/generators/mixed-authoring.ts
|
|
2967
3885
|
function buildMixedAuthoringSchemas(options) {
|
|
2968
3886
|
const { filePath, typeName, overlays, ...schemaOptions } = options;
|
|
2969
|
-
const analysis =
|
|
3887
|
+
const analysis = analyzeNamedTypeToIR(filePath, typeName, schemaOptions.extensionRegistry);
|
|
2970
3888
|
const composedAnalysis = composeAnalysisWithOverlays(analysis, overlays);
|
|
2971
3889
|
const ir = canonicalizeTSDoc(composedAnalysis, { file: filePath });
|
|
2972
3890
|
return {
|
|
@@ -2974,29 +3892,6 @@ function buildMixedAuthoringSchemas(options) {
|
|
|
2974
3892
|
uiSchema: generateUiSchemaFromIR(ir)
|
|
2975
3893
|
};
|
|
2976
3894
|
}
|
|
2977
|
-
function analyzeNamedType(filePath, typeName, extensionRegistry) {
|
|
2978
|
-
const ctx = createProgramContext(filePath);
|
|
2979
|
-
const source = { file: filePath };
|
|
2980
|
-
const classDecl = findClassByName(ctx.sourceFile, typeName);
|
|
2981
|
-
if (classDecl !== null) {
|
|
2982
|
-
return analyzeClassToIR(classDecl, ctx.checker, source.file, extensionRegistry);
|
|
2983
|
-
}
|
|
2984
|
-
const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
|
|
2985
|
-
if (interfaceDecl !== null) {
|
|
2986
|
-
return analyzeInterfaceToIR(interfaceDecl, ctx.checker, source.file, extensionRegistry);
|
|
2987
|
-
}
|
|
2988
|
-
const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
|
|
2989
|
-
if (typeAlias !== null) {
|
|
2990
|
-
const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, source.file, extensionRegistry);
|
|
2991
|
-
if (result.ok) {
|
|
2992
|
-
return result.analysis;
|
|
2993
|
-
}
|
|
2994
|
-
throw new Error(result.error);
|
|
2995
|
-
}
|
|
2996
|
-
throw new Error(
|
|
2997
|
-
`Type "${typeName}" not found as a class, interface, or type alias in ${filePath}`
|
|
2998
|
-
);
|
|
2999
|
-
}
|
|
3000
3895
|
function composeAnalysisWithOverlays(analysis, overlays) {
|
|
3001
3896
|
const overlayIR = canonicalizeChainDSL(overlays);
|
|
3002
3897
|
const overlayFields = collectOverlayFields(overlayIR.elements);
|
|
@@ -3168,7 +4063,6 @@ var init_mixed_authoring = __esm({
|
|
|
3168
4063
|
init_ir_generator2();
|
|
3169
4064
|
init_canonicalize();
|
|
3170
4065
|
init_program();
|
|
3171
|
-
init_class_analyzer();
|
|
3172
4066
|
}
|
|
3173
4067
|
});
|
|
3174
4068
|
|