@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.js
CHANGED
|
@@ -20,6 +20,7 @@ function canonicalizeChainDSL(form) {
|
|
|
20
20
|
kind: "form-ir",
|
|
21
21
|
irVersion: IR_VERSION,
|
|
22
22
|
elements: canonicalizeElements(form.elements),
|
|
23
|
+
rootAnnotations: [],
|
|
23
24
|
typeRegistry: {},
|
|
24
25
|
provenance: CHAIN_DSL_PROVENANCE
|
|
25
26
|
};
|
|
@@ -325,6 +326,7 @@ function canonicalizeTSDoc(analysis, source) {
|
|
|
325
326
|
irVersion: IR_VERSION2,
|
|
326
327
|
elements,
|
|
327
328
|
typeRegistry: analysis.typeRegistry,
|
|
329
|
+
...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { rootAnnotations: analysis.annotations },
|
|
328
330
|
...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { annotations: analysis.annotations },
|
|
329
331
|
provenance
|
|
330
332
|
};
|
|
@@ -402,6 +404,9 @@ function generateJsonSchemaFromIR(ir, options) {
|
|
|
402
404
|
const ctx = makeContext(options);
|
|
403
405
|
for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
|
|
404
406
|
ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
|
|
407
|
+
if (typeDef.constraints && typeDef.constraints.length > 0) {
|
|
408
|
+
applyConstraints(ctx.defs[name], typeDef.constraints, ctx);
|
|
409
|
+
}
|
|
405
410
|
if (typeDef.annotations && typeDef.annotations.length > 0) {
|
|
406
411
|
applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
|
|
407
412
|
}
|
|
@@ -570,7 +575,9 @@ function generateTypeNode(type, ctx) {
|
|
|
570
575
|
}
|
|
571
576
|
}
|
|
572
577
|
function generatePrimitiveType(type) {
|
|
573
|
-
return {
|
|
578
|
+
return {
|
|
579
|
+
type: type.primitiveKind === "integer" || type.primitiveKind === "bigint" ? "integer" : type.primitiveKind
|
|
580
|
+
};
|
|
574
581
|
}
|
|
575
582
|
function generateEnumType(type) {
|
|
576
583
|
const hasDisplayNames = type.members.some((m) => m.displayName !== void 0);
|
|
@@ -743,7 +750,7 @@ function applyAnnotations(schema, annotations, ctx) {
|
|
|
743
750
|
case "deprecated":
|
|
744
751
|
schema.deprecated = true;
|
|
745
752
|
if (annotation.message !== void 0 && annotation.message !== "") {
|
|
746
|
-
schema[
|
|
753
|
+
schema[`${ctx.vendorPrefix}-deprecation-description`] = annotation.message;
|
|
747
754
|
}
|
|
748
755
|
break;
|
|
749
756
|
case "placeholder":
|
|
@@ -1201,85 +1208,17 @@ var jsonSchema7Schema = z3.lazy(
|
|
|
1201
1208
|
);
|
|
1202
1209
|
|
|
1203
1210
|
// src/analyzer/program.ts
|
|
1204
|
-
import * as
|
|
1211
|
+
import * as ts4 from "typescript";
|
|
1205
1212
|
import * as path from "path";
|
|
1206
|
-
function createProgramContext(filePath) {
|
|
1207
|
-
const absolutePath = path.resolve(filePath);
|
|
1208
|
-
const fileDir = path.dirname(absolutePath);
|
|
1209
|
-
const configPath = ts.findConfigFile(fileDir, ts.sys.fileExists.bind(ts.sys), "tsconfig.json");
|
|
1210
|
-
let compilerOptions;
|
|
1211
|
-
let fileNames;
|
|
1212
|
-
if (configPath) {
|
|
1213
|
-
const configFile = ts.readConfigFile(configPath, ts.sys.readFile.bind(ts.sys));
|
|
1214
|
-
if (configFile.error) {
|
|
1215
|
-
throw new Error(
|
|
1216
|
-
`Error reading tsconfig.json: ${ts.flattenDiagnosticMessageText(configFile.error.messageText, "\n")}`
|
|
1217
|
-
);
|
|
1218
|
-
}
|
|
1219
|
-
const parsed = ts.parseJsonConfigFileContent(
|
|
1220
|
-
configFile.config,
|
|
1221
|
-
ts.sys,
|
|
1222
|
-
path.dirname(configPath)
|
|
1223
|
-
);
|
|
1224
|
-
if (parsed.errors.length > 0) {
|
|
1225
|
-
const errorMessages = parsed.errors.map((e) => ts.flattenDiagnosticMessageText(e.messageText, "\n")).join("\n");
|
|
1226
|
-
throw new Error(`Error parsing tsconfig.json: ${errorMessages}`);
|
|
1227
|
-
}
|
|
1228
|
-
compilerOptions = parsed.options;
|
|
1229
|
-
fileNames = parsed.fileNames.includes(absolutePath) ? parsed.fileNames : [...parsed.fileNames, absolutePath];
|
|
1230
|
-
} else {
|
|
1231
|
-
compilerOptions = {
|
|
1232
|
-
target: ts.ScriptTarget.ES2022,
|
|
1233
|
-
module: ts.ModuleKind.NodeNext,
|
|
1234
|
-
moduleResolution: ts.ModuleResolutionKind.NodeNext,
|
|
1235
|
-
strict: true,
|
|
1236
|
-
skipLibCheck: true,
|
|
1237
|
-
declaration: true
|
|
1238
|
-
};
|
|
1239
|
-
fileNames = [absolutePath];
|
|
1240
|
-
}
|
|
1241
|
-
const program = ts.createProgram(fileNames, compilerOptions);
|
|
1242
|
-
const sourceFile = program.getSourceFile(absolutePath);
|
|
1243
|
-
if (!sourceFile) {
|
|
1244
|
-
throw new Error(`Could not find source file: ${absolutePath}`);
|
|
1245
|
-
}
|
|
1246
|
-
return {
|
|
1247
|
-
program,
|
|
1248
|
-
checker: program.getTypeChecker(),
|
|
1249
|
-
sourceFile
|
|
1250
|
-
};
|
|
1251
|
-
}
|
|
1252
|
-
function findNodeByName(sourceFile, name, predicate, getName) {
|
|
1253
|
-
let result = null;
|
|
1254
|
-
function visit(node) {
|
|
1255
|
-
if (result) return;
|
|
1256
|
-
if (predicate(node) && getName(node) === name) {
|
|
1257
|
-
result = node;
|
|
1258
|
-
return;
|
|
1259
|
-
}
|
|
1260
|
-
ts.forEachChild(node, visit);
|
|
1261
|
-
}
|
|
1262
|
-
visit(sourceFile);
|
|
1263
|
-
return result;
|
|
1264
|
-
}
|
|
1265
|
-
function findClassByName(sourceFile, className) {
|
|
1266
|
-
return findNodeByName(sourceFile, className, ts.isClassDeclaration, (n) => n.name?.text);
|
|
1267
|
-
}
|
|
1268
|
-
function findInterfaceByName(sourceFile, interfaceName) {
|
|
1269
|
-
return findNodeByName(sourceFile, interfaceName, ts.isInterfaceDeclaration, (n) => n.name.text);
|
|
1270
|
-
}
|
|
1271
|
-
function findTypeAliasByName(sourceFile, aliasName) {
|
|
1272
|
-
return findNodeByName(sourceFile, aliasName, ts.isTypeAliasDeclaration, (n) => n.name.text);
|
|
1273
|
-
}
|
|
1274
1213
|
|
|
1275
1214
|
// src/analyzer/class-analyzer.ts
|
|
1276
|
-
import * as
|
|
1215
|
+
import * as ts3 from "typescript";
|
|
1277
1216
|
|
|
1278
1217
|
// src/analyzer/jsdoc-constraints.ts
|
|
1279
|
-
import * as
|
|
1218
|
+
import * as ts2 from "typescript";
|
|
1280
1219
|
|
|
1281
1220
|
// src/analyzer/tsdoc-parser.ts
|
|
1282
|
-
import * as
|
|
1221
|
+
import * as ts from "typescript";
|
|
1283
1222
|
import {
|
|
1284
1223
|
TSDocParser,
|
|
1285
1224
|
TSDocConfiguration,
|
|
@@ -1377,10 +1316,10 @@ function parseTSDocTags(node, file = "", options) {
|
|
|
1377
1316
|
let placeholderProvenance;
|
|
1378
1317
|
const sourceFile = node.getSourceFile();
|
|
1379
1318
|
const sourceText = sourceFile.getFullText();
|
|
1380
|
-
const commentRanges =
|
|
1319
|
+
const commentRanges = ts.getLeadingCommentRanges(sourceText, node.getFullStart());
|
|
1381
1320
|
if (commentRanges) {
|
|
1382
1321
|
for (const range of commentRanges) {
|
|
1383
|
-
if (range.kind !==
|
|
1322
|
+
if (range.kind !== ts.SyntaxKind.MultiLineCommentTrivia) {
|
|
1384
1323
|
continue;
|
|
1385
1324
|
}
|
|
1386
1325
|
const commentText = sourceText.substring(range.pos, range.end);
|
|
@@ -1398,26 +1337,31 @@ function parseTSDocTags(node, file = "", options) {
|
|
|
1398
1337
|
const text2 = extractBlockText(block).trim();
|
|
1399
1338
|
if (text2 === "") continue;
|
|
1400
1339
|
const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
displayName
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1340
|
+
switch (tagName) {
|
|
1341
|
+
case "displayName":
|
|
1342
|
+
if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
|
|
1343
|
+
displayName = text2;
|
|
1344
|
+
displayNameProvenance = provenance2;
|
|
1345
|
+
}
|
|
1346
|
+
break;
|
|
1347
|
+
case "format":
|
|
1348
|
+
annotations.push({
|
|
1349
|
+
kind: "annotation",
|
|
1350
|
+
annotationKind: "format",
|
|
1351
|
+
value: text2,
|
|
1352
|
+
provenance: provenance2
|
|
1353
|
+
});
|
|
1354
|
+
break;
|
|
1355
|
+
case "description":
|
|
1415
1356
|
description = text2;
|
|
1416
1357
|
descriptionProvenance = provenance2;
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1358
|
+
break;
|
|
1359
|
+
case "placeholder":
|
|
1360
|
+
if (placeholder === void 0) {
|
|
1361
|
+
placeholder = text2;
|
|
1362
|
+
placeholderProvenance = provenance2;
|
|
1363
|
+
}
|
|
1364
|
+
break;
|
|
1421
1365
|
}
|
|
1422
1366
|
continue;
|
|
1423
1367
|
}
|
|
@@ -1447,6 +1391,13 @@ function parseTSDocTags(node, file = "", options) {
|
|
|
1447
1391
|
descriptionProvenance = provenanceForComment(range, sourceFile, file, "remarks");
|
|
1448
1392
|
}
|
|
1449
1393
|
}
|
|
1394
|
+
if (description === void 0) {
|
|
1395
|
+
const summary = extractPlainText(docComment.summarySection).trim();
|
|
1396
|
+
if (summary !== "") {
|
|
1397
|
+
description = summary;
|
|
1398
|
+
descriptionProvenance = provenanceForComment(range, sourceFile, file, "summary");
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1450
1401
|
}
|
|
1451
1402
|
}
|
|
1452
1403
|
if (displayName !== void 0 && displayNameProvenance !== void 0) {
|
|
@@ -1473,7 +1424,7 @@ function parseTSDocTags(node, file = "", options) {
|
|
|
1473
1424
|
provenance: placeholderProvenance
|
|
1474
1425
|
});
|
|
1475
1426
|
}
|
|
1476
|
-
const jsDocTagsAll =
|
|
1427
|
+
const jsDocTagsAll = ts.getJSDocTags(node);
|
|
1477
1428
|
for (const tag of jsDocTagsAll) {
|
|
1478
1429
|
const tagName = normalizeConstraintTagName(tag.tagName.text);
|
|
1479
1430
|
if (!TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
@@ -1496,7 +1447,7 @@ function parseTSDocTags(node, file = "", options) {
|
|
|
1496
1447
|
function extractDisplayNameMetadata(node) {
|
|
1497
1448
|
let displayName;
|
|
1498
1449
|
const memberDisplayNames = /* @__PURE__ */ new Map();
|
|
1499
|
-
for (const tag of
|
|
1450
|
+
for (const tag of ts.getJSDocTags(node)) {
|
|
1500
1451
|
const tagName = normalizeConstraintTagName(tag.tagName.text);
|
|
1501
1452
|
if (tagName !== "displayName") continue;
|
|
1502
1453
|
const commentText = getTagCommentText(tag);
|
|
@@ -1517,11 +1468,11 @@ function extractDisplayNameMetadata(node) {
|
|
|
1517
1468
|
}
|
|
1518
1469
|
function extractPathTarget(text) {
|
|
1519
1470
|
const trimmed = text.trimStart();
|
|
1520
|
-
const match = /^:([a-zA-Z_]\w*)
|
|
1521
|
-
if (!match?.[1]
|
|
1471
|
+
const match = /^:([a-zA-Z_]\w*)(?:\s+([\s\S]*))?$/.exec(trimmed);
|
|
1472
|
+
if (!match?.[1]) return null;
|
|
1522
1473
|
return {
|
|
1523
1474
|
path: { segments: [match[1]] },
|
|
1524
|
-
remainingText: match[2]
|
|
1475
|
+
remainingText: match[2] ?? ""
|
|
1525
1476
|
};
|
|
1526
1477
|
}
|
|
1527
1478
|
function extractBlockText(block) {
|
|
@@ -1784,7 +1735,7 @@ function getTagCommentText(tag) {
|
|
|
1784
1735
|
if (typeof tag.comment === "string") {
|
|
1785
1736
|
return tag.comment;
|
|
1786
1737
|
}
|
|
1787
|
-
return
|
|
1738
|
+
return ts.getTextOfJSDocComment(tag.comment);
|
|
1788
1739
|
}
|
|
1789
1740
|
|
|
1790
1741
|
// src/analyzer/jsdoc-constraints.ts
|
|
@@ -1799,18 +1750,18 @@ function extractJSDocAnnotationNodes(node, file = "", options) {
|
|
|
1799
1750
|
function extractDefaultValueAnnotation(initializer, file = "") {
|
|
1800
1751
|
if (!initializer) return null;
|
|
1801
1752
|
let value;
|
|
1802
|
-
if (
|
|
1753
|
+
if (ts2.isStringLiteral(initializer)) {
|
|
1803
1754
|
value = initializer.text;
|
|
1804
|
-
} else if (
|
|
1755
|
+
} else if (ts2.isNumericLiteral(initializer)) {
|
|
1805
1756
|
value = Number(initializer.text);
|
|
1806
|
-
} else if (initializer.kind ===
|
|
1757
|
+
} else if (initializer.kind === ts2.SyntaxKind.TrueKeyword) {
|
|
1807
1758
|
value = true;
|
|
1808
|
-
} else if (initializer.kind ===
|
|
1759
|
+
} else if (initializer.kind === ts2.SyntaxKind.FalseKeyword) {
|
|
1809
1760
|
value = false;
|
|
1810
|
-
} else if (initializer.kind ===
|
|
1761
|
+
} else if (initializer.kind === ts2.SyntaxKind.NullKeyword) {
|
|
1811
1762
|
value = null;
|
|
1812
|
-
} else if (
|
|
1813
|
-
if (initializer.operator ===
|
|
1763
|
+
} else if (ts2.isPrefixUnaryExpression(initializer)) {
|
|
1764
|
+
if (initializer.operator === ts2.SyntaxKind.MinusToken && ts2.isNumericLiteral(initializer.operand)) {
|
|
1814
1765
|
value = -Number(initializer.operand.text);
|
|
1815
1766
|
}
|
|
1816
1767
|
}
|
|
@@ -1832,10 +1783,10 @@ function extractDefaultValueAnnotation(initializer, file = "") {
|
|
|
1832
1783
|
|
|
1833
1784
|
// src/analyzer/class-analyzer.ts
|
|
1834
1785
|
function isObjectType(type) {
|
|
1835
|
-
return !!(type.flags &
|
|
1786
|
+
return !!(type.flags & ts3.TypeFlags.Object);
|
|
1836
1787
|
}
|
|
1837
1788
|
function isTypeReference(type) {
|
|
1838
|
-
return !!(type.flags &
|
|
1789
|
+
return !!(type.flags & ts3.TypeFlags.Object) && !!(type.objectFlags & ts3.ObjectFlags.Reference);
|
|
1839
1790
|
}
|
|
1840
1791
|
var RESOLVING_TYPE_PLACEHOLDER = {
|
|
1841
1792
|
kind: "object",
|
|
@@ -1865,7 +1816,7 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
|
|
|
1865
1816
|
const instanceMethods = [];
|
|
1866
1817
|
const staticMethods = [];
|
|
1867
1818
|
for (const member of classDecl.members) {
|
|
1868
|
-
if (
|
|
1819
|
+
if (ts3.isPropertyDeclaration(member)) {
|
|
1869
1820
|
const fieldNode = analyzeFieldToIR(
|
|
1870
1821
|
member,
|
|
1871
1822
|
checker,
|
|
@@ -1878,10 +1829,10 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
|
|
|
1878
1829
|
fields.push(fieldNode);
|
|
1879
1830
|
fieldLayouts.push({});
|
|
1880
1831
|
}
|
|
1881
|
-
} else if (
|
|
1832
|
+
} else if (ts3.isMethodDeclaration(member)) {
|
|
1882
1833
|
const methodInfo = analyzeMethod(member, checker);
|
|
1883
1834
|
if (methodInfo) {
|
|
1884
|
-
const isStatic = member.modifiers?.some((m) => m.kind ===
|
|
1835
|
+
const isStatic = member.modifiers?.some((m) => m.kind === ts3.SyntaxKind.StaticKeyword);
|
|
1885
1836
|
if (isStatic) {
|
|
1886
1837
|
staticMethods.push(methodInfo);
|
|
1887
1838
|
} else {
|
|
@@ -1911,7 +1862,7 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
|
|
|
1911
1862
|
);
|
|
1912
1863
|
const visiting = /* @__PURE__ */ new Set();
|
|
1913
1864
|
for (const member of interfaceDecl.members) {
|
|
1914
|
-
if (
|
|
1865
|
+
if (ts3.isPropertySignature(member)) {
|
|
1915
1866
|
const fieldNode = analyzeInterfacePropertyToIR(
|
|
1916
1867
|
member,
|
|
1917
1868
|
checker,
|
|
@@ -1937,10 +1888,10 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
|
|
|
1937
1888
|
};
|
|
1938
1889
|
}
|
|
1939
1890
|
function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry) {
|
|
1940
|
-
if (!
|
|
1891
|
+
if (!ts3.isTypeLiteralNode(typeAlias.type)) {
|
|
1941
1892
|
const sourceFile = typeAlias.getSourceFile();
|
|
1942
1893
|
const { line } = sourceFile.getLineAndCharacterOfPosition(typeAlias.getStart());
|
|
1943
|
-
const kindDesc =
|
|
1894
|
+
const kindDesc = ts3.SyntaxKind[typeAlias.type.kind] ?? "unknown";
|
|
1944
1895
|
return {
|
|
1945
1896
|
ok: false,
|
|
1946
1897
|
error: `Type alias "${typeAlias.name.text}" at line ${String(line + 1)} is not an object type literal (found ${kindDesc})`
|
|
@@ -1956,7 +1907,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
|
|
|
1956
1907
|
);
|
|
1957
1908
|
const visiting = /* @__PURE__ */ new Set();
|
|
1958
1909
|
for (const member of typeAlias.type.members) {
|
|
1959
|
-
if (
|
|
1910
|
+
if (ts3.isPropertySignature(member)) {
|
|
1960
1911
|
const fieldNode = analyzeInterfacePropertyToIR(
|
|
1961
1912
|
member,
|
|
1962
1913
|
checker,
|
|
@@ -1984,7 +1935,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
|
|
|
1984
1935
|
};
|
|
1985
1936
|
}
|
|
1986
1937
|
function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1987
|
-
if (!
|
|
1938
|
+
if (!ts3.isIdentifier(prop.name)) {
|
|
1988
1939
|
return null;
|
|
1989
1940
|
}
|
|
1990
1941
|
const name = prop.name.text;
|
|
@@ -2001,12 +1952,14 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extension
|
|
|
2001
1952
|
extensionRegistry
|
|
2002
1953
|
);
|
|
2003
1954
|
const constraints = [];
|
|
2004
|
-
if (prop.type) {
|
|
1955
|
+
if (prop.type && !shouldEmitPrimitiveAliasDefinition(prop.type, checker)) {
|
|
2005
1956
|
constraints.push(
|
|
2006
1957
|
...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
|
|
2007
1958
|
);
|
|
2008
1959
|
}
|
|
2009
|
-
constraints.push(
|
|
1960
|
+
constraints.push(
|
|
1961
|
+
...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
1962
|
+
);
|
|
2010
1963
|
let annotations = [];
|
|
2011
1964
|
annotations.push(
|
|
2012
1965
|
...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
@@ -2027,7 +1980,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extension
|
|
|
2027
1980
|
};
|
|
2028
1981
|
}
|
|
2029
1982
|
function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2030
|
-
if (!
|
|
1983
|
+
if (!ts3.isIdentifier(prop.name)) {
|
|
2031
1984
|
return null;
|
|
2032
1985
|
}
|
|
2033
1986
|
const name = prop.name.text;
|
|
@@ -2044,12 +1997,14 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
|
|
|
2044
1997
|
extensionRegistry
|
|
2045
1998
|
);
|
|
2046
1999
|
const constraints = [];
|
|
2047
|
-
if (prop.type) {
|
|
2000
|
+
if (prop.type && !shouldEmitPrimitiveAliasDefinition(prop.type, checker)) {
|
|
2048
2001
|
constraints.push(
|
|
2049
2002
|
...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
|
|
2050
2003
|
);
|
|
2051
2004
|
}
|
|
2052
|
-
constraints.push(
|
|
2005
|
+
constraints.push(
|
|
2006
|
+
...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
2007
|
+
);
|
|
2053
2008
|
let annotations = [];
|
|
2054
2009
|
annotations.push(
|
|
2055
2010
|
...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
@@ -2138,7 +2093,7 @@ function resolveRegisteredCustomType(sourceNode, extensionRegistry, checker) {
|
|
|
2138
2093
|
return resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker);
|
|
2139
2094
|
}
|
|
2140
2095
|
function resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker) {
|
|
2141
|
-
if (
|
|
2096
|
+
if (ts3.isParenthesizedTypeNode(typeNode)) {
|
|
2142
2097
|
return resolveRegisteredCustomTypeFromTypeNode(typeNode.type, extensionRegistry, checker);
|
|
2143
2098
|
}
|
|
2144
2099
|
const typeName = getTypeNodeRegistrationName(typeNode);
|
|
@@ -2153,8 +2108,8 @@ function resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, ch
|
|
|
2153
2108
|
payload: null
|
|
2154
2109
|
};
|
|
2155
2110
|
}
|
|
2156
|
-
if (
|
|
2157
|
-
const aliasDecl = checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(
|
|
2111
|
+
if (ts3.isTypeReferenceNode(typeNode) && ts3.isIdentifier(typeNode.typeName)) {
|
|
2112
|
+
const aliasDecl = checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
2158
2113
|
if (aliasDecl !== void 0) {
|
|
2159
2114
|
return resolveRegisteredCustomTypeFromTypeNode(aliasDecl.type, extensionRegistry, checker);
|
|
2160
2115
|
}
|
|
@@ -2162,22 +2117,22 @@ function resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, ch
|
|
|
2162
2117
|
return null;
|
|
2163
2118
|
}
|
|
2164
2119
|
function extractTypeNodeFromSource(sourceNode) {
|
|
2165
|
-
if (
|
|
2120
|
+
if (ts3.isPropertyDeclaration(sourceNode) || ts3.isPropertySignature(sourceNode) || ts3.isParameter(sourceNode) || ts3.isTypeAliasDeclaration(sourceNode)) {
|
|
2166
2121
|
return sourceNode.type;
|
|
2167
2122
|
}
|
|
2168
|
-
if (
|
|
2123
|
+
if (ts3.isTypeNode(sourceNode)) {
|
|
2169
2124
|
return sourceNode;
|
|
2170
2125
|
}
|
|
2171
2126
|
return void 0;
|
|
2172
2127
|
}
|
|
2173
2128
|
function getTypeNodeRegistrationName(typeNode) {
|
|
2174
|
-
if (
|
|
2175
|
-
return
|
|
2129
|
+
if (ts3.isTypeReferenceNode(typeNode)) {
|
|
2130
|
+
return ts3.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : typeNode.typeName.right.text;
|
|
2176
2131
|
}
|
|
2177
|
-
if (
|
|
2132
|
+
if (ts3.isParenthesizedTypeNode(typeNode)) {
|
|
2178
2133
|
return getTypeNodeRegistrationName(typeNode.type);
|
|
2179
2134
|
}
|
|
2180
|
-
if (typeNode.kind ===
|
|
2135
|
+
if (typeNode.kind === ts3.SyntaxKind.BigIntKeyword || typeNode.kind === ts3.SyntaxKind.StringKeyword || typeNode.kind === ts3.SyntaxKind.NumberKeyword || typeNode.kind === ts3.SyntaxKind.BooleanKeyword) {
|
|
2181
2136
|
return typeNode.getText();
|
|
2182
2137
|
}
|
|
2183
2138
|
return null;
|
|
@@ -2187,19 +2142,34 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
|
|
|
2187
2142
|
if (customType) {
|
|
2188
2143
|
return customType;
|
|
2189
2144
|
}
|
|
2190
|
-
|
|
2145
|
+
const primitiveAlias = tryResolveNamedPrimitiveAlias(
|
|
2146
|
+
type,
|
|
2147
|
+
checker,
|
|
2148
|
+
file,
|
|
2149
|
+
typeRegistry,
|
|
2150
|
+
visiting,
|
|
2151
|
+
sourceNode,
|
|
2152
|
+
extensionRegistry
|
|
2153
|
+
);
|
|
2154
|
+
if (primitiveAlias) {
|
|
2155
|
+
return primitiveAlias;
|
|
2156
|
+
}
|
|
2157
|
+
if (type.flags & ts3.TypeFlags.String) {
|
|
2191
2158
|
return { kind: "primitive", primitiveKind: "string" };
|
|
2192
2159
|
}
|
|
2193
|
-
if (type.flags &
|
|
2160
|
+
if (type.flags & ts3.TypeFlags.Number) {
|
|
2194
2161
|
return { kind: "primitive", primitiveKind: "number" };
|
|
2195
2162
|
}
|
|
2196
|
-
if (type.flags &
|
|
2163
|
+
if (type.flags & (ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral)) {
|
|
2164
|
+
return { kind: "primitive", primitiveKind: "bigint" };
|
|
2165
|
+
}
|
|
2166
|
+
if (type.flags & ts3.TypeFlags.Boolean) {
|
|
2197
2167
|
return { kind: "primitive", primitiveKind: "boolean" };
|
|
2198
2168
|
}
|
|
2199
|
-
if (type.flags &
|
|
2169
|
+
if (type.flags & ts3.TypeFlags.Null) {
|
|
2200
2170
|
return { kind: "primitive", primitiveKind: "null" };
|
|
2201
2171
|
}
|
|
2202
|
-
if (type.flags &
|
|
2172
|
+
if (type.flags & ts3.TypeFlags.Undefined) {
|
|
2203
2173
|
return { kind: "primitive", primitiveKind: "null" };
|
|
2204
2174
|
}
|
|
2205
2175
|
if (type.isStringLiteral()) {
|
|
@@ -2241,6 +2211,75 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
|
|
|
2241
2211
|
}
|
|
2242
2212
|
return { kind: "primitive", primitiveKind: "string" };
|
|
2243
2213
|
}
|
|
2214
|
+
function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
2215
|
+
if (!(type.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null))) {
|
|
2216
|
+
return null;
|
|
2217
|
+
}
|
|
2218
|
+
const aliasDecl = type.aliasSymbol?.declarations?.find(ts3.isTypeAliasDeclaration) ?? getReferencedTypeAliasDeclaration(sourceNode, checker);
|
|
2219
|
+
if (!aliasDecl) {
|
|
2220
|
+
return null;
|
|
2221
|
+
}
|
|
2222
|
+
const aliasName = aliasDecl.name.text;
|
|
2223
|
+
if (!typeRegistry[aliasName]) {
|
|
2224
|
+
const aliasType = checker.getTypeFromTypeNode(aliasDecl.type);
|
|
2225
|
+
const constraints = [
|
|
2226
|
+
...extractJSDocConstraintNodes(aliasDecl, file, makeParseOptions(extensionRegistry)),
|
|
2227
|
+
...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, extensionRegistry)
|
|
2228
|
+
];
|
|
2229
|
+
const annotations = extractJSDocAnnotationNodes(
|
|
2230
|
+
aliasDecl,
|
|
2231
|
+
file,
|
|
2232
|
+
makeParseOptions(extensionRegistry)
|
|
2233
|
+
);
|
|
2234
|
+
typeRegistry[aliasName] = {
|
|
2235
|
+
name: aliasName,
|
|
2236
|
+
type: resolveAliasedPrimitiveTarget(
|
|
2237
|
+
aliasType,
|
|
2238
|
+
checker,
|
|
2239
|
+
file,
|
|
2240
|
+
typeRegistry,
|
|
2241
|
+
visiting,
|
|
2242
|
+
extensionRegistry
|
|
2243
|
+
),
|
|
2244
|
+
...constraints.length > 0 && { constraints },
|
|
2245
|
+
...annotations.length > 0 && { annotations },
|
|
2246
|
+
provenance: provenanceForDeclaration(aliasDecl, file)
|
|
2247
|
+
};
|
|
2248
|
+
}
|
|
2249
|
+
return { kind: "reference", name: aliasName, typeArguments: [] };
|
|
2250
|
+
}
|
|
2251
|
+
function getReferencedTypeAliasDeclaration(sourceNode, checker) {
|
|
2252
|
+
const typeNode = sourceNode && (ts3.isPropertyDeclaration(sourceNode) || ts3.isPropertySignature(sourceNode) || ts3.isParameter(sourceNode)) ? sourceNode.type : void 0;
|
|
2253
|
+
if (!typeNode || !ts3.isTypeReferenceNode(typeNode)) {
|
|
2254
|
+
return void 0;
|
|
2255
|
+
}
|
|
2256
|
+
return checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
2257
|
+
}
|
|
2258
|
+
function shouldEmitPrimitiveAliasDefinition(typeNode, checker) {
|
|
2259
|
+
if (!ts3.isTypeReferenceNode(typeNode)) {
|
|
2260
|
+
return false;
|
|
2261
|
+
}
|
|
2262
|
+
const aliasDecl = checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
2263
|
+
if (!aliasDecl) {
|
|
2264
|
+
return false;
|
|
2265
|
+
}
|
|
2266
|
+
const resolved = checker.getTypeFromTypeNode(aliasDecl.type);
|
|
2267
|
+
return !!(resolved.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null));
|
|
2268
|
+
}
|
|
2269
|
+
function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2270
|
+
const nestedAliasDecl = type.aliasSymbol?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
2271
|
+
if (nestedAliasDecl !== void 0) {
|
|
2272
|
+
return resolveAliasedPrimitiveTarget(
|
|
2273
|
+
checker.getTypeFromTypeNode(nestedAliasDecl.type),
|
|
2274
|
+
checker,
|
|
2275
|
+
file,
|
|
2276
|
+
typeRegistry,
|
|
2277
|
+
visiting,
|
|
2278
|
+
extensionRegistry
|
|
2279
|
+
);
|
|
2280
|
+
}
|
|
2281
|
+
return resolveTypeNode(type, checker, file, typeRegistry, visiting, void 0, extensionRegistry);
|
|
2282
|
+
}
|
|
2244
2283
|
function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
2245
2284
|
const typeName = getNamedTypeName(type);
|
|
2246
2285
|
const namedDecl = getNamedTypeDeclaration(type);
|
|
@@ -2253,13 +2292,13 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
2253
2292
|
(memberTypeNode) => !isNullishTypeNode(resolveAliasedTypeNode(memberTypeNode, checker))
|
|
2254
2293
|
);
|
|
2255
2294
|
const nonNullTypes = allTypes.filter(
|
|
2256
|
-
(memberType) => !(memberType.flags & (
|
|
2295
|
+
(memberType) => !(memberType.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined))
|
|
2257
2296
|
);
|
|
2258
2297
|
const nonNullMembers = nonNullTypes.map((memberType, index) => ({
|
|
2259
2298
|
memberType,
|
|
2260
2299
|
sourceNode: nonNullSourceNodes.length === nonNullTypes.length ? nonNullSourceNodes[index] : void 0
|
|
2261
2300
|
}));
|
|
2262
|
-
const hasNull = allTypes.some((t) => t.flags &
|
|
2301
|
+
const hasNull = allTypes.some((t) => t.flags & ts3.TypeFlags.Null);
|
|
2263
2302
|
const memberDisplayNames = /* @__PURE__ */ new Map();
|
|
2264
2303
|
if (namedDecl) {
|
|
2265
2304
|
for (const [value, label] of extractDisplayNameMetadata(namedDecl).memberDisplayNames) {
|
|
@@ -2288,7 +2327,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
2288
2327
|
const displayName = memberDisplayNames.get(String(value));
|
|
2289
2328
|
return displayName !== void 0 ? { value, displayName } : { value };
|
|
2290
2329
|
});
|
|
2291
|
-
const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags &
|
|
2330
|
+
const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts3.TypeFlags.BooleanLiteral);
|
|
2292
2331
|
if (isBooleanUnion2) {
|
|
2293
2332
|
const boolNode = { kind: "primitive", primitiveKind: "boolean" };
|
|
2294
2333
|
const result = hasNull ? {
|
|
@@ -2374,7 +2413,7 @@ function tryResolveRecordType(type, checker, file, typeRegistry, visiting, exten
|
|
|
2374
2413
|
if (type.getProperties().length > 0) {
|
|
2375
2414
|
return null;
|
|
2376
2415
|
}
|
|
2377
|
-
const indexInfo = checker.getIndexInfoOfType(type,
|
|
2416
|
+
const indexInfo = checker.getIndexInfoOfType(type, ts3.IndexKind.String);
|
|
2378
2417
|
if (!indexInfo) {
|
|
2379
2418
|
return null;
|
|
2380
2419
|
}
|
|
@@ -2485,7 +2524,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
|
|
|
2485
2524
|
const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
|
|
2486
2525
|
if (!declaration) continue;
|
|
2487
2526
|
const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
|
|
2488
|
-
const optional = !!(prop.flags &
|
|
2527
|
+
const optional = !!(prop.flags & ts3.SymbolFlags.Optional);
|
|
2489
2528
|
const propTypeNode = resolveTypeNode(
|
|
2490
2529
|
propType,
|
|
2491
2530
|
checker,
|
|
@@ -2530,11 +2569,11 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
2530
2569
|
for (const symbol of symbols) {
|
|
2531
2570
|
const declarations = symbol.declarations;
|
|
2532
2571
|
if (!declarations) continue;
|
|
2533
|
-
const classDecl = declarations.find(
|
|
2572
|
+
const classDecl = declarations.find(ts3.isClassDeclaration);
|
|
2534
2573
|
if (classDecl) {
|
|
2535
2574
|
const map = /* @__PURE__ */ new Map();
|
|
2536
2575
|
for (const member of classDecl.members) {
|
|
2537
|
-
if (
|
|
2576
|
+
if (ts3.isPropertyDeclaration(member) && ts3.isIdentifier(member.name)) {
|
|
2538
2577
|
const fieldNode = analyzeFieldToIR(
|
|
2539
2578
|
member,
|
|
2540
2579
|
checker,
|
|
@@ -2554,7 +2593,7 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
2554
2593
|
}
|
|
2555
2594
|
return map;
|
|
2556
2595
|
}
|
|
2557
|
-
const interfaceDecl = declarations.find(
|
|
2596
|
+
const interfaceDecl = declarations.find(ts3.isInterfaceDeclaration);
|
|
2558
2597
|
if (interfaceDecl) {
|
|
2559
2598
|
return buildFieldNodeInfoMap(
|
|
2560
2599
|
interfaceDecl.members,
|
|
@@ -2565,8 +2604,8 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
2565
2604
|
extensionRegistry
|
|
2566
2605
|
);
|
|
2567
2606
|
}
|
|
2568
|
-
const typeAliasDecl = declarations.find(
|
|
2569
|
-
if (typeAliasDecl &&
|
|
2607
|
+
const typeAliasDecl = declarations.find(ts3.isTypeAliasDeclaration);
|
|
2608
|
+
if (typeAliasDecl && ts3.isTypeLiteralNode(typeAliasDecl.type)) {
|
|
2570
2609
|
return buildFieldNodeInfoMap(
|
|
2571
2610
|
typeAliasDecl.type.members,
|
|
2572
2611
|
checker,
|
|
@@ -2585,10 +2624,10 @@ function extractArrayElementTypeNode(sourceNode, checker) {
|
|
|
2585
2624
|
return void 0;
|
|
2586
2625
|
}
|
|
2587
2626
|
const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
|
|
2588
|
-
if (
|
|
2627
|
+
if (ts3.isArrayTypeNode(resolvedTypeNode)) {
|
|
2589
2628
|
return resolvedTypeNode.elementType;
|
|
2590
2629
|
}
|
|
2591
|
-
if (
|
|
2630
|
+
if (ts3.isTypeReferenceNode(resolvedTypeNode) && ts3.isIdentifier(resolvedTypeNode.typeName) && resolvedTypeNode.typeName.text === "Array" && resolvedTypeNode.typeArguments?.[0]) {
|
|
2592
2631
|
return resolvedTypeNode.typeArguments[0];
|
|
2593
2632
|
}
|
|
2594
2633
|
return void 0;
|
|
@@ -2599,17 +2638,17 @@ function extractUnionMemberTypeNodes(sourceNode, checker) {
|
|
|
2599
2638
|
return [];
|
|
2600
2639
|
}
|
|
2601
2640
|
const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
|
|
2602
|
-
return
|
|
2641
|
+
return ts3.isUnionTypeNode(resolvedTypeNode) ? [...resolvedTypeNode.types] : [];
|
|
2603
2642
|
}
|
|
2604
2643
|
function resolveAliasedTypeNode(typeNode, checker, visited = /* @__PURE__ */ new Set()) {
|
|
2605
|
-
if (
|
|
2644
|
+
if (ts3.isParenthesizedTypeNode(typeNode)) {
|
|
2606
2645
|
return resolveAliasedTypeNode(typeNode.type, checker, visited);
|
|
2607
2646
|
}
|
|
2608
|
-
if (!
|
|
2647
|
+
if (!ts3.isTypeReferenceNode(typeNode) || !ts3.isIdentifier(typeNode.typeName)) {
|
|
2609
2648
|
return typeNode;
|
|
2610
2649
|
}
|
|
2611
2650
|
const symbol = checker.getSymbolAtLocation(typeNode.typeName);
|
|
2612
|
-
const aliasDecl = symbol?.declarations?.find(
|
|
2651
|
+
const aliasDecl = symbol?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
2613
2652
|
if (aliasDecl === void 0 || visited.has(aliasDecl)) {
|
|
2614
2653
|
return typeNode;
|
|
2615
2654
|
}
|
|
@@ -2617,15 +2656,15 @@ function resolveAliasedTypeNode(typeNode, checker, visited = /* @__PURE__ */ new
|
|
|
2617
2656
|
return resolveAliasedTypeNode(aliasDecl.type, checker, visited);
|
|
2618
2657
|
}
|
|
2619
2658
|
function isNullishTypeNode(typeNode) {
|
|
2620
|
-
if (typeNode.kind ===
|
|
2659
|
+
if (typeNode.kind === ts3.SyntaxKind.NullKeyword || typeNode.kind === ts3.SyntaxKind.UndefinedKeyword) {
|
|
2621
2660
|
return true;
|
|
2622
2661
|
}
|
|
2623
|
-
return
|
|
2662
|
+
return ts3.isLiteralTypeNode(typeNode) && (typeNode.literal.kind === ts3.SyntaxKind.NullKeyword || typeNode.literal.kind === ts3.SyntaxKind.UndefinedKeyword);
|
|
2624
2663
|
}
|
|
2625
2664
|
function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2626
2665
|
const map = /* @__PURE__ */ new Map();
|
|
2627
2666
|
for (const member of members) {
|
|
2628
|
-
if (
|
|
2667
|
+
if (ts3.isPropertySignature(member)) {
|
|
2629
2668
|
const fieldNode = analyzeInterfacePropertyToIR(
|
|
2630
2669
|
member,
|
|
2631
2670
|
checker,
|
|
@@ -2647,7 +2686,7 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, e
|
|
|
2647
2686
|
}
|
|
2648
2687
|
var MAX_ALIAS_CHAIN_DEPTH = 8;
|
|
2649
2688
|
function extractTypeAliasConstraintNodes(typeNode, checker, file, extensionRegistry, depth = 0) {
|
|
2650
|
-
if (!
|
|
2689
|
+
if (!ts3.isTypeReferenceNode(typeNode)) return [];
|
|
2651
2690
|
if (depth >= MAX_ALIAS_CHAIN_DEPTH) {
|
|
2652
2691
|
const aliasName = typeNode.typeName.getText();
|
|
2653
2692
|
throw new Error(
|
|
@@ -2656,9 +2695,9 @@ function extractTypeAliasConstraintNodes(typeNode, checker, file, extensionRegis
|
|
|
2656
2695
|
}
|
|
2657
2696
|
const symbol = checker.getSymbolAtLocation(typeNode.typeName);
|
|
2658
2697
|
if (!symbol?.declarations) return [];
|
|
2659
|
-
const aliasDecl = symbol.declarations.find(
|
|
2698
|
+
const aliasDecl = symbol.declarations.find(ts3.isTypeAliasDeclaration);
|
|
2660
2699
|
if (!aliasDecl) return [];
|
|
2661
|
-
if (
|
|
2700
|
+
if (ts3.isTypeLiteralNode(aliasDecl.type)) return [];
|
|
2662
2701
|
const aliasFieldType = resolveTypeNode(
|
|
2663
2702
|
checker.getTypeAtLocation(aliasDecl.type),
|
|
2664
2703
|
checker,
|
|
@@ -2674,13 +2713,7 @@ function extractTypeAliasConstraintNodes(typeNode, checker, file, extensionRegis
|
|
|
2674
2713
|
makeParseOptions(extensionRegistry, aliasFieldType)
|
|
2675
2714
|
);
|
|
2676
2715
|
constraints.push(
|
|
2677
|
-
...extractTypeAliasConstraintNodes(
|
|
2678
|
-
aliasDecl.type,
|
|
2679
|
-
checker,
|
|
2680
|
-
file,
|
|
2681
|
-
extensionRegistry,
|
|
2682
|
-
depth + 1
|
|
2683
|
-
)
|
|
2716
|
+
...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, extensionRegistry, depth + 1)
|
|
2684
2717
|
);
|
|
2685
2718
|
return constraints;
|
|
2686
2719
|
}
|
|
@@ -2707,14 +2740,14 @@ function getNamedTypeName(type) {
|
|
|
2707
2740
|
const symbol = type.getSymbol();
|
|
2708
2741
|
if (symbol?.declarations) {
|
|
2709
2742
|
const decl = symbol.declarations[0];
|
|
2710
|
-
if (decl && (
|
|
2711
|
-
const name =
|
|
2743
|
+
if (decl && (ts3.isClassDeclaration(decl) || ts3.isInterfaceDeclaration(decl) || ts3.isTypeAliasDeclaration(decl))) {
|
|
2744
|
+
const name = ts3.isClassDeclaration(decl) ? decl.name?.text : decl.name.text;
|
|
2712
2745
|
if (name) return name;
|
|
2713
2746
|
}
|
|
2714
2747
|
}
|
|
2715
2748
|
const aliasSymbol = type.aliasSymbol;
|
|
2716
2749
|
if (aliasSymbol?.declarations) {
|
|
2717
|
-
const aliasDecl = aliasSymbol.declarations.find(
|
|
2750
|
+
const aliasDecl = aliasSymbol.declarations.find(ts3.isTypeAliasDeclaration);
|
|
2718
2751
|
if (aliasDecl) {
|
|
2719
2752
|
return aliasDecl.name.text;
|
|
2720
2753
|
}
|
|
@@ -2725,24 +2758,24 @@ function getNamedTypeDeclaration(type) {
|
|
|
2725
2758
|
const symbol = type.getSymbol();
|
|
2726
2759
|
if (symbol?.declarations) {
|
|
2727
2760
|
const decl = symbol.declarations[0];
|
|
2728
|
-
if (decl && (
|
|
2761
|
+
if (decl && (ts3.isClassDeclaration(decl) || ts3.isInterfaceDeclaration(decl) || ts3.isTypeAliasDeclaration(decl))) {
|
|
2729
2762
|
return decl;
|
|
2730
2763
|
}
|
|
2731
2764
|
}
|
|
2732
2765
|
const aliasSymbol = type.aliasSymbol;
|
|
2733
2766
|
if (aliasSymbol?.declarations) {
|
|
2734
|
-
return aliasSymbol.declarations.find(
|
|
2767
|
+
return aliasSymbol.declarations.find(ts3.isTypeAliasDeclaration);
|
|
2735
2768
|
}
|
|
2736
2769
|
return void 0;
|
|
2737
2770
|
}
|
|
2738
2771
|
function analyzeMethod(method, checker) {
|
|
2739
|
-
if (!
|
|
2772
|
+
if (!ts3.isIdentifier(method.name)) {
|
|
2740
2773
|
return null;
|
|
2741
2774
|
}
|
|
2742
2775
|
const name = method.name.text;
|
|
2743
2776
|
const parameters = [];
|
|
2744
2777
|
for (const param of method.parameters) {
|
|
2745
|
-
if (
|
|
2778
|
+
if (ts3.isIdentifier(param.name)) {
|
|
2746
2779
|
const paramInfo = analyzeParameter(param, checker);
|
|
2747
2780
|
parameters.push(paramInfo);
|
|
2748
2781
|
}
|
|
@@ -2753,7 +2786,7 @@ function analyzeMethod(method, checker) {
|
|
|
2753
2786
|
return { name, parameters, returnTypeNode, returnType };
|
|
2754
2787
|
}
|
|
2755
2788
|
function analyzeParameter(param, checker) {
|
|
2756
|
-
const name =
|
|
2789
|
+
const name = ts3.isIdentifier(param.name) ? param.name.text : "param";
|
|
2757
2790
|
const typeNode = param.type;
|
|
2758
2791
|
const type = checker.getTypeAtLocation(param);
|
|
2759
2792
|
const formSpecExportName = detectFormSpecReference(typeNode);
|
|
@@ -2762,28 +2795,932 @@ function analyzeParameter(param, checker) {
|
|
|
2762
2795
|
}
|
|
2763
2796
|
function detectFormSpecReference(typeNode) {
|
|
2764
2797
|
if (!typeNode) return null;
|
|
2765
|
-
if (!
|
|
2766
|
-
const typeName =
|
|
2798
|
+
if (!ts3.isTypeReferenceNode(typeNode)) return null;
|
|
2799
|
+
const typeName = ts3.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : ts3.isQualifiedName(typeNode.typeName) ? typeNode.typeName.right.text : null;
|
|
2767
2800
|
if (typeName !== "InferSchema" && typeName !== "InferFormSchema") return null;
|
|
2768
2801
|
const typeArg = typeNode.typeArguments?.[0];
|
|
2769
|
-
if (!typeArg || !
|
|
2770
|
-
if (
|
|
2802
|
+
if (!typeArg || !ts3.isTypeQueryNode(typeArg)) return null;
|
|
2803
|
+
if (ts3.isIdentifier(typeArg.exprName)) {
|
|
2771
2804
|
return typeArg.exprName.text;
|
|
2772
2805
|
}
|
|
2773
|
-
if (
|
|
2806
|
+
if (ts3.isQualifiedName(typeArg.exprName)) {
|
|
2774
2807
|
return typeArg.exprName.right.text;
|
|
2775
2808
|
}
|
|
2776
2809
|
return null;
|
|
2777
2810
|
}
|
|
2778
2811
|
|
|
2812
|
+
// src/analyzer/program.ts
|
|
2813
|
+
function createProgramContext(filePath) {
|
|
2814
|
+
const absolutePath = path.resolve(filePath);
|
|
2815
|
+
const fileDir = path.dirname(absolutePath);
|
|
2816
|
+
const configPath = ts4.findConfigFile(fileDir, ts4.sys.fileExists.bind(ts4.sys), "tsconfig.json");
|
|
2817
|
+
let compilerOptions;
|
|
2818
|
+
let fileNames;
|
|
2819
|
+
if (configPath) {
|
|
2820
|
+
const configFile = ts4.readConfigFile(configPath, ts4.sys.readFile.bind(ts4.sys));
|
|
2821
|
+
if (configFile.error) {
|
|
2822
|
+
throw new Error(
|
|
2823
|
+
`Error reading tsconfig.json: ${ts4.flattenDiagnosticMessageText(configFile.error.messageText, "\n")}`
|
|
2824
|
+
);
|
|
2825
|
+
}
|
|
2826
|
+
const parsed = ts4.parseJsonConfigFileContent(
|
|
2827
|
+
configFile.config,
|
|
2828
|
+
ts4.sys,
|
|
2829
|
+
path.dirname(configPath)
|
|
2830
|
+
);
|
|
2831
|
+
if (parsed.errors.length > 0) {
|
|
2832
|
+
const errorMessages = parsed.errors.map((e) => ts4.flattenDiagnosticMessageText(e.messageText, "\n")).join("\n");
|
|
2833
|
+
throw new Error(`Error parsing tsconfig.json: ${errorMessages}`);
|
|
2834
|
+
}
|
|
2835
|
+
compilerOptions = parsed.options;
|
|
2836
|
+
fileNames = parsed.fileNames.includes(absolutePath) ? parsed.fileNames : [...parsed.fileNames, absolutePath];
|
|
2837
|
+
} else {
|
|
2838
|
+
compilerOptions = {
|
|
2839
|
+
target: ts4.ScriptTarget.ES2022,
|
|
2840
|
+
module: ts4.ModuleKind.NodeNext,
|
|
2841
|
+
moduleResolution: ts4.ModuleResolutionKind.NodeNext,
|
|
2842
|
+
strict: true,
|
|
2843
|
+
skipLibCheck: true,
|
|
2844
|
+
declaration: true
|
|
2845
|
+
};
|
|
2846
|
+
fileNames = [absolutePath];
|
|
2847
|
+
}
|
|
2848
|
+
const program = ts4.createProgram(fileNames, compilerOptions);
|
|
2849
|
+
const sourceFile = program.getSourceFile(absolutePath);
|
|
2850
|
+
if (!sourceFile) {
|
|
2851
|
+
throw new Error(`Could not find source file: ${absolutePath}`);
|
|
2852
|
+
}
|
|
2853
|
+
return {
|
|
2854
|
+
program,
|
|
2855
|
+
checker: program.getTypeChecker(),
|
|
2856
|
+
sourceFile
|
|
2857
|
+
};
|
|
2858
|
+
}
|
|
2859
|
+
function findNodeByName(sourceFile, name, predicate, getName) {
|
|
2860
|
+
let result = null;
|
|
2861
|
+
function visit(node) {
|
|
2862
|
+
if (result) return;
|
|
2863
|
+
if (predicate(node) && getName(node) === name) {
|
|
2864
|
+
result = node;
|
|
2865
|
+
return;
|
|
2866
|
+
}
|
|
2867
|
+
ts4.forEachChild(node, visit);
|
|
2868
|
+
}
|
|
2869
|
+
visit(sourceFile);
|
|
2870
|
+
return result;
|
|
2871
|
+
}
|
|
2872
|
+
function findClassByName(sourceFile, className) {
|
|
2873
|
+
return findNodeByName(sourceFile, className, ts4.isClassDeclaration, (n) => n.name?.text);
|
|
2874
|
+
}
|
|
2875
|
+
function findInterfaceByName(sourceFile, interfaceName) {
|
|
2876
|
+
return findNodeByName(sourceFile, interfaceName, ts4.isInterfaceDeclaration, (n) => n.name.text);
|
|
2877
|
+
}
|
|
2878
|
+
function findTypeAliasByName(sourceFile, aliasName) {
|
|
2879
|
+
return findNodeByName(sourceFile, aliasName, ts4.isTypeAliasDeclaration, (n) => n.name.text);
|
|
2880
|
+
}
|
|
2881
|
+
function analyzeNamedTypeToIR(filePath, typeName, extensionRegistry) {
|
|
2882
|
+
const ctx = createProgramContext(filePath);
|
|
2883
|
+
const classDecl = findClassByName(ctx.sourceFile, typeName);
|
|
2884
|
+
if (classDecl !== null) {
|
|
2885
|
+
return analyzeClassToIR(classDecl, ctx.checker, filePath, extensionRegistry);
|
|
2886
|
+
}
|
|
2887
|
+
const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
|
|
2888
|
+
if (interfaceDecl !== null) {
|
|
2889
|
+
return analyzeInterfaceToIR(interfaceDecl, ctx.checker, filePath, extensionRegistry);
|
|
2890
|
+
}
|
|
2891
|
+
const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
|
|
2892
|
+
if (typeAlias !== null) {
|
|
2893
|
+
const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, filePath, extensionRegistry);
|
|
2894
|
+
if (result.ok) {
|
|
2895
|
+
return result.analysis;
|
|
2896
|
+
}
|
|
2897
|
+
throw new Error(result.error);
|
|
2898
|
+
}
|
|
2899
|
+
throw new Error(
|
|
2900
|
+
`Type "${typeName}" not found as a class, interface, or type alias in ${filePath}`
|
|
2901
|
+
);
|
|
2902
|
+
}
|
|
2903
|
+
|
|
2904
|
+
// src/validate/constraint-validator.ts
|
|
2905
|
+
import { normalizeConstraintTagName as normalizeConstraintTagName2 } from "@formspec/core";
|
|
2906
|
+
function addContradiction(ctx, message, primary, related) {
|
|
2907
|
+
ctx.diagnostics.push({
|
|
2908
|
+
code: "CONTRADICTING_CONSTRAINTS",
|
|
2909
|
+
message,
|
|
2910
|
+
severity: "error",
|
|
2911
|
+
primaryLocation: primary,
|
|
2912
|
+
relatedLocations: [related]
|
|
2913
|
+
});
|
|
2914
|
+
}
|
|
2915
|
+
function addTypeMismatch(ctx, message, primary) {
|
|
2916
|
+
ctx.diagnostics.push({
|
|
2917
|
+
code: "TYPE_MISMATCH",
|
|
2918
|
+
message,
|
|
2919
|
+
severity: "error",
|
|
2920
|
+
primaryLocation: primary,
|
|
2921
|
+
relatedLocations: []
|
|
2922
|
+
});
|
|
2923
|
+
}
|
|
2924
|
+
function addUnknownExtension(ctx, message, primary) {
|
|
2925
|
+
ctx.diagnostics.push({
|
|
2926
|
+
code: "UNKNOWN_EXTENSION",
|
|
2927
|
+
message,
|
|
2928
|
+
severity: "warning",
|
|
2929
|
+
primaryLocation: primary,
|
|
2930
|
+
relatedLocations: []
|
|
2931
|
+
});
|
|
2932
|
+
}
|
|
2933
|
+
function addUnknownPathTarget(ctx, message, primary) {
|
|
2934
|
+
ctx.diagnostics.push({
|
|
2935
|
+
code: "UNKNOWN_PATH_TARGET",
|
|
2936
|
+
message,
|
|
2937
|
+
severity: "error",
|
|
2938
|
+
primaryLocation: primary,
|
|
2939
|
+
relatedLocations: []
|
|
2940
|
+
});
|
|
2941
|
+
}
|
|
2942
|
+
function addConstraintBroadening(ctx, message, primary, related) {
|
|
2943
|
+
ctx.diagnostics.push({
|
|
2944
|
+
code: "CONSTRAINT_BROADENING",
|
|
2945
|
+
message,
|
|
2946
|
+
severity: "error",
|
|
2947
|
+
primaryLocation: primary,
|
|
2948
|
+
relatedLocations: [related]
|
|
2949
|
+
});
|
|
2950
|
+
}
|
|
2951
|
+
function getExtensionIdFromConstraintId(constraintId) {
|
|
2952
|
+
const separator = constraintId.lastIndexOf("/");
|
|
2953
|
+
if (separator <= 0) {
|
|
2954
|
+
return null;
|
|
2955
|
+
}
|
|
2956
|
+
return constraintId.slice(0, separator);
|
|
2957
|
+
}
|
|
2958
|
+
function findNumeric(constraints, constraintKind) {
|
|
2959
|
+
return constraints.find((c) => c.constraintKind === constraintKind);
|
|
2960
|
+
}
|
|
2961
|
+
function findLength(constraints, constraintKind) {
|
|
2962
|
+
return constraints.find((c) => c.constraintKind === constraintKind);
|
|
2963
|
+
}
|
|
2964
|
+
function findAllowedMembers(constraints) {
|
|
2965
|
+
return constraints.filter(
|
|
2966
|
+
(c) => c.constraintKind === "allowedMembers"
|
|
2967
|
+
);
|
|
2968
|
+
}
|
|
2969
|
+
function findConstConstraints(constraints) {
|
|
2970
|
+
return constraints.filter(
|
|
2971
|
+
(c) => c.constraintKind === "const"
|
|
2972
|
+
);
|
|
2973
|
+
}
|
|
2974
|
+
function jsonValueEquals(left, right) {
|
|
2975
|
+
if (left === right) {
|
|
2976
|
+
return true;
|
|
2977
|
+
}
|
|
2978
|
+
if (Array.isArray(left) || Array.isArray(right)) {
|
|
2979
|
+
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
|
|
2980
|
+
return false;
|
|
2981
|
+
}
|
|
2982
|
+
return left.every((item, index) => jsonValueEquals(item, right[index]));
|
|
2983
|
+
}
|
|
2984
|
+
if (isJsonObject(left) || isJsonObject(right)) {
|
|
2985
|
+
if (!isJsonObject(left) || !isJsonObject(right)) {
|
|
2986
|
+
return false;
|
|
2987
|
+
}
|
|
2988
|
+
const leftKeys = Object.keys(left).sort();
|
|
2989
|
+
const rightKeys = Object.keys(right).sort();
|
|
2990
|
+
if (leftKeys.length !== rightKeys.length) {
|
|
2991
|
+
return false;
|
|
2992
|
+
}
|
|
2993
|
+
return leftKeys.every((key, index) => {
|
|
2994
|
+
const rightKey = rightKeys[index];
|
|
2995
|
+
if (rightKey !== key) {
|
|
2996
|
+
return false;
|
|
2997
|
+
}
|
|
2998
|
+
const leftValue = left[key];
|
|
2999
|
+
const rightValue = right[rightKey];
|
|
3000
|
+
return leftValue !== void 0 && rightValue !== void 0 && jsonValueEquals(leftValue, rightValue);
|
|
3001
|
+
});
|
|
3002
|
+
}
|
|
3003
|
+
return false;
|
|
3004
|
+
}
|
|
3005
|
+
function isJsonObject(value) {
|
|
3006
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3007
|
+
}
|
|
3008
|
+
function isOrderedBoundConstraint(constraint) {
|
|
3009
|
+
return constraint.constraintKind === "minimum" || constraint.constraintKind === "exclusiveMinimum" || constraint.constraintKind === "minLength" || constraint.constraintKind === "minItems" || constraint.constraintKind === "maximum" || constraint.constraintKind === "exclusiveMaximum" || constraint.constraintKind === "maxLength" || constraint.constraintKind === "maxItems";
|
|
3010
|
+
}
|
|
3011
|
+
function pathKey(constraint) {
|
|
3012
|
+
return constraint.path?.segments.join(".") ?? "";
|
|
3013
|
+
}
|
|
3014
|
+
function orderedBoundFamily(kind) {
|
|
3015
|
+
switch (kind) {
|
|
3016
|
+
case "minimum":
|
|
3017
|
+
case "exclusiveMinimum":
|
|
3018
|
+
return "numeric-lower";
|
|
3019
|
+
case "maximum":
|
|
3020
|
+
case "exclusiveMaximum":
|
|
3021
|
+
return "numeric-upper";
|
|
3022
|
+
case "minLength":
|
|
3023
|
+
return "minLength";
|
|
3024
|
+
case "minItems":
|
|
3025
|
+
return "minItems";
|
|
3026
|
+
case "maxLength":
|
|
3027
|
+
return "maxLength";
|
|
3028
|
+
case "maxItems":
|
|
3029
|
+
return "maxItems";
|
|
3030
|
+
default: {
|
|
3031
|
+
const _exhaustive = kind;
|
|
3032
|
+
return _exhaustive;
|
|
3033
|
+
}
|
|
3034
|
+
}
|
|
3035
|
+
}
|
|
3036
|
+
function isNumericLowerKind(kind) {
|
|
3037
|
+
return kind === "minimum" || kind === "exclusiveMinimum";
|
|
3038
|
+
}
|
|
3039
|
+
function isNumericUpperKind(kind) {
|
|
3040
|
+
return kind === "maximum" || kind === "exclusiveMaximum";
|
|
3041
|
+
}
|
|
3042
|
+
function describeConstraintTag(constraint) {
|
|
3043
|
+
return `@${constraint.constraintKind}`;
|
|
3044
|
+
}
|
|
3045
|
+
function compareConstraintStrength(current, previous) {
|
|
3046
|
+
const family = orderedBoundFamily(current.constraintKind);
|
|
3047
|
+
if (family === "numeric-lower") {
|
|
3048
|
+
if (!isNumericLowerKind(current.constraintKind) || !isNumericLowerKind(previous.constraintKind)) {
|
|
3049
|
+
throw new Error("numeric-lower family received non-numeric lower-bound constraint");
|
|
3050
|
+
}
|
|
3051
|
+
if (current.value !== previous.value) {
|
|
3052
|
+
return current.value > previous.value ? 1 : -1;
|
|
3053
|
+
}
|
|
3054
|
+
if (current.constraintKind === "exclusiveMinimum" && previous.constraintKind === "minimum") {
|
|
3055
|
+
return 1;
|
|
3056
|
+
}
|
|
3057
|
+
if (current.constraintKind === "minimum" && previous.constraintKind === "exclusiveMinimum") {
|
|
3058
|
+
return -1;
|
|
3059
|
+
}
|
|
3060
|
+
return 0;
|
|
3061
|
+
}
|
|
3062
|
+
if (family === "numeric-upper") {
|
|
3063
|
+
if (!isNumericUpperKind(current.constraintKind) || !isNumericUpperKind(previous.constraintKind)) {
|
|
3064
|
+
throw new Error("numeric-upper family received non-numeric upper-bound constraint");
|
|
3065
|
+
}
|
|
3066
|
+
if (current.value !== previous.value) {
|
|
3067
|
+
return current.value < previous.value ? 1 : -1;
|
|
3068
|
+
}
|
|
3069
|
+
if (current.constraintKind === "exclusiveMaximum" && previous.constraintKind === "maximum") {
|
|
3070
|
+
return 1;
|
|
3071
|
+
}
|
|
3072
|
+
if (current.constraintKind === "maximum" && previous.constraintKind === "exclusiveMaximum") {
|
|
3073
|
+
return -1;
|
|
3074
|
+
}
|
|
3075
|
+
return 0;
|
|
3076
|
+
}
|
|
3077
|
+
switch (family) {
|
|
3078
|
+
case "minLength":
|
|
3079
|
+
case "minItems":
|
|
3080
|
+
if (current.value === previous.value) {
|
|
3081
|
+
return 0;
|
|
3082
|
+
}
|
|
3083
|
+
return current.value > previous.value ? 1 : -1;
|
|
3084
|
+
case "maxLength":
|
|
3085
|
+
case "maxItems":
|
|
3086
|
+
if (current.value === previous.value) {
|
|
3087
|
+
return 0;
|
|
3088
|
+
}
|
|
3089
|
+
return current.value < previous.value ? 1 : -1;
|
|
3090
|
+
default: {
|
|
3091
|
+
const _exhaustive = family;
|
|
3092
|
+
return _exhaustive;
|
|
3093
|
+
}
|
|
3094
|
+
}
|
|
3095
|
+
}
|
|
3096
|
+
function checkConstraintBroadening(ctx, fieldName, constraints) {
|
|
3097
|
+
const strongestByKey = /* @__PURE__ */ new Map();
|
|
3098
|
+
for (const constraint of constraints) {
|
|
3099
|
+
if (!isOrderedBoundConstraint(constraint)) {
|
|
3100
|
+
continue;
|
|
3101
|
+
}
|
|
3102
|
+
const key = `${orderedBoundFamily(constraint.constraintKind)}:${pathKey(constraint)}`;
|
|
3103
|
+
const previous = strongestByKey.get(key);
|
|
3104
|
+
if (previous === void 0) {
|
|
3105
|
+
strongestByKey.set(key, constraint);
|
|
3106
|
+
continue;
|
|
3107
|
+
}
|
|
3108
|
+
const strength = compareConstraintStrength(constraint, previous);
|
|
3109
|
+
if (strength < 0) {
|
|
3110
|
+
const displayFieldName = formatPathTargetFieldName(
|
|
3111
|
+
fieldName,
|
|
3112
|
+
constraint.path?.segments ?? []
|
|
3113
|
+
);
|
|
3114
|
+
addConstraintBroadening(
|
|
3115
|
+
ctx,
|
|
3116
|
+
`Field "${displayFieldName}": ${describeConstraintTag(constraint)} (${String(constraint.value)}) is broader than earlier ${describeConstraintTag(previous)} (${String(previous.value)}). Constraints can only narrow.`,
|
|
3117
|
+
constraint.provenance,
|
|
3118
|
+
previous.provenance
|
|
3119
|
+
);
|
|
3120
|
+
continue;
|
|
3121
|
+
}
|
|
3122
|
+
if (strength <= 0) {
|
|
3123
|
+
continue;
|
|
3124
|
+
}
|
|
3125
|
+
strongestByKey.set(key, constraint);
|
|
3126
|
+
}
|
|
3127
|
+
}
|
|
3128
|
+
function compareCustomConstraintStrength(current, previous) {
|
|
3129
|
+
const order = current.comparePayloads(current.constraint.payload, previous.constraint.payload);
|
|
3130
|
+
const equalPayloadTiebreaker = order === 0 ? compareSemanticInclusivity(current.role.inclusive, previous.role.inclusive) : order;
|
|
3131
|
+
switch (current.role.bound) {
|
|
3132
|
+
case "lower":
|
|
3133
|
+
return equalPayloadTiebreaker;
|
|
3134
|
+
case "upper":
|
|
3135
|
+
return equalPayloadTiebreaker === 0 ? 0 : -equalPayloadTiebreaker;
|
|
3136
|
+
case "exact":
|
|
3137
|
+
return order === 0 ? 0 : Number.NaN;
|
|
3138
|
+
default: {
|
|
3139
|
+
const _exhaustive = current.role.bound;
|
|
3140
|
+
return _exhaustive;
|
|
3141
|
+
}
|
|
3142
|
+
}
|
|
3143
|
+
}
|
|
3144
|
+
function compareSemanticInclusivity(currentInclusive, previousInclusive) {
|
|
3145
|
+
if (currentInclusive === previousInclusive) {
|
|
3146
|
+
return 0;
|
|
3147
|
+
}
|
|
3148
|
+
return currentInclusive ? -1 : 1;
|
|
3149
|
+
}
|
|
3150
|
+
function customConstraintsContradict(lower, upper) {
|
|
3151
|
+
const order = lower.comparePayloads(lower.constraint.payload, upper.constraint.payload);
|
|
3152
|
+
if (order > 0) {
|
|
3153
|
+
return true;
|
|
3154
|
+
}
|
|
3155
|
+
if (order < 0) {
|
|
3156
|
+
return false;
|
|
3157
|
+
}
|
|
3158
|
+
return !lower.role.inclusive || !upper.role.inclusive;
|
|
3159
|
+
}
|
|
3160
|
+
function describeCustomConstraintTag(constraint) {
|
|
3161
|
+
return constraint.provenance.tagName ?? constraint.constraintId;
|
|
3162
|
+
}
|
|
3163
|
+
function checkCustomConstraintSemantics(ctx, fieldName, constraints) {
|
|
3164
|
+
if (ctx.extensionRegistry === void 0) {
|
|
3165
|
+
return;
|
|
3166
|
+
}
|
|
3167
|
+
const strongestByKey = /* @__PURE__ */ new Map();
|
|
3168
|
+
const lowerByFamily = /* @__PURE__ */ new Map();
|
|
3169
|
+
const upperByFamily = /* @__PURE__ */ new Map();
|
|
3170
|
+
for (const constraint of constraints) {
|
|
3171
|
+
if (constraint.constraintKind !== "custom") {
|
|
3172
|
+
continue;
|
|
3173
|
+
}
|
|
3174
|
+
const registration = ctx.extensionRegistry.findConstraint(constraint.constraintId);
|
|
3175
|
+
if (registration?.comparePayloads === void 0 || registration.semanticRole === void 0) {
|
|
3176
|
+
continue;
|
|
3177
|
+
}
|
|
3178
|
+
const entry = {
|
|
3179
|
+
constraint,
|
|
3180
|
+
comparePayloads: registration.comparePayloads,
|
|
3181
|
+
role: registration.semanticRole
|
|
3182
|
+
};
|
|
3183
|
+
const familyKey = `${registration.semanticRole.family}:${pathKey(constraint)}`;
|
|
3184
|
+
const boundKey = `${familyKey}:${registration.semanticRole.bound}`;
|
|
3185
|
+
const previous = strongestByKey.get(boundKey);
|
|
3186
|
+
if (previous !== void 0) {
|
|
3187
|
+
const strength = compareCustomConstraintStrength(entry, previous);
|
|
3188
|
+
if (Number.isNaN(strength)) {
|
|
3189
|
+
addContradiction(
|
|
3190
|
+
ctx,
|
|
3191
|
+
`Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} conflicts with ${describeCustomConstraintTag(previous.constraint)}`,
|
|
3192
|
+
constraint.provenance,
|
|
3193
|
+
previous.constraint.provenance
|
|
3194
|
+
);
|
|
3195
|
+
continue;
|
|
3196
|
+
}
|
|
3197
|
+
if (strength < 0) {
|
|
3198
|
+
addConstraintBroadening(
|
|
3199
|
+
ctx,
|
|
3200
|
+
`Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} is broader than earlier ${describeCustomConstraintTag(previous.constraint)}. Constraints can only narrow.`,
|
|
3201
|
+
constraint.provenance,
|
|
3202
|
+
previous.constraint.provenance
|
|
3203
|
+
);
|
|
3204
|
+
continue;
|
|
3205
|
+
}
|
|
3206
|
+
if (strength > 0) {
|
|
3207
|
+
strongestByKey.set(boundKey, entry);
|
|
3208
|
+
}
|
|
3209
|
+
} else {
|
|
3210
|
+
strongestByKey.set(boundKey, entry);
|
|
3211
|
+
}
|
|
3212
|
+
if (registration.semanticRole.bound === "lower") {
|
|
3213
|
+
lowerByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
|
|
3214
|
+
} else if (registration.semanticRole.bound === "upper") {
|
|
3215
|
+
upperByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
|
|
3216
|
+
}
|
|
3217
|
+
}
|
|
3218
|
+
for (const [familyKey, lower] of lowerByFamily) {
|
|
3219
|
+
const upper = upperByFamily.get(familyKey);
|
|
3220
|
+
if (upper === void 0) {
|
|
3221
|
+
continue;
|
|
3222
|
+
}
|
|
3223
|
+
if (!customConstraintsContradict(lower, upper)) {
|
|
3224
|
+
continue;
|
|
3225
|
+
}
|
|
3226
|
+
addContradiction(
|
|
3227
|
+
ctx,
|
|
3228
|
+
`Field "${formatPathTargetFieldName(fieldName, lower.constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(lower.constraint)} contradicts ${describeCustomConstraintTag(upper.constraint)}`,
|
|
3229
|
+
lower.constraint.provenance,
|
|
3230
|
+
upper.constraint.provenance
|
|
3231
|
+
);
|
|
3232
|
+
}
|
|
3233
|
+
}
|
|
3234
|
+
function checkNumericContradictions(ctx, fieldName, constraints) {
|
|
3235
|
+
const min = findNumeric(constraints, "minimum");
|
|
3236
|
+
const max = findNumeric(constraints, "maximum");
|
|
3237
|
+
const exMin = findNumeric(constraints, "exclusiveMinimum");
|
|
3238
|
+
const exMax = findNumeric(constraints, "exclusiveMaximum");
|
|
3239
|
+
if (min !== void 0 && max !== void 0 && min.value > max.value) {
|
|
3240
|
+
addContradiction(
|
|
3241
|
+
ctx,
|
|
3242
|
+
`Field "${fieldName}": minimum (${String(min.value)}) is greater than maximum (${String(max.value)})`,
|
|
3243
|
+
min.provenance,
|
|
3244
|
+
max.provenance
|
|
3245
|
+
);
|
|
3246
|
+
}
|
|
3247
|
+
if (exMin !== void 0 && max !== void 0 && exMin.value >= max.value) {
|
|
3248
|
+
addContradiction(
|
|
3249
|
+
ctx,
|
|
3250
|
+
`Field "${fieldName}": exclusiveMinimum (${String(exMin.value)}) is greater than or equal to maximum (${String(max.value)})`,
|
|
3251
|
+
exMin.provenance,
|
|
3252
|
+
max.provenance
|
|
3253
|
+
);
|
|
3254
|
+
}
|
|
3255
|
+
if (min !== void 0 && exMax !== void 0 && min.value >= exMax.value) {
|
|
3256
|
+
addContradiction(
|
|
3257
|
+
ctx,
|
|
3258
|
+
`Field "${fieldName}": minimum (${String(min.value)}) is greater than or equal to exclusiveMaximum (${String(exMax.value)})`,
|
|
3259
|
+
min.provenance,
|
|
3260
|
+
exMax.provenance
|
|
3261
|
+
);
|
|
3262
|
+
}
|
|
3263
|
+
if (exMin !== void 0 && exMax !== void 0 && exMin.value >= exMax.value) {
|
|
3264
|
+
addContradiction(
|
|
3265
|
+
ctx,
|
|
3266
|
+
`Field "${fieldName}": exclusiveMinimum (${String(exMin.value)}) is greater than or equal to exclusiveMaximum (${String(exMax.value)})`,
|
|
3267
|
+
exMin.provenance,
|
|
3268
|
+
exMax.provenance
|
|
3269
|
+
);
|
|
3270
|
+
}
|
|
3271
|
+
}
|
|
3272
|
+
function checkLengthContradictions(ctx, fieldName, constraints) {
|
|
3273
|
+
const minLen = findLength(constraints, "minLength");
|
|
3274
|
+
const maxLen = findLength(constraints, "maxLength");
|
|
3275
|
+
if (minLen !== void 0 && maxLen !== void 0 && minLen.value > maxLen.value) {
|
|
3276
|
+
addContradiction(
|
|
3277
|
+
ctx,
|
|
3278
|
+
`Field "${fieldName}": minLength (${String(minLen.value)}) is greater than maxLength (${String(maxLen.value)})`,
|
|
3279
|
+
minLen.provenance,
|
|
3280
|
+
maxLen.provenance
|
|
3281
|
+
);
|
|
3282
|
+
}
|
|
3283
|
+
const minItems = findLength(constraints, "minItems");
|
|
3284
|
+
const maxItems = findLength(constraints, "maxItems");
|
|
3285
|
+
if (minItems !== void 0 && maxItems !== void 0 && minItems.value > maxItems.value) {
|
|
3286
|
+
addContradiction(
|
|
3287
|
+
ctx,
|
|
3288
|
+
`Field "${fieldName}": minItems (${String(minItems.value)}) is greater than maxItems (${String(maxItems.value)})`,
|
|
3289
|
+
minItems.provenance,
|
|
3290
|
+
maxItems.provenance
|
|
3291
|
+
);
|
|
3292
|
+
}
|
|
3293
|
+
}
|
|
3294
|
+
function checkAllowedMembersContradiction(ctx, fieldName, constraints) {
|
|
3295
|
+
const members = findAllowedMembers(constraints);
|
|
3296
|
+
if (members.length < 2) return;
|
|
3297
|
+
const firstSet = new Set(members[0]?.members ?? []);
|
|
3298
|
+
for (let i = 1; i < members.length; i++) {
|
|
3299
|
+
const current = members[i];
|
|
3300
|
+
if (current === void 0) continue;
|
|
3301
|
+
for (const m of firstSet) {
|
|
3302
|
+
if (!current.members.includes(m)) {
|
|
3303
|
+
firstSet.delete(m);
|
|
3304
|
+
}
|
|
3305
|
+
}
|
|
3306
|
+
}
|
|
3307
|
+
if (firstSet.size === 0) {
|
|
3308
|
+
const first = members[0];
|
|
3309
|
+
const second = members[1];
|
|
3310
|
+
if (first !== void 0 && second !== void 0) {
|
|
3311
|
+
addContradiction(
|
|
3312
|
+
ctx,
|
|
3313
|
+
`Field "${fieldName}": allowedMembers constraints have an empty intersection (no valid values remain)`,
|
|
3314
|
+
first.provenance,
|
|
3315
|
+
second.provenance
|
|
3316
|
+
);
|
|
3317
|
+
}
|
|
3318
|
+
}
|
|
3319
|
+
}
|
|
3320
|
+
function checkConstContradictions(ctx, fieldName, constraints) {
|
|
3321
|
+
const constConstraints = findConstConstraints(constraints);
|
|
3322
|
+
if (constConstraints.length < 2) return;
|
|
3323
|
+
const first = constConstraints[0];
|
|
3324
|
+
if (first === void 0) return;
|
|
3325
|
+
for (let i = 1; i < constConstraints.length; i++) {
|
|
3326
|
+
const current = constConstraints[i];
|
|
3327
|
+
if (current === void 0) continue;
|
|
3328
|
+
if (jsonValueEquals(first.value, current.value)) {
|
|
3329
|
+
continue;
|
|
3330
|
+
}
|
|
3331
|
+
addContradiction(
|
|
3332
|
+
ctx,
|
|
3333
|
+
`Field "${fieldName}": conflicting @const constraints require both ${JSON.stringify(first.value)} and ${JSON.stringify(current.value)}`,
|
|
3334
|
+
first.provenance,
|
|
3335
|
+
current.provenance
|
|
3336
|
+
);
|
|
3337
|
+
}
|
|
3338
|
+
}
|
|
3339
|
+
function typeLabel(type) {
|
|
3340
|
+
switch (type.kind) {
|
|
3341
|
+
case "primitive":
|
|
3342
|
+
return type.primitiveKind;
|
|
3343
|
+
case "enum":
|
|
3344
|
+
return "enum";
|
|
3345
|
+
case "array":
|
|
3346
|
+
return "array";
|
|
3347
|
+
case "object":
|
|
3348
|
+
return "object";
|
|
3349
|
+
case "record":
|
|
3350
|
+
return "record";
|
|
3351
|
+
case "union":
|
|
3352
|
+
return "union";
|
|
3353
|
+
case "reference":
|
|
3354
|
+
return `reference(${type.name})`;
|
|
3355
|
+
case "dynamic":
|
|
3356
|
+
return `dynamic(${type.dynamicKind})`;
|
|
3357
|
+
case "custom":
|
|
3358
|
+
return `custom(${type.typeId})`;
|
|
3359
|
+
default: {
|
|
3360
|
+
const _exhaustive = type;
|
|
3361
|
+
return String(_exhaustive);
|
|
3362
|
+
}
|
|
3363
|
+
}
|
|
3364
|
+
}
|
|
3365
|
+
function dereferenceType(ctx, type) {
|
|
3366
|
+
let current = type;
|
|
3367
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3368
|
+
while (current.kind === "reference") {
|
|
3369
|
+
if (seen.has(current.name)) {
|
|
3370
|
+
return current;
|
|
3371
|
+
}
|
|
3372
|
+
seen.add(current.name);
|
|
3373
|
+
const definition = ctx.typeRegistry[current.name];
|
|
3374
|
+
if (definition === void 0) {
|
|
3375
|
+
return current;
|
|
3376
|
+
}
|
|
3377
|
+
current = definition.type;
|
|
3378
|
+
}
|
|
3379
|
+
return current;
|
|
3380
|
+
}
|
|
3381
|
+
function collectReferencedTypeConstraints(ctx, type) {
|
|
3382
|
+
const collected = [];
|
|
3383
|
+
let current = type;
|
|
3384
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3385
|
+
while (current.kind === "reference") {
|
|
3386
|
+
if (seen.has(current.name)) {
|
|
3387
|
+
break;
|
|
3388
|
+
}
|
|
3389
|
+
seen.add(current.name);
|
|
3390
|
+
const definition = ctx.typeRegistry[current.name];
|
|
3391
|
+
if (definition === void 0) {
|
|
3392
|
+
break;
|
|
3393
|
+
}
|
|
3394
|
+
if (definition.constraints !== void 0) {
|
|
3395
|
+
collected.push(...definition.constraints);
|
|
3396
|
+
}
|
|
3397
|
+
current = definition.type;
|
|
3398
|
+
}
|
|
3399
|
+
return collected;
|
|
3400
|
+
}
|
|
3401
|
+
function resolvePathTargetType(ctx, type, segments) {
|
|
3402
|
+
const effectiveType = dereferenceType(ctx, type);
|
|
3403
|
+
if (segments.length === 0) {
|
|
3404
|
+
return { kind: "resolved", type: effectiveType };
|
|
3405
|
+
}
|
|
3406
|
+
if (effectiveType.kind === "array") {
|
|
3407
|
+
return resolvePathTargetType(ctx, effectiveType.items, segments);
|
|
3408
|
+
}
|
|
3409
|
+
if (effectiveType.kind === "object") {
|
|
3410
|
+
const [segment, ...rest] = segments;
|
|
3411
|
+
if (segment === void 0) {
|
|
3412
|
+
throw new Error("Invariant violation: object path traversal requires a segment");
|
|
3413
|
+
}
|
|
3414
|
+
const property = effectiveType.properties.find((prop) => prop.name === segment);
|
|
3415
|
+
if (property === void 0) {
|
|
3416
|
+
return { kind: "missing-property", segment };
|
|
3417
|
+
}
|
|
3418
|
+
return resolvePathTargetType(ctx, property.type, rest);
|
|
3419
|
+
}
|
|
3420
|
+
return { kind: "unresolvable", type: effectiveType };
|
|
3421
|
+
}
|
|
3422
|
+
function isNullType(type) {
|
|
3423
|
+
return type.kind === "primitive" && type.primitiveKind === "null";
|
|
3424
|
+
}
|
|
3425
|
+
function collectCustomConstraintCandidateTypes(ctx, type) {
|
|
3426
|
+
const effectiveType = dereferenceType(ctx, type);
|
|
3427
|
+
const candidates = [effectiveType];
|
|
3428
|
+
if (effectiveType.kind === "array") {
|
|
3429
|
+
candidates.push(...collectCustomConstraintCandidateTypes(ctx, effectiveType.items));
|
|
3430
|
+
}
|
|
3431
|
+
if (effectiveType.kind === "union") {
|
|
3432
|
+
const memberTypes = effectiveType.members.map((member) => dereferenceType(ctx, member));
|
|
3433
|
+
const nonNullMembers = memberTypes.filter((member) => !isNullType(member));
|
|
3434
|
+
if (nonNullMembers.length === 1 && nonNullMembers.length < memberTypes.length) {
|
|
3435
|
+
const [nullableMember] = nonNullMembers;
|
|
3436
|
+
if (nullableMember !== void 0) {
|
|
3437
|
+
candidates.push(...collectCustomConstraintCandidateTypes(ctx, nullableMember));
|
|
3438
|
+
}
|
|
3439
|
+
}
|
|
3440
|
+
}
|
|
3441
|
+
return candidates;
|
|
3442
|
+
}
|
|
3443
|
+
function formatPathTargetFieldName(fieldName, path3) {
|
|
3444
|
+
return path3.length === 0 ? fieldName : `${fieldName}.${path3.join(".")}`;
|
|
3445
|
+
}
|
|
3446
|
+
function checkConstraintOnType(ctx, fieldName, type, constraint) {
|
|
3447
|
+
const effectiveType = dereferenceType(ctx, type);
|
|
3448
|
+
const isNumber = effectiveType.kind === "primitive" && ["number", "integer", "bigint"].includes(effectiveType.primitiveKind);
|
|
3449
|
+
const isString = effectiveType.kind === "primitive" && effectiveType.primitiveKind === "string";
|
|
3450
|
+
const isArray = effectiveType.kind === "array";
|
|
3451
|
+
const isEnum = effectiveType.kind === "enum";
|
|
3452
|
+
const arrayItemType = effectiveType.kind === "array" ? dereferenceType(ctx, effectiveType.items) : void 0;
|
|
3453
|
+
const isStringArray = arrayItemType?.kind === "primitive" && arrayItemType.primitiveKind === "string";
|
|
3454
|
+
const label = typeLabel(effectiveType);
|
|
3455
|
+
const ck = constraint.constraintKind;
|
|
3456
|
+
switch (ck) {
|
|
3457
|
+
case "minimum":
|
|
3458
|
+
case "maximum":
|
|
3459
|
+
case "exclusiveMinimum":
|
|
3460
|
+
case "exclusiveMaximum":
|
|
3461
|
+
case "multipleOf": {
|
|
3462
|
+
if (!isNumber) {
|
|
3463
|
+
addTypeMismatch(
|
|
3464
|
+
ctx,
|
|
3465
|
+
`Field "${fieldName}": constraint "${ck}" is only valid on number fields, but field type is "${label}"`,
|
|
3466
|
+
constraint.provenance
|
|
3467
|
+
);
|
|
3468
|
+
}
|
|
3469
|
+
break;
|
|
3470
|
+
}
|
|
3471
|
+
case "minLength":
|
|
3472
|
+
case "maxLength":
|
|
3473
|
+
case "pattern": {
|
|
3474
|
+
if (!isString && !isStringArray) {
|
|
3475
|
+
addTypeMismatch(
|
|
3476
|
+
ctx,
|
|
3477
|
+
`Field "${fieldName}": constraint "${ck}" is only valid on string fields or string array items, but field type is "${label}"`,
|
|
3478
|
+
constraint.provenance
|
|
3479
|
+
);
|
|
3480
|
+
}
|
|
3481
|
+
break;
|
|
3482
|
+
}
|
|
3483
|
+
case "minItems":
|
|
3484
|
+
case "maxItems":
|
|
3485
|
+
case "uniqueItems": {
|
|
3486
|
+
if (!isArray) {
|
|
3487
|
+
addTypeMismatch(
|
|
3488
|
+
ctx,
|
|
3489
|
+
`Field "${fieldName}": constraint "${ck}" is only valid on array fields, but field type is "${label}"`,
|
|
3490
|
+
constraint.provenance
|
|
3491
|
+
);
|
|
3492
|
+
}
|
|
3493
|
+
break;
|
|
3494
|
+
}
|
|
3495
|
+
case "allowedMembers": {
|
|
3496
|
+
if (!isEnum) {
|
|
3497
|
+
addTypeMismatch(
|
|
3498
|
+
ctx,
|
|
3499
|
+
`Field "${fieldName}": constraint "allowedMembers" is only valid on enum fields, but field type is "${label}"`,
|
|
3500
|
+
constraint.provenance
|
|
3501
|
+
);
|
|
3502
|
+
}
|
|
3503
|
+
break;
|
|
3504
|
+
}
|
|
3505
|
+
case "const": {
|
|
3506
|
+
const isPrimitiveConstType = effectiveType.kind === "primitive" && ["string", "number", "integer", "bigint", "boolean", "null"].includes(
|
|
3507
|
+
effectiveType.primitiveKind
|
|
3508
|
+
) || effectiveType.kind === "enum";
|
|
3509
|
+
if (!isPrimitiveConstType) {
|
|
3510
|
+
addTypeMismatch(
|
|
3511
|
+
ctx,
|
|
3512
|
+
`Field "${fieldName}": constraint "const" is only valid on primitive or enum fields, but field type is "${label}"`,
|
|
3513
|
+
constraint.provenance
|
|
3514
|
+
);
|
|
3515
|
+
break;
|
|
3516
|
+
}
|
|
3517
|
+
if (effectiveType.kind === "primitive") {
|
|
3518
|
+
const valueType = constraint.value === null ? "null" : Array.isArray(constraint.value) ? "array" : typeof constraint.value;
|
|
3519
|
+
const expectedValueType = effectiveType.primitiveKind === "integer" || effectiveType.primitiveKind === "bigint" ? "number" : effectiveType.primitiveKind;
|
|
3520
|
+
if (valueType !== expectedValueType) {
|
|
3521
|
+
addTypeMismatch(
|
|
3522
|
+
ctx,
|
|
3523
|
+
`Field "${fieldName}": @const value type "${valueType}" is incompatible with field type "${effectiveType.primitiveKind}"`,
|
|
3524
|
+
constraint.provenance
|
|
3525
|
+
);
|
|
3526
|
+
}
|
|
3527
|
+
break;
|
|
3528
|
+
}
|
|
3529
|
+
const memberValues = effectiveType.members.map((member) => member.value);
|
|
3530
|
+
if (!memberValues.some((member) => jsonValueEquals(member, constraint.value))) {
|
|
3531
|
+
addTypeMismatch(
|
|
3532
|
+
ctx,
|
|
3533
|
+
`Field "${fieldName}": @const value ${JSON.stringify(constraint.value)} is not one of the enum members`,
|
|
3534
|
+
constraint.provenance
|
|
3535
|
+
);
|
|
3536
|
+
}
|
|
3537
|
+
break;
|
|
3538
|
+
}
|
|
3539
|
+
case "custom": {
|
|
3540
|
+
checkCustomConstraint(ctx, fieldName, effectiveType, constraint);
|
|
3541
|
+
break;
|
|
3542
|
+
}
|
|
3543
|
+
default: {
|
|
3544
|
+
const _exhaustive = constraint;
|
|
3545
|
+
throw new Error(
|
|
3546
|
+
`Unhandled constraint kind: ${_exhaustive.constraintKind}`
|
|
3547
|
+
);
|
|
3548
|
+
}
|
|
3549
|
+
}
|
|
3550
|
+
}
|
|
3551
|
+
function checkTypeApplicability(ctx, fieldName, type, constraints) {
|
|
3552
|
+
for (const constraint of constraints) {
|
|
3553
|
+
if (constraint.path) {
|
|
3554
|
+
const resolution = resolvePathTargetType(ctx, type, constraint.path.segments);
|
|
3555
|
+
const targetFieldName = formatPathTargetFieldName(fieldName, constraint.path.segments);
|
|
3556
|
+
if (resolution.kind === "missing-property") {
|
|
3557
|
+
addUnknownPathTarget(
|
|
3558
|
+
ctx,
|
|
3559
|
+
`Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" references unknown path segment "${resolution.segment}"`,
|
|
3560
|
+
constraint.provenance
|
|
3561
|
+
);
|
|
3562
|
+
continue;
|
|
3563
|
+
}
|
|
3564
|
+
if (resolution.kind === "unresolvable") {
|
|
3565
|
+
addTypeMismatch(
|
|
3566
|
+
ctx,
|
|
3567
|
+
`Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" is invalid because type "${typeLabel(resolution.type)}" cannot be traversed`,
|
|
3568
|
+
constraint.provenance
|
|
3569
|
+
);
|
|
3570
|
+
continue;
|
|
3571
|
+
}
|
|
3572
|
+
checkConstraintOnType(ctx, targetFieldName, resolution.type, constraint);
|
|
3573
|
+
continue;
|
|
3574
|
+
}
|
|
3575
|
+
checkConstraintOnType(ctx, fieldName, type, constraint);
|
|
3576
|
+
}
|
|
3577
|
+
}
|
|
3578
|
+
function checkCustomConstraint(ctx, fieldName, type, constraint) {
|
|
3579
|
+
if (ctx.extensionRegistry === void 0) return;
|
|
3580
|
+
const registration = ctx.extensionRegistry.findConstraint(constraint.constraintId);
|
|
3581
|
+
if (registration === void 0) {
|
|
3582
|
+
addUnknownExtension(
|
|
3583
|
+
ctx,
|
|
3584
|
+
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not registered in the extension registry`,
|
|
3585
|
+
constraint.provenance
|
|
3586
|
+
);
|
|
3587
|
+
return;
|
|
3588
|
+
}
|
|
3589
|
+
const candidateTypes = collectCustomConstraintCandidateTypes(ctx, type);
|
|
3590
|
+
const normalizedTagName = constraint.provenance.tagName === void 0 ? void 0 : normalizeConstraintTagName2(constraint.provenance.tagName.replace(/^@/, ""));
|
|
3591
|
+
if (normalizedTagName !== void 0) {
|
|
3592
|
+
const tagRegistration = ctx.extensionRegistry.findConstraintTag(normalizedTagName);
|
|
3593
|
+
const extensionId = getExtensionIdFromConstraintId(constraint.constraintId);
|
|
3594
|
+
if (extensionId !== null && tagRegistration?.extensionId === extensionId && tagRegistration.registration.constraintName === registration.constraintName && !candidateTypes.some(
|
|
3595
|
+
(candidateType) => tagRegistration.registration.isApplicableToType?.(candidateType) !== false
|
|
3596
|
+
)) {
|
|
3597
|
+
addTypeMismatch(
|
|
3598
|
+
ctx,
|
|
3599
|
+
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
3600
|
+
constraint.provenance
|
|
3601
|
+
);
|
|
3602
|
+
return;
|
|
3603
|
+
}
|
|
3604
|
+
}
|
|
3605
|
+
if (registration.applicableTypes === null) {
|
|
3606
|
+
if (!candidateTypes.some((candidateType) => registration.isApplicableToType?.(candidateType) !== false)) {
|
|
3607
|
+
addTypeMismatch(
|
|
3608
|
+
ctx,
|
|
3609
|
+
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
3610
|
+
constraint.provenance
|
|
3611
|
+
);
|
|
3612
|
+
}
|
|
3613
|
+
return;
|
|
3614
|
+
}
|
|
3615
|
+
const applicableTypes = registration.applicableTypes;
|
|
3616
|
+
const matchesApplicableType = candidateTypes.some(
|
|
3617
|
+
(candidateType) => applicableTypes.includes(candidateType.kind) && registration.isApplicableToType?.(candidateType) !== false
|
|
3618
|
+
);
|
|
3619
|
+
if (!matchesApplicableType) {
|
|
3620
|
+
addTypeMismatch(
|
|
3621
|
+
ctx,
|
|
3622
|
+
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
3623
|
+
constraint.provenance
|
|
3624
|
+
);
|
|
3625
|
+
}
|
|
3626
|
+
}
|
|
3627
|
+
function validateFieldNode(ctx, field) {
|
|
3628
|
+
validateConstraints(ctx, field.name, field.type, [
|
|
3629
|
+
...collectReferencedTypeConstraints(ctx, field.type),
|
|
3630
|
+
...field.constraints
|
|
3631
|
+
]);
|
|
3632
|
+
if (field.type.kind === "object") {
|
|
3633
|
+
for (const prop of field.type.properties) {
|
|
3634
|
+
validateObjectProperty(ctx, field.name, prop);
|
|
3635
|
+
}
|
|
3636
|
+
}
|
|
3637
|
+
}
|
|
3638
|
+
function validateObjectProperty(ctx, parentName, prop) {
|
|
3639
|
+
const qualifiedName = `${parentName}.${prop.name}`;
|
|
3640
|
+
validateConstraints(ctx, qualifiedName, prop.type, [
|
|
3641
|
+
...collectReferencedTypeConstraints(ctx, prop.type),
|
|
3642
|
+
...prop.constraints
|
|
3643
|
+
]);
|
|
3644
|
+
if (prop.type.kind === "object") {
|
|
3645
|
+
for (const nestedProp of prop.type.properties) {
|
|
3646
|
+
validateObjectProperty(ctx, qualifiedName, nestedProp);
|
|
3647
|
+
}
|
|
3648
|
+
}
|
|
3649
|
+
}
|
|
3650
|
+
function validateConstraints(ctx, name, type, constraints) {
|
|
3651
|
+
checkNumericContradictions(ctx, name, constraints);
|
|
3652
|
+
checkLengthContradictions(ctx, name, constraints);
|
|
3653
|
+
checkAllowedMembersContradiction(ctx, name, constraints);
|
|
3654
|
+
checkConstContradictions(ctx, name, constraints);
|
|
3655
|
+
checkConstraintBroadening(ctx, name, constraints);
|
|
3656
|
+
checkCustomConstraintSemantics(ctx, name, constraints);
|
|
3657
|
+
checkTypeApplicability(ctx, name, type, constraints);
|
|
3658
|
+
}
|
|
3659
|
+
function validateElement(ctx, element) {
|
|
3660
|
+
switch (element.kind) {
|
|
3661
|
+
case "field":
|
|
3662
|
+
validateFieldNode(ctx, element);
|
|
3663
|
+
break;
|
|
3664
|
+
case "group":
|
|
3665
|
+
for (const child of element.elements) {
|
|
3666
|
+
validateElement(ctx, child);
|
|
3667
|
+
}
|
|
3668
|
+
break;
|
|
3669
|
+
case "conditional":
|
|
3670
|
+
for (const child of element.elements) {
|
|
3671
|
+
validateElement(ctx, child);
|
|
3672
|
+
}
|
|
3673
|
+
break;
|
|
3674
|
+
default: {
|
|
3675
|
+
const _exhaustive = element;
|
|
3676
|
+
throw new Error(`Unhandled element kind: ${_exhaustive.kind}`);
|
|
3677
|
+
}
|
|
3678
|
+
}
|
|
3679
|
+
}
|
|
3680
|
+
function validateIR(ir, options) {
|
|
3681
|
+
const ctx = {
|
|
3682
|
+
diagnostics: [],
|
|
3683
|
+
extensionRegistry: options?.extensionRegistry,
|
|
3684
|
+
typeRegistry: ir.typeRegistry
|
|
3685
|
+
};
|
|
3686
|
+
for (const element of ir.elements) {
|
|
3687
|
+
validateElement(ctx, element);
|
|
3688
|
+
}
|
|
3689
|
+
return {
|
|
3690
|
+
diagnostics: ctx.diagnostics,
|
|
3691
|
+
valid: ctx.diagnostics.every((d) => d.severity !== "error")
|
|
3692
|
+
};
|
|
3693
|
+
}
|
|
3694
|
+
|
|
2779
3695
|
// src/generators/class-schema.ts
|
|
2780
3696
|
function generateClassSchemas(analysis, source, options) {
|
|
2781
3697
|
const ir = canonicalizeTSDoc(analysis, source);
|
|
3698
|
+
const validationResult = validateIR(ir, {
|
|
3699
|
+
...options?.extensionRegistry !== void 0 && {
|
|
3700
|
+
extensionRegistry: options.extensionRegistry
|
|
3701
|
+
},
|
|
3702
|
+
...options?.vendorPrefix !== void 0 && { vendorPrefix: options.vendorPrefix }
|
|
3703
|
+
});
|
|
3704
|
+
if (!validationResult.valid) {
|
|
3705
|
+
throw new Error(formatValidationError(validationResult.diagnostics));
|
|
3706
|
+
}
|
|
2782
3707
|
return {
|
|
2783
3708
|
jsonSchema: generateJsonSchemaFromIR(ir, options),
|
|
2784
3709
|
uiSchema: generateUiSchemaFromIR(ir)
|
|
2785
3710
|
};
|
|
2786
3711
|
}
|
|
3712
|
+
function formatValidationError(diagnostics) {
|
|
3713
|
+
const lines = diagnostics.map((diagnostic) => {
|
|
3714
|
+
const primary = formatLocation(diagnostic.primaryLocation);
|
|
3715
|
+
const related = diagnostic.relatedLocations.length > 0 ? ` [related: ${diagnostic.relatedLocations.map(formatLocation).join(", ")}]` : "";
|
|
3716
|
+
return `${diagnostic.code}: ${diagnostic.message} (${primary})${related}`;
|
|
3717
|
+
});
|
|
3718
|
+
return `FormSpec validation failed:
|
|
3719
|
+
${lines.map((line) => `- ${line}`).join("\n")}`;
|
|
3720
|
+
}
|
|
3721
|
+
function formatLocation(location) {
|
|
3722
|
+
return `${location.file}:${String(location.line)}:${String(location.column)}`;
|
|
3723
|
+
}
|
|
2787
3724
|
function generateSchemasFromClass(options) {
|
|
2788
3725
|
const ctx = createProgramContext(options.filePath);
|
|
2789
3726
|
const classDecl = findClassByName(ctx.sourceFile, options.className);
|
|
@@ -2806,50 +3743,18 @@ function generateSchemasFromClass(options) {
|
|
|
2806
3743
|
);
|
|
2807
3744
|
}
|
|
2808
3745
|
function generateSchemas(options) {
|
|
2809
|
-
const
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
const analysis = analyzeClassToIR(
|
|
2814
|
-
classDecl,
|
|
2815
|
-
ctx.checker,
|
|
2816
|
-
options.filePath,
|
|
2817
|
-
options.extensionRegistry
|
|
2818
|
-
);
|
|
2819
|
-
return generateClassSchemas(analysis, source, options);
|
|
2820
|
-
}
|
|
2821
|
-
const interfaceDecl = findInterfaceByName(ctx.sourceFile, options.typeName);
|
|
2822
|
-
if (interfaceDecl) {
|
|
2823
|
-
const analysis = analyzeInterfaceToIR(
|
|
2824
|
-
interfaceDecl,
|
|
2825
|
-
ctx.checker,
|
|
2826
|
-
options.filePath,
|
|
2827
|
-
options.extensionRegistry
|
|
2828
|
-
);
|
|
2829
|
-
return generateClassSchemas(analysis, source, options);
|
|
2830
|
-
}
|
|
2831
|
-
const typeAlias = findTypeAliasByName(ctx.sourceFile, options.typeName);
|
|
2832
|
-
if (typeAlias) {
|
|
2833
|
-
const result = analyzeTypeAliasToIR(
|
|
2834
|
-
typeAlias,
|
|
2835
|
-
ctx.checker,
|
|
2836
|
-
options.filePath,
|
|
2837
|
-
options.extensionRegistry
|
|
2838
|
-
);
|
|
2839
|
-
if (result.ok) {
|
|
2840
|
-
return generateClassSchemas(result.analysis, source, options);
|
|
2841
|
-
}
|
|
2842
|
-
throw new Error(result.error);
|
|
2843
|
-
}
|
|
2844
|
-
throw new Error(
|
|
2845
|
-
`Type "${options.typeName}" not found as a class, interface, or type alias in ${options.filePath}`
|
|
3746
|
+
const analysis = analyzeNamedTypeToIR(
|
|
3747
|
+
options.filePath,
|
|
3748
|
+
options.typeName,
|
|
3749
|
+
options.extensionRegistry
|
|
2846
3750
|
);
|
|
3751
|
+
return generateClassSchemas(analysis, { file: options.filePath }, options);
|
|
2847
3752
|
}
|
|
2848
3753
|
|
|
2849
3754
|
// src/generators/mixed-authoring.ts
|
|
2850
3755
|
function buildMixedAuthoringSchemas(options) {
|
|
2851
3756
|
const { filePath, typeName, overlays, ...schemaOptions } = options;
|
|
2852
|
-
const analysis =
|
|
3757
|
+
const analysis = analyzeNamedTypeToIR(filePath, typeName, schemaOptions.extensionRegistry);
|
|
2853
3758
|
const composedAnalysis = composeAnalysisWithOverlays(analysis, overlays);
|
|
2854
3759
|
const ir = canonicalizeTSDoc(composedAnalysis, { file: filePath });
|
|
2855
3760
|
return {
|
|
@@ -2857,29 +3762,6 @@ function buildMixedAuthoringSchemas(options) {
|
|
|
2857
3762
|
uiSchema: generateUiSchemaFromIR(ir)
|
|
2858
3763
|
};
|
|
2859
3764
|
}
|
|
2860
|
-
function analyzeNamedType(filePath, typeName, extensionRegistry) {
|
|
2861
|
-
const ctx = createProgramContext(filePath);
|
|
2862
|
-
const source = { file: filePath };
|
|
2863
|
-
const classDecl = findClassByName(ctx.sourceFile, typeName);
|
|
2864
|
-
if (classDecl !== null) {
|
|
2865
|
-
return analyzeClassToIR(classDecl, ctx.checker, source.file, extensionRegistry);
|
|
2866
|
-
}
|
|
2867
|
-
const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
|
|
2868
|
-
if (interfaceDecl !== null) {
|
|
2869
|
-
return analyzeInterfaceToIR(interfaceDecl, ctx.checker, source.file, extensionRegistry);
|
|
2870
|
-
}
|
|
2871
|
-
const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
|
|
2872
|
-
if (typeAlias !== null) {
|
|
2873
|
-
const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, source.file, extensionRegistry);
|
|
2874
|
-
if (result.ok) {
|
|
2875
|
-
return result.analysis;
|
|
2876
|
-
}
|
|
2877
|
-
throw new Error(result.error);
|
|
2878
|
-
}
|
|
2879
|
-
throw new Error(
|
|
2880
|
-
`Type "${typeName}" not found as a class, interface, or type alias in ${filePath}`
|
|
2881
|
-
);
|
|
2882
|
-
}
|
|
2883
3765
|
function composeAnalysisWithOverlays(analysis, overlays) {
|
|
2884
3766
|
const overlayIR = canonicalizeChainDSL(overlays);
|
|
2885
3767
|
const overlayFields = collectOverlayFields(overlayIR.elements);
|