@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/index.cjs
CHANGED
|
@@ -82,6 +82,7 @@ function canonicalizeChainDSL(form) {
|
|
|
82
82
|
kind: "form-ir",
|
|
83
83
|
irVersion: import_core.IR_VERSION,
|
|
84
84
|
elements: canonicalizeElements(form.elements),
|
|
85
|
+
rootAnnotations: [],
|
|
85
86
|
typeRegistry: {},
|
|
86
87
|
provenance: CHAIN_DSL_PROVENANCE
|
|
87
88
|
};
|
|
@@ -387,6 +388,7 @@ function canonicalizeTSDoc(analysis, source) {
|
|
|
387
388
|
irVersion: import_core2.IR_VERSION,
|
|
388
389
|
elements,
|
|
389
390
|
typeRegistry: analysis.typeRegistry,
|
|
391
|
+
...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { rootAnnotations: analysis.annotations },
|
|
390
392
|
...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { annotations: analysis.annotations },
|
|
391
393
|
provenance
|
|
392
394
|
};
|
|
@@ -464,6 +466,9 @@ function generateJsonSchemaFromIR(ir, options) {
|
|
|
464
466
|
const ctx = makeContext(options);
|
|
465
467
|
for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
|
|
466
468
|
ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
|
|
469
|
+
if (typeDef.constraints && typeDef.constraints.length > 0) {
|
|
470
|
+
applyConstraints(ctx.defs[name], typeDef.constraints, ctx);
|
|
471
|
+
}
|
|
467
472
|
if (typeDef.annotations && typeDef.annotations.length > 0) {
|
|
468
473
|
applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
|
|
469
474
|
}
|
|
@@ -632,7 +637,9 @@ function generateTypeNode(type, ctx) {
|
|
|
632
637
|
}
|
|
633
638
|
}
|
|
634
639
|
function generatePrimitiveType(type) {
|
|
635
|
-
return {
|
|
640
|
+
return {
|
|
641
|
+
type: type.primitiveKind === "integer" || type.primitiveKind === "bigint" ? "integer" : type.primitiveKind
|
|
642
|
+
};
|
|
636
643
|
}
|
|
637
644
|
function generateEnumType(type) {
|
|
638
645
|
const hasDisplayNames = type.members.some((m) => m.displayName !== void 0);
|
|
@@ -805,7 +812,7 @@ function applyAnnotations(schema, annotations, ctx) {
|
|
|
805
812
|
case "deprecated":
|
|
806
813
|
schema.deprecated = true;
|
|
807
814
|
if (annotation.message !== void 0 && annotation.message !== "") {
|
|
808
|
-
schema[
|
|
815
|
+
schema[`${ctx.vendorPrefix}-deprecation-description`] = annotation.message;
|
|
809
816
|
}
|
|
810
817
|
break;
|
|
811
818
|
case "placeholder":
|
|
@@ -1263,85 +1270,17 @@ var jsonSchema7Schema = import_zod3.z.lazy(
|
|
|
1263
1270
|
);
|
|
1264
1271
|
|
|
1265
1272
|
// src/analyzer/program.ts
|
|
1266
|
-
var
|
|
1273
|
+
var ts4 = __toESM(require("typescript"), 1);
|
|
1267
1274
|
var path = __toESM(require("path"), 1);
|
|
1268
|
-
function createProgramContext(filePath) {
|
|
1269
|
-
const absolutePath = path.resolve(filePath);
|
|
1270
|
-
const fileDir = path.dirname(absolutePath);
|
|
1271
|
-
const configPath = ts.findConfigFile(fileDir, ts.sys.fileExists.bind(ts.sys), "tsconfig.json");
|
|
1272
|
-
let compilerOptions;
|
|
1273
|
-
let fileNames;
|
|
1274
|
-
if (configPath) {
|
|
1275
|
-
const configFile = ts.readConfigFile(configPath, ts.sys.readFile.bind(ts.sys));
|
|
1276
|
-
if (configFile.error) {
|
|
1277
|
-
throw new Error(
|
|
1278
|
-
`Error reading tsconfig.json: ${ts.flattenDiagnosticMessageText(configFile.error.messageText, "\n")}`
|
|
1279
|
-
);
|
|
1280
|
-
}
|
|
1281
|
-
const parsed = ts.parseJsonConfigFileContent(
|
|
1282
|
-
configFile.config,
|
|
1283
|
-
ts.sys,
|
|
1284
|
-
path.dirname(configPath)
|
|
1285
|
-
);
|
|
1286
|
-
if (parsed.errors.length > 0) {
|
|
1287
|
-
const errorMessages = parsed.errors.map((e) => ts.flattenDiagnosticMessageText(e.messageText, "\n")).join("\n");
|
|
1288
|
-
throw new Error(`Error parsing tsconfig.json: ${errorMessages}`);
|
|
1289
|
-
}
|
|
1290
|
-
compilerOptions = parsed.options;
|
|
1291
|
-
fileNames = parsed.fileNames.includes(absolutePath) ? parsed.fileNames : [...parsed.fileNames, absolutePath];
|
|
1292
|
-
} else {
|
|
1293
|
-
compilerOptions = {
|
|
1294
|
-
target: ts.ScriptTarget.ES2022,
|
|
1295
|
-
module: ts.ModuleKind.NodeNext,
|
|
1296
|
-
moduleResolution: ts.ModuleResolutionKind.NodeNext,
|
|
1297
|
-
strict: true,
|
|
1298
|
-
skipLibCheck: true,
|
|
1299
|
-
declaration: true
|
|
1300
|
-
};
|
|
1301
|
-
fileNames = [absolutePath];
|
|
1302
|
-
}
|
|
1303
|
-
const program = ts.createProgram(fileNames, compilerOptions);
|
|
1304
|
-
const sourceFile = program.getSourceFile(absolutePath);
|
|
1305
|
-
if (!sourceFile) {
|
|
1306
|
-
throw new Error(`Could not find source file: ${absolutePath}`);
|
|
1307
|
-
}
|
|
1308
|
-
return {
|
|
1309
|
-
program,
|
|
1310
|
-
checker: program.getTypeChecker(),
|
|
1311
|
-
sourceFile
|
|
1312
|
-
};
|
|
1313
|
-
}
|
|
1314
|
-
function findNodeByName(sourceFile, name, predicate, getName) {
|
|
1315
|
-
let result = null;
|
|
1316
|
-
function visit(node) {
|
|
1317
|
-
if (result) return;
|
|
1318
|
-
if (predicate(node) && getName(node) === name) {
|
|
1319
|
-
result = node;
|
|
1320
|
-
return;
|
|
1321
|
-
}
|
|
1322
|
-
ts.forEachChild(node, visit);
|
|
1323
|
-
}
|
|
1324
|
-
visit(sourceFile);
|
|
1325
|
-
return result;
|
|
1326
|
-
}
|
|
1327
|
-
function findClassByName(sourceFile, className) {
|
|
1328
|
-
return findNodeByName(sourceFile, className, ts.isClassDeclaration, (n) => n.name?.text);
|
|
1329
|
-
}
|
|
1330
|
-
function findInterfaceByName(sourceFile, interfaceName) {
|
|
1331
|
-
return findNodeByName(sourceFile, interfaceName, ts.isInterfaceDeclaration, (n) => n.name.text);
|
|
1332
|
-
}
|
|
1333
|
-
function findTypeAliasByName(sourceFile, aliasName) {
|
|
1334
|
-
return findNodeByName(sourceFile, aliasName, ts.isTypeAliasDeclaration, (n) => n.name.text);
|
|
1335
|
-
}
|
|
1336
1275
|
|
|
1337
1276
|
// src/analyzer/class-analyzer.ts
|
|
1338
|
-
var
|
|
1277
|
+
var ts3 = __toESM(require("typescript"), 1);
|
|
1339
1278
|
|
|
1340
1279
|
// src/analyzer/jsdoc-constraints.ts
|
|
1341
|
-
var
|
|
1280
|
+
var ts2 = __toESM(require("typescript"), 1);
|
|
1342
1281
|
|
|
1343
1282
|
// src/analyzer/tsdoc-parser.ts
|
|
1344
|
-
var
|
|
1283
|
+
var ts = __toESM(require("typescript"), 1);
|
|
1345
1284
|
var import_tsdoc = require("@microsoft/tsdoc");
|
|
1346
1285
|
var import_core3 = require("@formspec/core");
|
|
1347
1286
|
|
|
@@ -1427,10 +1366,10 @@ function parseTSDocTags(node, file = "", options) {
|
|
|
1427
1366
|
let placeholderProvenance;
|
|
1428
1367
|
const sourceFile = node.getSourceFile();
|
|
1429
1368
|
const sourceText = sourceFile.getFullText();
|
|
1430
|
-
const commentRanges =
|
|
1369
|
+
const commentRanges = ts.getLeadingCommentRanges(sourceText, node.getFullStart());
|
|
1431
1370
|
if (commentRanges) {
|
|
1432
1371
|
for (const range of commentRanges) {
|
|
1433
|
-
if (range.kind !==
|
|
1372
|
+
if (range.kind !== ts.SyntaxKind.MultiLineCommentTrivia) {
|
|
1434
1373
|
continue;
|
|
1435
1374
|
}
|
|
1436
1375
|
const commentText = sourceText.substring(range.pos, range.end);
|
|
@@ -1448,26 +1387,31 @@ function parseTSDocTags(node, file = "", options) {
|
|
|
1448
1387
|
const text2 = extractBlockText(block).trim();
|
|
1449
1388
|
if (text2 === "") continue;
|
|
1450
1389
|
const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
displayName
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1390
|
+
switch (tagName) {
|
|
1391
|
+
case "displayName":
|
|
1392
|
+
if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
|
|
1393
|
+
displayName = text2;
|
|
1394
|
+
displayNameProvenance = provenance2;
|
|
1395
|
+
}
|
|
1396
|
+
break;
|
|
1397
|
+
case "format":
|
|
1398
|
+
annotations.push({
|
|
1399
|
+
kind: "annotation",
|
|
1400
|
+
annotationKind: "format",
|
|
1401
|
+
value: text2,
|
|
1402
|
+
provenance: provenance2
|
|
1403
|
+
});
|
|
1404
|
+
break;
|
|
1405
|
+
case "description":
|
|
1465
1406
|
description = text2;
|
|
1466
1407
|
descriptionProvenance = provenance2;
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1408
|
+
break;
|
|
1409
|
+
case "placeholder":
|
|
1410
|
+
if (placeholder === void 0) {
|
|
1411
|
+
placeholder = text2;
|
|
1412
|
+
placeholderProvenance = provenance2;
|
|
1413
|
+
}
|
|
1414
|
+
break;
|
|
1471
1415
|
}
|
|
1472
1416
|
continue;
|
|
1473
1417
|
}
|
|
@@ -1497,6 +1441,13 @@ function parseTSDocTags(node, file = "", options) {
|
|
|
1497
1441
|
descriptionProvenance = provenanceForComment(range, sourceFile, file, "remarks");
|
|
1498
1442
|
}
|
|
1499
1443
|
}
|
|
1444
|
+
if (description === void 0) {
|
|
1445
|
+
const summary = extractPlainText(docComment.summarySection).trim();
|
|
1446
|
+
if (summary !== "") {
|
|
1447
|
+
description = summary;
|
|
1448
|
+
descriptionProvenance = provenanceForComment(range, sourceFile, file, "summary");
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1500
1451
|
}
|
|
1501
1452
|
}
|
|
1502
1453
|
if (displayName !== void 0 && displayNameProvenance !== void 0) {
|
|
@@ -1523,7 +1474,7 @@ function parseTSDocTags(node, file = "", options) {
|
|
|
1523
1474
|
provenance: placeholderProvenance
|
|
1524
1475
|
});
|
|
1525
1476
|
}
|
|
1526
|
-
const jsDocTagsAll =
|
|
1477
|
+
const jsDocTagsAll = ts.getJSDocTags(node);
|
|
1527
1478
|
for (const tag of jsDocTagsAll) {
|
|
1528
1479
|
const tagName = (0, import_core3.normalizeConstraintTagName)(tag.tagName.text);
|
|
1529
1480
|
if (!TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
@@ -1546,7 +1497,7 @@ function parseTSDocTags(node, file = "", options) {
|
|
|
1546
1497
|
function extractDisplayNameMetadata(node) {
|
|
1547
1498
|
let displayName;
|
|
1548
1499
|
const memberDisplayNames = /* @__PURE__ */ new Map();
|
|
1549
|
-
for (const tag of
|
|
1500
|
+
for (const tag of ts.getJSDocTags(node)) {
|
|
1550
1501
|
const tagName = (0, import_core3.normalizeConstraintTagName)(tag.tagName.text);
|
|
1551
1502
|
if (tagName !== "displayName") continue;
|
|
1552
1503
|
const commentText = getTagCommentText(tag);
|
|
@@ -1567,11 +1518,11 @@ function extractDisplayNameMetadata(node) {
|
|
|
1567
1518
|
}
|
|
1568
1519
|
function extractPathTarget(text) {
|
|
1569
1520
|
const trimmed = text.trimStart();
|
|
1570
|
-
const match = /^:([a-zA-Z_]\w*)
|
|
1571
|
-
if (!match?.[1]
|
|
1521
|
+
const match = /^:([a-zA-Z_]\w*)(?:\s+([\s\S]*))?$/.exec(trimmed);
|
|
1522
|
+
if (!match?.[1]) return null;
|
|
1572
1523
|
return {
|
|
1573
1524
|
path: { segments: [match[1]] },
|
|
1574
|
-
remainingText: match[2]
|
|
1525
|
+
remainingText: match[2] ?? ""
|
|
1575
1526
|
};
|
|
1576
1527
|
}
|
|
1577
1528
|
function extractBlockText(block) {
|
|
@@ -1834,7 +1785,7 @@ function getTagCommentText(tag) {
|
|
|
1834
1785
|
if (typeof tag.comment === "string") {
|
|
1835
1786
|
return tag.comment;
|
|
1836
1787
|
}
|
|
1837
|
-
return
|
|
1788
|
+
return ts.getTextOfJSDocComment(tag.comment);
|
|
1838
1789
|
}
|
|
1839
1790
|
|
|
1840
1791
|
// src/analyzer/jsdoc-constraints.ts
|
|
@@ -1849,18 +1800,18 @@ function extractJSDocAnnotationNodes(node, file = "", options) {
|
|
|
1849
1800
|
function extractDefaultValueAnnotation(initializer, file = "") {
|
|
1850
1801
|
if (!initializer) return null;
|
|
1851
1802
|
let value;
|
|
1852
|
-
if (
|
|
1803
|
+
if (ts2.isStringLiteral(initializer)) {
|
|
1853
1804
|
value = initializer.text;
|
|
1854
|
-
} else if (
|
|
1805
|
+
} else if (ts2.isNumericLiteral(initializer)) {
|
|
1855
1806
|
value = Number(initializer.text);
|
|
1856
|
-
} else if (initializer.kind ===
|
|
1807
|
+
} else if (initializer.kind === ts2.SyntaxKind.TrueKeyword) {
|
|
1857
1808
|
value = true;
|
|
1858
|
-
} else if (initializer.kind ===
|
|
1809
|
+
} else if (initializer.kind === ts2.SyntaxKind.FalseKeyword) {
|
|
1859
1810
|
value = false;
|
|
1860
|
-
} else if (initializer.kind ===
|
|
1811
|
+
} else if (initializer.kind === ts2.SyntaxKind.NullKeyword) {
|
|
1861
1812
|
value = null;
|
|
1862
|
-
} else if (
|
|
1863
|
-
if (initializer.operator ===
|
|
1813
|
+
} else if (ts2.isPrefixUnaryExpression(initializer)) {
|
|
1814
|
+
if (initializer.operator === ts2.SyntaxKind.MinusToken && ts2.isNumericLiteral(initializer.operand)) {
|
|
1864
1815
|
value = -Number(initializer.operand.text);
|
|
1865
1816
|
}
|
|
1866
1817
|
}
|
|
@@ -1882,10 +1833,10 @@ function extractDefaultValueAnnotation(initializer, file = "") {
|
|
|
1882
1833
|
|
|
1883
1834
|
// src/analyzer/class-analyzer.ts
|
|
1884
1835
|
function isObjectType(type) {
|
|
1885
|
-
return !!(type.flags &
|
|
1836
|
+
return !!(type.flags & ts3.TypeFlags.Object);
|
|
1886
1837
|
}
|
|
1887
1838
|
function isTypeReference(type) {
|
|
1888
|
-
return !!(type.flags &
|
|
1839
|
+
return !!(type.flags & ts3.TypeFlags.Object) && !!(type.objectFlags & ts3.ObjectFlags.Reference);
|
|
1889
1840
|
}
|
|
1890
1841
|
var RESOLVING_TYPE_PLACEHOLDER = {
|
|
1891
1842
|
kind: "object",
|
|
@@ -1915,7 +1866,7 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
|
|
|
1915
1866
|
const instanceMethods = [];
|
|
1916
1867
|
const staticMethods = [];
|
|
1917
1868
|
for (const member of classDecl.members) {
|
|
1918
|
-
if (
|
|
1869
|
+
if (ts3.isPropertyDeclaration(member)) {
|
|
1919
1870
|
const fieldNode = analyzeFieldToIR(
|
|
1920
1871
|
member,
|
|
1921
1872
|
checker,
|
|
@@ -1928,10 +1879,10 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
|
|
|
1928
1879
|
fields.push(fieldNode);
|
|
1929
1880
|
fieldLayouts.push({});
|
|
1930
1881
|
}
|
|
1931
|
-
} else if (
|
|
1882
|
+
} else if (ts3.isMethodDeclaration(member)) {
|
|
1932
1883
|
const methodInfo = analyzeMethod(member, checker);
|
|
1933
1884
|
if (methodInfo) {
|
|
1934
|
-
const isStatic = member.modifiers?.some((m) => m.kind ===
|
|
1885
|
+
const isStatic = member.modifiers?.some((m) => m.kind === ts3.SyntaxKind.StaticKeyword);
|
|
1935
1886
|
if (isStatic) {
|
|
1936
1887
|
staticMethods.push(methodInfo);
|
|
1937
1888
|
} else {
|
|
@@ -1961,7 +1912,7 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
|
|
|
1961
1912
|
);
|
|
1962
1913
|
const visiting = /* @__PURE__ */ new Set();
|
|
1963
1914
|
for (const member of interfaceDecl.members) {
|
|
1964
|
-
if (
|
|
1915
|
+
if (ts3.isPropertySignature(member)) {
|
|
1965
1916
|
const fieldNode = analyzeInterfacePropertyToIR(
|
|
1966
1917
|
member,
|
|
1967
1918
|
checker,
|
|
@@ -1987,10 +1938,10 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
|
|
|
1987
1938
|
};
|
|
1988
1939
|
}
|
|
1989
1940
|
function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry) {
|
|
1990
|
-
if (!
|
|
1941
|
+
if (!ts3.isTypeLiteralNode(typeAlias.type)) {
|
|
1991
1942
|
const sourceFile = typeAlias.getSourceFile();
|
|
1992
1943
|
const { line } = sourceFile.getLineAndCharacterOfPosition(typeAlias.getStart());
|
|
1993
|
-
const kindDesc =
|
|
1944
|
+
const kindDesc = ts3.SyntaxKind[typeAlias.type.kind] ?? "unknown";
|
|
1994
1945
|
return {
|
|
1995
1946
|
ok: false,
|
|
1996
1947
|
error: `Type alias "${typeAlias.name.text}" at line ${String(line + 1)} is not an object type literal (found ${kindDesc})`
|
|
@@ -2006,7 +1957,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
|
|
|
2006
1957
|
);
|
|
2007
1958
|
const visiting = /* @__PURE__ */ new Set();
|
|
2008
1959
|
for (const member of typeAlias.type.members) {
|
|
2009
|
-
if (
|
|
1960
|
+
if (ts3.isPropertySignature(member)) {
|
|
2010
1961
|
const fieldNode = analyzeInterfacePropertyToIR(
|
|
2011
1962
|
member,
|
|
2012
1963
|
checker,
|
|
@@ -2034,7 +1985,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
|
|
|
2034
1985
|
};
|
|
2035
1986
|
}
|
|
2036
1987
|
function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2037
|
-
if (!
|
|
1988
|
+
if (!ts3.isIdentifier(prop.name)) {
|
|
2038
1989
|
return null;
|
|
2039
1990
|
}
|
|
2040
1991
|
const name = prop.name.text;
|
|
@@ -2051,12 +2002,14 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extension
|
|
|
2051
2002
|
extensionRegistry
|
|
2052
2003
|
);
|
|
2053
2004
|
const constraints = [];
|
|
2054
|
-
if (prop.type) {
|
|
2005
|
+
if (prop.type && !shouldEmitPrimitiveAliasDefinition(prop.type, checker)) {
|
|
2055
2006
|
constraints.push(
|
|
2056
2007
|
...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
|
|
2057
2008
|
);
|
|
2058
2009
|
}
|
|
2059
|
-
constraints.push(
|
|
2010
|
+
constraints.push(
|
|
2011
|
+
...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
2012
|
+
);
|
|
2060
2013
|
let annotations = [];
|
|
2061
2014
|
annotations.push(
|
|
2062
2015
|
...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
@@ -2077,7 +2030,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extension
|
|
|
2077
2030
|
};
|
|
2078
2031
|
}
|
|
2079
2032
|
function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2080
|
-
if (!
|
|
2033
|
+
if (!ts3.isIdentifier(prop.name)) {
|
|
2081
2034
|
return null;
|
|
2082
2035
|
}
|
|
2083
2036
|
const name = prop.name.text;
|
|
@@ -2094,12 +2047,14 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
|
|
|
2094
2047
|
extensionRegistry
|
|
2095
2048
|
);
|
|
2096
2049
|
const constraints = [];
|
|
2097
|
-
if (prop.type) {
|
|
2050
|
+
if (prop.type && !shouldEmitPrimitiveAliasDefinition(prop.type, checker)) {
|
|
2098
2051
|
constraints.push(
|
|
2099
2052
|
...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
|
|
2100
2053
|
);
|
|
2101
2054
|
}
|
|
2102
|
-
constraints.push(
|
|
2055
|
+
constraints.push(
|
|
2056
|
+
...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
2057
|
+
);
|
|
2103
2058
|
let annotations = [];
|
|
2104
2059
|
annotations.push(
|
|
2105
2060
|
...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
@@ -2188,7 +2143,7 @@ function resolveRegisteredCustomType(sourceNode, extensionRegistry, checker) {
|
|
|
2188
2143
|
return resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker);
|
|
2189
2144
|
}
|
|
2190
2145
|
function resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker) {
|
|
2191
|
-
if (
|
|
2146
|
+
if (ts3.isParenthesizedTypeNode(typeNode)) {
|
|
2192
2147
|
return resolveRegisteredCustomTypeFromTypeNode(typeNode.type, extensionRegistry, checker);
|
|
2193
2148
|
}
|
|
2194
2149
|
const typeName = getTypeNodeRegistrationName(typeNode);
|
|
@@ -2203,8 +2158,8 @@ function resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, ch
|
|
|
2203
2158
|
payload: null
|
|
2204
2159
|
};
|
|
2205
2160
|
}
|
|
2206
|
-
if (
|
|
2207
|
-
const aliasDecl = checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(
|
|
2161
|
+
if (ts3.isTypeReferenceNode(typeNode) && ts3.isIdentifier(typeNode.typeName)) {
|
|
2162
|
+
const aliasDecl = checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
2208
2163
|
if (aliasDecl !== void 0) {
|
|
2209
2164
|
return resolveRegisteredCustomTypeFromTypeNode(aliasDecl.type, extensionRegistry, checker);
|
|
2210
2165
|
}
|
|
@@ -2212,22 +2167,22 @@ function resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, ch
|
|
|
2212
2167
|
return null;
|
|
2213
2168
|
}
|
|
2214
2169
|
function extractTypeNodeFromSource(sourceNode) {
|
|
2215
|
-
if (
|
|
2170
|
+
if (ts3.isPropertyDeclaration(sourceNode) || ts3.isPropertySignature(sourceNode) || ts3.isParameter(sourceNode) || ts3.isTypeAliasDeclaration(sourceNode)) {
|
|
2216
2171
|
return sourceNode.type;
|
|
2217
2172
|
}
|
|
2218
|
-
if (
|
|
2173
|
+
if (ts3.isTypeNode(sourceNode)) {
|
|
2219
2174
|
return sourceNode;
|
|
2220
2175
|
}
|
|
2221
2176
|
return void 0;
|
|
2222
2177
|
}
|
|
2223
2178
|
function getTypeNodeRegistrationName(typeNode) {
|
|
2224
|
-
if (
|
|
2225
|
-
return
|
|
2179
|
+
if (ts3.isTypeReferenceNode(typeNode)) {
|
|
2180
|
+
return ts3.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : typeNode.typeName.right.text;
|
|
2226
2181
|
}
|
|
2227
|
-
if (
|
|
2182
|
+
if (ts3.isParenthesizedTypeNode(typeNode)) {
|
|
2228
2183
|
return getTypeNodeRegistrationName(typeNode.type);
|
|
2229
2184
|
}
|
|
2230
|
-
if (typeNode.kind ===
|
|
2185
|
+
if (typeNode.kind === ts3.SyntaxKind.BigIntKeyword || typeNode.kind === ts3.SyntaxKind.StringKeyword || typeNode.kind === ts3.SyntaxKind.NumberKeyword || typeNode.kind === ts3.SyntaxKind.BooleanKeyword) {
|
|
2231
2186
|
return typeNode.getText();
|
|
2232
2187
|
}
|
|
2233
2188
|
return null;
|
|
@@ -2237,19 +2192,34 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
|
|
|
2237
2192
|
if (customType) {
|
|
2238
2193
|
return customType;
|
|
2239
2194
|
}
|
|
2240
|
-
|
|
2195
|
+
const primitiveAlias = tryResolveNamedPrimitiveAlias(
|
|
2196
|
+
type,
|
|
2197
|
+
checker,
|
|
2198
|
+
file,
|
|
2199
|
+
typeRegistry,
|
|
2200
|
+
visiting,
|
|
2201
|
+
sourceNode,
|
|
2202
|
+
extensionRegistry
|
|
2203
|
+
);
|
|
2204
|
+
if (primitiveAlias) {
|
|
2205
|
+
return primitiveAlias;
|
|
2206
|
+
}
|
|
2207
|
+
if (type.flags & ts3.TypeFlags.String) {
|
|
2241
2208
|
return { kind: "primitive", primitiveKind: "string" };
|
|
2242
2209
|
}
|
|
2243
|
-
if (type.flags &
|
|
2210
|
+
if (type.flags & ts3.TypeFlags.Number) {
|
|
2244
2211
|
return { kind: "primitive", primitiveKind: "number" };
|
|
2245
2212
|
}
|
|
2246
|
-
if (type.flags &
|
|
2213
|
+
if (type.flags & (ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral)) {
|
|
2214
|
+
return { kind: "primitive", primitiveKind: "bigint" };
|
|
2215
|
+
}
|
|
2216
|
+
if (type.flags & ts3.TypeFlags.Boolean) {
|
|
2247
2217
|
return { kind: "primitive", primitiveKind: "boolean" };
|
|
2248
2218
|
}
|
|
2249
|
-
if (type.flags &
|
|
2219
|
+
if (type.flags & ts3.TypeFlags.Null) {
|
|
2250
2220
|
return { kind: "primitive", primitiveKind: "null" };
|
|
2251
2221
|
}
|
|
2252
|
-
if (type.flags &
|
|
2222
|
+
if (type.flags & ts3.TypeFlags.Undefined) {
|
|
2253
2223
|
return { kind: "primitive", primitiveKind: "null" };
|
|
2254
2224
|
}
|
|
2255
2225
|
if (type.isStringLiteral()) {
|
|
@@ -2291,6 +2261,75 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
|
|
|
2291
2261
|
}
|
|
2292
2262
|
return { kind: "primitive", primitiveKind: "string" };
|
|
2293
2263
|
}
|
|
2264
|
+
function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
2265
|
+
if (!(type.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null))) {
|
|
2266
|
+
return null;
|
|
2267
|
+
}
|
|
2268
|
+
const aliasDecl = type.aliasSymbol?.declarations?.find(ts3.isTypeAliasDeclaration) ?? getReferencedTypeAliasDeclaration(sourceNode, checker);
|
|
2269
|
+
if (!aliasDecl) {
|
|
2270
|
+
return null;
|
|
2271
|
+
}
|
|
2272
|
+
const aliasName = aliasDecl.name.text;
|
|
2273
|
+
if (!typeRegistry[aliasName]) {
|
|
2274
|
+
const aliasType = checker.getTypeFromTypeNode(aliasDecl.type);
|
|
2275
|
+
const constraints = [
|
|
2276
|
+
...extractJSDocConstraintNodes(aliasDecl, file, makeParseOptions(extensionRegistry)),
|
|
2277
|
+
...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, extensionRegistry)
|
|
2278
|
+
];
|
|
2279
|
+
const annotations = extractJSDocAnnotationNodes(
|
|
2280
|
+
aliasDecl,
|
|
2281
|
+
file,
|
|
2282
|
+
makeParseOptions(extensionRegistry)
|
|
2283
|
+
);
|
|
2284
|
+
typeRegistry[aliasName] = {
|
|
2285
|
+
name: aliasName,
|
|
2286
|
+
type: resolveAliasedPrimitiveTarget(
|
|
2287
|
+
aliasType,
|
|
2288
|
+
checker,
|
|
2289
|
+
file,
|
|
2290
|
+
typeRegistry,
|
|
2291
|
+
visiting,
|
|
2292
|
+
extensionRegistry
|
|
2293
|
+
),
|
|
2294
|
+
...constraints.length > 0 && { constraints },
|
|
2295
|
+
...annotations.length > 0 && { annotations },
|
|
2296
|
+
provenance: provenanceForDeclaration(aliasDecl, file)
|
|
2297
|
+
};
|
|
2298
|
+
}
|
|
2299
|
+
return { kind: "reference", name: aliasName, typeArguments: [] };
|
|
2300
|
+
}
|
|
2301
|
+
function getReferencedTypeAliasDeclaration(sourceNode, checker) {
|
|
2302
|
+
const typeNode = sourceNode && (ts3.isPropertyDeclaration(sourceNode) || ts3.isPropertySignature(sourceNode) || ts3.isParameter(sourceNode)) ? sourceNode.type : void 0;
|
|
2303
|
+
if (!typeNode || !ts3.isTypeReferenceNode(typeNode)) {
|
|
2304
|
+
return void 0;
|
|
2305
|
+
}
|
|
2306
|
+
return checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
2307
|
+
}
|
|
2308
|
+
function shouldEmitPrimitiveAliasDefinition(typeNode, checker) {
|
|
2309
|
+
if (!ts3.isTypeReferenceNode(typeNode)) {
|
|
2310
|
+
return false;
|
|
2311
|
+
}
|
|
2312
|
+
const aliasDecl = checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
2313
|
+
if (!aliasDecl) {
|
|
2314
|
+
return false;
|
|
2315
|
+
}
|
|
2316
|
+
const resolved = checker.getTypeFromTypeNode(aliasDecl.type);
|
|
2317
|
+
return !!(resolved.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null));
|
|
2318
|
+
}
|
|
2319
|
+
function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2320
|
+
const nestedAliasDecl = type.aliasSymbol?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
2321
|
+
if (nestedAliasDecl !== void 0) {
|
|
2322
|
+
return resolveAliasedPrimitiveTarget(
|
|
2323
|
+
checker.getTypeFromTypeNode(nestedAliasDecl.type),
|
|
2324
|
+
checker,
|
|
2325
|
+
file,
|
|
2326
|
+
typeRegistry,
|
|
2327
|
+
visiting,
|
|
2328
|
+
extensionRegistry
|
|
2329
|
+
);
|
|
2330
|
+
}
|
|
2331
|
+
return resolveTypeNode(type, checker, file, typeRegistry, visiting, void 0, extensionRegistry);
|
|
2332
|
+
}
|
|
2294
2333
|
function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
2295
2334
|
const typeName = getNamedTypeName(type);
|
|
2296
2335
|
const namedDecl = getNamedTypeDeclaration(type);
|
|
@@ -2303,13 +2342,13 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
2303
2342
|
(memberTypeNode) => !isNullishTypeNode(resolveAliasedTypeNode(memberTypeNode, checker))
|
|
2304
2343
|
);
|
|
2305
2344
|
const nonNullTypes = allTypes.filter(
|
|
2306
|
-
(memberType) => !(memberType.flags & (
|
|
2345
|
+
(memberType) => !(memberType.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined))
|
|
2307
2346
|
);
|
|
2308
2347
|
const nonNullMembers = nonNullTypes.map((memberType, index) => ({
|
|
2309
2348
|
memberType,
|
|
2310
2349
|
sourceNode: nonNullSourceNodes.length === nonNullTypes.length ? nonNullSourceNodes[index] : void 0
|
|
2311
2350
|
}));
|
|
2312
|
-
const hasNull = allTypes.some((t) => t.flags &
|
|
2351
|
+
const hasNull = allTypes.some((t) => t.flags & ts3.TypeFlags.Null);
|
|
2313
2352
|
const memberDisplayNames = /* @__PURE__ */ new Map();
|
|
2314
2353
|
if (namedDecl) {
|
|
2315
2354
|
for (const [value, label] of extractDisplayNameMetadata(namedDecl).memberDisplayNames) {
|
|
@@ -2338,7 +2377,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
2338
2377
|
const displayName = memberDisplayNames.get(String(value));
|
|
2339
2378
|
return displayName !== void 0 ? { value, displayName } : { value };
|
|
2340
2379
|
});
|
|
2341
|
-
const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags &
|
|
2380
|
+
const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts3.TypeFlags.BooleanLiteral);
|
|
2342
2381
|
if (isBooleanUnion2) {
|
|
2343
2382
|
const boolNode = { kind: "primitive", primitiveKind: "boolean" };
|
|
2344
2383
|
const result = hasNull ? {
|
|
@@ -2424,7 +2463,7 @@ function tryResolveRecordType(type, checker, file, typeRegistry, visiting, exten
|
|
|
2424
2463
|
if (type.getProperties().length > 0) {
|
|
2425
2464
|
return null;
|
|
2426
2465
|
}
|
|
2427
|
-
const indexInfo = checker.getIndexInfoOfType(type,
|
|
2466
|
+
const indexInfo = checker.getIndexInfoOfType(type, ts3.IndexKind.String);
|
|
2428
2467
|
if (!indexInfo) {
|
|
2429
2468
|
return null;
|
|
2430
2469
|
}
|
|
@@ -2535,7 +2574,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
2535
2574
|
const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
|
|
2536
2575
|
if (!declaration) continue;
|
|
2537
2576
|
const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
|
|
2538
|
-
const optional = !!(prop.flags &
|
|
2577
|
+
const optional = !!(prop.flags & ts3.SymbolFlags.Optional);
|
|
2539
2578
|
const propTypeNode = resolveTypeNode(
|
|
2540
2579
|
propType,
|
|
2541
2580
|
checker,
|
|
@@ -2580,11 +2619,11 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
2580
2619
|
for (const symbol of symbols) {
|
|
2581
2620
|
const declarations = symbol.declarations;
|
|
2582
2621
|
if (!declarations) continue;
|
|
2583
|
-
const classDecl = declarations.find(
|
|
2622
|
+
const classDecl = declarations.find(ts3.isClassDeclaration);
|
|
2584
2623
|
if (classDecl) {
|
|
2585
2624
|
const map = /* @__PURE__ */ new Map();
|
|
2586
2625
|
for (const member of classDecl.members) {
|
|
2587
|
-
if (
|
|
2626
|
+
if (ts3.isPropertyDeclaration(member) && ts3.isIdentifier(member.name)) {
|
|
2588
2627
|
const fieldNode = analyzeFieldToIR(
|
|
2589
2628
|
member,
|
|
2590
2629
|
checker,
|
|
@@ -2604,7 +2643,7 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
2604
2643
|
}
|
|
2605
2644
|
return map;
|
|
2606
2645
|
}
|
|
2607
|
-
const interfaceDecl = declarations.find(
|
|
2646
|
+
const interfaceDecl = declarations.find(ts3.isInterfaceDeclaration);
|
|
2608
2647
|
if (interfaceDecl) {
|
|
2609
2648
|
return buildFieldNodeInfoMap(
|
|
2610
2649
|
interfaceDecl.members,
|
|
@@ -2615,8 +2654,8 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
2615
2654
|
extensionRegistry
|
|
2616
2655
|
);
|
|
2617
2656
|
}
|
|
2618
|
-
const typeAliasDecl = declarations.find(
|
|
2619
|
-
if (typeAliasDecl &&
|
|
2657
|
+
const typeAliasDecl = declarations.find(ts3.isTypeAliasDeclaration);
|
|
2658
|
+
if (typeAliasDecl && ts3.isTypeLiteralNode(typeAliasDecl.type)) {
|
|
2620
2659
|
return buildFieldNodeInfoMap(
|
|
2621
2660
|
typeAliasDecl.type.members,
|
|
2622
2661
|
checker,
|
|
@@ -2635,10 +2674,10 @@ function extractArrayElementTypeNode(sourceNode, checker) {
|
|
|
2635
2674
|
return void 0;
|
|
2636
2675
|
}
|
|
2637
2676
|
const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
|
|
2638
|
-
if (
|
|
2677
|
+
if (ts3.isArrayTypeNode(resolvedTypeNode)) {
|
|
2639
2678
|
return resolvedTypeNode.elementType;
|
|
2640
2679
|
}
|
|
2641
|
-
if (
|
|
2680
|
+
if (ts3.isTypeReferenceNode(resolvedTypeNode) && ts3.isIdentifier(resolvedTypeNode.typeName) && resolvedTypeNode.typeName.text === "Array" && resolvedTypeNode.typeArguments?.[0]) {
|
|
2642
2681
|
return resolvedTypeNode.typeArguments[0];
|
|
2643
2682
|
}
|
|
2644
2683
|
return void 0;
|
|
@@ -2649,17 +2688,17 @@ function extractUnionMemberTypeNodes(sourceNode, checker) {
|
|
|
2649
2688
|
return [];
|
|
2650
2689
|
}
|
|
2651
2690
|
const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
|
|
2652
|
-
return
|
|
2691
|
+
return ts3.isUnionTypeNode(resolvedTypeNode) ? [...resolvedTypeNode.types] : [];
|
|
2653
2692
|
}
|
|
2654
2693
|
function resolveAliasedTypeNode(typeNode, checker, visited = /* @__PURE__ */ new Set()) {
|
|
2655
|
-
if (
|
|
2694
|
+
if (ts3.isParenthesizedTypeNode(typeNode)) {
|
|
2656
2695
|
return resolveAliasedTypeNode(typeNode.type, checker, visited);
|
|
2657
2696
|
}
|
|
2658
|
-
if (!
|
|
2697
|
+
if (!ts3.isTypeReferenceNode(typeNode) || !ts3.isIdentifier(typeNode.typeName)) {
|
|
2659
2698
|
return typeNode;
|
|
2660
2699
|
}
|
|
2661
2700
|
const symbol = checker.getSymbolAtLocation(typeNode.typeName);
|
|
2662
|
-
const aliasDecl = symbol?.declarations?.find(
|
|
2701
|
+
const aliasDecl = symbol?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
2663
2702
|
if (aliasDecl === void 0 || visited.has(aliasDecl)) {
|
|
2664
2703
|
return typeNode;
|
|
2665
2704
|
}
|
|
@@ -2667,15 +2706,15 @@ function resolveAliasedTypeNode(typeNode, checker, visited = /* @__PURE__ */ new
|
|
|
2667
2706
|
return resolveAliasedTypeNode(aliasDecl.type, checker, visited);
|
|
2668
2707
|
}
|
|
2669
2708
|
function isNullishTypeNode(typeNode) {
|
|
2670
|
-
if (typeNode.kind ===
|
|
2709
|
+
if (typeNode.kind === ts3.SyntaxKind.NullKeyword || typeNode.kind === ts3.SyntaxKind.UndefinedKeyword) {
|
|
2671
2710
|
return true;
|
|
2672
2711
|
}
|
|
2673
|
-
return
|
|
2712
|
+
return ts3.isLiteralTypeNode(typeNode) && (typeNode.literal.kind === ts3.SyntaxKind.NullKeyword || typeNode.literal.kind === ts3.SyntaxKind.UndefinedKeyword);
|
|
2674
2713
|
}
|
|
2675
2714
|
function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2676
2715
|
const map = /* @__PURE__ */ new Map();
|
|
2677
2716
|
for (const member of members) {
|
|
2678
|
-
if (
|
|
2717
|
+
if (ts3.isPropertySignature(member)) {
|
|
2679
2718
|
const fieldNode = analyzeInterfacePropertyToIR(
|
|
2680
2719
|
member,
|
|
2681
2720
|
checker,
|
|
@@ -2697,7 +2736,7 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, e
|
|
|
2697
2736
|
}
|
|
2698
2737
|
var MAX_ALIAS_CHAIN_DEPTH = 8;
|
|
2699
2738
|
function extractTypeAliasConstraintNodes(typeNode, checker, file, extensionRegistry, depth = 0) {
|
|
2700
|
-
if (!
|
|
2739
|
+
if (!ts3.isTypeReferenceNode(typeNode)) return [];
|
|
2701
2740
|
if (depth >= MAX_ALIAS_CHAIN_DEPTH) {
|
|
2702
2741
|
const aliasName = typeNode.typeName.getText();
|
|
2703
2742
|
throw new Error(
|
|
@@ -2706,9 +2745,9 @@ function extractTypeAliasConstraintNodes(typeNode, checker, file, extensionRegis
|
|
|
2706
2745
|
}
|
|
2707
2746
|
const symbol = checker.getSymbolAtLocation(typeNode.typeName);
|
|
2708
2747
|
if (!symbol?.declarations) return [];
|
|
2709
|
-
const aliasDecl = symbol.declarations.find(
|
|
2748
|
+
const aliasDecl = symbol.declarations.find(ts3.isTypeAliasDeclaration);
|
|
2710
2749
|
if (!aliasDecl) return [];
|
|
2711
|
-
if (
|
|
2750
|
+
if (ts3.isTypeLiteralNode(aliasDecl.type)) return [];
|
|
2712
2751
|
const aliasFieldType = resolveTypeNode(
|
|
2713
2752
|
checker.getTypeAtLocation(aliasDecl.type),
|
|
2714
2753
|
checker,
|
|
@@ -2724,13 +2763,7 @@ function extractTypeAliasConstraintNodes(typeNode, checker, file, extensionRegis
|
|
|
2724
2763
|
makeParseOptions(extensionRegistry, aliasFieldType)
|
|
2725
2764
|
);
|
|
2726
2765
|
constraints.push(
|
|
2727
|
-
...extractTypeAliasConstraintNodes(
|
|
2728
|
-
aliasDecl.type,
|
|
2729
|
-
checker,
|
|
2730
|
-
file,
|
|
2731
|
-
extensionRegistry,
|
|
2732
|
-
depth + 1
|
|
2733
|
-
)
|
|
2766
|
+
...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, extensionRegistry, depth + 1)
|
|
2734
2767
|
);
|
|
2735
2768
|
return constraints;
|
|
2736
2769
|
}
|
|
@@ -2757,14 +2790,14 @@ function getNamedTypeName(type) {
|
|
|
2757
2790
|
const symbol = type.getSymbol();
|
|
2758
2791
|
if (symbol?.declarations) {
|
|
2759
2792
|
const decl = symbol.declarations[0];
|
|
2760
|
-
if (decl && (
|
|
2761
|
-
const name =
|
|
2793
|
+
if (decl && (ts3.isClassDeclaration(decl) || ts3.isInterfaceDeclaration(decl) || ts3.isTypeAliasDeclaration(decl))) {
|
|
2794
|
+
const name = ts3.isClassDeclaration(decl) ? decl.name?.text : decl.name.text;
|
|
2762
2795
|
if (name) return name;
|
|
2763
2796
|
}
|
|
2764
2797
|
}
|
|
2765
2798
|
const aliasSymbol = type.aliasSymbol;
|
|
2766
2799
|
if (aliasSymbol?.declarations) {
|
|
2767
|
-
const aliasDecl = aliasSymbol.declarations.find(
|
|
2800
|
+
const aliasDecl = aliasSymbol.declarations.find(ts3.isTypeAliasDeclaration);
|
|
2768
2801
|
if (aliasDecl) {
|
|
2769
2802
|
return aliasDecl.name.text;
|
|
2770
2803
|
}
|
|
@@ -2775,24 +2808,24 @@ function getNamedTypeDeclaration(type) {
|
|
|
2775
2808
|
const symbol = type.getSymbol();
|
|
2776
2809
|
if (symbol?.declarations) {
|
|
2777
2810
|
const decl = symbol.declarations[0];
|
|
2778
|
-
if (decl && (
|
|
2811
|
+
if (decl && (ts3.isClassDeclaration(decl) || ts3.isInterfaceDeclaration(decl) || ts3.isTypeAliasDeclaration(decl))) {
|
|
2779
2812
|
return decl;
|
|
2780
2813
|
}
|
|
2781
2814
|
}
|
|
2782
2815
|
const aliasSymbol = type.aliasSymbol;
|
|
2783
2816
|
if (aliasSymbol?.declarations) {
|
|
2784
|
-
return aliasSymbol.declarations.find(
|
|
2817
|
+
return aliasSymbol.declarations.find(ts3.isTypeAliasDeclaration);
|
|
2785
2818
|
}
|
|
2786
2819
|
return void 0;
|
|
2787
2820
|
}
|
|
2788
2821
|
function analyzeMethod(method, checker) {
|
|
2789
|
-
if (!
|
|
2822
|
+
if (!ts3.isIdentifier(method.name)) {
|
|
2790
2823
|
return null;
|
|
2791
2824
|
}
|
|
2792
2825
|
const name = method.name.text;
|
|
2793
2826
|
const parameters = [];
|
|
2794
2827
|
for (const param of method.parameters) {
|
|
2795
|
-
if (
|
|
2828
|
+
if (ts3.isIdentifier(param.name)) {
|
|
2796
2829
|
const paramInfo = analyzeParameter(param, checker);
|
|
2797
2830
|
parameters.push(paramInfo);
|
|
2798
2831
|
}
|
|
@@ -2803,7 +2836,7 @@ function analyzeMethod(method, checker) {
|
|
|
2803
2836
|
return { name, parameters, returnTypeNode, returnType };
|
|
2804
2837
|
}
|
|
2805
2838
|
function analyzeParameter(param, checker) {
|
|
2806
|
-
const name =
|
|
2839
|
+
const name = ts3.isIdentifier(param.name) ? param.name.text : "param";
|
|
2807
2840
|
const typeNode = param.type;
|
|
2808
2841
|
const type = checker.getTypeAtLocation(param);
|
|
2809
2842
|
const formSpecExportName = detectFormSpecReference(typeNode);
|
|
@@ -2812,28 +2845,932 @@ function analyzeParameter(param, checker) {
|
|
|
2812
2845
|
}
|
|
2813
2846
|
function detectFormSpecReference(typeNode) {
|
|
2814
2847
|
if (!typeNode) return null;
|
|
2815
|
-
if (!
|
|
2816
|
-
const typeName =
|
|
2848
|
+
if (!ts3.isTypeReferenceNode(typeNode)) return null;
|
|
2849
|
+
const typeName = ts3.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : ts3.isQualifiedName(typeNode.typeName) ? typeNode.typeName.right.text : null;
|
|
2817
2850
|
if (typeName !== "InferSchema" && typeName !== "InferFormSchema") return null;
|
|
2818
2851
|
const typeArg = typeNode.typeArguments?.[0];
|
|
2819
|
-
if (!typeArg || !
|
|
2820
|
-
if (
|
|
2852
|
+
if (!typeArg || !ts3.isTypeQueryNode(typeArg)) return null;
|
|
2853
|
+
if (ts3.isIdentifier(typeArg.exprName)) {
|
|
2821
2854
|
return typeArg.exprName.text;
|
|
2822
2855
|
}
|
|
2823
|
-
if (
|
|
2856
|
+
if (ts3.isQualifiedName(typeArg.exprName)) {
|
|
2824
2857
|
return typeArg.exprName.right.text;
|
|
2825
2858
|
}
|
|
2826
2859
|
return null;
|
|
2827
2860
|
}
|
|
2828
2861
|
|
|
2862
|
+
// src/analyzer/program.ts
|
|
2863
|
+
function createProgramContext(filePath) {
|
|
2864
|
+
const absolutePath = path.resolve(filePath);
|
|
2865
|
+
const fileDir = path.dirname(absolutePath);
|
|
2866
|
+
const configPath = ts4.findConfigFile(fileDir, ts4.sys.fileExists.bind(ts4.sys), "tsconfig.json");
|
|
2867
|
+
let compilerOptions;
|
|
2868
|
+
let fileNames;
|
|
2869
|
+
if (configPath) {
|
|
2870
|
+
const configFile = ts4.readConfigFile(configPath, ts4.sys.readFile.bind(ts4.sys));
|
|
2871
|
+
if (configFile.error) {
|
|
2872
|
+
throw new Error(
|
|
2873
|
+
`Error reading tsconfig.json: ${ts4.flattenDiagnosticMessageText(configFile.error.messageText, "\n")}`
|
|
2874
|
+
);
|
|
2875
|
+
}
|
|
2876
|
+
const parsed = ts4.parseJsonConfigFileContent(
|
|
2877
|
+
configFile.config,
|
|
2878
|
+
ts4.sys,
|
|
2879
|
+
path.dirname(configPath)
|
|
2880
|
+
);
|
|
2881
|
+
if (parsed.errors.length > 0) {
|
|
2882
|
+
const errorMessages = parsed.errors.map((e) => ts4.flattenDiagnosticMessageText(e.messageText, "\n")).join("\n");
|
|
2883
|
+
throw new Error(`Error parsing tsconfig.json: ${errorMessages}`);
|
|
2884
|
+
}
|
|
2885
|
+
compilerOptions = parsed.options;
|
|
2886
|
+
fileNames = parsed.fileNames.includes(absolutePath) ? parsed.fileNames : [...parsed.fileNames, absolutePath];
|
|
2887
|
+
} else {
|
|
2888
|
+
compilerOptions = {
|
|
2889
|
+
target: ts4.ScriptTarget.ES2022,
|
|
2890
|
+
module: ts4.ModuleKind.NodeNext,
|
|
2891
|
+
moduleResolution: ts4.ModuleResolutionKind.NodeNext,
|
|
2892
|
+
strict: true,
|
|
2893
|
+
skipLibCheck: true,
|
|
2894
|
+
declaration: true
|
|
2895
|
+
};
|
|
2896
|
+
fileNames = [absolutePath];
|
|
2897
|
+
}
|
|
2898
|
+
const program = ts4.createProgram(fileNames, compilerOptions);
|
|
2899
|
+
const sourceFile = program.getSourceFile(absolutePath);
|
|
2900
|
+
if (!sourceFile) {
|
|
2901
|
+
throw new Error(`Could not find source file: ${absolutePath}`);
|
|
2902
|
+
}
|
|
2903
|
+
return {
|
|
2904
|
+
program,
|
|
2905
|
+
checker: program.getTypeChecker(),
|
|
2906
|
+
sourceFile
|
|
2907
|
+
};
|
|
2908
|
+
}
|
|
2909
|
+
function findNodeByName(sourceFile, name, predicate, getName) {
|
|
2910
|
+
let result = null;
|
|
2911
|
+
function visit(node) {
|
|
2912
|
+
if (result) return;
|
|
2913
|
+
if (predicate(node) && getName(node) === name) {
|
|
2914
|
+
result = node;
|
|
2915
|
+
return;
|
|
2916
|
+
}
|
|
2917
|
+
ts4.forEachChild(node, visit);
|
|
2918
|
+
}
|
|
2919
|
+
visit(sourceFile);
|
|
2920
|
+
return result;
|
|
2921
|
+
}
|
|
2922
|
+
function findClassByName(sourceFile, className) {
|
|
2923
|
+
return findNodeByName(sourceFile, className, ts4.isClassDeclaration, (n) => n.name?.text);
|
|
2924
|
+
}
|
|
2925
|
+
function findInterfaceByName(sourceFile, interfaceName) {
|
|
2926
|
+
return findNodeByName(sourceFile, interfaceName, ts4.isInterfaceDeclaration, (n) => n.name.text);
|
|
2927
|
+
}
|
|
2928
|
+
function findTypeAliasByName(sourceFile, aliasName) {
|
|
2929
|
+
return findNodeByName(sourceFile, aliasName, ts4.isTypeAliasDeclaration, (n) => n.name.text);
|
|
2930
|
+
}
|
|
2931
|
+
function analyzeNamedTypeToIR(filePath, typeName, extensionRegistry) {
|
|
2932
|
+
const ctx = createProgramContext(filePath);
|
|
2933
|
+
const classDecl = findClassByName(ctx.sourceFile, typeName);
|
|
2934
|
+
if (classDecl !== null) {
|
|
2935
|
+
return analyzeClassToIR(classDecl, ctx.checker, filePath, extensionRegistry);
|
|
2936
|
+
}
|
|
2937
|
+
const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
|
|
2938
|
+
if (interfaceDecl !== null) {
|
|
2939
|
+
return analyzeInterfaceToIR(interfaceDecl, ctx.checker, filePath, extensionRegistry);
|
|
2940
|
+
}
|
|
2941
|
+
const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
|
|
2942
|
+
if (typeAlias !== null) {
|
|
2943
|
+
const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, filePath, extensionRegistry);
|
|
2944
|
+
if (result.ok) {
|
|
2945
|
+
return result.analysis;
|
|
2946
|
+
}
|
|
2947
|
+
throw new Error(result.error);
|
|
2948
|
+
}
|
|
2949
|
+
throw new Error(
|
|
2950
|
+
`Type "${typeName}" not found as a class, interface, or type alias in ${filePath}`
|
|
2951
|
+
);
|
|
2952
|
+
}
|
|
2953
|
+
|
|
2954
|
+
// src/validate/constraint-validator.ts
|
|
2955
|
+
var import_core4 = require("@formspec/core");
|
|
2956
|
+
function addContradiction(ctx, message, primary, related) {
|
|
2957
|
+
ctx.diagnostics.push({
|
|
2958
|
+
code: "CONTRADICTING_CONSTRAINTS",
|
|
2959
|
+
message,
|
|
2960
|
+
severity: "error",
|
|
2961
|
+
primaryLocation: primary,
|
|
2962
|
+
relatedLocations: [related]
|
|
2963
|
+
});
|
|
2964
|
+
}
|
|
2965
|
+
function addTypeMismatch(ctx, message, primary) {
|
|
2966
|
+
ctx.diagnostics.push({
|
|
2967
|
+
code: "TYPE_MISMATCH",
|
|
2968
|
+
message,
|
|
2969
|
+
severity: "error",
|
|
2970
|
+
primaryLocation: primary,
|
|
2971
|
+
relatedLocations: []
|
|
2972
|
+
});
|
|
2973
|
+
}
|
|
2974
|
+
function addUnknownExtension(ctx, message, primary) {
|
|
2975
|
+
ctx.diagnostics.push({
|
|
2976
|
+
code: "UNKNOWN_EXTENSION",
|
|
2977
|
+
message,
|
|
2978
|
+
severity: "warning",
|
|
2979
|
+
primaryLocation: primary,
|
|
2980
|
+
relatedLocations: []
|
|
2981
|
+
});
|
|
2982
|
+
}
|
|
2983
|
+
function addUnknownPathTarget(ctx, message, primary) {
|
|
2984
|
+
ctx.diagnostics.push({
|
|
2985
|
+
code: "UNKNOWN_PATH_TARGET",
|
|
2986
|
+
message,
|
|
2987
|
+
severity: "error",
|
|
2988
|
+
primaryLocation: primary,
|
|
2989
|
+
relatedLocations: []
|
|
2990
|
+
});
|
|
2991
|
+
}
|
|
2992
|
+
function addConstraintBroadening(ctx, message, primary, related) {
|
|
2993
|
+
ctx.diagnostics.push({
|
|
2994
|
+
code: "CONSTRAINT_BROADENING",
|
|
2995
|
+
message,
|
|
2996
|
+
severity: "error",
|
|
2997
|
+
primaryLocation: primary,
|
|
2998
|
+
relatedLocations: [related]
|
|
2999
|
+
});
|
|
3000
|
+
}
|
|
3001
|
+
function getExtensionIdFromConstraintId(constraintId) {
|
|
3002
|
+
const separator = constraintId.lastIndexOf("/");
|
|
3003
|
+
if (separator <= 0) {
|
|
3004
|
+
return null;
|
|
3005
|
+
}
|
|
3006
|
+
return constraintId.slice(0, separator);
|
|
3007
|
+
}
|
|
3008
|
+
function findNumeric(constraints, constraintKind) {
|
|
3009
|
+
return constraints.find((c) => c.constraintKind === constraintKind);
|
|
3010
|
+
}
|
|
3011
|
+
function findLength(constraints, constraintKind) {
|
|
3012
|
+
return constraints.find((c) => c.constraintKind === constraintKind);
|
|
3013
|
+
}
|
|
3014
|
+
function findAllowedMembers(constraints) {
|
|
3015
|
+
return constraints.filter(
|
|
3016
|
+
(c) => c.constraintKind === "allowedMembers"
|
|
3017
|
+
);
|
|
3018
|
+
}
|
|
3019
|
+
function findConstConstraints(constraints) {
|
|
3020
|
+
return constraints.filter(
|
|
3021
|
+
(c) => c.constraintKind === "const"
|
|
3022
|
+
);
|
|
3023
|
+
}
|
|
3024
|
+
function jsonValueEquals(left, right) {
|
|
3025
|
+
if (left === right) {
|
|
3026
|
+
return true;
|
|
3027
|
+
}
|
|
3028
|
+
if (Array.isArray(left) || Array.isArray(right)) {
|
|
3029
|
+
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
|
|
3030
|
+
return false;
|
|
3031
|
+
}
|
|
3032
|
+
return left.every((item, index) => jsonValueEquals(item, right[index]));
|
|
3033
|
+
}
|
|
3034
|
+
if (isJsonObject(left) || isJsonObject(right)) {
|
|
3035
|
+
if (!isJsonObject(left) || !isJsonObject(right)) {
|
|
3036
|
+
return false;
|
|
3037
|
+
}
|
|
3038
|
+
const leftKeys = Object.keys(left).sort();
|
|
3039
|
+
const rightKeys = Object.keys(right).sort();
|
|
3040
|
+
if (leftKeys.length !== rightKeys.length) {
|
|
3041
|
+
return false;
|
|
3042
|
+
}
|
|
3043
|
+
return leftKeys.every((key, index) => {
|
|
3044
|
+
const rightKey = rightKeys[index];
|
|
3045
|
+
if (rightKey !== key) {
|
|
3046
|
+
return false;
|
|
3047
|
+
}
|
|
3048
|
+
const leftValue = left[key];
|
|
3049
|
+
const rightValue = right[rightKey];
|
|
3050
|
+
return leftValue !== void 0 && rightValue !== void 0 && jsonValueEquals(leftValue, rightValue);
|
|
3051
|
+
});
|
|
3052
|
+
}
|
|
3053
|
+
return false;
|
|
3054
|
+
}
|
|
3055
|
+
function isJsonObject(value) {
|
|
3056
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3057
|
+
}
|
|
3058
|
+
function isOrderedBoundConstraint(constraint) {
|
|
3059
|
+
return constraint.constraintKind === "minimum" || constraint.constraintKind === "exclusiveMinimum" || constraint.constraintKind === "minLength" || constraint.constraintKind === "minItems" || constraint.constraintKind === "maximum" || constraint.constraintKind === "exclusiveMaximum" || constraint.constraintKind === "maxLength" || constraint.constraintKind === "maxItems";
|
|
3060
|
+
}
|
|
3061
|
+
function pathKey(constraint) {
|
|
3062
|
+
return constraint.path?.segments.join(".") ?? "";
|
|
3063
|
+
}
|
|
3064
|
+
function orderedBoundFamily(kind) {
|
|
3065
|
+
switch (kind) {
|
|
3066
|
+
case "minimum":
|
|
3067
|
+
case "exclusiveMinimum":
|
|
3068
|
+
return "numeric-lower";
|
|
3069
|
+
case "maximum":
|
|
3070
|
+
case "exclusiveMaximum":
|
|
3071
|
+
return "numeric-upper";
|
|
3072
|
+
case "minLength":
|
|
3073
|
+
return "minLength";
|
|
3074
|
+
case "minItems":
|
|
3075
|
+
return "minItems";
|
|
3076
|
+
case "maxLength":
|
|
3077
|
+
return "maxLength";
|
|
3078
|
+
case "maxItems":
|
|
3079
|
+
return "maxItems";
|
|
3080
|
+
default: {
|
|
3081
|
+
const _exhaustive = kind;
|
|
3082
|
+
return _exhaustive;
|
|
3083
|
+
}
|
|
3084
|
+
}
|
|
3085
|
+
}
|
|
3086
|
+
function isNumericLowerKind(kind) {
|
|
3087
|
+
return kind === "minimum" || kind === "exclusiveMinimum";
|
|
3088
|
+
}
|
|
3089
|
+
function isNumericUpperKind(kind) {
|
|
3090
|
+
return kind === "maximum" || kind === "exclusiveMaximum";
|
|
3091
|
+
}
|
|
3092
|
+
function describeConstraintTag(constraint) {
|
|
3093
|
+
return `@${constraint.constraintKind}`;
|
|
3094
|
+
}
|
|
3095
|
+
function compareConstraintStrength(current, previous) {
|
|
3096
|
+
const family = orderedBoundFamily(current.constraintKind);
|
|
3097
|
+
if (family === "numeric-lower") {
|
|
3098
|
+
if (!isNumericLowerKind(current.constraintKind) || !isNumericLowerKind(previous.constraintKind)) {
|
|
3099
|
+
throw new Error("numeric-lower family received non-numeric lower-bound constraint");
|
|
3100
|
+
}
|
|
3101
|
+
if (current.value !== previous.value) {
|
|
3102
|
+
return current.value > previous.value ? 1 : -1;
|
|
3103
|
+
}
|
|
3104
|
+
if (current.constraintKind === "exclusiveMinimum" && previous.constraintKind === "minimum") {
|
|
3105
|
+
return 1;
|
|
3106
|
+
}
|
|
3107
|
+
if (current.constraintKind === "minimum" && previous.constraintKind === "exclusiveMinimum") {
|
|
3108
|
+
return -1;
|
|
3109
|
+
}
|
|
3110
|
+
return 0;
|
|
3111
|
+
}
|
|
3112
|
+
if (family === "numeric-upper") {
|
|
3113
|
+
if (!isNumericUpperKind(current.constraintKind) || !isNumericUpperKind(previous.constraintKind)) {
|
|
3114
|
+
throw new Error("numeric-upper family received non-numeric upper-bound constraint");
|
|
3115
|
+
}
|
|
3116
|
+
if (current.value !== previous.value) {
|
|
3117
|
+
return current.value < previous.value ? 1 : -1;
|
|
3118
|
+
}
|
|
3119
|
+
if (current.constraintKind === "exclusiveMaximum" && previous.constraintKind === "maximum") {
|
|
3120
|
+
return 1;
|
|
3121
|
+
}
|
|
3122
|
+
if (current.constraintKind === "maximum" && previous.constraintKind === "exclusiveMaximum") {
|
|
3123
|
+
return -1;
|
|
3124
|
+
}
|
|
3125
|
+
return 0;
|
|
3126
|
+
}
|
|
3127
|
+
switch (family) {
|
|
3128
|
+
case "minLength":
|
|
3129
|
+
case "minItems":
|
|
3130
|
+
if (current.value === previous.value) {
|
|
3131
|
+
return 0;
|
|
3132
|
+
}
|
|
3133
|
+
return current.value > previous.value ? 1 : -1;
|
|
3134
|
+
case "maxLength":
|
|
3135
|
+
case "maxItems":
|
|
3136
|
+
if (current.value === previous.value) {
|
|
3137
|
+
return 0;
|
|
3138
|
+
}
|
|
3139
|
+
return current.value < previous.value ? 1 : -1;
|
|
3140
|
+
default: {
|
|
3141
|
+
const _exhaustive = family;
|
|
3142
|
+
return _exhaustive;
|
|
3143
|
+
}
|
|
3144
|
+
}
|
|
3145
|
+
}
|
|
3146
|
+
function checkConstraintBroadening(ctx, fieldName, constraints) {
|
|
3147
|
+
const strongestByKey = /* @__PURE__ */ new Map();
|
|
3148
|
+
for (const constraint of constraints) {
|
|
3149
|
+
if (!isOrderedBoundConstraint(constraint)) {
|
|
3150
|
+
continue;
|
|
3151
|
+
}
|
|
3152
|
+
const key = `${orderedBoundFamily(constraint.constraintKind)}:${pathKey(constraint)}`;
|
|
3153
|
+
const previous = strongestByKey.get(key);
|
|
3154
|
+
if (previous === void 0) {
|
|
3155
|
+
strongestByKey.set(key, constraint);
|
|
3156
|
+
continue;
|
|
3157
|
+
}
|
|
3158
|
+
const strength = compareConstraintStrength(constraint, previous);
|
|
3159
|
+
if (strength < 0) {
|
|
3160
|
+
const displayFieldName = formatPathTargetFieldName(
|
|
3161
|
+
fieldName,
|
|
3162
|
+
constraint.path?.segments ?? []
|
|
3163
|
+
);
|
|
3164
|
+
addConstraintBroadening(
|
|
3165
|
+
ctx,
|
|
3166
|
+
`Field "${displayFieldName}": ${describeConstraintTag(constraint)} (${String(constraint.value)}) is broader than earlier ${describeConstraintTag(previous)} (${String(previous.value)}). Constraints can only narrow.`,
|
|
3167
|
+
constraint.provenance,
|
|
3168
|
+
previous.provenance
|
|
3169
|
+
);
|
|
3170
|
+
continue;
|
|
3171
|
+
}
|
|
3172
|
+
if (strength <= 0) {
|
|
3173
|
+
continue;
|
|
3174
|
+
}
|
|
3175
|
+
strongestByKey.set(key, constraint);
|
|
3176
|
+
}
|
|
3177
|
+
}
|
|
3178
|
+
function compareCustomConstraintStrength(current, previous) {
|
|
3179
|
+
const order = current.comparePayloads(current.constraint.payload, previous.constraint.payload);
|
|
3180
|
+
const equalPayloadTiebreaker = order === 0 ? compareSemanticInclusivity(current.role.inclusive, previous.role.inclusive) : order;
|
|
3181
|
+
switch (current.role.bound) {
|
|
3182
|
+
case "lower":
|
|
3183
|
+
return equalPayloadTiebreaker;
|
|
3184
|
+
case "upper":
|
|
3185
|
+
return equalPayloadTiebreaker === 0 ? 0 : -equalPayloadTiebreaker;
|
|
3186
|
+
case "exact":
|
|
3187
|
+
return order === 0 ? 0 : Number.NaN;
|
|
3188
|
+
default: {
|
|
3189
|
+
const _exhaustive = current.role.bound;
|
|
3190
|
+
return _exhaustive;
|
|
3191
|
+
}
|
|
3192
|
+
}
|
|
3193
|
+
}
|
|
3194
|
+
function compareSemanticInclusivity(currentInclusive, previousInclusive) {
|
|
3195
|
+
if (currentInclusive === previousInclusive) {
|
|
3196
|
+
return 0;
|
|
3197
|
+
}
|
|
3198
|
+
return currentInclusive ? -1 : 1;
|
|
3199
|
+
}
|
|
3200
|
+
function customConstraintsContradict(lower, upper) {
|
|
3201
|
+
const order = lower.comparePayloads(lower.constraint.payload, upper.constraint.payload);
|
|
3202
|
+
if (order > 0) {
|
|
3203
|
+
return true;
|
|
3204
|
+
}
|
|
3205
|
+
if (order < 0) {
|
|
3206
|
+
return false;
|
|
3207
|
+
}
|
|
3208
|
+
return !lower.role.inclusive || !upper.role.inclusive;
|
|
3209
|
+
}
|
|
3210
|
+
function describeCustomConstraintTag(constraint) {
|
|
3211
|
+
return constraint.provenance.tagName ?? constraint.constraintId;
|
|
3212
|
+
}
|
|
3213
|
+
function checkCustomConstraintSemantics(ctx, fieldName, constraints) {
|
|
3214
|
+
if (ctx.extensionRegistry === void 0) {
|
|
3215
|
+
return;
|
|
3216
|
+
}
|
|
3217
|
+
const strongestByKey = /* @__PURE__ */ new Map();
|
|
3218
|
+
const lowerByFamily = /* @__PURE__ */ new Map();
|
|
3219
|
+
const upperByFamily = /* @__PURE__ */ new Map();
|
|
3220
|
+
for (const constraint of constraints) {
|
|
3221
|
+
if (constraint.constraintKind !== "custom") {
|
|
3222
|
+
continue;
|
|
3223
|
+
}
|
|
3224
|
+
const registration = ctx.extensionRegistry.findConstraint(constraint.constraintId);
|
|
3225
|
+
if (registration?.comparePayloads === void 0 || registration.semanticRole === void 0) {
|
|
3226
|
+
continue;
|
|
3227
|
+
}
|
|
3228
|
+
const entry = {
|
|
3229
|
+
constraint,
|
|
3230
|
+
comparePayloads: registration.comparePayloads,
|
|
3231
|
+
role: registration.semanticRole
|
|
3232
|
+
};
|
|
3233
|
+
const familyKey = `${registration.semanticRole.family}:${pathKey(constraint)}`;
|
|
3234
|
+
const boundKey = `${familyKey}:${registration.semanticRole.bound}`;
|
|
3235
|
+
const previous = strongestByKey.get(boundKey);
|
|
3236
|
+
if (previous !== void 0) {
|
|
3237
|
+
const strength = compareCustomConstraintStrength(entry, previous);
|
|
3238
|
+
if (Number.isNaN(strength)) {
|
|
3239
|
+
addContradiction(
|
|
3240
|
+
ctx,
|
|
3241
|
+
`Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} conflicts with ${describeCustomConstraintTag(previous.constraint)}`,
|
|
3242
|
+
constraint.provenance,
|
|
3243
|
+
previous.constraint.provenance
|
|
3244
|
+
);
|
|
3245
|
+
continue;
|
|
3246
|
+
}
|
|
3247
|
+
if (strength < 0) {
|
|
3248
|
+
addConstraintBroadening(
|
|
3249
|
+
ctx,
|
|
3250
|
+
`Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} is broader than earlier ${describeCustomConstraintTag(previous.constraint)}. Constraints can only narrow.`,
|
|
3251
|
+
constraint.provenance,
|
|
3252
|
+
previous.constraint.provenance
|
|
3253
|
+
);
|
|
3254
|
+
continue;
|
|
3255
|
+
}
|
|
3256
|
+
if (strength > 0) {
|
|
3257
|
+
strongestByKey.set(boundKey, entry);
|
|
3258
|
+
}
|
|
3259
|
+
} else {
|
|
3260
|
+
strongestByKey.set(boundKey, entry);
|
|
3261
|
+
}
|
|
3262
|
+
if (registration.semanticRole.bound === "lower") {
|
|
3263
|
+
lowerByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
|
|
3264
|
+
} else if (registration.semanticRole.bound === "upper") {
|
|
3265
|
+
upperByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
|
|
3266
|
+
}
|
|
3267
|
+
}
|
|
3268
|
+
for (const [familyKey, lower] of lowerByFamily) {
|
|
3269
|
+
const upper = upperByFamily.get(familyKey);
|
|
3270
|
+
if (upper === void 0) {
|
|
3271
|
+
continue;
|
|
3272
|
+
}
|
|
3273
|
+
if (!customConstraintsContradict(lower, upper)) {
|
|
3274
|
+
continue;
|
|
3275
|
+
}
|
|
3276
|
+
addContradiction(
|
|
3277
|
+
ctx,
|
|
3278
|
+
`Field "${formatPathTargetFieldName(fieldName, lower.constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(lower.constraint)} contradicts ${describeCustomConstraintTag(upper.constraint)}`,
|
|
3279
|
+
lower.constraint.provenance,
|
|
3280
|
+
upper.constraint.provenance
|
|
3281
|
+
);
|
|
3282
|
+
}
|
|
3283
|
+
}
|
|
3284
|
+
function checkNumericContradictions(ctx, fieldName, constraints) {
|
|
3285
|
+
const min = findNumeric(constraints, "minimum");
|
|
3286
|
+
const max = findNumeric(constraints, "maximum");
|
|
3287
|
+
const exMin = findNumeric(constraints, "exclusiveMinimum");
|
|
3288
|
+
const exMax = findNumeric(constraints, "exclusiveMaximum");
|
|
3289
|
+
if (min !== void 0 && max !== void 0 && min.value > max.value) {
|
|
3290
|
+
addContradiction(
|
|
3291
|
+
ctx,
|
|
3292
|
+
`Field "${fieldName}": minimum (${String(min.value)}) is greater than maximum (${String(max.value)})`,
|
|
3293
|
+
min.provenance,
|
|
3294
|
+
max.provenance
|
|
3295
|
+
);
|
|
3296
|
+
}
|
|
3297
|
+
if (exMin !== void 0 && max !== void 0 && exMin.value >= max.value) {
|
|
3298
|
+
addContradiction(
|
|
3299
|
+
ctx,
|
|
3300
|
+
`Field "${fieldName}": exclusiveMinimum (${String(exMin.value)}) is greater than or equal to maximum (${String(max.value)})`,
|
|
3301
|
+
exMin.provenance,
|
|
3302
|
+
max.provenance
|
|
3303
|
+
);
|
|
3304
|
+
}
|
|
3305
|
+
if (min !== void 0 && exMax !== void 0 && min.value >= exMax.value) {
|
|
3306
|
+
addContradiction(
|
|
3307
|
+
ctx,
|
|
3308
|
+
`Field "${fieldName}": minimum (${String(min.value)}) is greater than or equal to exclusiveMaximum (${String(exMax.value)})`,
|
|
3309
|
+
min.provenance,
|
|
3310
|
+
exMax.provenance
|
|
3311
|
+
);
|
|
3312
|
+
}
|
|
3313
|
+
if (exMin !== void 0 && exMax !== void 0 && exMin.value >= exMax.value) {
|
|
3314
|
+
addContradiction(
|
|
3315
|
+
ctx,
|
|
3316
|
+
`Field "${fieldName}": exclusiveMinimum (${String(exMin.value)}) is greater than or equal to exclusiveMaximum (${String(exMax.value)})`,
|
|
3317
|
+
exMin.provenance,
|
|
3318
|
+
exMax.provenance
|
|
3319
|
+
);
|
|
3320
|
+
}
|
|
3321
|
+
}
|
|
3322
|
+
function checkLengthContradictions(ctx, fieldName, constraints) {
|
|
3323
|
+
const minLen = findLength(constraints, "minLength");
|
|
3324
|
+
const maxLen = findLength(constraints, "maxLength");
|
|
3325
|
+
if (minLen !== void 0 && maxLen !== void 0 && minLen.value > maxLen.value) {
|
|
3326
|
+
addContradiction(
|
|
3327
|
+
ctx,
|
|
3328
|
+
`Field "${fieldName}": minLength (${String(minLen.value)}) is greater than maxLength (${String(maxLen.value)})`,
|
|
3329
|
+
minLen.provenance,
|
|
3330
|
+
maxLen.provenance
|
|
3331
|
+
);
|
|
3332
|
+
}
|
|
3333
|
+
const minItems = findLength(constraints, "minItems");
|
|
3334
|
+
const maxItems = findLength(constraints, "maxItems");
|
|
3335
|
+
if (minItems !== void 0 && maxItems !== void 0 && minItems.value > maxItems.value) {
|
|
3336
|
+
addContradiction(
|
|
3337
|
+
ctx,
|
|
3338
|
+
`Field "${fieldName}": minItems (${String(minItems.value)}) is greater than maxItems (${String(maxItems.value)})`,
|
|
3339
|
+
minItems.provenance,
|
|
3340
|
+
maxItems.provenance
|
|
3341
|
+
);
|
|
3342
|
+
}
|
|
3343
|
+
}
|
|
3344
|
+
function checkAllowedMembersContradiction(ctx, fieldName, constraints) {
|
|
3345
|
+
const members = findAllowedMembers(constraints);
|
|
3346
|
+
if (members.length < 2) return;
|
|
3347
|
+
const firstSet = new Set(members[0]?.members ?? []);
|
|
3348
|
+
for (let i = 1; i < members.length; i++) {
|
|
3349
|
+
const current = members[i];
|
|
3350
|
+
if (current === void 0) continue;
|
|
3351
|
+
for (const m of firstSet) {
|
|
3352
|
+
if (!current.members.includes(m)) {
|
|
3353
|
+
firstSet.delete(m);
|
|
3354
|
+
}
|
|
3355
|
+
}
|
|
3356
|
+
}
|
|
3357
|
+
if (firstSet.size === 0) {
|
|
3358
|
+
const first = members[0];
|
|
3359
|
+
const second = members[1];
|
|
3360
|
+
if (first !== void 0 && second !== void 0) {
|
|
3361
|
+
addContradiction(
|
|
3362
|
+
ctx,
|
|
3363
|
+
`Field "${fieldName}": allowedMembers constraints have an empty intersection (no valid values remain)`,
|
|
3364
|
+
first.provenance,
|
|
3365
|
+
second.provenance
|
|
3366
|
+
);
|
|
3367
|
+
}
|
|
3368
|
+
}
|
|
3369
|
+
}
|
|
3370
|
+
function checkConstContradictions(ctx, fieldName, constraints) {
|
|
3371
|
+
const constConstraints = findConstConstraints(constraints);
|
|
3372
|
+
if (constConstraints.length < 2) return;
|
|
3373
|
+
const first = constConstraints[0];
|
|
3374
|
+
if (first === void 0) return;
|
|
3375
|
+
for (let i = 1; i < constConstraints.length; i++) {
|
|
3376
|
+
const current = constConstraints[i];
|
|
3377
|
+
if (current === void 0) continue;
|
|
3378
|
+
if (jsonValueEquals(first.value, current.value)) {
|
|
3379
|
+
continue;
|
|
3380
|
+
}
|
|
3381
|
+
addContradiction(
|
|
3382
|
+
ctx,
|
|
3383
|
+
`Field "${fieldName}": conflicting @const constraints require both ${JSON.stringify(first.value)} and ${JSON.stringify(current.value)}`,
|
|
3384
|
+
first.provenance,
|
|
3385
|
+
current.provenance
|
|
3386
|
+
);
|
|
3387
|
+
}
|
|
3388
|
+
}
|
|
3389
|
+
function typeLabel(type) {
|
|
3390
|
+
switch (type.kind) {
|
|
3391
|
+
case "primitive":
|
|
3392
|
+
return type.primitiveKind;
|
|
3393
|
+
case "enum":
|
|
3394
|
+
return "enum";
|
|
3395
|
+
case "array":
|
|
3396
|
+
return "array";
|
|
3397
|
+
case "object":
|
|
3398
|
+
return "object";
|
|
3399
|
+
case "record":
|
|
3400
|
+
return "record";
|
|
3401
|
+
case "union":
|
|
3402
|
+
return "union";
|
|
3403
|
+
case "reference":
|
|
3404
|
+
return `reference(${type.name})`;
|
|
3405
|
+
case "dynamic":
|
|
3406
|
+
return `dynamic(${type.dynamicKind})`;
|
|
3407
|
+
case "custom":
|
|
3408
|
+
return `custom(${type.typeId})`;
|
|
3409
|
+
default: {
|
|
3410
|
+
const _exhaustive = type;
|
|
3411
|
+
return String(_exhaustive);
|
|
3412
|
+
}
|
|
3413
|
+
}
|
|
3414
|
+
}
|
|
3415
|
+
function dereferenceType(ctx, type) {
|
|
3416
|
+
let current = type;
|
|
3417
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3418
|
+
while (current.kind === "reference") {
|
|
3419
|
+
if (seen.has(current.name)) {
|
|
3420
|
+
return current;
|
|
3421
|
+
}
|
|
3422
|
+
seen.add(current.name);
|
|
3423
|
+
const definition = ctx.typeRegistry[current.name];
|
|
3424
|
+
if (definition === void 0) {
|
|
3425
|
+
return current;
|
|
3426
|
+
}
|
|
3427
|
+
current = definition.type;
|
|
3428
|
+
}
|
|
3429
|
+
return current;
|
|
3430
|
+
}
|
|
3431
|
+
function collectReferencedTypeConstraints(ctx, type) {
|
|
3432
|
+
const collected = [];
|
|
3433
|
+
let current = type;
|
|
3434
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3435
|
+
while (current.kind === "reference") {
|
|
3436
|
+
if (seen.has(current.name)) {
|
|
3437
|
+
break;
|
|
3438
|
+
}
|
|
3439
|
+
seen.add(current.name);
|
|
3440
|
+
const definition = ctx.typeRegistry[current.name];
|
|
3441
|
+
if (definition === void 0) {
|
|
3442
|
+
break;
|
|
3443
|
+
}
|
|
3444
|
+
if (definition.constraints !== void 0) {
|
|
3445
|
+
collected.push(...definition.constraints);
|
|
3446
|
+
}
|
|
3447
|
+
current = definition.type;
|
|
3448
|
+
}
|
|
3449
|
+
return collected;
|
|
3450
|
+
}
|
|
3451
|
+
function resolvePathTargetType(ctx, type, segments) {
|
|
3452
|
+
const effectiveType = dereferenceType(ctx, type);
|
|
3453
|
+
if (segments.length === 0) {
|
|
3454
|
+
return { kind: "resolved", type: effectiveType };
|
|
3455
|
+
}
|
|
3456
|
+
if (effectiveType.kind === "array") {
|
|
3457
|
+
return resolvePathTargetType(ctx, effectiveType.items, segments);
|
|
3458
|
+
}
|
|
3459
|
+
if (effectiveType.kind === "object") {
|
|
3460
|
+
const [segment, ...rest] = segments;
|
|
3461
|
+
if (segment === void 0) {
|
|
3462
|
+
throw new Error("Invariant violation: object path traversal requires a segment");
|
|
3463
|
+
}
|
|
3464
|
+
const property = effectiveType.properties.find((prop) => prop.name === segment);
|
|
3465
|
+
if (property === void 0) {
|
|
3466
|
+
return { kind: "missing-property", segment };
|
|
3467
|
+
}
|
|
3468
|
+
return resolvePathTargetType(ctx, property.type, rest);
|
|
3469
|
+
}
|
|
3470
|
+
return { kind: "unresolvable", type: effectiveType };
|
|
3471
|
+
}
|
|
3472
|
+
function isNullType(type) {
|
|
3473
|
+
return type.kind === "primitive" && type.primitiveKind === "null";
|
|
3474
|
+
}
|
|
3475
|
+
function collectCustomConstraintCandidateTypes(ctx, type) {
|
|
3476
|
+
const effectiveType = dereferenceType(ctx, type);
|
|
3477
|
+
const candidates = [effectiveType];
|
|
3478
|
+
if (effectiveType.kind === "array") {
|
|
3479
|
+
candidates.push(...collectCustomConstraintCandidateTypes(ctx, effectiveType.items));
|
|
3480
|
+
}
|
|
3481
|
+
if (effectiveType.kind === "union") {
|
|
3482
|
+
const memberTypes = effectiveType.members.map((member) => dereferenceType(ctx, member));
|
|
3483
|
+
const nonNullMembers = memberTypes.filter((member) => !isNullType(member));
|
|
3484
|
+
if (nonNullMembers.length === 1 && nonNullMembers.length < memberTypes.length) {
|
|
3485
|
+
const [nullableMember] = nonNullMembers;
|
|
3486
|
+
if (nullableMember !== void 0) {
|
|
3487
|
+
candidates.push(...collectCustomConstraintCandidateTypes(ctx, nullableMember));
|
|
3488
|
+
}
|
|
3489
|
+
}
|
|
3490
|
+
}
|
|
3491
|
+
return candidates;
|
|
3492
|
+
}
|
|
3493
|
+
function formatPathTargetFieldName(fieldName, path3) {
|
|
3494
|
+
return path3.length === 0 ? fieldName : `${fieldName}.${path3.join(".")}`;
|
|
3495
|
+
}
|
|
3496
|
+
function checkConstraintOnType(ctx, fieldName, type, constraint) {
|
|
3497
|
+
const effectiveType = dereferenceType(ctx, type);
|
|
3498
|
+
const isNumber = effectiveType.kind === "primitive" && ["number", "integer", "bigint"].includes(effectiveType.primitiveKind);
|
|
3499
|
+
const isString = effectiveType.kind === "primitive" && effectiveType.primitiveKind === "string";
|
|
3500
|
+
const isArray = effectiveType.kind === "array";
|
|
3501
|
+
const isEnum = effectiveType.kind === "enum";
|
|
3502
|
+
const arrayItemType = effectiveType.kind === "array" ? dereferenceType(ctx, effectiveType.items) : void 0;
|
|
3503
|
+
const isStringArray = arrayItemType?.kind === "primitive" && arrayItemType.primitiveKind === "string";
|
|
3504
|
+
const label = typeLabel(effectiveType);
|
|
3505
|
+
const ck = constraint.constraintKind;
|
|
3506
|
+
switch (ck) {
|
|
3507
|
+
case "minimum":
|
|
3508
|
+
case "maximum":
|
|
3509
|
+
case "exclusiveMinimum":
|
|
3510
|
+
case "exclusiveMaximum":
|
|
3511
|
+
case "multipleOf": {
|
|
3512
|
+
if (!isNumber) {
|
|
3513
|
+
addTypeMismatch(
|
|
3514
|
+
ctx,
|
|
3515
|
+
`Field "${fieldName}": constraint "${ck}" is only valid on number fields, but field type is "${label}"`,
|
|
3516
|
+
constraint.provenance
|
|
3517
|
+
);
|
|
3518
|
+
}
|
|
3519
|
+
break;
|
|
3520
|
+
}
|
|
3521
|
+
case "minLength":
|
|
3522
|
+
case "maxLength":
|
|
3523
|
+
case "pattern": {
|
|
3524
|
+
if (!isString && !isStringArray) {
|
|
3525
|
+
addTypeMismatch(
|
|
3526
|
+
ctx,
|
|
3527
|
+
`Field "${fieldName}": constraint "${ck}" is only valid on string fields or string array items, but field type is "${label}"`,
|
|
3528
|
+
constraint.provenance
|
|
3529
|
+
);
|
|
3530
|
+
}
|
|
3531
|
+
break;
|
|
3532
|
+
}
|
|
3533
|
+
case "minItems":
|
|
3534
|
+
case "maxItems":
|
|
3535
|
+
case "uniqueItems": {
|
|
3536
|
+
if (!isArray) {
|
|
3537
|
+
addTypeMismatch(
|
|
3538
|
+
ctx,
|
|
3539
|
+
`Field "${fieldName}": constraint "${ck}" is only valid on array fields, but field type is "${label}"`,
|
|
3540
|
+
constraint.provenance
|
|
3541
|
+
);
|
|
3542
|
+
}
|
|
3543
|
+
break;
|
|
3544
|
+
}
|
|
3545
|
+
case "allowedMembers": {
|
|
3546
|
+
if (!isEnum) {
|
|
3547
|
+
addTypeMismatch(
|
|
3548
|
+
ctx,
|
|
3549
|
+
`Field "${fieldName}": constraint "allowedMembers" is only valid on enum fields, but field type is "${label}"`,
|
|
3550
|
+
constraint.provenance
|
|
3551
|
+
);
|
|
3552
|
+
}
|
|
3553
|
+
break;
|
|
3554
|
+
}
|
|
3555
|
+
case "const": {
|
|
3556
|
+
const isPrimitiveConstType = effectiveType.kind === "primitive" && ["string", "number", "integer", "bigint", "boolean", "null"].includes(
|
|
3557
|
+
effectiveType.primitiveKind
|
|
3558
|
+
) || effectiveType.kind === "enum";
|
|
3559
|
+
if (!isPrimitiveConstType) {
|
|
3560
|
+
addTypeMismatch(
|
|
3561
|
+
ctx,
|
|
3562
|
+
`Field "${fieldName}": constraint "const" is only valid on primitive or enum fields, but field type is "${label}"`,
|
|
3563
|
+
constraint.provenance
|
|
3564
|
+
);
|
|
3565
|
+
break;
|
|
3566
|
+
}
|
|
3567
|
+
if (effectiveType.kind === "primitive") {
|
|
3568
|
+
const valueType = constraint.value === null ? "null" : Array.isArray(constraint.value) ? "array" : typeof constraint.value;
|
|
3569
|
+
const expectedValueType = effectiveType.primitiveKind === "integer" || effectiveType.primitiveKind === "bigint" ? "number" : effectiveType.primitiveKind;
|
|
3570
|
+
if (valueType !== expectedValueType) {
|
|
3571
|
+
addTypeMismatch(
|
|
3572
|
+
ctx,
|
|
3573
|
+
`Field "${fieldName}": @const value type "${valueType}" is incompatible with field type "${effectiveType.primitiveKind}"`,
|
|
3574
|
+
constraint.provenance
|
|
3575
|
+
);
|
|
3576
|
+
}
|
|
3577
|
+
break;
|
|
3578
|
+
}
|
|
3579
|
+
const memberValues = effectiveType.members.map((member) => member.value);
|
|
3580
|
+
if (!memberValues.some((member) => jsonValueEquals(member, constraint.value))) {
|
|
3581
|
+
addTypeMismatch(
|
|
3582
|
+
ctx,
|
|
3583
|
+
`Field "${fieldName}": @const value ${JSON.stringify(constraint.value)} is not one of the enum members`,
|
|
3584
|
+
constraint.provenance
|
|
3585
|
+
);
|
|
3586
|
+
}
|
|
3587
|
+
break;
|
|
3588
|
+
}
|
|
3589
|
+
case "custom": {
|
|
3590
|
+
checkCustomConstraint(ctx, fieldName, effectiveType, constraint);
|
|
3591
|
+
break;
|
|
3592
|
+
}
|
|
3593
|
+
default: {
|
|
3594
|
+
const _exhaustive = constraint;
|
|
3595
|
+
throw new Error(
|
|
3596
|
+
`Unhandled constraint kind: ${_exhaustive.constraintKind}`
|
|
3597
|
+
);
|
|
3598
|
+
}
|
|
3599
|
+
}
|
|
3600
|
+
}
|
|
3601
|
+
function checkTypeApplicability(ctx, fieldName, type, constraints) {
|
|
3602
|
+
for (const constraint of constraints) {
|
|
3603
|
+
if (constraint.path) {
|
|
3604
|
+
const resolution = resolvePathTargetType(ctx, type, constraint.path.segments);
|
|
3605
|
+
const targetFieldName = formatPathTargetFieldName(fieldName, constraint.path.segments);
|
|
3606
|
+
if (resolution.kind === "missing-property") {
|
|
3607
|
+
addUnknownPathTarget(
|
|
3608
|
+
ctx,
|
|
3609
|
+
`Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" references unknown path segment "${resolution.segment}"`,
|
|
3610
|
+
constraint.provenance
|
|
3611
|
+
);
|
|
3612
|
+
continue;
|
|
3613
|
+
}
|
|
3614
|
+
if (resolution.kind === "unresolvable") {
|
|
3615
|
+
addTypeMismatch(
|
|
3616
|
+
ctx,
|
|
3617
|
+
`Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" is invalid because type "${typeLabel(resolution.type)}" cannot be traversed`,
|
|
3618
|
+
constraint.provenance
|
|
3619
|
+
);
|
|
3620
|
+
continue;
|
|
3621
|
+
}
|
|
3622
|
+
checkConstraintOnType(ctx, targetFieldName, resolution.type, constraint);
|
|
3623
|
+
continue;
|
|
3624
|
+
}
|
|
3625
|
+
checkConstraintOnType(ctx, fieldName, type, constraint);
|
|
3626
|
+
}
|
|
3627
|
+
}
|
|
3628
|
+
function checkCustomConstraint(ctx, fieldName, type, constraint) {
|
|
3629
|
+
if (ctx.extensionRegistry === void 0) return;
|
|
3630
|
+
const registration = ctx.extensionRegistry.findConstraint(constraint.constraintId);
|
|
3631
|
+
if (registration === void 0) {
|
|
3632
|
+
addUnknownExtension(
|
|
3633
|
+
ctx,
|
|
3634
|
+
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not registered in the extension registry`,
|
|
3635
|
+
constraint.provenance
|
|
3636
|
+
);
|
|
3637
|
+
return;
|
|
3638
|
+
}
|
|
3639
|
+
const candidateTypes = collectCustomConstraintCandidateTypes(ctx, type);
|
|
3640
|
+
const normalizedTagName = constraint.provenance.tagName === void 0 ? void 0 : (0, import_core4.normalizeConstraintTagName)(constraint.provenance.tagName.replace(/^@/, ""));
|
|
3641
|
+
if (normalizedTagName !== void 0) {
|
|
3642
|
+
const tagRegistration = ctx.extensionRegistry.findConstraintTag(normalizedTagName);
|
|
3643
|
+
const extensionId = getExtensionIdFromConstraintId(constraint.constraintId);
|
|
3644
|
+
if (extensionId !== null && tagRegistration?.extensionId === extensionId && tagRegistration.registration.constraintName === registration.constraintName && !candidateTypes.some(
|
|
3645
|
+
(candidateType) => tagRegistration.registration.isApplicableToType?.(candidateType) !== false
|
|
3646
|
+
)) {
|
|
3647
|
+
addTypeMismatch(
|
|
3648
|
+
ctx,
|
|
3649
|
+
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
3650
|
+
constraint.provenance
|
|
3651
|
+
);
|
|
3652
|
+
return;
|
|
3653
|
+
}
|
|
3654
|
+
}
|
|
3655
|
+
if (registration.applicableTypes === null) {
|
|
3656
|
+
if (!candidateTypes.some((candidateType) => registration.isApplicableToType?.(candidateType) !== false)) {
|
|
3657
|
+
addTypeMismatch(
|
|
3658
|
+
ctx,
|
|
3659
|
+
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
3660
|
+
constraint.provenance
|
|
3661
|
+
);
|
|
3662
|
+
}
|
|
3663
|
+
return;
|
|
3664
|
+
}
|
|
3665
|
+
const applicableTypes = registration.applicableTypes;
|
|
3666
|
+
const matchesApplicableType = candidateTypes.some(
|
|
3667
|
+
(candidateType) => applicableTypes.includes(candidateType.kind) && registration.isApplicableToType?.(candidateType) !== false
|
|
3668
|
+
);
|
|
3669
|
+
if (!matchesApplicableType) {
|
|
3670
|
+
addTypeMismatch(
|
|
3671
|
+
ctx,
|
|
3672
|
+
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
3673
|
+
constraint.provenance
|
|
3674
|
+
);
|
|
3675
|
+
}
|
|
3676
|
+
}
|
|
3677
|
+
function validateFieldNode(ctx, field) {
|
|
3678
|
+
validateConstraints(ctx, field.name, field.type, [
|
|
3679
|
+
...collectReferencedTypeConstraints(ctx, field.type),
|
|
3680
|
+
...field.constraints
|
|
3681
|
+
]);
|
|
3682
|
+
if (field.type.kind === "object") {
|
|
3683
|
+
for (const prop of field.type.properties) {
|
|
3684
|
+
validateObjectProperty(ctx, field.name, prop);
|
|
3685
|
+
}
|
|
3686
|
+
}
|
|
3687
|
+
}
|
|
3688
|
+
function validateObjectProperty(ctx, parentName, prop) {
|
|
3689
|
+
const qualifiedName = `${parentName}.${prop.name}`;
|
|
3690
|
+
validateConstraints(ctx, qualifiedName, prop.type, [
|
|
3691
|
+
...collectReferencedTypeConstraints(ctx, prop.type),
|
|
3692
|
+
...prop.constraints
|
|
3693
|
+
]);
|
|
3694
|
+
if (prop.type.kind === "object") {
|
|
3695
|
+
for (const nestedProp of prop.type.properties) {
|
|
3696
|
+
validateObjectProperty(ctx, qualifiedName, nestedProp);
|
|
3697
|
+
}
|
|
3698
|
+
}
|
|
3699
|
+
}
|
|
3700
|
+
function validateConstraints(ctx, name, type, constraints) {
|
|
3701
|
+
checkNumericContradictions(ctx, name, constraints);
|
|
3702
|
+
checkLengthContradictions(ctx, name, constraints);
|
|
3703
|
+
checkAllowedMembersContradiction(ctx, name, constraints);
|
|
3704
|
+
checkConstContradictions(ctx, name, constraints);
|
|
3705
|
+
checkConstraintBroadening(ctx, name, constraints);
|
|
3706
|
+
checkCustomConstraintSemantics(ctx, name, constraints);
|
|
3707
|
+
checkTypeApplicability(ctx, name, type, constraints);
|
|
3708
|
+
}
|
|
3709
|
+
function validateElement(ctx, element) {
|
|
3710
|
+
switch (element.kind) {
|
|
3711
|
+
case "field":
|
|
3712
|
+
validateFieldNode(ctx, element);
|
|
3713
|
+
break;
|
|
3714
|
+
case "group":
|
|
3715
|
+
for (const child of element.elements) {
|
|
3716
|
+
validateElement(ctx, child);
|
|
3717
|
+
}
|
|
3718
|
+
break;
|
|
3719
|
+
case "conditional":
|
|
3720
|
+
for (const child of element.elements) {
|
|
3721
|
+
validateElement(ctx, child);
|
|
3722
|
+
}
|
|
3723
|
+
break;
|
|
3724
|
+
default: {
|
|
3725
|
+
const _exhaustive = element;
|
|
3726
|
+
throw new Error(`Unhandled element kind: ${_exhaustive.kind}`);
|
|
3727
|
+
}
|
|
3728
|
+
}
|
|
3729
|
+
}
|
|
3730
|
+
function validateIR(ir, options) {
|
|
3731
|
+
const ctx = {
|
|
3732
|
+
diagnostics: [],
|
|
3733
|
+
extensionRegistry: options?.extensionRegistry,
|
|
3734
|
+
typeRegistry: ir.typeRegistry
|
|
3735
|
+
};
|
|
3736
|
+
for (const element of ir.elements) {
|
|
3737
|
+
validateElement(ctx, element);
|
|
3738
|
+
}
|
|
3739
|
+
return {
|
|
3740
|
+
diagnostics: ctx.diagnostics,
|
|
3741
|
+
valid: ctx.diagnostics.every((d) => d.severity !== "error")
|
|
3742
|
+
};
|
|
3743
|
+
}
|
|
3744
|
+
|
|
2829
3745
|
// src/generators/class-schema.ts
|
|
2830
3746
|
function generateClassSchemas(analysis, source, options) {
|
|
2831
3747
|
const ir = canonicalizeTSDoc(analysis, source);
|
|
3748
|
+
const validationResult = validateIR(ir, {
|
|
3749
|
+
...options?.extensionRegistry !== void 0 && {
|
|
3750
|
+
extensionRegistry: options.extensionRegistry
|
|
3751
|
+
},
|
|
3752
|
+
...options?.vendorPrefix !== void 0 && { vendorPrefix: options.vendorPrefix }
|
|
3753
|
+
});
|
|
3754
|
+
if (!validationResult.valid) {
|
|
3755
|
+
throw new Error(formatValidationError(validationResult.diagnostics));
|
|
3756
|
+
}
|
|
2832
3757
|
return {
|
|
2833
3758
|
jsonSchema: generateJsonSchemaFromIR(ir, options),
|
|
2834
3759
|
uiSchema: generateUiSchemaFromIR(ir)
|
|
2835
3760
|
};
|
|
2836
3761
|
}
|
|
3762
|
+
function formatValidationError(diagnostics) {
|
|
3763
|
+
const lines = diagnostics.map((diagnostic) => {
|
|
3764
|
+
const primary = formatLocation(diagnostic.primaryLocation);
|
|
3765
|
+
const related = diagnostic.relatedLocations.length > 0 ? ` [related: ${diagnostic.relatedLocations.map(formatLocation).join(", ")}]` : "";
|
|
3766
|
+
return `${diagnostic.code}: ${diagnostic.message} (${primary})${related}`;
|
|
3767
|
+
});
|
|
3768
|
+
return `FormSpec validation failed:
|
|
3769
|
+
${lines.map((line) => `- ${line}`).join("\n")}`;
|
|
3770
|
+
}
|
|
3771
|
+
function formatLocation(location) {
|
|
3772
|
+
return `${location.file}:${String(location.line)}:${String(location.column)}`;
|
|
3773
|
+
}
|
|
2837
3774
|
function generateSchemasFromClass(options) {
|
|
2838
3775
|
const ctx = createProgramContext(options.filePath);
|
|
2839
3776
|
const classDecl = findClassByName(ctx.sourceFile, options.className);
|
|
@@ -2856,50 +3793,18 @@ function generateSchemasFromClass(options) {
|
|
|
2856
3793
|
);
|
|
2857
3794
|
}
|
|
2858
3795
|
function generateSchemas(options) {
|
|
2859
|
-
const
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
const analysis = analyzeClassToIR(
|
|
2864
|
-
classDecl,
|
|
2865
|
-
ctx.checker,
|
|
2866
|
-
options.filePath,
|
|
2867
|
-
options.extensionRegistry
|
|
2868
|
-
);
|
|
2869
|
-
return generateClassSchemas(analysis, source, options);
|
|
2870
|
-
}
|
|
2871
|
-
const interfaceDecl = findInterfaceByName(ctx.sourceFile, options.typeName);
|
|
2872
|
-
if (interfaceDecl) {
|
|
2873
|
-
const analysis = analyzeInterfaceToIR(
|
|
2874
|
-
interfaceDecl,
|
|
2875
|
-
ctx.checker,
|
|
2876
|
-
options.filePath,
|
|
2877
|
-
options.extensionRegistry
|
|
2878
|
-
);
|
|
2879
|
-
return generateClassSchemas(analysis, source, options);
|
|
2880
|
-
}
|
|
2881
|
-
const typeAlias = findTypeAliasByName(ctx.sourceFile, options.typeName);
|
|
2882
|
-
if (typeAlias) {
|
|
2883
|
-
const result = analyzeTypeAliasToIR(
|
|
2884
|
-
typeAlias,
|
|
2885
|
-
ctx.checker,
|
|
2886
|
-
options.filePath,
|
|
2887
|
-
options.extensionRegistry
|
|
2888
|
-
);
|
|
2889
|
-
if (result.ok) {
|
|
2890
|
-
return generateClassSchemas(result.analysis, source, options);
|
|
2891
|
-
}
|
|
2892
|
-
throw new Error(result.error);
|
|
2893
|
-
}
|
|
2894
|
-
throw new Error(
|
|
2895
|
-
`Type "${options.typeName}" not found as a class, interface, or type alias in ${options.filePath}`
|
|
3796
|
+
const analysis = analyzeNamedTypeToIR(
|
|
3797
|
+
options.filePath,
|
|
3798
|
+
options.typeName,
|
|
3799
|
+
options.extensionRegistry
|
|
2896
3800
|
);
|
|
3801
|
+
return generateClassSchemas(analysis, { file: options.filePath }, options);
|
|
2897
3802
|
}
|
|
2898
3803
|
|
|
2899
3804
|
// src/generators/mixed-authoring.ts
|
|
2900
3805
|
function buildMixedAuthoringSchemas(options) {
|
|
2901
3806
|
const { filePath, typeName, overlays, ...schemaOptions } = options;
|
|
2902
|
-
const analysis =
|
|
3807
|
+
const analysis = analyzeNamedTypeToIR(filePath, typeName, schemaOptions.extensionRegistry);
|
|
2903
3808
|
const composedAnalysis = composeAnalysisWithOverlays(analysis, overlays);
|
|
2904
3809
|
const ir = canonicalizeTSDoc(composedAnalysis, { file: filePath });
|
|
2905
3810
|
return {
|
|
@@ -2907,29 +3812,6 @@ function buildMixedAuthoringSchemas(options) {
|
|
|
2907
3812
|
uiSchema: generateUiSchemaFromIR(ir)
|
|
2908
3813
|
};
|
|
2909
3814
|
}
|
|
2910
|
-
function analyzeNamedType(filePath, typeName, extensionRegistry) {
|
|
2911
|
-
const ctx = createProgramContext(filePath);
|
|
2912
|
-
const source = { file: filePath };
|
|
2913
|
-
const classDecl = findClassByName(ctx.sourceFile, typeName);
|
|
2914
|
-
if (classDecl !== null) {
|
|
2915
|
-
return analyzeClassToIR(classDecl, ctx.checker, source.file, extensionRegistry);
|
|
2916
|
-
}
|
|
2917
|
-
const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
|
|
2918
|
-
if (interfaceDecl !== null) {
|
|
2919
|
-
return analyzeInterfaceToIR(interfaceDecl, ctx.checker, source.file, extensionRegistry);
|
|
2920
|
-
}
|
|
2921
|
-
const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
|
|
2922
|
-
if (typeAlias !== null) {
|
|
2923
|
-
const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, source.file, extensionRegistry);
|
|
2924
|
-
if (result.ok) {
|
|
2925
|
-
return result.analysis;
|
|
2926
|
-
}
|
|
2927
|
-
throw new Error(result.error);
|
|
2928
|
-
}
|
|
2929
|
-
throw new Error(
|
|
2930
|
-
`Type "${typeName}" not found as a class, interface, or type alias in ${filePath}`
|
|
2931
|
-
);
|
|
2932
|
-
}
|
|
2933
3815
|
function composeAnalysisWithOverlays(analysis, overlays) {
|
|
2934
3816
|
const overlayIR = canonicalizeChainDSL(overlays);
|
|
2935
3817
|
const overlayFields = collectOverlayFields(overlayIR.elements);
|