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