@formspec/build 0.1.0-alpha.16 → 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/example-numeric-extension.d.ts +20 -0
- package/dist/__tests__/fixtures/example-numeric-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 +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__/numeric-extension.integration.test.d.ts +2 -0
- package/dist/__tests__/numeric-extension.integration.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 +5 -4
- package/dist/analyzer/class-analyzer.d.ts.map +1 -1
- package/dist/analyzer/jsdoc-constraints.d.ts +3 -2
- 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 +23 -2
- package/dist/analyzer/tsdoc-parser.d.ts.map +1 -1
- package/dist/browser.cjs +269 -11
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.js +269 -11
- package/dist/browser.js.map +1 -1
- package/dist/build.d.ts +28 -2
- 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 +1640 -282
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1638 -281
- package/dist/cli.js.map +1 -1
- package/dist/extensions/registry.d.ts +25 -1
- package/dist/extensions/registry.d.ts.map +1 -1
- package/dist/generators/class-schema.d.ts +4 -4
- 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 +1615 -271
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1615 -271
- package/dist/index.js.map +1 -1
- package/dist/internals.cjs +990 -236
- package/dist/internals.cjs.map +1 -1
- package/dist/internals.js +988 -234
- package/dist/internals.js.map +1 -1
- package/dist/json-schema/ir-generator.d.ts.map +1 -1
- package/dist/validate/constraint-validator.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/cli.cjs
CHANGED
|
@@ -45,6 +45,7 @@ function canonicalizeChainDSL(form) {
|
|
|
45
45
|
kind: "form-ir",
|
|
46
46
|
irVersion: import_core.IR_VERSION,
|
|
47
47
|
elements: canonicalizeElements(form.elements),
|
|
48
|
+
rootAnnotations: [],
|
|
48
49
|
typeRegistry: {},
|
|
49
50
|
provenance: CHAIN_DSL_PROVENANCE
|
|
50
51
|
};
|
|
@@ -362,6 +363,7 @@ function canonicalizeTSDoc(analysis, source) {
|
|
|
362
363
|
irVersion: import_core2.IR_VERSION,
|
|
363
364
|
elements,
|
|
364
365
|
typeRegistry: analysis.typeRegistry,
|
|
366
|
+
...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { rootAnnotations: analysis.annotations },
|
|
365
367
|
...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { annotations: analysis.annotations },
|
|
366
368
|
provenance
|
|
367
369
|
};
|
|
@@ -455,6 +457,9 @@ function generateJsonSchemaFromIR(ir, options) {
|
|
|
455
457
|
const ctx = makeContext(options);
|
|
456
458
|
for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
|
|
457
459
|
ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
|
|
460
|
+
if (typeDef.constraints && typeDef.constraints.length > 0) {
|
|
461
|
+
applyConstraints(ctx.defs[name], typeDef.constraints, ctx);
|
|
462
|
+
}
|
|
458
463
|
if (typeDef.annotations && typeDef.annotations.length > 0) {
|
|
459
464
|
applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
|
|
460
465
|
}
|
|
@@ -623,7 +628,9 @@ function generateTypeNode(type, ctx) {
|
|
|
623
628
|
}
|
|
624
629
|
}
|
|
625
630
|
function generatePrimitiveType(type) {
|
|
626
|
-
return {
|
|
631
|
+
return {
|
|
632
|
+
type: type.primitiveKind === "integer" || type.primitiveKind === "bigint" ? "integer" : type.primitiveKind
|
|
633
|
+
};
|
|
627
634
|
}
|
|
628
635
|
function generateEnumType(type) {
|
|
629
636
|
const hasDisplayNames = type.members.some((m) => m.displayName !== void 0);
|
|
@@ -796,7 +803,7 @@ function applyAnnotations(schema, annotations, ctx) {
|
|
|
796
803
|
case "deprecated":
|
|
797
804
|
schema.deprecated = true;
|
|
798
805
|
if (annotation.message !== void 0 && annotation.message !== "") {
|
|
799
|
-
schema[
|
|
806
|
+
schema[`${ctx.vendorPrefix}-deprecation-description`] = annotation.message;
|
|
800
807
|
}
|
|
801
808
|
break;
|
|
802
809
|
case "placeholder":
|
|
@@ -829,7 +836,12 @@ function applyCustomConstraint(schema, constraint, ctx) {
|
|
|
829
836
|
`Cannot generate JSON Schema for custom constraint "${constraint.constraintId}" without a matching extension registration`
|
|
830
837
|
);
|
|
831
838
|
}
|
|
832
|
-
|
|
839
|
+
assignVendorPrefixedExtensionKeywords(
|
|
840
|
+
schema,
|
|
841
|
+
registration.toJsonSchema(constraint.payload, ctx.vendorPrefix),
|
|
842
|
+
ctx.vendorPrefix,
|
|
843
|
+
`custom constraint "${constraint.constraintId}"`
|
|
844
|
+
);
|
|
833
845
|
}
|
|
834
846
|
function applyCustomAnnotation(schema, annotation, ctx) {
|
|
835
847
|
const registration = ctx.extensionRegistry?.findAnnotation(annotation.annotationId);
|
|
@@ -841,7 +853,22 @@ function applyCustomAnnotation(schema, annotation, ctx) {
|
|
|
841
853
|
if (registration.toJsonSchema === void 0) {
|
|
842
854
|
return;
|
|
843
855
|
}
|
|
844
|
-
|
|
856
|
+
assignVendorPrefixedExtensionKeywords(
|
|
857
|
+
schema,
|
|
858
|
+
registration.toJsonSchema(annotation.value, ctx.vendorPrefix),
|
|
859
|
+
ctx.vendorPrefix,
|
|
860
|
+
`custom annotation "${annotation.annotationId}"`
|
|
861
|
+
);
|
|
862
|
+
}
|
|
863
|
+
function assignVendorPrefixedExtensionKeywords(schema, extensionSchema, vendorPrefix, source) {
|
|
864
|
+
for (const [key, value] of Object.entries(extensionSchema)) {
|
|
865
|
+
if (!key.startsWith(`${vendorPrefix}-`)) {
|
|
866
|
+
throw new Error(
|
|
867
|
+
`Cannot apply ${source}: extension hooks may only emit "${vendorPrefix}-*" JSON Schema keywords`
|
|
868
|
+
);
|
|
869
|
+
}
|
|
870
|
+
schema[key] = value;
|
|
871
|
+
}
|
|
845
872
|
}
|
|
846
873
|
var init_ir_generator = __esm({
|
|
847
874
|
"src/json-schema/ir-generator.ts"() {
|
|
@@ -1127,7 +1154,10 @@ var init_types = __esm({
|
|
|
1127
1154
|
// src/extensions/registry.ts
|
|
1128
1155
|
function createExtensionRegistry(extensions) {
|
|
1129
1156
|
const typeMap = /* @__PURE__ */ new Map();
|
|
1157
|
+
const typeNameMap = /* @__PURE__ */ new Map();
|
|
1130
1158
|
const constraintMap = /* @__PURE__ */ new Map();
|
|
1159
|
+
const constraintTagMap = /* @__PURE__ */ new Map();
|
|
1160
|
+
const builtinBroadeningMap = /* @__PURE__ */ new Map();
|
|
1131
1161
|
const annotationMap = /* @__PURE__ */ new Map();
|
|
1132
1162
|
for (const ext of extensions) {
|
|
1133
1163
|
if (ext.types !== void 0) {
|
|
@@ -1137,6 +1167,27 @@ function createExtensionRegistry(extensions) {
|
|
|
1137
1167
|
throw new Error(`Duplicate custom type ID: "${qualifiedId}"`);
|
|
1138
1168
|
}
|
|
1139
1169
|
typeMap.set(qualifiedId, type);
|
|
1170
|
+
for (const sourceTypeName of type.tsTypeNames ?? [type.typeName]) {
|
|
1171
|
+
if (typeNameMap.has(sourceTypeName)) {
|
|
1172
|
+
throw new Error(`Duplicate custom type source name: "${sourceTypeName}"`);
|
|
1173
|
+
}
|
|
1174
|
+
typeNameMap.set(sourceTypeName, {
|
|
1175
|
+
extensionId: ext.extensionId,
|
|
1176
|
+
registration: type
|
|
1177
|
+
});
|
|
1178
|
+
}
|
|
1179
|
+
if (type.builtinConstraintBroadenings !== void 0) {
|
|
1180
|
+
for (const broadening of type.builtinConstraintBroadenings) {
|
|
1181
|
+
const key = `${qualifiedId}:${broadening.tagName}`;
|
|
1182
|
+
if (builtinBroadeningMap.has(key)) {
|
|
1183
|
+
throw new Error(`Duplicate built-in constraint broadening: "${key}"`);
|
|
1184
|
+
}
|
|
1185
|
+
builtinBroadeningMap.set(key, {
|
|
1186
|
+
extensionId: ext.extensionId,
|
|
1187
|
+
registration: broadening
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1140
1191
|
}
|
|
1141
1192
|
}
|
|
1142
1193
|
if (ext.constraints !== void 0) {
|
|
@@ -1148,6 +1199,17 @@ function createExtensionRegistry(extensions) {
|
|
|
1148
1199
|
constraintMap.set(qualifiedId, constraint);
|
|
1149
1200
|
}
|
|
1150
1201
|
}
|
|
1202
|
+
if (ext.constraintTags !== void 0) {
|
|
1203
|
+
for (const tag of ext.constraintTags) {
|
|
1204
|
+
if (constraintTagMap.has(tag.tagName)) {
|
|
1205
|
+
throw new Error(`Duplicate custom constraint tag: "@${tag.tagName}"`);
|
|
1206
|
+
}
|
|
1207
|
+
constraintTagMap.set(tag.tagName, {
|
|
1208
|
+
extensionId: ext.extensionId,
|
|
1209
|
+
registration: tag
|
|
1210
|
+
});
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1151
1213
|
if (ext.annotations !== void 0) {
|
|
1152
1214
|
for (const annotation of ext.annotations) {
|
|
1153
1215
|
const qualifiedId = `${ext.extensionId}/${annotation.annotationName}`;
|
|
@@ -1161,7 +1223,10 @@ function createExtensionRegistry(extensions) {
|
|
|
1161
1223
|
return {
|
|
1162
1224
|
extensions,
|
|
1163
1225
|
findType: (typeId) => typeMap.get(typeId),
|
|
1226
|
+
findTypeByName: (typeName) => typeNameMap.get(typeName),
|
|
1164
1227
|
findConstraint: (constraintId) => constraintMap.get(constraintId),
|
|
1228
|
+
findConstraintTag: (tagName) => constraintTagMap.get(tagName),
|
|
1229
|
+
findBuiltinConstraintBroadening: (typeId, tagName) => builtinBroadeningMap.get(`${typeId}:${tagName}`),
|
|
1165
1230
|
findAnnotation: (annotationId) => annotationMap.get(annotationId)
|
|
1166
1231
|
};
|
|
1167
1232
|
}
|
|
@@ -1247,84 +1312,6 @@ var init_schema2 = __esm({
|
|
|
1247
1312
|
}
|
|
1248
1313
|
});
|
|
1249
1314
|
|
|
1250
|
-
// src/analyzer/program.ts
|
|
1251
|
-
function createProgramContext(filePath) {
|
|
1252
|
-
const absolutePath = path.resolve(filePath);
|
|
1253
|
-
const fileDir = path.dirname(absolutePath);
|
|
1254
|
-
const configPath = ts.findConfigFile(fileDir, ts.sys.fileExists.bind(ts.sys), "tsconfig.json");
|
|
1255
|
-
let compilerOptions;
|
|
1256
|
-
let fileNames;
|
|
1257
|
-
if (configPath) {
|
|
1258
|
-
const configFile = ts.readConfigFile(configPath, ts.sys.readFile.bind(ts.sys));
|
|
1259
|
-
if (configFile.error) {
|
|
1260
|
-
throw new Error(
|
|
1261
|
-
`Error reading tsconfig.json: ${ts.flattenDiagnosticMessageText(configFile.error.messageText, "\n")}`
|
|
1262
|
-
);
|
|
1263
|
-
}
|
|
1264
|
-
const parsed = ts.parseJsonConfigFileContent(
|
|
1265
|
-
configFile.config,
|
|
1266
|
-
ts.sys,
|
|
1267
|
-
path.dirname(configPath)
|
|
1268
|
-
);
|
|
1269
|
-
if (parsed.errors.length > 0) {
|
|
1270
|
-
const errorMessages = parsed.errors.map((e) => ts.flattenDiagnosticMessageText(e.messageText, "\n")).join("\n");
|
|
1271
|
-
throw new Error(`Error parsing tsconfig.json: ${errorMessages}`);
|
|
1272
|
-
}
|
|
1273
|
-
compilerOptions = parsed.options;
|
|
1274
|
-
fileNames = parsed.fileNames.includes(absolutePath) ? parsed.fileNames : [...parsed.fileNames, absolutePath];
|
|
1275
|
-
} else {
|
|
1276
|
-
compilerOptions = {
|
|
1277
|
-
target: ts.ScriptTarget.ES2022,
|
|
1278
|
-
module: ts.ModuleKind.NodeNext,
|
|
1279
|
-
moduleResolution: ts.ModuleResolutionKind.NodeNext,
|
|
1280
|
-
strict: true,
|
|
1281
|
-
skipLibCheck: true,
|
|
1282
|
-
declaration: true
|
|
1283
|
-
};
|
|
1284
|
-
fileNames = [absolutePath];
|
|
1285
|
-
}
|
|
1286
|
-
const program = ts.createProgram(fileNames, compilerOptions);
|
|
1287
|
-
const sourceFile = program.getSourceFile(absolutePath);
|
|
1288
|
-
if (!sourceFile) {
|
|
1289
|
-
throw new Error(`Could not find source file: ${absolutePath}`);
|
|
1290
|
-
}
|
|
1291
|
-
return {
|
|
1292
|
-
program,
|
|
1293
|
-
checker: program.getTypeChecker(),
|
|
1294
|
-
sourceFile
|
|
1295
|
-
};
|
|
1296
|
-
}
|
|
1297
|
-
function findNodeByName(sourceFile, name, predicate, getName) {
|
|
1298
|
-
let result = null;
|
|
1299
|
-
function visit(node) {
|
|
1300
|
-
if (result) return;
|
|
1301
|
-
if (predicate(node) && getName(node) === name) {
|
|
1302
|
-
result = node;
|
|
1303
|
-
return;
|
|
1304
|
-
}
|
|
1305
|
-
ts.forEachChild(node, visit);
|
|
1306
|
-
}
|
|
1307
|
-
visit(sourceFile);
|
|
1308
|
-
return result;
|
|
1309
|
-
}
|
|
1310
|
-
function findClassByName(sourceFile, className) {
|
|
1311
|
-
return findNodeByName(sourceFile, className, ts.isClassDeclaration, (n) => n.name?.text);
|
|
1312
|
-
}
|
|
1313
|
-
function findInterfaceByName(sourceFile, interfaceName) {
|
|
1314
|
-
return findNodeByName(sourceFile, interfaceName, ts.isInterfaceDeclaration, (n) => n.name.text);
|
|
1315
|
-
}
|
|
1316
|
-
function findTypeAliasByName(sourceFile, aliasName) {
|
|
1317
|
-
return findNodeByName(sourceFile, aliasName, ts.isTypeAliasDeclaration, (n) => n.name.text);
|
|
1318
|
-
}
|
|
1319
|
-
var ts, path;
|
|
1320
|
-
var init_program = __esm({
|
|
1321
|
-
"src/analyzer/program.ts"() {
|
|
1322
|
-
"use strict";
|
|
1323
|
-
ts = __toESM(require("typescript"), 1);
|
|
1324
|
-
path = __toESM(require("path"), 1);
|
|
1325
|
-
}
|
|
1326
|
-
});
|
|
1327
|
-
|
|
1328
1315
|
// src/analyzer/json-utils.ts
|
|
1329
1316
|
function tryParseJson(text) {
|
|
1330
1317
|
try {
|
|
@@ -1340,7 +1327,7 @@ var init_json_utils = __esm({
|
|
|
1340
1327
|
});
|
|
1341
1328
|
|
|
1342
1329
|
// src/analyzer/tsdoc-parser.ts
|
|
1343
|
-
function createFormSpecTSDocConfig() {
|
|
1330
|
+
function createFormSpecTSDocConfig(extensionTagNames = []) {
|
|
1344
1331
|
const config = new import_tsdoc.TSDocConfiguration();
|
|
1345
1332
|
for (const tagName of Object.keys(import_core3.BUILTIN_CONSTRAINT_DEFINITIONS)) {
|
|
1346
1333
|
config.addTagDefinition(
|
|
@@ -1360,13 +1347,33 @@ function createFormSpecTSDocConfig() {
|
|
|
1360
1347
|
})
|
|
1361
1348
|
);
|
|
1362
1349
|
}
|
|
1350
|
+
for (const tagName of extensionTagNames) {
|
|
1351
|
+
config.addTagDefinition(
|
|
1352
|
+
new import_tsdoc.TSDocTagDefinition({
|
|
1353
|
+
tagName: "@" + tagName,
|
|
1354
|
+
syntaxKind: import_tsdoc.TSDocTagSyntaxKind.BlockTag,
|
|
1355
|
+
allowMultiple: true
|
|
1356
|
+
})
|
|
1357
|
+
);
|
|
1358
|
+
}
|
|
1363
1359
|
return config;
|
|
1364
1360
|
}
|
|
1365
|
-
function getParser() {
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1361
|
+
function getParser(options) {
|
|
1362
|
+
const extensionTagNames = [
|
|
1363
|
+
...options?.extensionRegistry?.extensions.flatMap(
|
|
1364
|
+
(extension) => (extension.constraintTags ?? []).map((tag) => tag.tagName)
|
|
1365
|
+
) ?? []
|
|
1366
|
+
].sort();
|
|
1367
|
+
const cacheKey = extensionTagNames.join("|");
|
|
1368
|
+
const existing = parserCache.get(cacheKey);
|
|
1369
|
+
if (existing) {
|
|
1370
|
+
return existing;
|
|
1371
|
+
}
|
|
1372
|
+
const parser = new import_tsdoc.TSDocParser(createFormSpecTSDocConfig(extensionTagNames));
|
|
1373
|
+
parserCache.set(cacheKey, parser);
|
|
1374
|
+
return parser;
|
|
1375
|
+
}
|
|
1376
|
+
function parseTSDocTags(node, file = "", options) {
|
|
1370
1377
|
const constraints = [];
|
|
1371
1378
|
const annotations = [];
|
|
1372
1379
|
let displayName;
|
|
@@ -1377,17 +1384,17 @@ function parseTSDocTags(node, file = "") {
|
|
|
1377
1384
|
let placeholderProvenance;
|
|
1378
1385
|
const sourceFile = node.getSourceFile();
|
|
1379
1386
|
const sourceText = sourceFile.getFullText();
|
|
1380
|
-
const commentRanges =
|
|
1387
|
+
const commentRanges = ts.getLeadingCommentRanges(sourceText, node.getFullStart());
|
|
1381
1388
|
if (commentRanges) {
|
|
1382
1389
|
for (const range of commentRanges) {
|
|
1383
|
-
if (range.kind !==
|
|
1390
|
+
if (range.kind !== ts.SyntaxKind.MultiLineCommentTrivia) {
|
|
1384
1391
|
continue;
|
|
1385
1392
|
}
|
|
1386
1393
|
const commentText = sourceText.substring(range.pos, range.end);
|
|
1387
1394
|
if (!commentText.startsWith("/**")) {
|
|
1388
1395
|
continue;
|
|
1389
1396
|
}
|
|
1390
|
-
const parser = getParser();
|
|
1397
|
+
const parser = getParser(options);
|
|
1391
1398
|
const parserContext = parser.parseRange(
|
|
1392
1399
|
import_tsdoc.TextRange.fromStringRange(sourceText, range.pos, range.end)
|
|
1393
1400
|
);
|
|
@@ -1398,26 +1405,31 @@ function parseTSDocTags(node, file = "") {
|
|
|
1398
1405
|
const text2 = extractBlockText(block).trim();
|
|
1399
1406
|
if (text2 === "") continue;
|
|
1400
1407
|
const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
displayName
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1408
|
+
switch (tagName) {
|
|
1409
|
+
case "displayName":
|
|
1410
|
+
if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
|
|
1411
|
+
displayName = text2;
|
|
1412
|
+
displayNameProvenance = provenance2;
|
|
1413
|
+
}
|
|
1414
|
+
break;
|
|
1415
|
+
case "format":
|
|
1416
|
+
annotations.push({
|
|
1417
|
+
kind: "annotation",
|
|
1418
|
+
annotationKind: "format",
|
|
1419
|
+
value: text2,
|
|
1420
|
+
provenance: provenance2
|
|
1421
|
+
});
|
|
1422
|
+
break;
|
|
1423
|
+
case "description":
|
|
1415
1424
|
description = text2;
|
|
1416
1425
|
descriptionProvenance = provenance2;
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1426
|
+
break;
|
|
1427
|
+
case "placeholder":
|
|
1428
|
+
if (placeholder === void 0) {
|
|
1429
|
+
placeholder = text2;
|
|
1430
|
+
placeholderProvenance = provenance2;
|
|
1431
|
+
}
|
|
1432
|
+
break;
|
|
1421
1433
|
}
|
|
1422
1434
|
continue;
|
|
1423
1435
|
}
|
|
@@ -1426,7 +1438,7 @@ function parseTSDocTags(node, file = "") {
|
|
|
1426
1438
|
const expectedType = (0, import_core3.isBuiltinConstraintName)(tagName) ? import_core3.BUILTIN_CONSTRAINT_DEFINITIONS[tagName] : void 0;
|
|
1427
1439
|
if (text === "" && expectedType !== "boolean") continue;
|
|
1428
1440
|
const provenance = provenanceForComment(range, sourceFile, file, tagName);
|
|
1429
|
-
const constraintNode = parseConstraintValue(tagName, text, provenance);
|
|
1441
|
+
const constraintNode = parseConstraintValue(tagName, text, provenance, options);
|
|
1430
1442
|
if (constraintNode) {
|
|
1431
1443
|
constraints.push(constraintNode);
|
|
1432
1444
|
}
|
|
@@ -1447,6 +1459,13 @@ function parseTSDocTags(node, file = "") {
|
|
|
1447
1459
|
descriptionProvenance = provenanceForComment(range, sourceFile, file, "remarks");
|
|
1448
1460
|
}
|
|
1449
1461
|
}
|
|
1462
|
+
if (description === void 0) {
|
|
1463
|
+
const summary = extractPlainText(docComment.summarySection).trim();
|
|
1464
|
+
if (summary !== "") {
|
|
1465
|
+
description = summary;
|
|
1466
|
+
descriptionProvenance = provenanceForComment(range, sourceFile, file, "summary");
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1450
1469
|
}
|
|
1451
1470
|
}
|
|
1452
1471
|
if (displayName !== void 0 && displayNameProvenance !== void 0) {
|
|
@@ -1473,7 +1492,7 @@ function parseTSDocTags(node, file = "") {
|
|
|
1473
1492
|
provenance: placeholderProvenance
|
|
1474
1493
|
});
|
|
1475
1494
|
}
|
|
1476
|
-
const jsDocTagsAll =
|
|
1495
|
+
const jsDocTagsAll = ts.getJSDocTags(node);
|
|
1477
1496
|
for (const tag of jsDocTagsAll) {
|
|
1478
1497
|
const tagName = (0, import_core3.normalizeConstraintTagName)(tag.tagName.text);
|
|
1479
1498
|
if (!TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
@@ -1486,7 +1505,7 @@ function parseTSDocTags(node, file = "") {
|
|
|
1486
1505
|
annotations.push(defaultValueNode);
|
|
1487
1506
|
continue;
|
|
1488
1507
|
}
|
|
1489
|
-
const constraintNode = parseConstraintValue(tagName, text, provenance);
|
|
1508
|
+
const constraintNode = parseConstraintValue(tagName, text, provenance, options);
|
|
1490
1509
|
if (constraintNode) {
|
|
1491
1510
|
constraints.push(constraintNode);
|
|
1492
1511
|
}
|
|
@@ -1496,7 +1515,7 @@ function parseTSDocTags(node, file = "") {
|
|
|
1496
1515
|
function extractDisplayNameMetadata(node) {
|
|
1497
1516
|
let displayName;
|
|
1498
1517
|
const memberDisplayNames = /* @__PURE__ */ new Map();
|
|
1499
|
-
for (const tag of
|
|
1518
|
+
for (const tag of ts.getJSDocTags(node)) {
|
|
1500
1519
|
const tagName = (0, import_core3.normalizeConstraintTagName)(tag.tagName.text);
|
|
1501
1520
|
if (tagName !== "displayName") continue;
|
|
1502
1521
|
const commentText = getTagCommentText(tag);
|
|
@@ -1517,11 +1536,11 @@ function extractDisplayNameMetadata(node) {
|
|
|
1517
1536
|
}
|
|
1518
1537
|
function extractPathTarget(text) {
|
|
1519
1538
|
const trimmed = text.trimStart();
|
|
1520
|
-
const match = /^:([a-zA-Z_]\w*)
|
|
1521
|
-
if (!match?.[1]
|
|
1539
|
+
const match = /^:([a-zA-Z_]\w*)(?:\s+([\s\S]*))?$/.exec(trimmed);
|
|
1540
|
+
if (!match?.[1]) return null;
|
|
1522
1541
|
return {
|
|
1523
1542
|
path: { segments: [match[1]] },
|
|
1524
|
-
remainingText: match[2]
|
|
1543
|
+
remainingText: match[2] ?? ""
|
|
1525
1544
|
};
|
|
1526
1545
|
}
|
|
1527
1546
|
function extractBlockText(block) {
|
|
@@ -1542,7 +1561,11 @@ function extractPlainText(node) {
|
|
|
1542
1561
|
}
|
|
1543
1562
|
return result;
|
|
1544
1563
|
}
|
|
1545
|
-
function parseConstraintValue(tagName, text, provenance) {
|
|
1564
|
+
function parseConstraintValue(tagName, text, provenance, options) {
|
|
1565
|
+
const customConstraint = parseExtensionConstraintValue(tagName, text, provenance, options);
|
|
1566
|
+
if (customConstraint) {
|
|
1567
|
+
return customConstraint;
|
|
1568
|
+
}
|
|
1546
1569
|
if (!(0, import_core3.isBuiltinConstraintName)(tagName)) {
|
|
1547
1570
|
return null;
|
|
1548
1571
|
}
|
|
@@ -1647,6 +1670,83 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
1647
1670
|
provenance
|
|
1648
1671
|
};
|
|
1649
1672
|
}
|
|
1673
|
+
function parseExtensionConstraintValue(tagName, text, provenance, options) {
|
|
1674
|
+
const pathResult = extractPathTarget(text);
|
|
1675
|
+
const effectiveText = pathResult ? pathResult.remainingText : text;
|
|
1676
|
+
const path4 = pathResult?.path;
|
|
1677
|
+
const registry = options?.extensionRegistry;
|
|
1678
|
+
if (registry === void 0) {
|
|
1679
|
+
return null;
|
|
1680
|
+
}
|
|
1681
|
+
const directTag = registry.findConstraintTag(tagName);
|
|
1682
|
+
if (directTag !== void 0) {
|
|
1683
|
+
return makeCustomConstraintNode(
|
|
1684
|
+
directTag.extensionId,
|
|
1685
|
+
directTag.registration.constraintName,
|
|
1686
|
+
directTag.registration.parseValue(effectiveText),
|
|
1687
|
+
provenance,
|
|
1688
|
+
path4,
|
|
1689
|
+
registry
|
|
1690
|
+
);
|
|
1691
|
+
}
|
|
1692
|
+
if (!(0, import_core3.isBuiltinConstraintName)(tagName)) {
|
|
1693
|
+
return null;
|
|
1694
|
+
}
|
|
1695
|
+
const broadenedTypeId = getBroadenedCustomTypeId(options?.fieldType);
|
|
1696
|
+
if (broadenedTypeId === void 0) {
|
|
1697
|
+
return null;
|
|
1698
|
+
}
|
|
1699
|
+
const broadened = registry.findBuiltinConstraintBroadening(broadenedTypeId, tagName);
|
|
1700
|
+
if (broadened === void 0) {
|
|
1701
|
+
return null;
|
|
1702
|
+
}
|
|
1703
|
+
return makeCustomConstraintNode(
|
|
1704
|
+
broadened.extensionId,
|
|
1705
|
+
broadened.registration.constraintName,
|
|
1706
|
+
broadened.registration.parseValue(effectiveText),
|
|
1707
|
+
provenance,
|
|
1708
|
+
path4,
|
|
1709
|
+
registry
|
|
1710
|
+
);
|
|
1711
|
+
}
|
|
1712
|
+
function getBroadenedCustomTypeId(fieldType) {
|
|
1713
|
+
if (fieldType?.kind === "custom") {
|
|
1714
|
+
return fieldType.typeId;
|
|
1715
|
+
}
|
|
1716
|
+
if (fieldType?.kind !== "union") {
|
|
1717
|
+
return void 0;
|
|
1718
|
+
}
|
|
1719
|
+
const customMembers = fieldType.members.filter(
|
|
1720
|
+
(member) => member.kind === "custom"
|
|
1721
|
+
);
|
|
1722
|
+
if (customMembers.length !== 1) {
|
|
1723
|
+
return void 0;
|
|
1724
|
+
}
|
|
1725
|
+
const nonCustomMembers = fieldType.members.filter((member) => member.kind !== "custom");
|
|
1726
|
+
const allOtherMembersAreNull = nonCustomMembers.every(
|
|
1727
|
+
(member) => member.kind === "primitive" && member.primitiveKind === "null"
|
|
1728
|
+
);
|
|
1729
|
+
const customMember = customMembers[0];
|
|
1730
|
+
return allOtherMembersAreNull && customMember !== void 0 ? customMember.typeId : void 0;
|
|
1731
|
+
}
|
|
1732
|
+
function makeCustomConstraintNode(extensionId, constraintName, payload, provenance, path4, registry) {
|
|
1733
|
+
const constraintId = `${extensionId}/${constraintName}`;
|
|
1734
|
+
const registration = registry.findConstraint(constraintId);
|
|
1735
|
+
if (registration === void 0) {
|
|
1736
|
+
throw new Error(
|
|
1737
|
+
`Custom TSDoc tag resolved to unregistered constraint "${constraintId}". Register the constraint before using its tag.`
|
|
1738
|
+
);
|
|
1739
|
+
}
|
|
1740
|
+
return {
|
|
1741
|
+
kind: "constraint",
|
|
1742
|
+
constraintKind: "custom",
|
|
1743
|
+
constraintId,
|
|
1744
|
+
payload,
|
|
1745
|
+
compositionRule: registration.compositionRule,
|
|
1746
|
+
...path4 && { path: path4 },
|
|
1747
|
+
provenance
|
|
1748
|
+
};
|
|
1749
|
+
}
|
|
1650
1750
|
function parseDefaultValueValue(text, provenance) {
|
|
1651
1751
|
const trimmed = text.trim();
|
|
1652
1752
|
let value;
|
|
@@ -1703,13 +1803,13 @@ function getTagCommentText(tag) {
|
|
|
1703
1803
|
if (typeof tag.comment === "string") {
|
|
1704
1804
|
return tag.comment;
|
|
1705
1805
|
}
|
|
1706
|
-
return
|
|
1806
|
+
return ts.getTextOfJSDocComment(tag.comment);
|
|
1707
1807
|
}
|
|
1708
|
-
var
|
|
1808
|
+
var ts, import_tsdoc, import_core3, NUMERIC_CONSTRAINT_MAP, LENGTH_CONSTRAINT_MAP, TAGS_REQUIRING_RAW_TEXT, parserCache;
|
|
1709
1809
|
var init_tsdoc_parser = __esm({
|
|
1710
1810
|
"src/analyzer/tsdoc-parser.ts"() {
|
|
1711
1811
|
"use strict";
|
|
1712
|
-
|
|
1812
|
+
ts = __toESM(require("typescript"), 1);
|
|
1713
1813
|
import_tsdoc = require("@microsoft/tsdoc");
|
|
1714
1814
|
import_core3 = require("@formspec/core");
|
|
1715
1815
|
init_json_utils();
|
|
@@ -1727,33 +1827,34 @@ var init_tsdoc_parser = __esm({
|
|
|
1727
1827
|
maxItems: "maxItems"
|
|
1728
1828
|
};
|
|
1729
1829
|
TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions", "defaultValue"]);
|
|
1830
|
+
parserCache = /* @__PURE__ */ new Map();
|
|
1730
1831
|
}
|
|
1731
1832
|
});
|
|
1732
1833
|
|
|
1733
1834
|
// src/analyzer/jsdoc-constraints.ts
|
|
1734
|
-
function extractJSDocConstraintNodes(node, file = "") {
|
|
1735
|
-
const result = parseTSDocTags(node, file);
|
|
1835
|
+
function extractJSDocConstraintNodes(node, file = "", options) {
|
|
1836
|
+
const result = parseTSDocTags(node, file, options);
|
|
1736
1837
|
return [...result.constraints];
|
|
1737
1838
|
}
|
|
1738
|
-
function extractJSDocAnnotationNodes(node, file = "") {
|
|
1739
|
-
const result = parseTSDocTags(node, file);
|
|
1839
|
+
function extractJSDocAnnotationNodes(node, file = "", options) {
|
|
1840
|
+
const result = parseTSDocTags(node, file, options);
|
|
1740
1841
|
return [...result.annotations];
|
|
1741
1842
|
}
|
|
1742
1843
|
function extractDefaultValueAnnotation(initializer, file = "") {
|
|
1743
1844
|
if (!initializer) return null;
|
|
1744
1845
|
let value;
|
|
1745
|
-
if (
|
|
1846
|
+
if (ts2.isStringLiteral(initializer)) {
|
|
1746
1847
|
value = initializer.text;
|
|
1747
|
-
} else if (
|
|
1848
|
+
} else if (ts2.isNumericLiteral(initializer)) {
|
|
1748
1849
|
value = Number(initializer.text);
|
|
1749
|
-
} else if (initializer.kind ===
|
|
1850
|
+
} else if (initializer.kind === ts2.SyntaxKind.TrueKeyword) {
|
|
1750
1851
|
value = true;
|
|
1751
|
-
} else if (initializer.kind ===
|
|
1852
|
+
} else if (initializer.kind === ts2.SyntaxKind.FalseKeyword) {
|
|
1752
1853
|
value = false;
|
|
1753
|
-
} else if (initializer.kind ===
|
|
1854
|
+
} else if (initializer.kind === ts2.SyntaxKind.NullKeyword) {
|
|
1754
1855
|
value = null;
|
|
1755
|
-
} else if (
|
|
1756
|
-
if (initializer.operator ===
|
|
1856
|
+
} else if (ts2.isPrefixUnaryExpression(initializer)) {
|
|
1857
|
+
if (initializer.operator === ts2.SyntaxKind.MinusToken && ts2.isNumericLiteral(initializer.operand)) {
|
|
1757
1858
|
value = -Number(initializer.operand.text);
|
|
1758
1859
|
}
|
|
1759
1860
|
}
|
|
@@ -1772,42 +1873,62 @@ function extractDefaultValueAnnotation(initializer, file = "") {
|
|
|
1772
1873
|
}
|
|
1773
1874
|
};
|
|
1774
1875
|
}
|
|
1775
|
-
var
|
|
1876
|
+
var ts2;
|
|
1776
1877
|
var init_jsdoc_constraints = __esm({
|
|
1777
1878
|
"src/analyzer/jsdoc-constraints.ts"() {
|
|
1778
1879
|
"use strict";
|
|
1779
|
-
|
|
1880
|
+
ts2 = __toESM(require("typescript"), 1);
|
|
1780
1881
|
init_tsdoc_parser();
|
|
1781
1882
|
}
|
|
1782
1883
|
});
|
|
1783
1884
|
|
|
1784
1885
|
// src/analyzer/class-analyzer.ts
|
|
1785
1886
|
function isObjectType(type) {
|
|
1786
|
-
return !!(type.flags &
|
|
1887
|
+
return !!(type.flags & ts3.TypeFlags.Object);
|
|
1787
1888
|
}
|
|
1788
1889
|
function isTypeReference(type) {
|
|
1789
|
-
return !!(type.flags &
|
|
1890
|
+
return !!(type.flags & ts3.TypeFlags.Object) && !!(type.objectFlags & ts3.ObjectFlags.Reference);
|
|
1891
|
+
}
|
|
1892
|
+
function makeParseOptions(extensionRegistry, fieldType) {
|
|
1893
|
+
if (extensionRegistry === void 0 && fieldType === void 0) {
|
|
1894
|
+
return void 0;
|
|
1895
|
+
}
|
|
1896
|
+
return {
|
|
1897
|
+
...extensionRegistry !== void 0 && { extensionRegistry },
|
|
1898
|
+
...fieldType !== void 0 && { fieldType }
|
|
1899
|
+
};
|
|
1790
1900
|
}
|
|
1791
|
-
function analyzeClassToIR(classDecl, checker, file = "") {
|
|
1901
|
+
function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
|
|
1792
1902
|
const name = classDecl.name?.text ?? "AnonymousClass";
|
|
1793
1903
|
const fields = [];
|
|
1794
1904
|
const fieldLayouts = [];
|
|
1795
1905
|
const typeRegistry = {};
|
|
1796
|
-
const annotations = extractJSDocAnnotationNodes(
|
|
1906
|
+
const annotations = extractJSDocAnnotationNodes(
|
|
1907
|
+
classDecl,
|
|
1908
|
+
file,
|
|
1909
|
+
makeParseOptions(extensionRegistry)
|
|
1910
|
+
);
|
|
1797
1911
|
const visiting = /* @__PURE__ */ new Set();
|
|
1798
1912
|
const instanceMethods = [];
|
|
1799
1913
|
const staticMethods = [];
|
|
1800
1914
|
for (const member of classDecl.members) {
|
|
1801
|
-
if (
|
|
1802
|
-
const fieldNode = analyzeFieldToIR(
|
|
1915
|
+
if (ts3.isPropertyDeclaration(member)) {
|
|
1916
|
+
const fieldNode = analyzeFieldToIR(
|
|
1917
|
+
member,
|
|
1918
|
+
checker,
|
|
1919
|
+
file,
|
|
1920
|
+
typeRegistry,
|
|
1921
|
+
visiting,
|
|
1922
|
+
extensionRegistry
|
|
1923
|
+
);
|
|
1803
1924
|
if (fieldNode) {
|
|
1804
1925
|
fields.push(fieldNode);
|
|
1805
1926
|
fieldLayouts.push({});
|
|
1806
1927
|
}
|
|
1807
|
-
} else if (
|
|
1928
|
+
} else if (ts3.isMethodDeclaration(member)) {
|
|
1808
1929
|
const methodInfo = analyzeMethod(member, checker);
|
|
1809
1930
|
if (methodInfo) {
|
|
1810
|
-
const isStatic = member.modifiers?.some((m) => m.kind ===
|
|
1931
|
+
const isStatic = member.modifiers?.some((m) => m.kind === ts3.SyntaxKind.StaticKeyword);
|
|
1811
1932
|
if (isStatic) {
|
|
1812
1933
|
staticMethods.push(methodInfo);
|
|
1813
1934
|
} else {
|
|
@@ -1826,15 +1947,26 @@ function analyzeClassToIR(classDecl, checker, file = "") {
|
|
|
1826
1947
|
staticMethods
|
|
1827
1948
|
};
|
|
1828
1949
|
}
|
|
1829
|
-
function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
|
|
1950
|
+
function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegistry) {
|
|
1830
1951
|
const name = interfaceDecl.name.text;
|
|
1831
1952
|
const fields = [];
|
|
1832
1953
|
const typeRegistry = {};
|
|
1833
|
-
const annotations = extractJSDocAnnotationNodes(
|
|
1954
|
+
const annotations = extractJSDocAnnotationNodes(
|
|
1955
|
+
interfaceDecl,
|
|
1956
|
+
file,
|
|
1957
|
+
makeParseOptions(extensionRegistry)
|
|
1958
|
+
);
|
|
1834
1959
|
const visiting = /* @__PURE__ */ new Set();
|
|
1835
1960
|
for (const member of interfaceDecl.members) {
|
|
1836
|
-
if (
|
|
1837
|
-
const fieldNode = analyzeInterfacePropertyToIR(
|
|
1961
|
+
if (ts3.isPropertySignature(member)) {
|
|
1962
|
+
const fieldNode = analyzeInterfacePropertyToIR(
|
|
1963
|
+
member,
|
|
1964
|
+
checker,
|
|
1965
|
+
file,
|
|
1966
|
+
typeRegistry,
|
|
1967
|
+
visiting,
|
|
1968
|
+
extensionRegistry
|
|
1969
|
+
);
|
|
1838
1970
|
if (fieldNode) {
|
|
1839
1971
|
fields.push(fieldNode);
|
|
1840
1972
|
}
|
|
@@ -1851,11 +1983,11 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
|
|
|
1851
1983
|
staticMethods: []
|
|
1852
1984
|
};
|
|
1853
1985
|
}
|
|
1854
|
-
function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
1855
|
-
if (!
|
|
1986
|
+
function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry) {
|
|
1987
|
+
if (!ts3.isTypeLiteralNode(typeAlias.type)) {
|
|
1856
1988
|
const sourceFile = typeAlias.getSourceFile();
|
|
1857
1989
|
const { line } = sourceFile.getLineAndCharacterOfPosition(typeAlias.getStart());
|
|
1858
|
-
const kindDesc =
|
|
1990
|
+
const kindDesc = ts3.SyntaxKind[typeAlias.type.kind] ?? "unknown";
|
|
1859
1991
|
return {
|
|
1860
1992
|
ok: false,
|
|
1861
1993
|
error: `Type alias "${typeAlias.name.text}" at line ${String(line + 1)} is not an object type literal (found ${kindDesc})`
|
|
@@ -1864,11 +1996,22 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
|
1864
1996
|
const name = typeAlias.name.text;
|
|
1865
1997
|
const fields = [];
|
|
1866
1998
|
const typeRegistry = {};
|
|
1867
|
-
const annotations = extractJSDocAnnotationNodes(
|
|
1999
|
+
const annotations = extractJSDocAnnotationNodes(
|
|
2000
|
+
typeAlias,
|
|
2001
|
+
file,
|
|
2002
|
+
makeParseOptions(extensionRegistry)
|
|
2003
|
+
);
|
|
1868
2004
|
const visiting = /* @__PURE__ */ new Set();
|
|
1869
2005
|
for (const member of typeAlias.type.members) {
|
|
1870
|
-
if (
|
|
1871
|
-
const fieldNode = analyzeInterfacePropertyToIR(
|
|
2006
|
+
if (ts3.isPropertySignature(member)) {
|
|
2007
|
+
const fieldNode = analyzeInterfacePropertyToIR(
|
|
2008
|
+
member,
|
|
2009
|
+
checker,
|
|
2010
|
+
file,
|
|
2011
|
+
typeRegistry,
|
|
2012
|
+
visiting,
|
|
2013
|
+
extensionRegistry
|
|
2014
|
+
);
|
|
1872
2015
|
if (fieldNode) {
|
|
1873
2016
|
fields.push(fieldNode);
|
|
1874
2017
|
}
|
|
@@ -1887,22 +2030,36 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
|
1887
2030
|
}
|
|
1888
2031
|
};
|
|
1889
2032
|
}
|
|
1890
|
-
function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
|
|
1891
|
-
if (!
|
|
2033
|
+
function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2034
|
+
if (!ts3.isIdentifier(prop.name)) {
|
|
1892
2035
|
return null;
|
|
1893
2036
|
}
|
|
1894
2037
|
const name = prop.name.text;
|
|
1895
2038
|
const tsType = checker.getTypeAtLocation(prop);
|
|
1896
2039
|
const optional = prop.questionToken !== void 0;
|
|
1897
2040
|
const provenance = provenanceForNode(prop, file);
|
|
1898
|
-
let type = resolveTypeNode(
|
|
2041
|
+
let type = resolveTypeNode(
|
|
2042
|
+
tsType,
|
|
2043
|
+
checker,
|
|
2044
|
+
file,
|
|
2045
|
+
typeRegistry,
|
|
2046
|
+
visiting,
|
|
2047
|
+
prop,
|
|
2048
|
+
extensionRegistry
|
|
2049
|
+
);
|
|
1899
2050
|
const constraints = [];
|
|
1900
|
-
if (prop.type) {
|
|
1901
|
-
constraints.push(
|
|
2051
|
+
if (prop.type && !shouldEmitPrimitiveAliasDefinition(prop.type, checker)) {
|
|
2052
|
+
constraints.push(
|
|
2053
|
+
...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
|
|
2054
|
+
);
|
|
1902
2055
|
}
|
|
1903
|
-
constraints.push(
|
|
2056
|
+
constraints.push(
|
|
2057
|
+
...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
2058
|
+
);
|
|
1904
2059
|
let annotations = [];
|
|
1905
|
-
annotations.push(
|
|
2060
|
+
annotations.push(
|
|
2061
|
+
...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
2062
|
+
);
|
|
1906
2063
|
const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
|
|
1907
2064
|
if (defaultAnnotation && !annotations.some((a) => a.annotationKind === "defaultValue")) {
|
|
1908
2065
|
annotations.push(defaultAnnotation);
|
|
@@ -1918,22 +2075,36 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
|
|
|
1918
2075
|
provenance
|
|
1919
2076
|
};
|
|
1920
2077
|
}
|
|
1921
|
-
function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting) {
|
|
1922
|
-
if (!
|
|
2078
|
+
function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2079
|
+
if (!ts3.isIdentifier(prop.name)) {
|
|
1923
2080
|
return null;
|
|
1924
2081
|
}
|
|
1925
2082
|
const name = prop.name.text;
|
|
1926
2083
|
const tsType = checker.getTypeAtLocation(prop);
|
|
1927
2084
|
const optional = prop.questionToken !== void 0;
|
|
1928
2085
|
const provenance = provenanceForNode(prop, file);
|
|
1929
|
-
let type = resolveTypeNode(
|
|
2086
|
+
let type = resolveTypeNode(
|
|
2087
|
+
tsType,
|
|
2088
|
+
checker,
|
|
2089
|
+
file,
|
|
2090
|
+
typeRegistry,
|
|
2091
|
+
visiting,
|
|
2092
|
+
prop,
|
|
2093
|
+
extensionRegistry
|
|
2094
|
+
);
|
|
1930
2095
|
const constraints = [];
|
|
1931
|
-
if (prop.type) {
|
|
1932
|
-
constraints.push(
|
|
2096
|
+
if (prop.type && !shouldEmitPrimitiveAliasDefinition(prop.type, checker)) {
|
|
2097
|
+
constraints.push(
|
|
2098
|
+
...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
|
|
2099
|
+
);
|
|
1933
2100
|
}
|
|
1934
|
-
constraints.push(
|
|
2101
|
+
constraints.push(
|
|
2102
|
+
...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
2103
|
+
);
|
|
1935
2104
|
let annotations = [];
|
|
1936
|
-
annotations.push(
|
|
2105
|
+
annotations.push(
|
|
2106
|
+
...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
2107
|
+
);
|
|
1937
2108
|
({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
|
|
1938
2109
|
return {
|
|
1939
2110
|
kind: "field",
|
|
@@ -2007,20 +2178,94 @@ function parseEnumMemberDisplayName(value) {
|
|
|
2007
2178
|
if (label === "") return null;
|
|
2008
2179
|
return { value: match[1], label };
|
|
2009
2180
|
}
|
|
2010
|
-
function
|
|
2011
|
-
if (
|
|
2181
|
+
function resolveRegisteredCustomType(sourceNode, extensionRegistry, checker) {
|
|
2182
|
+
if (sourceNode === void 0 || extensionRegistry === void 0) {
|
|
2183
|
+
return null;
|
|
2184
|
+
}
|
|
2185
|
+
const typeNode = extractTypeNodeFromSource(sourceNode);
|
|
2186
|
+
if (typeNode === void 0) {
|
|
2187
|
+
return null;
|
|
2188
|
+
}
|
|
2189
|
+
return resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker);
|
|
2190
|
+
}
|
|
2191
|
+
function resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker) {
|
|
2192
|
+
if (ts3.isParenthesizedTypeNode(typeNode)) {
|
|
2193
|
+
return resolveRegisteredCustomTypeFromTypeNode(typeNode.type, extensionRegistry, checker);
|
|
2194
|
+
}
|
|
2195
|
+
const typeName = getTypeNodeRegistrationName(typeNode);
|
|
2196
|
+
if (typeName === null) {
|
|
2197
|
+
return null;
|
|
2198
|
+
}
|
|
2199
|
+
const registration = extensionRegistry.findTypeByName(typeName);
|
|
2200
|
+
if (registration !== void 0) {
|
|
2201
|
+
return {
|
|
2202
|
+
kind: "custom",
|
|
2203
|
+
typeId: `${registration.extensionId}/${registration.registration.typeName}`,
|
|
2204
|
+
payload: null
|
|
2205
|
+
};
|
|
2206
|
+
}
|
|
2207
|
+
if (ts3.isTypeReferenceNode(typeNode) && ts3.isIdentifier(typeNode.typeName)) {
|
|
2208
|
+
const aliasDecl = checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
2209
|
+
if (aliasDecl !== void 0) {
|
|
2210
|
+
return resolveRegisteredCustomTypeFromTypeNode(aliasDecl.type, extensionRegistry, checker);
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
return null;
|
|
2214
|
+
}
|
|
2215
|
+
function extractTypeNodeFromSource(sourceNode) {
|
|
2216
|
+
if (ts3.isPropertyDeclaration(sourceNode) || ts3.isPropertySignature(sourceNode) || ts3.isParameter(sourceNode) || ts3.isTypeAliasDeclaration(sourceNode)) {
|
|
2217
|
+
return sourceNode.type;
|
|
2218
|
+
}
|
|
2219
|
+
if (ts3.isTypeNode(sourceNode)) {
|
|
2220
|
+
return sourceNode;
|
|
2221
|
+
}
|
|
2222
|
+
return void 0;
|
|
2223
|
+
}
|
|
2224
|
+
function getTypeNodeRegistrationName(typeNode) {
|
|
2225
|
+
if (ts3.isTypeReferenceNode(typeNode)) {
|
|
2226
|
+
return ts3.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : typeNode.typeName.right.text;
|
|
2227
|
+
}
|
|
2228
|
+
if (ts3.isParenthesizedTypeNode(typeNode)) {
|
|
2229
|
+
return getTypeNodeRegistrationName(typeNode.type);
|
|
2230
|
+
}
|
|
2231
|
+
if (typeNode.kind === ts3.SyntaxKind.BigIntKeyword || typeNode.kind === ts3.SyntaxKind.StringKeyword || typeNode.kind === ts3.SyntaxKind.NumberKeyword || typeNode.kind === ts3.SyntaxKind.BooleanKeyword) {
|
|
2232
|
+
return typeNode.getText();
|
|
2233
|
+
}
|
|
2234
|
+
return null;
|
|
2235
|
+
}
|
|
2236
|
+
function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
2237
|
+
const customType = resolveRegisteredCustomType(sourceNode, extensionRegistry, checker);
|
|
2238
|
+
if (customType) {
|
|
2239
|
+
return customType;
|
|
2240
|
+
}
|
|
2241
|
+
const primitiveAlias = tryResolveNamedPrimitiveAlias(
|
|
2242
|
+
type,
|
|
2243
|
+
checker,
|
|
2244
|
+
file,
|
|
2245
|
+
typeRegistry,
|
|
2246
|
+
visiting,
|
|
2247
|
+
sourceNode,
|
|
2248
|
+
extensionRegistry
|
|
2249
|
+
);
|
|
2250
|
+
if (primitiveAlias) {
|
|
2251
|
+
return primitiveAlias;
|
|
2252
|
+
}
|
|
2253
|
+
if (type.flags & ts3.TypeFlags.String) {
|
|
2012
2254
|
return { kind: "primitive", primitiveKind: "string" };
|
|
2013
2255
|
}
|
|
2014
|
-
if (type.flags &
|
|
2256
|
+
if (type.flags & ts3.TypeFlags.Number) {
|
|
2015
2257
|
return { kind: "primitive", primitiveKind: "number" };
|
|
2016
2258
|
}
|
|
2017
|
-
if (type.flags &
|
|
2259
|
+
if (type.flags & (ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral)) {
|
|
2260
|
+
return { kind: "primitive", primitiveKind: "bigint" };
|
|
2261
|
+
}
|
|
2262
|
+
if (type.flags & ts3.TypeFlags.Boolean) {
|
|
2018
2263
|
return { kind: "primitive", primitiveKind: "boolean" };
|
|
2019
2264
|
}
|
|
2020
|
-
if (type.flags &
|
|
2265
|
+
if (type.flags & ts3.TypeFlags.Null) {
|
|
2021
2266
|
return { kind: "primitive", primitiveKind: "null" };
|
|
2022
2267
|
}
|
|
2023
|
-
if (type.flags &
|
|
2268
|
+
if (type.flags & ts3.TypeFlags.Undefined) {
|
|
2024
2269
|
return { kind: "primitive", primitiveKind: "null" };
|
|
2025
2270
|
}
|
|
2026
2271
|
if (type.isStringLiteral()) {
|
|
@@ -2036,27 +2281,120 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
|
|
|
2036
2281
|
};
|
|
2037
2282
|
}
|
|
2038
2283
|
if (type.isUnion()) {
|
|
2039
|
-
return resolveUnionType(
|
|
2284
|
+
return resolveUnionType(
|
|
2285
|
+
type,
|
|
2286
|
+
checker,
|
|
2287
|
+
file,
|
|
2288
|
+
typeRegistry,
|
|
2289
|
+
visiting,
|
|
2290
|
+
sourceNode,
|
|
2291
|
+
extensionRegistry
|
|
2292
|
+
);
|
|
2040
2293
|
}
|
|
2041
2294
|
if (checker.isArrayType(type)) {
|
|
2042
|
-
return resolveArrayType(
|
|
2295
|
+
return resolveArrayType(
|
|
2296
|
+
type,
|
|
2297
|
+
checker,
|
|
2298
|
+
file,
|
|
2299
|
+
typeRegistry,
|
|
2300
|
+
visiting,
|
|
2301
|
+
sourceNode,
|
|
2302
|
+
extensionRegistry
|
|
2303
|
+
);
|
|
2043
2304
|
}
|
|
2044
2305
|
if (isObjectType(type)) {
|
|
2045
|
-
return resolveObjectType(type, checker, file, typeRegistry, visiting);
|
|
2306
|
+
return resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry);
|
|
2046
2307
|
}
|
|
2047
2308
|
return { kind: "primitive", primitiveKind: "string" };
|
|
2048
2309
|
}
|
|
2049
|
-
function
|
|
2310
|
+
function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
2311
|
+
if (!(type.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null))) {
|
|
2312
|
+
return null;
|
|
2313
|
+
}
|
|
2314
|
+
const aliasDecl = type.aliasSymbol?.declarations?.find(ts3.isTypeAliasDeclaration) ?? getReferencedTypeAliasDeclaration(sourceNode, checker);
|
|
2315
|
+
if (!aliasDecl) {
|
|
2316
|
+
return null;
|
|
2317
|
+
}
|
|
2318
|
+
const aliasName = aliasDecl.name.text;
|
|
2319
|
+
if (!typeRegistry[aliasName]) {
|
|
2320
|
+
const aliasType = checker.getTypeFromTypeNode(aliasDecl.type);
|
|
2321
|
+
const constraints = [
|
|
2322
|
+
...extractJSDocConstraintNodes(aliasDecl, file, makeParseOptions(extensionRegistry)),
|
|
2323
|
+
...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, extensionRegistry)
|
|
2324
|
+
];
|
|
2325
|
+
const annotations = extractJSDocAnnotationNodes(
|
|
2326
|
+
aliasDecl,
|
|
2327
|
+
file,
|
|
2328
|
+
makeParseOptions(extensionRegistry)
|
|
2329
|
+
);
|
|
2330
|
+
typeRegistry[aliasName] = {
|
|
2331
|
+
name: aliasName,
|
|
2332
|
+
type: resolveAliasedPrimitiveTarget(
|
|
2333
|
+
aliasType,
|
|
2334
|
+
checker,
|
|
2335
|
+
file,
|
|
2336
|
+
typeRegistry,
|
|
2337
|
+
visiting,
|
|
2338
|
+
extensionRegistry
|
|
2339
|
+
),
|
|
2340
|
+
...constraints.length > 0 && { constraints },
|
|
2341
|
+
...annotations.length > 0 && { annotations },
|
|
2342
|
+
provenance: provenanceForDeclaration(aliasDecl, file)
|
|
2343
|
+
};
|
|
2344
|
+
}
|
|
2345
|
+
return { kind: "reference", name: aliasName, typeArguments: [] };
|
|
2346
|
+
}
|
|
2347
|
+
function getReferencedTypeAliasDeclaration(sourceNode, checker) {
|
|
2348
|
+
const typeNode = sourceNode && (ts3.isPropertyDeclaration(sourceNode) || ts3.isPropertySignature(sourceNode) || ts3.isParameter(sourceNode)) ? sourceNode.type : void 0;
|
|
2349
|
+
if (!typeNode || !ts3.isTypeReferenceNode(typeNode)) {
|
|
2350
|
+
return void 0;
|
|
2351
|
+
}
|
|
2352
|
+
return checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
2353
|
+
}
|
|
2354
|
+
function shouldEmitPrimitiveAliasDefinition(typeNode, checker) {
|
|
2355
|
+
if (!ts3.isTypeReferenceNode(typeNode)) {
|
|
2356
|
+
return false;
|
|
2357
|
+
}
|
|
2358
|
+
const aliasDecl = checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
2359
|
+
if (!aliasDecl) {
|
|
2360
|
+
return false;
|
|
2361
|
+
}
|
|
2362
|
+
const resolved = checker.getTypeFromTypeNode(aliasDecl.type);
|
|
2363
|
+
return !!(resolved.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null));
|
|
2364
|
+
}
|
|
2365
|
+
function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2366
|
+
const nestedAliasDecl = type.aliasSymbol?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
2367
|
+
if (nestedAliasDecl !== void 0) {
|
|
2368
|
+
return resolveAliasedPrimitiveTarget(
|
|
2369
|
+
checker.getTypeFromTypeNode(nestedAliasDecl.type),
|
|
2370
|
+
checker,
|
|
2371
|
+
file,
|
|
2372
|
+
typeRegistry,
|
|
2373
|
+
visiting,
|
|
2374
|
+
extensionRegistry
|
|
2375
|
+
);
|
|
2376
|
+
}
|
|
2377
|
+
return resolveTypeNode(type, checker, file, typeRegistry, visiting, void 0, extensionRegistry);
|
|
2378
|
+
}
|
|
2379
|
+
function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
2050
2380
|
const typeName = getNamedTypeName(type);
|
|
2051
2381
|
const namedDecl = getNamedTypeDeclaration(type);
|
|
2052
2382
|
if (typeName && typeName in typeRegistry) {
|
|
2053
2383
|
return { kind: "reference", name: typeName, typeArguments: [] };
|
|
2054
2384
|
}
|
|
2055
2385
|
const allTypes = type.types;
|
|
2386
|
+
const unionMemberTypeNodes = extractUnionMemberTypeNodes(sourceNode, checker);
|
|
2387
|
+
const nonNullSourceNodes = unionMemberTypeNodes.filter(
|
|
2388
|
+
(memberTypeNode) => !isNullishTypeNode(resolveAliasedTypeNode(memberTypeNode, checker))
|
|
2389
|
+
);
|
|
2056
2390
|
const nonNullTypes = allTypes.filter(
|
|
2057
|
-
(
|
|
2391
|
+
(memberType) => !(memberType.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined))
|
|
2058
2392
|
);
|
|
2059
|
-
const
|
|
2393
|
+
const nonNullMembers = nonNullTypes.map((memberType, index) => ({
|
|
2394
|
+
memberType,
|
|
2395
|
+
sourceNode: nonNullSourceNodes.length === nonNullTypes.length ? nonNullSourceNodes[index] : void 0
|
|
2396
|
+
}));
|
|
2397
|
+
const hasNull = allTypes.some((t) => t.flags & ts3.TypeFlags.Null);
|
|
2060
2398
|
const memberDisplayNames = /* @__PURE__ */ new Map();
|
|
2061
2399
|
if (namedDecl) {
|
|
2062
2400
|
for (const [value, label] of extractDisplayNameMetadata(namedDecl).memberDisplayNames) {
|
|
@@ -2072,7 +2410,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
2072
2410
|
if (!typeName) {
|
|
2073
2411
|
return result;
|
|
2074
2412
|
}
|
|
2075
|
-
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
|
|
2413
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
2076
2414
|
typeRegistry[typeName] = {
|
|
2077
2415
|
name: typeName,
|
|
2078
2416
|
type: result,
|
|
@@ -2085,7 +2423,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
2085
2423
|
const displayName = memberDisplayNames.get(String(value));
|
|
2086
2424
|
return displayName !== void 0 ? { value, displayName } : { value };
|
|
2087
2425
|
});
|
|
2088
|
-
const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags &
|
|
2426
|
+
const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts3.TypeFlags.BooleanLiteral);
|
|
2089
2427
|
if (isBooleanUnion2) {
|
|
2090
2428
|
const boolNode = { kind: "primitive", primitiveKind: "boolean" };
|
|
2091
2429
|
const result = hasNull ? {
|
|
@@ -2120,14 +2458,15 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
2120
2458
|
} : enumNode;
|
|
2121
2459
|
return registerNamed(result);
|
|
2122
2460
|
}
|
|
2123
|
-
if (
|
|
2461
|
+
if (nonNullMembers.length === 1 && nonNullMembers[0]) {
|
|
2124
2462
|
const inner = resolveTypeNode(
|
|
2125
|
-
|
|
2463
|
+
nonNullMembers[0].memberType,
|
|
2126
2464
|
checker,
|
|
2127
2465
|
file,
|
|
2128
2466
|
typeRegistry,
|
|
2129
2467
|
visiting,
|
|
2130
|
-
sourceNode
|
|
2468
|
+
nonNullMembers[0].sourceNode ?? sourceNode,
|
|
2469
|
+
extensionRegistry
|
|
2131
2470
|
);
|
|
2132
2471
|
const result = hasNull ? {
|
|
2133
2472
|
kind: "union",
|
|
@@ -2135,29 +2474,54 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
2135
2474
|
} : inner;
|
|
2136
2475
|
return registerNamed(result);
|
|
2137
2476
|
}
|
|
2138
|
-
const members =
|
|
2139
|
-
(
|
|
2477
|
+
const members = nonNullMembers.map(
|
|
2478
|
+
({ memberType, sourceNode: memberSourceNode }) => resolveTypeNode(
|
|
2479
|
+
memberType,
|
|
2480
|
+
checker,
|
|
2481
|
+
file,
|
|
2482
|
+
typeRegistry,
|
|
2483
|
+
visiting,
|
|
2484
|
+
memberSourceNode ?? sourceNode,
|
|
2485
|
+
extensionRegistry
|
|
2486
|
+
)
|
|
2140
2487
|
);
|
|
2141
2488
|
if (hasNull) {
|
|
2142
2489
|
members.push({ kind: "primitive", primitiveKind: "null" });
|
|
2143
2490
|
}
|
|
2144
2491
|
return registerNamed({ kind: "union", members });
|
|
2145
2492
|
}
|
|
2146
|
-
function resolveArrayType(type, checker, file, typeRegistry, visiting) {
|
|
2493
|
+
function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
2147
2494
|
const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
|
|
2148
2495
|
const elementType = typeArgs?.[0];
|
|
2149
|
-
const
|
|
2496
|
+
const elementSourceNode = extractArrayElementTypeNode(sourceNode, checker);
|
|
2497
|
+
const items = elementType ? resolveTypeNode(
|
|
2498
|
+
elementType,
|
|
2499
|
+
checker,
|
|
2500
|
+
file,
|
|
2501
|
+
typeRegistry,
|
|
2502
|
+
visiting,
|
|
2503
|
+
elementSourceNode,
|
|
2504
|
+
extensionRegistry
|
|
2505
|
+
) : { kind: "primitive", primitiveKind: "string" };
|
|
2150
2506
|
return { kind: "array", items };
|
|
2151
2507
|
}
|
|
2152
|
-
function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
|
|
2508
|
+
function tryResolveRecordType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2153
2509
|
if (type.getProperties().length > 0) {
|
|
2154
2510
|
return null;
|
|
2155
2511
|
}
|
|
2156
|
-
const indexInfo = checker.getIndexInfoOfType(type,
|
|
2512
|
+
const indexInfo = checker.getIndexInfoOfType(type, ts3.IndexKind.String);
|
|
2157
2513
|
if (!indexInfo) {
|
|
2158
2514
|
return null;
|
|
2159
2515
|
}
|
|
2160
|
-
const valueType = resolveTypeNode(
|
|
2516
|
+
const valueType = resolveTypeNode(
|
|
2517
|
+
indexInfo.type,
|
|
2518
|
+
checker,
|
|
2519
|
+
file,
|
|
2520
|
+
typeRegistry,
|
|
2521
|
+
visiting,
|
|
2522
|
+
void 0,
|
|
2523
|
+
extensionRegistry
|
|
2524
|
+
);
|
|
2161
2525
|
return { kind: "record", valueType };
|
|
2162
2526
|
}
|
|
2163
2527
|
function typeNodeContainsReference(type, targetName) {
|
|
@@ -2185,7 +2549,7 @@ function typeNodeContainsReference(type, targetName) {
|
|
|
2185
2549
|
}
|
|
2186
2550
|
}
|
|
2187
2551
|
}
|
|
2188
|
-
function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
2552
|
+
function resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2189
2553
|
const typeName = getNamedTypeName(type);
|
|
2190
2554
|
const namedTypeName = typeName ?? void 0;
|
|
2191
2555
|
const namedDecl = getNamedTypeDeclaration(type);
|
|
@@ -2216,7 +2580,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
|
2216
2580
|
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
2217
2581
|
}
|
|
2218
2582
|
}
|
|
2219
|
-
const recordNode = tryResolveRecordType(
|
|
2583
|
+
const recordNode = tryResolveRecordType(
|
|
2584
|
+
type,
|
|
2585
|
+
checker,
|
|
2586
|
+
file,
|
|
2587
|
+
typeRegistry,
|
|
2588
|
+
visiting,
|
|
2589
|
+
extensionRegistry
|
|
2590
|
+
);
|
|
2220
2591
|
if (recordNode) {
|
|
2221
2592
|
visiting.delete(type);
|
|
2222
2593
|
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
@@ -2225,7 +2596,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
|
2225
2596
|
clearNamedTypeRegistration();
|
|
2226
2597
|
return recordNode;
|
|
2227
2598
|
}
|
|
2228
|
-
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
|
|
2599
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
2229
2600
|
typeRegistry[namedTypeName] = {
|
|
2230
2601
|
name: namedTypeName,
|
|
2231
2602
|
type: recordNode,
|
|
@@ -2237,19 +2608,27 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
|
2237
2608
|
return recordNode;
|
|
2238
2609
|
}
|
|
2239
2610
|
const properties = [];
|
|
2240
|
-
const fieldInfoMap = getNamedTypeFieldNodeInfoMap(
|
|
2611
|
+
const fieldInfoMap = getNamedTypeFieldNodeInfoMap(
|
|
2612
|
+
type,
|
|
2613
|
+
checker,
|
|
2614
|
+
file,
|
|
2615
|
+
typeRegistry,
|
|
2616
|
+
visiting,
|
|
2617
|
+
extensionRegistry
|
|
2618
|
+
);
|
|
2241
2619
|
for (const prop of type.getProperties()) {
|
|
2242
2620
|
const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
|
|
2243
2621
|
if (!declaration) continue;
|
|
2244
2622
|
const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
|
|
2245
|
-
const optional = !!(prop.flags &
|
|
2623
|
+
const optional = !!(prop.flags & ts3.SymbolFlags.Optional);
|
|
2246
2624
|
const propTypeNode = resolveTypeNode(
|
|
2247
2625
|
propType,
|
|
2248
2626
|
checker,
|
|
2249
2627
|
file,
|
|
2250
2628
|
typeRegistry,
|
|
2251
2629
|
visiting,
|
|
2252
|
-
declaration
|
|
2630
|
+
declaration,
|
|
2631
|
+
extensionRegistry
|
|
2253
2632
|
);
|
|
2254
2633
|
const fieldNodeInfo = fieldInfoMap?.get(prop.name);
|
|
2255
2634
|
properties.push({
|
|
@@ -2268,7 +2647,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
|
2268
2647
|
additionalProperties: true
|
|
2269
2648
|
};
|
|
2270
2649
|
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
2271
|
-
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
|
|
2650
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
2272
2651
|
typeRegistry[namedTypeName] = {
|
|
2273
2652
|
name: namedTypeName,
|
|
2274
2653
|
type: objectNode,
|
|
@@ -2279,19 +2658,26 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
|
2279
2658
|
}
|
|
2280
2659
|
return objectNode;
|
|
2281
2660
|
}
|
|
2282
|
-
function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting) {
|
|
2661
|
+
function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2283
2662
|
const symbols = [type.getSymbol(), type.aliasSymbol].filter(
|
|
2284
2663
|
(s) => s?.declarations != null && s.declarations.length > 0
|
|
2285
2664
|
);
|
|
2286
2665
|
for (const symbol of symbols) {
|
|
2287
2666
|
const declarations = symbol.declarations;
|
|
2288
2667
|
if (!declarations) continue;
|
|
2289
|
-
const classDecl = declarations.find(
|
|
2668
|
+
const classDecl = declarations.find(ts3.isClassDeclaration);
|
|
2290
2669
|
if (classDecl) {
|
|
2291
2670
|
const map = /* @__PURE__ */ new Map();
|
|
2292
2671
|
for (const member of classDecl.members) {
|
|
2293
|
-
if (
|
|
2294
|
-
const fieldNode = analyzeFieldToIR(
|
|
2672
|
+
if (ts3.isPropertyDeclaration(member) && ts3.isIdentifier(member.name)) {
|
|
2673
|
+
const fieldNode = analyzeFieldToIR(
|
|
2674
|
+
member,
|
|
2675
|
+
checker,
|
|
2676
|
+
file,
|
|
2677
|
+
typeRegistry,
|
|
2678
|
+
visiting,
|
|
2679
|
+
extensionRegistry
|
|
2680
|
+
);
|
|
2295
2681
|
if (fieldNode) {
|
|
2296
2682
|
map.set(fieldNode.name, {
|
|
2297
2683
|
constraints: [...fieldNode.constraints],
|
|
@@ -2303,28 +2689,86 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
2303
2689
|
}
|
|
2304
2690
|
return map;
|
|
2305
2691
|
}
|
|
2306
|
-
const interfaceDecl = declarations.find(
|
|
2692
|
+
const interfaceDecl = declarations.find(ts3.isInterfaceDeclaration);
|
|
2307
2693
|
if (interfaceDecl) {
|
|
2308
|
-
return buildFieldNodeInfoMap(
|
|
2694
|
+
return buildFieldNodeInfoMap(
|
|
2695
|
+
interfaceDecl.members,
|
|
2696
|
+
checker,
|
|
2697
|
+
file,
|
|
2698
|
+
typeRegistry,
|
|
2699
|
+
visiting,
|
|
2700
|
+
extensionRegistry
|
|
2701
|
+
);
|
|
2309
2702
|
}
|
|
2310
|
-
const typeAliasDecl = declarations.find(
|
|
2311
|
-
if (typeAliasDecl &&
|
|
2703
|
+
const typeAliasDecl = declarations.find(ts3.isTypeAliasDeclaration);
|
|
2704
|
+
if (typeAliasDecl && ts3.isTypeLiteralNode(typeAliasDecl.type)) {
|
|
2312
2705
|
return buildFieldNodeInfoMap(
|
|
2313
2706
|
typeAliasDecl.type.members,
|
|
2314
2707
|
checker,
|
|
2315
2708
|
file,
|
|
2316
2709
|
typeRegistry,
|
|
2317
|
-
visiting
|
|
2710
|
+
visiting,
|
|
2711
|
+
extensionRegistry
|
|
2318
2712
|
);
|
|
2319
2713
|
}
|
|
2320
2714
|
}
|
|
2321
2715
|
return null;
|
|
2322
2716
|
}
|
|
2323
|
-
function
|
|
2717
|
+
function extractArrayElementTypeNode(sourceNode, checker) {
|
|
2718
|
+
const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
|
|
2719
|
+
if (typeNode === void 0) {
|
|
2720
|
+
return void 0;
|
|
2721
|
+
}
|
|
2722
|
+
const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
|
|
2723
|
+
if (ts3.isArrayTypeNode(resolvedTypeNode)) {
|
|
2724
|
+
return resolvedTypeNode.elementType;
|
|
2725
|
+
}
|
|
2726
|
+
if (ts3.isTypeReferenceNode(resolvedTypeNode) && ts3.isIdentifier(resolvedTypeNode.typeName) && resolvedTypeNode.typeName.text === "Array" && resolvedTypeNode.typeArguments?.[0]) {
|
|
2727
|
+
return resolvedTypeNode.typeArguments[0];
|
|
2728
|
+
}
|
|
2729
|
+
return void 0;
|
|
2730
|
+
}
|
|
2731
|
+
function extractUnionMemberTypeNodes(sourceNode, checker) {
|
|
2732
|
+
const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
|
|
2733
|
+
if (!typeNode) {
|
|
2734
|
+
return [];
|
|
2735
|
+
}
|
|
2736
|
+
const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
|
|
2737
|
+
return ts3.isUnionTypeNode(resolvedTypeNode) ? [...resolvedTypeNode.types] : [];
|
|
2738
|
+
}
|
|
2739
|
+
function resolveAliasedTypeNode(typeNode, checker, visited = /* @__PURE__ */ new Set()) {
|
|
2740
|
+
if (ts3.isParenthesizedTypeNode(typeNode)) {
|
|
2741
|
+
return resolveAliasedTypeNode(typeNode.type, checker, visited);
|
|
2742
|
+
}
|
|
2743
|
+
if (!ts3.isTypeReferenceNode(typeNode) || !ts3.isIdentifier(typeNode.typeName)) {
|
|
2744
|
+
return typeNode;
|
|
2745
|
+
}
|
|
2746
|
+
const symbol = checker.getSymbolAtLocation(typeNode.typeName);
|
|
2747
|
+
const aliasDecl = symbol?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
2748
|
+
if (aliasDecl === void 0 || visited.has(aliasDecl)) {
|
|
2749
|
+
return typeNode;
|
|
2750
|
+
}
|
|
2751
|
+
visited.add(aliasDecl);
|
|
2752
|
+
return resolveAliasedTypeNode(aliasDecl.type, checker, visited);
|
|
2753
|
+
}
|
|
2754
|
+
function isNullishTypeNode(typeNode) {
|
|
2755
|
+
if (typeNode.kind === ts3.SyntaxKind.NullKeyword || typeNode.kind === ts3.SyntaxKind.UndefinedKeyword) {
|
|
2756
|
+
return true;
|
|
2757
|
+
}
|
|
2758
|
+
return ts3.isLiteralTypeNode(typeNode) && (typeNode.literal.kind === ts3.SyntaxKind.NullKeyword || typeNode.literal.kind === ts3.SyntaxKind.UndefinedKeyword);
|
|
2759
|
+
}
|
|
2760
|
+
function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2324
2761
|
const map = /* @__PURE__ */ new Map();
|
|
2325
2762
|
for (const member of members) {
|
|
2326
|
-
if (
|
|
2327
|
-
const fieldNode = analyzeInterfacePropertyToIR(
|
|
2763
|
+
if (ts3.isPropertySignature(member)) {
|
|
2764
|
+
const fieldNode = analyzeInterfacePropertyToIR(
|
|
2765
|
+
member,
|
|
2766
|
+
checker,
|
|
2767
|
+
file,
|
|
2768
|
+
typeRegistry,
|
|
2769
|
+
visiting,
|
|
2770
|
+
extensionRegistry
|
|
2771
|
+
);
|
|
2328
2772
|
if (fieldNode) {
|
|
2329
2773
|
map.set(fieldNode.name, {
|
|
2330
2774
|
constraints: [...fieldNode.constraints],
|
|
@@ -2336,8 +2780,8 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting) {
|
|
|
2336
2780
|
}
|
|
2337
2781
|
return map;
|
|
2338
2782
|
}
|
|
2339
|
-
function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
|
|
2340
|
-
if (!
|
|
2783
|
+
function extractTypeAliasConstraintNodes(typeNode, checker, file, extensionRegistry, depth = 0) {
|
|
2784
|
+
if (!ts3.isTypeReferenceNode(typeNode)) return [];
|
|
2341
2785
|
if (depth >= MAX_ALIAS_CHAIN_DEPTH) {
|
|
2342
2786
|
const aliasName = typeNode.typeName.getText();
|
|
2343
2787
|
throw new Error(
|
|
@@ -2346,11 +2790,26 @@ function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
|
|
|
2346
2790
|
}
|
|
2347
2791
|
const symbol = checker.getSymbolAtLocation(typeNode.typeName);
|
|
2348
2792
|
if (!symbol?.declarations) return [];
|
|
2349
|
-
const aliasDecl = symbol.declarations.find(
|
|
2793
|
+
const aliasDecl = symbol.declarations.find(ts3.isTypeAliasDeclaration);
|
|
2350
2794
|
if (!aliasDecl) return [];
|
|
2351
|
-
if (
|
|
2352
|
-
const
|
|
2353
|
-
|
|
2795
|
+
if (ts3.isTypeLiteralNode(aliasDecl.type)) return [];
|
|
2796
|
+
const aliasFieldType = resolveTypeNode(
|
|
2797
|
+
checker.getTypeAtLocation(aliasDecl.type),
|
|
2798
|
+
checker,
|
|
2799
|
+
file,
|
|
2800
|
+
{},
|
|
2801
|
+
/* @__PURE__ */ new Set(),
|
|
2802
|
+
aliasDecl.type,
|
|
2803
|
+
extensionRegistry
|
|
2804
|
+
);
|
|
2805
|
+
const constraints = extractJSDocConstraintNodes(
|
|
2806
|
+
aliasDecl,
|
|
2807
|
+
file,
|
|
2808
|
+
makeParseOptions(extensionRegistry, aliasFieldType)
|
|
2809
|
+
);
|
|
2810
|
+
constraints.push(
|
|
2811
|
+
...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, extensionRegistry, depth + 1)
|
|
2812
|
+
);
|
|
2354
2813
|
return constraints;
|
|
2355
2814
|
}
|
|
2356
2815
|
function provenanceForNode(node, file) {
|
|
@@ -2376,14 +2835,14 @@ function getNamedTypeName(type) {
|
|
|
2376
2835
|
const symbol = type.getSymbol();
|
|
2377
2836
|
if (symbol?.declarations) {
|
|
2378
2837
|
const decl = symbol.declarations[0];
|
|
2379
|
-
if (decl && (
|
|
2380
|
-
const name =
|
|
2838
|
+
if (decl && (ts3.isClassDeclaration(decl) || ts3.isInterfaceDeclaration(decl) || ts3.isTypeAliasDeclaration(decl))) {
|
|
2839
|
+
const name = ts3.isClassDeclaration(decl) ? decl.name?.text : decl.name.text;
|
|
2381
2840
|
if (name) return name;
|
|
2382
2841
|
}
|
|
2383
2842
|
}
|
|
2384
2843
|
const aliasSymbol = type.aliasSymbol;
|
|
2385
2844
|
if (aliasSymbol?.declarations) {
|
|
2386
|
-
const aliasDecl = aliasSymbol.declarations.find(
|
|
2845
|
+
const aliasDecl = aliasSymbol.declarations.find(ts3.isTypeAliasDeclaration);
|
|
2387
2846
|
if (aliasDecl) {
|
|
2388
2847
|
return aliasDecl.name.text;
|
|
2389
2848
|
}
|
|
@@ -2394,24 +2853,24 @@ function getNamedTypeDeclaration(type) {
|
|
|
2394
2853
|
const symbol = type.getSymbol();
|
|
2395
2854
|
if (symbol?.declarations) {
|
|
2396
2855
|
const decl = symbol.declarations[0];
|
|
2397
|
-
if (decl && (
|
|
2856
|
+
if (decl && (ts3.isClassDeclaration(decl) || ts3.isInterfaceDeclaration(decl) || ts3.isTypeAliasDeclaration(decl))) {
|
|
2398
2857
|
return decl;
|
|
2399
2858
|
}
|
|
2400
2859
|
}
|
|
2401
2860
|
const aliasSymbol = type.aliasSymbol;
|
|
2402
2861
|
if (aliasSymbol?.declarations) {
|
|
2403
|
-
return aliasSymbol.declarations.find(
|
|
2862
|
+
return aliasSymbol.declarations.find(ts3.isTypeAliasDeclaration);
|
|
2404
2863
|
}
|
|
2405
2864
|
return void 0;
|
|
2406
2865
|
}
|
|
2407
2866
|
function analyzeMethod(method, checker) {
|
|
2408
|
-
if (!
|
|
2867
|
+
if (!ts3.isIdentifier(method.name)) {
|
|
2409
2868
|
return null;
|
|
2410
2869
|
}
|
|
2411
2870
|
const name = method.name.text;
|
|
2412
2871
|
const parameters = [];
|
|
2413
2872
|
for (const param of method.parameters) {
|
|
2414
|
-
if (
|
|
2873
|
+
if (ts3.isIdentifier(param.name)) {
|
|
2415
2874
|
const paramInfo = analyzeParameter(param, checker);
|
|
2416
2875
|
parameters.push(paramInfo);
|
|
2417
2876
|
}
|
|
@@ -2422,7 +2881,7 @@ function analyzeMethod(method, checker) {
|
|
|
2422
2881
|
return { name, parameters, returnTypeNode, returnType };
|
|
2423
2882
|
}
|
|
2424
2883
|
function analyzeParameter(param, checker) {
|
|
2425
|
-
const name =
|
|
2884
|
+
const name = ts3.isIdentifier(param.name) ? param.name.text : "param";
|
|
2426
2885
|
const typeNode = param.type;
|
|
2427
2886
|
const type = checker.getTypeAtLocation(param);
|
|
2428
2887
|
const formSpecExportName = detectFormSpecReference(typeNode);
|
|
@@ -2431,24 +2890,24 @@ function analyzeParameter(param, checker) {
|
|
|
2431
2890
|
}
|
|
2432
2891
|
function detectFormSpecReference(typeNode) {
|
|
2433
2892
|
if (!typeNode) return null;
|
|
2434
|
-
if (!
|
|
2435
|
-
const typeName =
|
|
2893
|
+
if (!ts3.isTypeReferenceNode(typeNode)) return null;
|
|
2894
|
+
const typeName = ts3.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : ts3.isQualifiedName(typeNode.typeName) ? typeNode.typeName.right.text : null;
|
|
2436
2895
|
if (typeName !== "InferSchema" && typeName !== "InferFormSchema") return null;
|
|
2437
2896
|
const typeArg = typeNode.typeArguments?.[0];
|
|
2438
|
-
if (!typeArg || !
|
|
2439
|
-
if (
|
|
2897
|
+
if (!typeArg || !ts3.isTypeQueryNode(typeArg)) return null;
|
|
2898
|
+
if (ts3.isIdentifier(typeArg.exprName)) {
|
|
2440
2899
|
return typeArg.exprName.text;
|
|
2441
2900
|
}
|
|
2442
|
-
if (
|
|
2901
|
+
if (ts3.isQualifiedName(typeArg.exprName)) {
|
|
2443
2902
|
return typeArg.exprName.right.text;
|
|
2444
2903
|
}
|
|
2445
2904
|
return null;
|
|
2446
2905
|
}
|
|
2447
|
-
var
|
|
2906
|
+
var ts3, RESOLVING_TYPE_PLACEHOLDER, MAX_ALIAS_CHAIN_DEPTH;
|
|
2448
2907
|
var init_class_analyzer = __esm({
|
|
2449
2908
|
"src/analyzer/class-analyzer.ts"() {
|
|
2450
2909
|
"use strict";
|
|
2451
|
-
|
|
2910
|
+
ts3 = __toESM(require("typescript"), 1);
|
|
2452
2911
|
init_jsdoc_constraints();
|
|
2453
2912
|
init_tsdoc_parser();
|
|
2454
2913
|
RESOLVING_TYPE_PLACEHOLDER = {
|
|
@@ -2460,47 +2919,969 @@ var init_class_analyzer = __esm({
|
|
|
2460
2919
|
}
|
|
2461
2920
|
});
|
|
2462
2921
|
|
|
2922
|
+
// src/analyzer/program.ts
|
|
2923
|
+
function createProgramContext(filePath) {
|
|
2924
|
+
const absolutePath = path.resolve(filePath);
|
|
2925
|
+
const fileDir = path.dirname(absolutePath);
|
|
2926
|
+
const configPath = ts4.findConfigFile(fileDir, ts4.sys.fileExists.bind(ts4.sys), "tsconfig.json");
|
|
2927
|
+
let compilerOptions;
|
|
2928
|
+
let fileNames;
|
|
2929
|
+
if (configPath) {
|
|
2930
|
+
const configFile = ts4.readConfigFile(configPath, ts4.sys.readFile.bind(ts4.sys));
|
|
2931
|
+
if (configFile.error) {
|
|
2932
|
+
throw new Error(
|
|
2933
|
+
`Error reading tsconfig.json: ${ts4.flattenDiagnosticMessageText(configFile.error.messageText, "\n")}`
|
|
2934
|
+
);
|
|
2935
|
+
}
|
|
2936
|
+
const parsed = ts4.parseJsonConfigFileContent(
|
|
2937
|
+
configFile.config,
|
|
2938
|
+
ts4.sys,
|
|
2939
|
+
path.dirname(configPath)
|
|
2940
|
+
);
|
|
2941
|
+
if (parsed.errors.length > 0) {
|
|
2942
|
+
const errorMessages = parsed.errors.map((e) => ts4.flattenDiagnosticMessageText(e.messageText, "\n")).join("\n");
|
|
2943
|
+
throw new Error(`Error parsing tsconfig.json: ${errorMessages}`);
|
|
2944
|
+
}
|
|
2945
|
+
compilerOptions = parsed.options;
|
|
2946
|
+
fileNames = parsed.fileNames.includes(absolutePath) ? parsed.fileNames : [...parsed.fileNames, absolutePath];
|
|
2947
|
+
} else {
|
|
2948
|
+
compilerOptions = {
|
|
2949
|
+
target: ts4.ScriptTarget.ES2022,
|
|
2950
|
+
module: ts4.ModuleKind.NodeNext,
|
|
2951
|
+
moduleResolution: ts4.ModuleResolutionKind.NodeNext,
|
|
2952
|
+
strict: true,
|
|
2953
|
+
skipLibCheck: true,
|
|
2954
|
+
declaration: true
|
|
2955
|
+
};
|
|
2956
|
+
fileNames = [absolutePath];
|
|
2957
|
+
}
|
|
2958
|
+
const program = ts4.createProgram(fileNames, compilerOptions);
|
|
2959
|
+
const sourceFile = program.getSourceFile(absolutePath);
|
|
2960
|
+
if (!sourceFile) {
|
|
2961
|
+
throw new Error(`Could not find source file: ${absolutePath}`);
|
|
2962
|
+
}
|
|
2963
|
+
return {
|
|
2964
|
+
program,
|
|
2965
|
+
checker: program.getTypeChecker(),
|
|
2966
|
+
sourceFile
|
|
2967
|
+
};
|
|
2968
|
+
}
|
|
2969
|
+
function findNodeByName(sourceFile, name, predicate, getName) {
|
|
2970
|
+
let result = null;
|
|
2971
|
+
function visit(node) {
|
|
2972
|
+
if (result) return;
|
|
2973
|
+
if (predicate(node) && getName(node) === name) {
|
|
2974
|
+
result = node;
|
|
2975
|
+
return;
|
|
2976
|
+
}
|
|
2977
|
+
ts4.forEachChild(node, visit);
|
|
2978
|
+
}
|
|
2979
|
+
visit(sourceFile);
|
|
2980
|
+
return result;
|
|
2981
|
+
}
|
|
2982
|
+
function findClassByName(sourceFile, className) {
|
|
2983
|
+
return findNodeByName(sourceFile, className, ts4.isClassDeclaration, (n) => n.name?.text);
|
|
2984
|
+
}
|
|
2985
|
+
function findInterfaceByName(sourceFile, interfaceName) {
|
|
2986
|
+
return findNodeByName(sourceFile, interfaceName, ts4.isInterfaceDeclaration, (n) => n.name.text);
|
|
2987
|
+
}
|
|
2988
|
+
function findTypeAliasByName(sourceFile, aliasName) {
|
|
2989
|
+
return findNodeByName(sourceFile, aliasName, ts4.isTypeAliasDeclaration, (n) => n.name.text);
|
|
2990
|
+
}
|
|
2991
|
+
function analyzeNamedTypeToIR(filePath, typeName, extensionRegistry) {
|
|
2992
|
+
const ctx = createProgramContext(filePath);
|
|
2993
|
+
const classDecl = findClassByName(ctx.sourceFile, typeName);
|
|
2994
|
+
if (classDecl !== null) {
|
|
2995
|
+
return analyzeClassToIR(classDecl, ctx.checker, filePath, extensionRegistry);
|
|
2996
|
+
}
|
|
2997
|
+
const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
|
|
2998
|
+
if (interfaceDecl !== null) {
|
|
2999
|
+
return analyzeInterfaceToIR(interfaceDecl, ctx.checker, filePath, extensionRegistry);
|
|
3000
|
+
}
|
|
3001
|
+
const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
|
|
3002
|
+
if (typeAlias !== null) {
|
|
3003
|
+
const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, filePath, extensionRegistry);
|
|
3004
|
+
if (result.ok) {
|
|
3005
|
+
return result.analysis;
|
|
3006
|
+
}
|
|
3007
|
+
throw new Error(result.error);
|
|
3008
|
+
}
|
|
3009
|
+
throw new Error(
|
|
3010
|
+
`Type "${typeName}" not found as a class, interface, or type alias in ${filePath}`
|
|
3011
|
+
);
|
|
3012
|
+
}
|
|
3013
|
+
var ts4, path;
|
|
3014
|
+
var init_program = __esm({
|
|
3015
|
+
"src/analyzer/program.ts"() {
|
|
3016
|
+
"use strict";
|
|
3017
|
+
ts4 = __toESM(require("typescript"), 1);
|
|
3018
|
+
path = __toESM(require("path"), 1);
|
|
3019
|
+
init_class_analyzer();
|
|
3020
|
+
}
|
|
3021
|
+
});
|
|
3022
|
+
|
|
3023
|
+
// src/validate/constraint-validator.ts
|
|
3024
|
+
function addContradiction(ctx, message, primary, related) {
|
|
3025
|
+
ctx.diagnostics.push({
|
|
3026
|
+
code: "CONTRADICTING_CONSTRAINTS",
|
|
3027
|
+
message,
|
|
3028
|
+
severity: "error",
|
|
3029
|
+
primaryLocation: primary,
|
|
3030
|
+
relatedLocations: [related]
|
|
3031
|
+
});
|
|
3032
|
+
}
|
|
3033
|
+
function addTypeMismatch(ctx, message, primary) {
|
|
3034
|
+
ctx.diagnostics.push({
|
|
3035
|
+
code: "TYPE_MISMATCH",
|
|
3036
|
+
message,
|
|
3037
|
+
severity: "error",
|
|
3038
|
+
primaryLocation: primary,
|
|
3039
|
+
relatedLocations: []
|
|
3040
|
+
});
|
|
3041
|
+
}
|
|
3042
|
+
function addUnknownExtension(ctx, message, primary) {
|
|
3043
|
+
ctx.diagnostics.push({
|
|
3044
|
+
code: "UNKNOWN_EXTENSION",
|
|
3045
|
+
message,
|
|
3046
|
+
severity: "warning",
|
|
3047
|
+
primaryLocation: primary,
|
|
3048
|
+
relatedLocations: []
|
|
3049
|
+
});
|
|
3050
|
+
}
|
|
3051
|
+
function addUnknownPathTarget(ctx, message, primary) {
|
|
3052
|
+
ctx.diagnostics.push({
|
|
3053
|
+
code: "UNKNOWN_PATH_TARGET",
|
|
3054
|
+
message,
|
|
3055
|
+
severity: "error",
|
|
3056
|
+
primaryLocation: primary,
|
|
3057
|
+
relatedLocations: []
|
|
3058
|
+
});
|
|
3059
|
+
}
|
|
3060
|
+
function addConstraintBroadening(ctx, message, primary, related) {
|
|
3061
|
+
ctx.diagnostics.push({
|
|
3062
|
+
code: "CONSTRAINT_BROADENING",
|
|
3063
|
+
message,
|
|
3064
|
+
severity: "error",
|
|
3065
|
+
primaryLocation: primary,
|
|
3066
|
+
relatedLocations: [related]
|
|
3067
|
+
});
|
|
3068
|
+
}
|
|
3069
|
+
function getExtensionIdFromConstraintId(constraintId) {
|
|
3070
|
+
const separator = constraintId.lastIndexOf("/");
|
|
3071
|
+
if (separator <= 0) {
|
|
3072
|
+
return null;
|
|
3073
|
+
}
|
|
3074
|
+
return constraintId.slice(0, separator);
|
|
3075
|
+
}
|
|
3076
|
+
function findNumeric(constraints, constraintKind) {
|
|
3077
|
+
return constraints.find((c) => c.constraintKind === constraintKind);
|
|
3078
|
+
}
|
|
3079
|
+
function findLength(constraints, constraintKind) {
|
|
3080
|
+
return constraints.find((c) => c.constraintKind === constraintKind);
|
|
3081
|
+
}
|
|
3082
|
+
function findAllowedMembers(constraints) {
|
|
3083
|
+
return constraints.filter(
|
|
3084
|
+
(c) => c.constraintKind === "allowedMembers"
|
|
3085
|
+
);
|
|
3086
|
+
}
|
|
3087
|
+
function findConstConstraints(constraints) {
|
|
3088
|
+
return constraints.filter(
|
|
3089
|
+
(c) => c.constraintKind === "const"
|
|
3090
|
+
);
|
|
3091
|
+
}
|
|
3092
|
+
function jsonValueEquals(left, right) {
|
|
3093
|
+
if (left === right) {
|
|
3094
|
+
return true;
|
|
3095
|
+
}
|
|
3096
|
+
if (Array.isArray(left) || Array.isArray(right)) {
|
|
3097
|
+
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
|
|
3098
|
+
return false;
|
|
3099
|
+
}
|
|
3100
|
+
return left.every((item, index) => jsonValueEquals(item, right[index]));
|
|
3101
|
+
}
|
|
3102
|
+
if (isJsonObject(left) || isJsonObject(right)) {
|
|
3103
|
+
if (!isJsonObject(left) || !isJsonObject(right)) {
|
|
3104
|
+
return false;
|
|
3105
|
+
}
|
|
3106
|
+
const leftKeys = Object.keys(left).sort();
|
|
3107
|
+
const rightKeys = Object.keys(right).sort();
|
|
3108
|
+
if (leftKeys.length !== rightKeys.length) {
|
|
3109
|
+
return false;
|
|
3110
|
+
}
|
|
3111
|
+
return leftKeys.every((key, index) => {
|
|
3112
|
+
const rightKey = rightKeys[index];
|
|
3113
|
+
if (rightKey !== key) {
|
|
3114
|
+
return false;
|
|
3115
|
+
}
|
|
3116
|
+
const leftValue = left[key];
|
|
3117
|
+
const rightValue = right[rightKey];
|
|
3118
|
+
return leftValue !== void 0 && rightValue !== void 0 && jsonValueEquals(leftValue, rightValue);
|
|
3119
|
+
});
|
|
3120
|
+
}
|
|
3121
|
+
return false;
|
|
3122
|
+
}
|
|
3123
|
+
function isJsonObject(value) {
|
|
3124
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3125
|
+
}
|
|
3126
|
+
function isOrderedBoundConstraint(constraint) {
|
|
3127
|
+
return constraint.constraintKind === "minimum" || constraint.constraintKind === "exclusiveMinimum" || constraint.constraintKind === "minLength" || constraint.constraintKind === "minItems" || constraint.constraintKind === "maximum" || constraint.constraintKind === "exclusiveMaximum" || constraint.constraintKind === "maxLength" || constraint.constraintKind === "maxItems";
|
|
3128
|
+
}
|
|
3129
|
+
function pathKey(constraint) {
|
|
3130
|
+
return constraint.path?.segments.join(".") ?? "";
|
|
3131
|
+
}
|
|
3132
|
+
function orderedBoundFamily(kind) {
|
|
3133
|
+
switch (kind) {
|
|
3134
|
+
case "minimum":
|
|
3135
|
+
case "exclusiveMinimum":
|
|
3136
|
+
return "numeric-lower";
|
|
3137
|
+
case "maximum":
|
|
3138
|
+
case "exclusiveMaximum":
|
|
3139
|
+
return "numeric-upper";
|
|
3140
|
+
case "minLength":
|
|
3141
|
+
return "minLength";
|
|
3142
|
+
case "minItems":
|
|
3143
|
+
return "minItems";
|
|
3144
|
+
case "maxLength":
|
|
3145
|
+
return "maxLength";
|
|
3146
|
+
case "maxItems":
|
|
3147
|
+
return "maxItems";
|
|
3148
|
+
default: {
|
|
3149
|
+
const _exhaustive = kind;
|
|
3150
|
+
return _exhaustive;
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
}
|
|
3154
|
+
function isNumericLowerKind(kind) {
|
|
3155
|
+
return kind === "minimum" || kind === "exclusiveMinimum";
|
|
3156
|
+
}
|
|
3157
|
+
function isNumericUpperKind(kind) {
|
|
3158
|
+
return kind === "maximum" || kind === "exclusiveMaximum";
|
|
3159
|
+
}
|
|
3160
|
+
function describeConstraintTag(constraint) {
|
|
3161
|
+
return `@${constraint.constraintKind}`;
|
|
3162
|
+
}
|
|
3163
|
+
function compareConstraintStrength(current, previous) {
|
|
3164
|
+
const family = orderedBoundFamily(current.constraintKind);
|
|
3165
|
+
if (family === "numeric-lower") {
|
|
3166
|
+
if (!isNumericLowerKind(current.constraintKind) || !isNumericLowerKind(previous.constraintKind)) {
|
|
3167
|
+
throw new Error("numeric-lower family received non-numeric lower-bound constraint");
|
|
3168
|
+
}
|
|
3169
|
+
if (current.value !== previous.value) {
|
|
3170
|
+
return current.value > previous.value ? 1 : -1;
|
|
3171
|
+
}
|
|
3172
|
+
if (current.constraintKind === "exclusiveMinimum" && previous.constraintKind === "minimum") {
|
|
3173
|
+
return 1;
|
|
3174
|
+
}
|
|
3175
|
+
if (current.constraintKind === "minimum" && previous.constraintKind === "exclusiveMinimum") {
|
|
3176
|
+
return -1;
|
|
3177
|
+
}
|
|
3178
|
+
return 0;
|
|
3179
|
+
}
|
|
3180
|
+
if (family === "numeric-upper") {
|
|
3181
|
+
if (!isNumericUpperKind(current.constraintKind) || !isNumericUpperKind(previous.constraintKind)) {
|
|
3182
|
+
throw new Error("numeric-upper family received non-numeric upper-bound constraint");
|
|
3183
|
+
}
|
|
3184
|
+
if (current.value !== previous.value) {
|
|
3185
|
+
return current.value < previous.value ? 1 : -1;
|
|
3186
|
+
}
|
|
3187
|
+
if (current.constraintKind === "exclusiveMaximum" && previous.constraintKind === "maximum") {
|
|
3188
|
+
return 1;
|
|
3189
|
+
}
|
|
3190
|
+
if (current.constraintKind === "maximum" && previous.constraintKind === "exclusiveMaximum") {
|
|
3191
|
+
return -1;
|
|
3192
|
+
}
|
|
3193
|
+
return 0;
|
|
3194
|
+
}
|
|
3195
|
+
switch (family) {
|
|
3196
|
+
case "minLength":
|
|
3197
|
+
case "minItems":
|
|
3198
|
+
if (current.value === previous.value) {
|
|
3199
|
+
return 0;
|
|
3200
|
+
}
|
|
3201
|
+
return current.value > previous.value ? 1 : -1;
|
|
3202
|
+
case "maxLength":
|
|
3203
|
+
case "maxItems":
|
|
3204
|
+
if (current.value === previous.value) {
|
|
3205
|
+
return 0;
|
|
3206
|
+
}
|
|
3207
|
+
return current.value < previous.value ? 1 : -1;
|
|
3208
|
+
default: {
|
|
3209
|
+
const _exhaustive = family;
|
|
3210
|
+
return _exhaustive;
|
|
3211
|
+
}
|
|
3212
|
+
}
|
|
3213
|
+
}
|
|
3214
|
+
function checkConstraintBroadening(ctx, fieldName, constraints) {
|
|
3215
|
+
const strongestByKey = /* @__PURE__ */ new Map();
|
|
3216
|
+
for (const constraint of constraints) {
|
|
3217
|
+
if (!isOrderedBoundConstraint(constraint)) {
|
|
3218
|
+
continue;
|
|
3219
|
+
}
|
|
3220
|
+
const key = `${orderedBoundFamily(constraint.constraintKind)}:${pathKey(constraint)}`;
|
|
3221
|
+
const previous = strongestByKey.get(key);
|
|
3222
|
+
if (previous === void 0) {
|
|
3223
|
+
strongestByKey.set(key, constraint);
|
|
3224
|
+
continue;
|
|
3225
|
+
}
|
|
3226
|
+
const strength = compareConstraintStrength(constraint, previous);
|
|
3227
|
+
if (strength < 0) {
|
|
3228
|
+
const displayFieldName = formatPathTargetFieldName(
|
|
3229
|
+
fieldName,
|
|
3230
|
+
constraint.path?.segments ?? []
|
|
3231
|
+
);
|
|
3232
|
+
addConstraintBroadening(
|
|
3233
|
+
ctx,
|
|
3234
|
+
`Field "${displayFieldName}": ${describeConstraintTag(constraint)} (${String(constraint.value)}) is broader than earlier ${describeConstraintTag(previous)} (${String(previous.value)}). Constraints can only narrow.`,
|
|
3235
|
+
constraint.provenance,
|
|
3236
|
+
previous.provenance
|
|
3237
|
+
);
|
|
3238
|
+
continue;
|
|
3239
|
+
}
|
|
3240
|
+
if (strength <= 0) {
|
|
3241
|
+
continue;
|
|
3242
|
+
}
|
|
3243
|
+
strongestByKey.set(key, constraint);
|
|
3244
|
+
}
|
|
3245
|
+
}
|
|
3246
|
+
function compareCustomConstraintStrength(current, previous) {
|
|
3247
|
+
const order = current.comparePayloads(current.constraint.payload, previous.constraint.payload);
|
|
3248
|
+
const equalPayloadTiebreaker = order === 0 ? compareSemanticInclusivity(current.role.inclusive, previous.role.inclusive) : order;
|
|
3249
|
+
switch (current.role.bound) {
|
|
3250
|
+
case "lower":
|
|
3251
|
+
return equalPayloadTiebreaker;
|
|
3252
|
+
case "upper":
|
|
3253
|
+
return equalPayloadTiebreaker === 0 ? 0 : -equalPayloadTiebreaker;
|
|
3254
|
+
case "exact":
|
|
3255
|
+
return order === 0 ? 0 : Number.NaN;
|
|
3256
|
+
default: {
|
|
3257
|
+
const _exhaustive = current.role.bound;
|
|
3258
|
+
return _exhaustive;
|
|
3259
|
+
}
|
|
3260
|
+
}
|
|
3261
|
+
}
|
|
3262
|
+
function compareSemanticInclusivity(currentInclusive, previousInclusive) {
|
|
3263
|
+
if (currentInclusive === previousInclusive) {
|
|
3264
|
+
return 0;
|
|
3265
|
+
}
|
|
3266
|
+
return currentInclusive ? -1 : 1;
|
|
3267
|
+
}
|
|
3268
|
+
function customConstraintsContradict(lower, upper) {
|
|
3269
|
+
const order = lower.comparePayloads(lower.constraint.payload, upper.constraint.payload);
|
|
3270
|
+
if (order > 0) {
|
|
3271
|
+
return true;
|
|
3272
|
+
}
|
|
3273
|
+
if (order < 0) {
|
|
3274
|
+
return false;
|
|
3275
|
+
}
|
|
3276
|
+
return !lower.role.inclusive || !upper.role.inclusive;
|
|
3277
|
+
}
|
|
3278
|
+
function describeCustomConstraintTag(constraint) {
|
|
3279
|
+
return constraint.provenance.tagName ?? constraint.constraintId;
|
|
3280
|
+
}
|
|
3281
|
+
function checkCustomConstraintSemantics(ctx, fieldName, constraints) {
|
|
3282
|
+
if (ctx.extensionRegistry === void 0) {
|
|
3283
|
+
return;
|
|
3284
|
+
}
|
|
3285
|
+
const strongestByKey = /* @__PURE__ */ new Map();
|
|
3286
|
+
const lowerByFamily = /* @__PURE__ */ new Map();
|
|
3287
|
+
const upperByFamily = /* @__PURE__ */ new Map();
|
|
3288
|
+
for (const constraint of constraints) {
|
|
3289
|
+
if (constraint.constraintKind !== "custom") {
|
|
3290
|
+
continue;
|
|
3291
|
+
}
|
|
3292
|
+
const registration = ctx.extensionRegistry.findConstraint(constraint.constraintId);
|
|
3293
|
+
if (registration?.comparePayloads === void 0 || registration.semanticRole === void 0) {
|
|
3294
|
+
continue;
|
|
3295
|
+
}
|
|
3296
|
+
const entry = {
|
|
3297
|
+
constraint,
|
|
3298
|
+
comparePayloads: registration.comparePayloads,
|
|
3299
|
+
role: registration.semanticRole
|
|
3300
|
+
};
|
|
3301
|
+
const familyKey = `${registration.semanticRole.family}:${pathKey(constraint)}`;
|
|
3302
|
+
const boundKey = `${familyKey}:${registration.semanticRole.bound}`;
|
|
3303
|
+
const previous = strongestByKey.get(boundKey);
|
|
3304
|
+
if (previous !== void 0) {
|
|
3305
|
+
const strength = compareCustomConstraintStrength(entry, previous);
|
|
3306
|
+
if (Number.isNaN(strength)) {
|
|
3307
|
+
addContradiction(
|
|
3308
|
+
ctx,
|
|
3309
|
+
`Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} conflicts with ${describeCustomConstraintTag(previous.constraint)}`,
|
|
3310
|
+
constraint.provenance,
|
|
3311
|
+
previous.constraint.provenance
|
|
3312
|
+
);
|
|
3313
|
+
continue;
|
|
3314
|
+
}
|
|
3315
|
+
if (strength < 0) {
|
|
3316
|
+
addConstraintBroadening(
|
|
3317
|
+
ctx,
|
|
3318
|
+
`Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} is broader than earlier ${describeCustomConstraintTag(previous.constraint)}. Constraints can only narrow.`,
|
|
3319
|
+
constraint.provenance,
|
|
3320
|
+
previous.constraint.provenance
|
|
3321
|
+
);
|
|
3322
|
+
continue;
|
|
3323
|
+
}
|
|
3324
|
+
if (strength > 0) {
|
|
3325
|
+
strongestByKey.set(boundKey, entry);
|
|
3326
|
+
}
|
|
3327
|
+
} else {
|
|
3328
|
+
strongestByKey.set(boundKey, entry);
|
|
3329
|
+
}
|
|
3330
|
+
if (registration.semanticRole.bound === "lower") {
|
|
3331
|
+
lowerByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
|
|
3332
|
+
} else if (registration.semanticRole.bound === "upper") {
|
|
3333
|
+
upperByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
|
|
3334
|
+
}
|
|
3335
|
+
}
|
|
3336
|
+
for (const [familyKey, lower] of lowerByFamily) {
|
|
3337
|
+
const upper = upperByFamily.get(familyKey);
|
|
3338
|
+
if (upper === void 0) {
|
|
3339
|
+
continue;
|
|
3340
|
+
}
|
|
3341
|
+
if (!customConstraintsContradict(lower, upper)) {
|
|
3342
|
+
continue;
|
|
3343
|
+
}
|
|
3344
|
+
addContradiction(
|
|
3345
|
+
ctx,
|
|
3346
|
+
`Field "${formatPathTargetFieldName(fieldName, lower.constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(lower.constraint)} contradicts ${describeCustomConstraintTag(upper.constraint)}`,
|
|
3347
|
+
lower.constraint.provenance,
|
|
3348
|
+
upper.constraint.provenance
|
|
3349
|
+
);
|
|
3350
|
+
}
|
|
3351
|
+
}
|
|
3352
|
+
function checkNumericContradictions(ctx, fieldName, constraints) {
|
|
3353
|
+
const min = findNumeric(constraints, "minimum");
|
|
3354
|
+
const max = findNumeric(constraints, "maximum");
|
|
3355
|
+
const exMin = findNumeric(constraints, "exclusiveMinimum");
|
|
3356
|
+
const exMax = findNumeric(constraints, "exclusiveMaximum");
|
|
3357
|
+
if (min !== void 0 && max !== void 0 && min.value > max.value) {
|
|
3358
|
+
addContradiction(
|
|
3359
|
+
ctx,
|
|
3360
|
+
`Field "${fieldName}": minimum (${String(min.value)}) is greater than maximum (${String(max.value)})`,
|
|
3361
|
+
min.provenance,
|
|
3362
|
+
max.provenance
|
|
3363
|
+
);
|
|
3364
|
+
}
|
|
3365
|
+
if (exMin !== void 0 && max !== void 0 && exMin.value >= max.value) {
|
|
3366
|
+
addContradiction(
|
|
3367
|
+
ctx,
|
|
3368
|
+
`Field "${fieldName}": exclusiveMinimum (${String(exMin.value)}) is greater than or equal to maximum (${String(max.value)})`,
|
|
3369
|
+
exMin.provenance,
|
|
3370
|
+
max.provenance
|
|
3371
|
+
);
|
|
3372
|
+
}
|
|
3373
|
+
if (min !== void 0 && exMax !== void 0 && min.value >= exMax.value) {
|
|
3374
|
+
addContradiction(
|
|
3375
|
+
ctx,
|
|
3376
|
+
`Field "${fieldName}": minimum (${String(min.value)}) is greater than or equal to exclusiveMaximum (${String(exMax.value)})`,
|
|
3377
|
+
min.provenance,
|
|
3378
|
+
exMax.provenance
|
|
3379
|
+
);
|
|
3380
|
+
}
|
|
3381
|
+
if (exMin !== void 0 && exMax !== void 0 && exMin.value >= exMax.value) {
|
|
3382
|
+
addContradiction(
|
|
3383
|
+
ctx,
|
|
3384
|
+
`Field "${fieldName}": exclusiveMinimum (${String(exMin.value)}) is greater than or equal to exclusiveMaximum (${String(exMax.value)})`,
|
|
3385
|
+
exMin.provenance,
|
|
3386
|
+
exMax.provenance
|
|
3387
|
+
);
|
|
3388
|
+
}
|
|
3389
|
+
}
|
|
3390
|
+
function checkLengthContradictions(ctx, fieldName, constraints) {
|
|
3391
|
+
const minLen = findLength(constraints, "minLength");
|
|
3392
|
+
const maxLen = findLength(constraints, "maxLength");
|
|
3393
|
+
if (minLen !== void 0 && maxLen !== void 0 && minLen.value > maxLen.value) {
|
|
3394
|
+
addContradiction(
|
|
3395
|
+
ctx,
|
|
3396
|
+
`Field "${fieldName}": minLength (${String(minLen.value)}) is greater than maxLength (${String(maxLen.value)})`,
|
|
3397
|
+
minLen.provenance,
|
|
3398
|
+
maxLen.provenance
|
|
3399
|
+
);
|
|
3400
|
+
}
|
|
3401
|
+
const minItems = findLength(constraints, "minItems");
|
|
3402
|
+
const maxItems = findLength(constraints, "maxItems");
|
|
3403
|
+
if (minItems !== void 0 && maxItems !== void 0 && minItems.value > maxItems.value) {
|
|
3404
|
+
addContradiction(
|
|
3405
|
+
ctx,
|
|
3406
|
+
`Field "${fieldName}": minItems (${String(minItems.value)}) is greater than maxItems (${String(maxItems.value)})`,
|
|
3407
|
+
minItems.provenance,
|
|
3408
|
+
maxItems.provenance
|
|
3409
|
+
);
|
|
3410
|
+
}
|
|
3411
|
+
}
|
|
3412
|
+
function checkAllowedMembersContradiction(ctx, fieldName, constraints) {
|
|
3413
|
+
const members = findAllowedMembers(constraints);
|
|
3414
|
+
if (members.length < 2) return;
|
|
3415
|
+
const firstSet = new Set(members[0]?.members ?? []);
|
|
3416
|
+
for (let i = 1; i < members.length; i++) {
|
|
3417
|
+
const current = members[i];
|
|
3418
|
+
if (current === void 0) continue;
|
|
3419
|
+
for (const m of firstSet) {
|
|
3420
|
+
if (!current.members.includes(m)) {
|
|
3421
|
+
firstSet.delete(m);
|
|
3422
|
+
}
|
|
3423
|
+
}
|
|
3424
|
+
}
|
|
3425
|
+
if (firstSet.size === 0) {
|
|
3426
|
+
const first = members[0];
|
|
3427
|
+
const second = members[1];
|
|
3428
|
+
if (first !== void 0 && second !== void 0) {
|
|
3429
|
+
addContradiction(
|
|
3430
|
+
ctx,
|
|
3431
|
+
`Field "${fieldName}": allowedMembers constraints have an empty intersection (no valid values remain)`,
|
|
3432
|
+
first.provenance,
|
|
3433
|
+
second.provenance
|
|
3434
|
+
);
|
|
3435
|
+
}
|
|
3436
|
+
}
|
|
3437
|
+
}
|
|
3438
|
+
function checkConstContradictions(ctx, fieldName, constraints) {
|
|
3439
|
+
const constConstraints = findConstConstraints(constraints);
|
|
3440
|
+
if (constConstraints.length < 2) return;
|
|
3441
|
+
const first = constConstraints[0];
|
|
3442
|
+
if (first === void 0) return;
|
|
3443
|
+
for (let i = 1; i < constConstraints.length; i++) {
|
|
3444
|
+
const current = constConstraints[i];
|
|
3445
|
+
if (current === void 0) continue;
|
|
3446
|
+
if (jsonValueEquals(first.value, current.value)) {
|
|
3447
|
+
continue;
|
|
3448
|
+
}
|
|
3449
|
+
addContradiction(
|
|
3450
|
+
ctx,
|
|
3451
|
+
`Field "${fieldName}": conflicting @const constraints require both ${JSON.stringify(first.value)} and ${JSON.stringify(current.value)}`,
|
|
3452
|
+
first.provenance,
|
|
3453
|
+
current.provenance
|
|
3454
|
+
);
|
|
3455
|
+
}
|
|
3456
|
+
}
|
|
3457
|
+
function typeLabel(type) {
|
|
3458
|
+
switch (type.kind) {
|
|
3459
|
+
case "primitive":
|
|
3460
|
+
return type.primitiveKind;
|
|
3461
|
+
case "enum":
|
|
3462
|
+
return "enum";
|
|
3463
|
+
case "array":
|
|
3464
|
+
return "array";
|
|
3465
|
+
case "object":
|
|
3466
|
+
return "object";
|
|
3467
|
+
case "record":
|
|
3468
|
+
return "record";
|
|
3469
|
+
case "union":
|
|
3470
|
+
return "union";
|
|
3471
|
+
case "reference":
|
|
3472
|
+
return `reference(${type.name})`;
|
|
3473
|
+
case "dynamic":
|
|
3474
|
+
return `dynamic(${type.dynamicKind})`;
|
|
3475
|
+
case "custom":
|
|
3476
|
+
return `custom(${type.typeId})`;
|
|
3477
|
+
default: {
|
|
3478
|
+
const _exhaustive = type;
|
|
3479
|
+
return String(_exhaustive);
|
|
3480
|
+
}
|
|
3481
|
+
}
|
|
3482
|
+
}
|
|
3483
|
+
function dereferenceType(ctx, type) {
|
|
3484
|
+
let current = type;
|
|
3485
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3486
|
+
while (current.kind === "reference") {
|
|
3487
|
+
if (seen.has(current.name)) {
|
|
3488
|
+
return current;
|
|
3489
|
+
}
|
|
3490
|
+
seen.add(current.name);
|
|
3491
|
+
const definition = ctx.typeRegistry[current.name];
|
|
3492
|
+
if (definition === void 0) {
|
|
3493
|
+
return current;
|
|
3494
|
+
}
|
|
3495
|
+
current = definition.type;
|
|
3496
|
+
}
|
|
3497
|
+
return current;
|
|
3498
|
+
}
|
|
3499
|
+
function collectReferencedTypeConstraints(ctx, type) {
|
|
3500
|
+
const collected = [];
|
|
3501
|
+
let current = type;
|
|
3502
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3503
|
+
while (current.kind === "reference") {
|
|
3504
|
+
if (seen.has(current.name)) {
|
|
3505
|
+
break;
|
|
3506
|
+
}
|
|
3507
|
+
seen.add(current.name);
|
|
3508
|
+
const definition = ctx.typeRegistry[current.name];
|
|
3509
|
+
if (definition === void 0) {
|
|
3510
|
+
break;
|
|
3511
|
+
}
|
|
3512
|
+
if (definition.constraints !== void 0) {
|
|
3513
|
+
collected.push(...definition.constraints);
|
|
3514
|
+
}
|
|
3515
|
+
current = definition.type;
|
|
3516
|
+
}
|
|
3517
|
+
return collected;
|
|
3518
|
+
}
|
|
3519
|
+
function resolvePathTargetType(ctx, type, segments) {
|
|
3520
|
+
const effectiveType = dereferenceType(ctx, type);
|
|
3521
|
+
if (segments.length === 0) {
|
|
3522
|
+
return { kind: "resolved", type: effectiveType };
|
|
3523
|
+
}
|
|
3524
|
+
if (effectiveType.kind === "array") {
|
|
3525
|
+
return resolvePathTargetType(ctx, effectiveType.items, segments);
|
|
3526
|
+
}
|
|
3527
|
+
if (effectiveType.kind === "object") {
|
|
3528
|
+
const [segment, ...rest] = segments;
|
|
3529
|
+
if (segment === void 0) {
|
|
3530
|
+
throw new Error("Invariant violation: object path traversal requires a segment");
|
|
3531
|
+
}
|
|
3532
|
+
const property = effectiveType.properties.find((prop) => prop.name === segment);
|
|
3533
|
+
if (property === void 0) {
|
|
3534
|
+
return { kind: "missing-property", segment };
|
|
3535
|
+
}
|
|
3536
|
+
return resolvePathTargetType(ctx, property.type, rest);
|
|
3537
|
+
}
|
|
3538
|
+
return { kind: "unresolvable", type: effectiveType };
|
|
3539
|
+
}
|
|
3540
|
+
function isNullType(type) {
|
|
3541
|
+
return type.kind === "primitive" && type.primitiveKind === "null";
|
|
3542
|
+
}
|
|
3543
|
+
function collectCustomConstraintCandidateTypes(ctx, type) {
|
|
3544
|
+
const effectiveType = dereferenceType(ctx, type);
|
|
3545
|
+
const candidates = [effectiveType];
|
|
3546
|
+
if (effectiveType.kind === "array") {
|
|
3547
|
+
candidates.push(...collectCustomConstraintCandidateTypes(ctx, effectiveType.items));
|
|
3548
|
+
}
|
|
3549
|
+
if (effectiveType.kind === "union") {
|
|
3550
|
+
const memberTypes = effectiveType.members.map((member) => dereferenceType(ctx, member));
|
|
3551
|
+
const nonNullMembers = memberTypes.filter((member) => !isNullType(member));
|
|
3552
|
+
if (nonNullMembers.length === 1 && nonNullMembers.length < memberTypes.length) {
|
|
3553
|
+
const [nullableMember] = nonNullMembers;
|
|
3554
|
+
if (nullableMember !== void 0) {
|
|
3555
|
+
candidates.push(...collectCustomConstraintCandidateTypes(ctx, nullableMember));
|
|
3556
|
+
}
|
|
3557
|
+
}
|
|
3558
|
+
}
|
|
3559
|
+
return candidates;
|
|
3560
|
+
}
|
|
3561
|
+
function formatPathTargetFieldName(fieldName, path4) {
|
|
3562
|
+
return path4.length === 0 ? fieldName : `${fieldName}.${path4.join(".")}`;
|
|
3563
|
+
}
|
|
3564
|
+
function checkConstraintOnType(ctx, fieldName, type, constraint) {
|
|
3565
|
+
const effectiveType = dereferenceType(ctx, type);
|
|
3566
|
+
const isNumber = effectiveType.kind === "primitive" && ["number", "integer", "bigint"].includes(effectiveType.primitiveKind);
|
|
3567
|
+
const isString = effectiveType.kind === "primitive" && effectiveType.primitiveKind === "string";
|
|
3568
|
+
const isArray = effectiveType.kind === "array";
|
|
3569
|
+
const isEnum = effectiveType.kind === "enum";
|
|
3570
|
+
const arrayItemType = effectiveType.kind === "array" ? dereferenceType(ctx, effectiveType.items) : void 0;
|
|
3571
|
+
const isStringArray = arrayItemType?.kind === "primitive" && arrayItemType.primitiveKind === "string";
|
|
3572
|
+
const label = typeLabel(effectiveType);
|
|
3573
|
+
const ck = constraint.constraintKind;
|
|
3574
|
+
switch (ck) {
|
|
3575
|
+
case "minimum":
|
|
3576
|
+
case "maximum":
|
|
3577
|
+
case "exclusiveMinimum":
|
|
3578
|
+
case "exclusiveMaximum":
|
|
3579
|
+
case "multipleOf": {
|
|
3580
|
+
if (!isNumber) {
|
|
3581
|
+
addTypeMismatch(
|
|
3582
|
+
ctx,
|
|
3583
|
+
`Field "${fieldName}": constraint "${ck}" is only valid on number fields, but field type is "${label}"`,
|
|
3584
|
+
constraint.provenance
|
|
3585
|
+
);
|
|
3586
|
+
}
|
|
3587
|
+
break;
|
|
3588
|
+
}
|
|
3589
|
+
case "minLength":
|
|
3590
|
+
case "maxLength":
|
|
3591
|
+
case "pattern": {
|
|
3592
|
+
if (!isString && !isStringArray) {
|
|
3593
|
+
addTypeMismatch(
|
|
3594
|
+
ctx,
|
|
3595
|
+
`Field "${fieldName}": constraint "${ck}" is only valid on string fields or string array items, but field type is "${label}"`,
|
|
3596
|
+
constraint.provenance
|
|
3597
|
+
);
|
|
3598
|
+
}
|
|
3599
|
+
break;
|
|
3600
|
+
}
|
|
3601
|
+
case "minItems":
|
|
3602
|
+
case "maxItems":
|
|
3603
|
+
case "uniqueItems": {
|
|
3604
|
+
if (!isArray) {
|
|
3605
|
+
addTypeMismatch(
|
|
3606
|
+
ctx,
|
|
3607
|
+
`Field "${fieldName}": constraint "${ck}" is only valid on array fields, but field type is "${label}"`,
|
|
3608
|
+
constraint.provenance
|
|
3609
|
+
);
|
|
3610
|
+
}
|
|
3611
|
+
break;
|
|
3612
|
+
}
|
|
3613
|
+
case "allowedMembers": {
|
|
3614
|
+
if (!isEnum) {
|
|
3615
|
+
addTypeMismatch(
|
|
3616
|
+
ctx,
|
|
3617
|
+
`Field "${fieldName}": constraint "allowedMembers" is only valid on enum fields, but field type is "${label}"`,
|
|
3618
|
+
constraint.provenance
|
|
3619
|
+
);
|
|
3620
|
+
}
|
|
3621
|
+
break;
|
|
3622
|
+
}
|
|
3623
|
+
case "const": {
|
|
3624
|
+
const isPrimitiveConstType = effectiveType.kind === "primitive" && ["string", "number", "integer", "bigint", "boolean", "null"].includes(
|
|
3625
|
+
effectiveType.primitiveKind
|
|
3626
|
+
) || effectiveType.kind === "enum";
|
|
3627
|
+
if (!isPrimitiveConstType) {
|
|
3628
|
+
addTypeMismatch(
|
|
3629
|
+
ctx,
|
|
3630
|
+
`Field "${fieldName}": constraint "const" is only valid on primitive or enum fields, but field type is "${label}"`,
|
|
3631
|
+
constraint.provenance
|
|
3632
|
+
);
|
|
3633
|
+
break;
|
|
3634
|
+
}
|
|
3635
|
+
if (effectiveType.kind === "primitive") {
|
|
3636
|
+
const valueType = constraint.value === null ? "null" : Array.isArray(constraint.value) ? "array" : typeof constraint.value;
|
|
3637
|
+
const expectedValueType = effectiveType.primitiveKind === "integer" || effectiveType.primitiveKind === "bigint" ? "number" : effectiveType.primitiveKind;
|
|
3638
|
+
if (valueType !== expectedValueType) {
|
|
3639
|
+
addTypeMismatch(
|
|
3640
|
+
ctx,
|
|
3641
|
+
`Field "${fieldName}": @const value type "${valueType}" is incompatible with field type "${effectiveType.primitiveKind}"`,
|
|
3642
|
+
constraint.provenance
|
|
3643
|
+
);
|
|
3644
|
+
}
|
|
3645
|
+
break;
|
|
3646
|
+
}
|
|
3647
|
+
const memberValues = effectiveType.members.map((member) => member.value);
|
|
3648
|
+
if (!memberValues.some((member) => jsonValueEquals(member, constraint.value))) {
|
|
3649
|
+
addTypeMismatch(
|
|
3650
|
+
ctx,
|
|
3651
|
+
`Field "${fieldName}": @const value ${JSON.stringify(constraint.value)} is not one of the enum members`,
|
|
3652
|
+
constraint.provenance
|
|
3653
|
+
);
|
|
3654
|
+
}
|
|
3655
|
+
break;
|
|
3656
|
+
}
|
|
3657
|
+
case "custom": {
|
|
3658
|
+
checkCustomConstraint(ctx, fieldName, effectiveType, constraint);
|
|
3659
|
+
break;
|
|
3660
|
+
}
|
|
3661
|
+
default: {
|
|
3662
|
+
const _exhaustive = constraint;
|
|
3663
|
+
throw new Error(
|
|
3664
|
+
`Unhandled constraint kind: ${_exhaustive.constraintKind}`
|
|
3665
|
+
);
|
|
3666
|
+
}
|
|
3667
|
+
}
|
|
3668
|
+
}
|
|
3669
|
+
function checkTypeApplicability(ctx, fieldName, type, constraints) {
|
|
3670
|
+
for (const constraint of constraints) {
|
|
3671
|
+
if (constraint.path) {
|
|
3672
|
+
const resolution = resolvePathTargetType(ctx, type, constraint.path.segments);
|
|
3673
|
+
const targetFieldName = formatPathTargetFieldName(fieldName, constraint.path.segments);
|
|
3674
|
+
if (resolution.kind === "missing-property") {
|
|
3675
|
+
addUnknownPathTarget(
|
|
3676
|
+
ctx,
|
|
3677
|
+
`Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" references unknown path segment "${resolution.segment}"`,
|
|
3678
|
+
constraint.provenance
|
|
3679
|
+
);
|
|
3680
|
+
continue;
|
|
3681
|
+
}
|
|
3682
|
+
if (resolution.kind === "unresolvable") {
|
|
3683
|
+
addTypeMismatch(
|
|
3684
|
+
ctx,
|
|
3685
|
+
`Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" is invalid because type "${typeLabel(resolution.type)}" cannot be traversed`,
|
|
3686
|
+
constraint.provenance
|
|
3687
|
+
);
|
|
3688
|
+
continue;
|
|
3689
|
+
}
|
|
3690
|
+
checkConstraintOnType(ctx, targetFieldName, resolution.type, constraint);
|
|
3691
|
+
continue;
|
|
3692
|
+
}
|
|
3693
|
+
checkConstraintOnType(ctx, fieldName, type, constraint);
|
|
3694
|
+
}
|
|
3695
|
+
}
|
|
3696
|
+
function checkCustomConstraint(ctx, fieldName, type, constraint) {
|
|
3697
|
+
if (ctx.extensionRegistry === void 0) return;
|
|
3698
|
+
const registration = ctx.extensionRegistry.findConstraint(constraint.constraintId);
|
|
3699
|
+
if (registration === void 0) {
|
|
3700
|
+
addUnknownExtension(
|
|
3701
|
+
ctx,
|
|
3702
|
+
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not registered in the extension registry`,
|
|
3703
|
+
constraint.provenance
|
|
3704
|
+
);
|
|
3705
|
+
return;
|
|
3706
|
+
}
|
|
3707
|
+
const candidateTypes = collectCustomConstraintCandidateTypes(ctx, type);
|
|
3708
|
+
const normalizedTagName = constraint.provenance.tagName === void 0 ? void 0 : (0, import_core4.normalizeConstraintTagName)(constraint.provenance.tagName.replace(/^@/, ""));
|
|
3709
|
+
if (normalizedTagName !== void 0) {
|
|
3710
|
+
const tagRegistration = ctx.extensionRegistry.findConstraintTag(normalizedTagName);
|
|
3711
|
+
const extensionId = getExtensionIdFromConstraintId(constraint.constraintId);
|
|
3712
|
+
if (extensionId !== null && tagRegistration?.extensionId === extensionId && tagRegistration.registration.constraintName === registration.constraintName && !candidateTypes.some(
|
|
3713
|
+
(candidateType) => tagRegistration.registration.isApplicableToType?.(candidateType) !== false
|
|
3714
|
+
)) {
|
|
3715
|
+
addTypeMismatch(
|
|
3716
|
+
ctx,
|
|
3717
|
+
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
3718
|
+
constraint.provenance
|
|
3719
|
+
);
|
|
3720
|
+
return;
|
|
3721
|
+
}
|
|
3722
|
+
}
|
|
3723
|
+
if (registration.applicableTypes === null) {
|
|
3724
|
+
if (!candidateTypes.some((candidateType) => registration.isApplicableToType?.(candidateType) !== false)) {
|
|
3725
|
+
addTypeMismatch(
|
|
3726
|
+
ctx,
|
|
3727
|
+
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
3728
|
+
constraint.provenance
|
|
3729
|
+
);
|
|
3730
|
+
}
|
|
3731
|
+
return;
|
|
3732
|
+
}
|
|
3733
|
+
const applicableTypes = registration.applicableTypes;
|
|
3734
|
+
const matchesApplicableType = candidateTypes.some(
|
|
3735
|
+
(candidateType) => applicableTypes.includes(candidateType.kind) && registration.isApplicableToType?.(candidateType) !== false
|
|
3736
|
+
);
|
|
3737
|
+
if (!matchesApplicableType) {
|
|
3738
|
+
addTypeMismatch(
|
|
3739
|
+
ctx,
|
|
3740
|
+
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
3741
|
+
constraint.provenance
|
|
3742
|
+
);
|
|
3743
|
+
}
|
|
3744
|
+
}
|
|
3745
|
+
function validateFieldNode(ctx, field) {
|
|
3746
|
+
validateConstraints(ctx, field.name, field.type, [
|
|
3747
|
+
...collectReferencedTypeConstraints(ctx, field.type),
|
|
3748
|
+
...field.constraints
|
|
3749
|
+
]);
|
|
3750
|
+
if (field.type.kind === "object") {
|
|
3751
|
+
for (const prop of field.type.properties) {
|
|
3752
|
+
validateObjectProperty(ctx, field.name, prop);
|
|
3753
|
+
}
|
|
3754
|
+
}
|
|
3755
|
+
}
|
|
3756
|
+
function validateObjectProperty(ctx, parentName, prop) {
|
|
3757
|
+
const qualifiedName = `${parentName}.${prop.name}`;
|
|
3758
|
+
validateConstraints(ctx, qualifiedName, prop.type, [
|
|
3759
|
+
...collectReferencedTypeConstraints(ctx, prop.type),
|
|
3760
|
+
...prop.constraints
|
|
3761
|
+
]);
|
|
3762
|
+
if (prop.type.kind === "object") {
|
|
3763
|
+
for (const nestedProp of prop.type.properties) {
|
|
3764
|
+
validateObjectProperty(ctx, qualifiedName, nestedProp);
|
|
3765
|
+
}
|
|
3766
|
+
}
|
|
3767
|
+
}
|
|
3768
|
+
function validateConstraints(ctx, name, type, constraints) {
|
|
3769
|
+
checkNumericContradictions(ctx, name, constraints);
|
|
3770
|
+
checkLengthContradictions(ctx, name, constraints);
|
|
3771
|
+
checkAllowedMembersContradiction(ctx, name, constraints);
|
|
3772
|
+
checkConstContradictions(ctx, name, constraints);
|
|
3773
|
+
checkConstraintBroadening(ctx, name, constraints);
|
|
3774
|
+
checkCustomConstraintSemantics(ctx, name, constraints);
|
|
3775
|
+
checkTypeApplicability(ctx, name, type, constraints);
|
|
3776
|
+
}
|
|
3777
|
+
function validateElement(ctx, element) {
|
|
3778
|
+
switch (element.kind) {
|
|
3779
|
+
case "field":
|
|
3780
|
+
validateFieldNode(ctx, element);
|
|
3781
|
+
break;
|
|
3782
|
+
case "group":
|
|
3783
|
+
for (const child of element.elements) {
|
|
3784
|
+
validateElement(ctx, child);
|
|
3785
|
+
}
|
|
3786
|
+
break;
|
|
3787
|
+
case "conditional":
|
|
3788
|
+
for (const child of element.elements) {
|
|
3789
|
+
validateElement(ctx, child);
|
|
3790
|
+
}
|
|
3791
|
+
break;
|
|
3792
|
+
default: {
|
|
3793
|
+
const _exhaustive = element;
|
|
3794
|
+
throw new Error(`Unhandled element kind: ${_exhaustive.kind}`);
|
|
3795
|
+
}
|
|
3796
|
+
}
|
|
3797
|
+
}
|
|
3798
|
+
function validateIR(ir, options) {
|
|
3799
|
+
const ctx = {
|
|
3800
|
+
diagnostics: [],
|
|
3801
|
+
extensionRegistry: options?.extensionRegistry,
|
|
3802
|
+
typeRegistry: ir.typeRegistry
|
|
3803
|
+
};
|
|
3804
|
+
for (const element of ir.elements) {
|
|
3805
|
+
validateElement(ctx, element);
|
|
3806
|
+
}
|
|
3807
|
+
return {
|
|
3808
|
+
diagnostics: ctx.diagnostics,
|
|
3809
|
+
valid: ctx.diagnostics.every((d) => d.severity !== "error")
|
|
3810
|
+
};
|
|
3811
|
+
}
|
|
3812
|
+
var import_core4;
|
|
3813
|
+
var init_constraint_validator = __esm({
|
|
3814
|
+
"src/validate/constraint-validator.ts"() {
|
|
3815
|
+
"use strict";
|
|
3816
|
+
import_core4 = require("@formspec/core");
|
|
3817
|
+
}
|
|
3818
|
+
});
|
|
3819
|
+
|
|
3820
|
+
// src/validate/index.ts
|
|
3821
|
+
var init_validate = __esm({
|
|
3822
|
+
"src/validate/index.ts"() {
|
|
3823
|
+
"use strict";
|
|
3824
|
+
init_constraint_validator();
|
|
3825
|
+
}
|
|
3826
|
+
});
|
|
3827
|
+
|
|
2463
3828
|
// src/generators/class-schema.ts
|
|
2464
|
-
function generateClassSchemas(analysis, source) {
|
|
3829
|
+
function generateClassSchemas(analysis, source, options) {
|
|
2465
3830
|
const ir = canonicalizeTSDoc(analysis, source);
|
|
3831
|
+
const validationResult = validateIR(ir, {
|
|
3832
|
+
...options?.extensionRegistry !== void 0 && {
|
|
3833
|
+
extensionRegistry: options.extensionRegistry
|
|
3834
|
+
},
|
|
3835
|
+
...options?.vendorPrefix !== void 0 && { vendorPrefix: options.vendorPrefix }
|
|
3836
|
+
});
|
|
3837
|
+
if (!validationResult.valid) {
|
|
3838
|
+
throw new Error(formatValidationError(validationResult.diagnostics));
|
|
3839
|
+
}
|
|
2466
3840
|
return {
|
|
2467
|
-
jsonSchema: generateJsonSchemaFromIR(ir),
|
|
3841
|
+
jsonSchema: generateJsonSchemaFromIR(ir, options),
|
|
2468
3842
|
uiSchema: generateUiSchemaFromIR(ir)
|
|
2469
3843
|
};
|
|
2470
3844
|
}
|
|
3845
|
+
function formatValidationError(diagnostics) {
|
|
3846
|
+
const lines = diagnostics.map((diagnostic) => {
|
|
3847
|
+
const primary = formatLocation(diagnostic.primaryLocation);
|
|
3848
|
+
const related = diagnostic.relatedLocations.length > 0 ? ` [related: ${diagnostic.relatedLocations.map(formatLocation).join(", ")}]` : "";
|
|
3849
|
+
return `${diagnostic.code}: ${diagnostic.message} (${primary})${related}`;
|
|
3850
|
+
});
|
|
3851
|
+
return `FormSpec validation failed:
|
|
3852
|
+
${lines.map((line) => `- ${line}`).join("\n")}`;
|
|
3853
|
+
}
|
|
3854
|
+
function formatLocation(location) {
|
|
3855
|
+
return `${location.file}:${String(location.line)}:${String(location.column)}`;
|
|
3856
|
+
}
|
|
2471
3857
|
function generateSchemasFromClass(options) {
|
|
2472
3858
|
const ctx = createProgramContext(options.filePath);
|
|
2473
3859
|
const classDecl = findClassByName(ctx.sourceFile, options.className);
|
|
2474
3860
|
if (!classDecl) {
|
|
2475
3861
|
throw new Error(`Class "${options.className}" not found in ${options.filePath}`);
|
|
2476
3862
|
}
|
|
2477
|
-
const analysis = analyzeClassToIR(
|
|
2478
|
-
|
|
3863
|
+
const analysis = analyzeClassToIR(
|
|
3864
|
+
classDecl,
|
|
3865
|
+
ctx.checker,
|
|
3866
|
+
options.filePath,
|
|
3867
|
+
options.extensionRegistry
|
|
3868
|
+
);
|
|
3869
|
+
return generateClassSchemas(
|
|
3870
|
+
analysis,
|
|
3871
|
+
{ file: options.filePath },
|
|
3872
|
+
{
|
|
3873
|
+
extensionRegistry: options.extensionRegistry,
|
|
3874
|
+
vendorPrefix: options.vendorPrefix
|
|
3875
|
+
}
|
|
3876
|
+
);
|
|
2479
3877
|
}
|
|
2480
3878
|
function generateSchemas(options) {
|
|
2481
|
-
const
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
const analysis = analyzeClassToIR(classDecl, ctx.checker, options.filePath);
|
|
2486
|
-
return generateClassSchemas(analysis, source);
|
|
2487
|
-
}
|
|
2488
|
-
const interfaceDecl = findInterfaceByName(ctx.sourceFile, options.typeName);
|
|
2489
|
-
if (interfaceDecl) {
|
|
2490
|
-
const analysis = analyzeInterfaceToIR(interfaceDecl, ctx.checker, options.filePath);
|
|
2491
|
-
return generateClassSchemas(analysis, source);
|
|
2492
|
-
}
|
|
2493
|
-
const typeAlias = findTypeAliasByName(ctx.sourceFile, options.typeName);
|
|
2494
|
-
if (typeAlias) {
|
|
2495
|
-
const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, options.filePath);
|
|
2496
|
-
if (result.ok) {
|
|
2497
|
-
return generateClassSchemas(result.analysis, source);
|
|
2498
|
-
}
|
|
2499
|
-
throw new Error(result.error);
|
|
2500
|
-
}
|
|
2501
|
-
throw new Error(
|
|
2502
|
-
`Type "${options.typeName}" not found as a class, interface, or type alias in ${options.filePath}`
|
|
3879
|
+
const analysis = analyzeNamedTypeToIR(
|
|
3880
|
+
options.filePath,
|
|
3881
|
+
options.typeName,
|
|
3882
|
+
options.extensionRegistry
|
|
2503
3883
|
);
|
|
3884
|
+
return generateClassSchemas(analysis, { file: options.filePath }, options);
|
|
2504
3885
|
}
|
|
2505
3886
|
var init_class_schema = __esm({
|
|
2506
3887
|
"src/generators/class-schema.ts"() {
|
|
@@ -2510,13 +3891,14 @@ var init_class_schema = __esm({
|
|
|
2510
3891
|
init_canonicalize();
|
|
2511
3892
|
init_ir_generator();
|
|
2512
3893
|
init_ir_generator2();
|
|
3894
|
+
init_validate();
|
|
2513
3895
|
}
|
|
2514
3896
|
});
|
|
2515
3897
|
|
|
2516
3898
|
// src/generators/mixed-authoring.ts
|
|
2517
3899
|
function buildMixedAuthoringSchemas(options) {
|
|
2518
3900
|
const { filePath, typeName, overlays, ...schemaOptions } = options;
|
|
2519
|
-
const analysis =
|
|
3901
|
+
const analysis = analyzeNamedTypeToIR(filePath, typeName, schemaOptions.extensionRegistry);
|
|
2520
3902
|
const composedAnalysis = composeAnalysisWithOverlays(analysis, overlays);
|
|
2521
3903
|
const ir = canonicalizeTSDoc(composedAnalysis, { file: filePath });
|
|
2522
3904
|
return {
|
|
@@ -2524,29 +3906,6 @@ function buildMixedAuthoringSchemas(options) {
|
|
|
2524
3906
|
uiSchema: generateUiSchemaFromIR(ir)
|
|
2525
3907
|
};
|
|
2526
3908
|
}
|
|
2527
|
-
function analyzeNamedType(filePath, typeName) {
|
|
2528
|
-
const ctx = createProgramContext(filePath);
|
|
2529
|
-
const source = { file: filePath };
|
|
2530
|
-
const classDecl = findClassByName(ctx.sourceFile, typeName);
|
|
2531
|
-
if (classDecl !== null) {
|
|
2532
|
-
return analyzeClassToIR(classDecl, ctx.checker, source.file);
|
|
2533
|
-
}
|
|
2534
|
-
const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
|
|
2535
|
-
if (interfaceDecl !== null) {
|
|
2536
|
-
return analyzeInterfaceToIR(interfaceDecl, ctx.checker, source.file);
|
|
2537
|
-
}
|
|
2538
|
-
const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
|
|
2539
|
-
if (typeAlias !== null) {
|
|
2540
|
-
const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, source.file);
|
|
2541
|
-
if (result.ok) {
|
|
2542
|
-
return result.analysis;
|
|
2543
|
-
}
|
|
2544
|
-
throw new Error(result.error);
|
|
2545
|
-
}
|
|
2546
|
-
throw new Error(
|
|
2547
|
-
`Type "${typeName}" not found as a class, interface, or type alias in ${filePath}`
|
|
2548
|
-
);
|
|
2549
|
-
}
|
|
2550
3909
|
function composeAnalysisWithOverlays(analysis, overlays) {
|
|
2551
3910
|
const overlayIR = canonicalizeChainDSL(overlays);
|
|
2552
3911
|
const overlayFields = collectOverlayFields(overlayIR.elements);
|
|
@@ -2616,7 +3975,7 @@ function assertSupportedOverlayField(baseField, overlayField) {
|
|
|
2616
3975
|
`Mixed-authoring overlay for "${baseField.name}" cannot define constraints; keep constraints on the static model`
|
|
2617
3976
|
);
|
|
2618
3977
|
}
|
|
2619
|
-
if (overlayField.required) {
|
|
3978
|
+
if (overlayField.required && !baseField.required) {
|
|
2620
3979
|
throw new Error(
|
|
2621
3980
|
`Mixed-authoring overlay for "${baseField.name}" cannot change requiredness; keep requiredness on the static model`
|
|
2622
3981
|
);
|
|
@@ -2706,7 +4065,7 @@ function mergeAnnotations(baseAnnotations, overlayAnnotations) {
|
|
|
2706
4065
|
const overlayOnly = overlayAnnotations.filter(
|
|
2707
4066
|
(annotation) => !baseKeys.has(annotationKey(annotation))
|
|
2708
4067
|
);
|
|
2709
|
-
return [...
|
|
4068
|
+
return [...baseAnnotations, ...overlayOnly];
|
|
2710
4069
|
}
|
|
2711
4070
|
function annotationKey(annotation) {
|
|
2712
4071
|
return annotation.annotationKind === "custom" ? `${annotation.annotationKind}:${annotation.annotationId}` : annotation.annotationKind;
|
|
@@ -2718,7 +4077,6 @@ var init_mixed_authoring = __esm({
|
|
|
2718
4077
|
init_ir_generator2();
|
|
2719
4078
|
init_canonicalize();
|
|
2720
4079
|
init_program();
|
|
2721
|
-
init_class_analyzer();
|
|
2722
4080
|
}
|
|
2723
4081
|
});
|
|
2724
4082
|
|