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