@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.js
CHANGED
|
@@ -20,6 +20,7 @@ function canonicalizeChainDSL(form) {
|
|
|
20
20
|
kind: "form-ir",
|
|
21
21
|
irVersion: IR_VERSION,
|
|
22
22
|
elements: canonicalizeElements(form.elements),
|
|
23
|
+
rootAnnotations: [],
|
|
23
24
|
typeRegistry: {},
|
|
24
25
|
provenance: CHAIN_DSL_PROVENANCE
|
|
25
26
|
};
|
|
@@ -325,6 +326,7 @@ function canonicalizeTSDoc(analysis, source) {
|
|
|
325
326
|
irVersion: IR_VERSION2,
|
|
326
327
|
elements,
|
|
327
328
|
typeRegistry: analysis.typeRegistry,
|
|
329
|
+
...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { rootAnnotations: analysis.annotations },
|
|
328
330
|
...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { annotations: analysis.annotations },
|
|
329
331
|
provenance
|
|
330
332
|
};
|
|
@@ -402,6 +404,9 @@ function generateJsonSchemaFromIR(ir, options) {
|
|
|
402
404
|
const ctx = makeContext(options);
|
|
403
405
|
for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
|
|
404
406
|
ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
|
|
407
|
+
if (typeDef.constraints && typeDef.constraints.length > 0) {
|
|
408
|
+
applyConstraints(ctx.defs[name], typeDef.constraints, ctx);
|
|
409
|
+
}
|
|
405
410
|
if (typeDef.annotations && typeDef.annotations.length > 0) {
|
|
406
411
|
applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
|
|
407
412
|
}
|
|
@@ -570,7 +575,9 @@ function generateTypeNode(type, ctx) {
|
|
|
570
575
|
}
|
|
571
576
|
}
|
|
572
577
|
function generatePrimitiveType(type) {
|
|
573
|
-
return {
|
|
578
|
+
return {
|
|
579
|
+
type: type.primitiveKind === "integer" || type.primitiveKind === "bigint" ? "integer" : type.primitiveKind
|
|
580
|
+
};
|
|
574
581
|
}
|
|
575
582
|
function generateEnumType(type) {
|
|
576
583
|
const hasDisplayNames = type.members.some((m) => m.displayName !== void 0);
|
|
@@ -743,7 +750,7 @@ function applyAnnotations(schema, annotations, ctx) {
|
|
|
743
750
|
case "deprecated":
|
|
744
751
|
schema.deprecated = true;
|
|
745
752
|
if (annotation.message !== void 0 && annotation.message !== "") {
|
|
746
|
-
schema[
|
|
753
|
+
schema[`${ctx.vendorPrefix}-deprecation-description`] = annotation.message;
|
|
747
754
|
}
|
|
748
755
|
break;
|
|
749
756
|
case "placeholder":
|
|
@@ -776,7 +783,12 @@ function applyCustomConstraint(schema, constraint, ctx) {
|
|
|
776
783
|
`Cannot generate JSON Schema for custom constraint "${constraint.constraintId}" without a matching extension registration`
|
|
777
784
|
);
|
|
778
785
|
}
|
|
779
|
-
|
|
786
|
+
assignVendorPrefixedExtensionKeywords(
|
|
787
|
+
schema,
|
|
788
|
+
registration.toJsonSchema(constraint.payload, ctx.vendorPrefix),
|
|
789
|
+
ctx.vendorPrefix,
|
|
790
|
+
`custom constraint "${constraint.constraintId}"`
|
|
791
|
+
);
|
|
780
792
|
}
|
|
781
793
|
function applyCustomAnnotation(schema, annotation, ctx) {
|
|
782
794
|
const registration = ctx.extensionRegistry?.findAnnotation(annotation.annotationId);
|
|
@@ -788,7 +800,22 @@ function applyCustomAnnotation(schema, annotation, ctx) {
|
|
|
788
800
|
if (registration.toJsonSchema === void 0) {
|
|
789
801
|
return;
|
|
790
802
|
}
|
|
791
|
-
|
|
803
|
+
assignVendorPrefixedExtensionKeywords(
|
|
804
|
+
schema,
|
|
805
|
+
registration.toJsonSchema(annotation.value, ctx.vendorPrefix),
|
|
806
|
+
ctx.vendorPrefix,
|
|
807
|
+
`custom annotation "${annotation.annotationId}"`
|
|
808
|
+
);
|
|
809
|
+
}
|
|
810
|
+
function assignVendorPrefixedExtensionKeywords(schema, extensionSchema, vendorPrefix, source) {
|
|
811
|
+
for (const [key, value] of Object.entries(extensionSchema)) {
|
|
812
|
+
if (!key.startsWith(`${vendorPrefix}-`)) {
|
|
813
|
+
throw new Error(
|
|
814
|
+
`Cannot apply ${source}: extension hooks may only emit "${vendorPrefix}-*" JSON Schema keywords`
|
|
815
|
+
);
|
|
816
|
+
}
|
|
817
|
+
schema[key] = value;
|
|
818
|
+
}
|
|
792
819
|
}
|
|
793
820
|
|
|
794
821
|
// src/json-schema/generator.ts
|
|
@@ -1041,7 +1068,10 @@ function getSchemaExtension(schema, key) {
|
|
|
1041
1068
|
// src/extensions/registry.ts
|
|
1042
1069
|
function createExtensionRegistry(extensions) {
|
|
1043
1070
|
const typeMap = /* @__PURE__ */ new Map();
|
|
1071
|
+
const typeNameMap = /* @__PURE__ */ new Map();
|
|
1044
1072
|
const constraintMap = /* @__PURE__ */ new Map();
|
|
1073
|
+
const constraintTagMap = /* @__PURE__ */ new Map();
|
|
1074
|
+
const builtinBroadeningMap = /* @__PURE__ */ new Map();
|
|
1045
1075
|
const annotationMap = /* @__PURE__ */ new Map();
|
|
1046
1076
|
for (const ext of extensions) {
|
|
1047
1077
|
if (ext.types !== void 0) {
|
|
@@ -1051,6 +1081,27 @@ function createExtensionRegistry(extensions) {
|
|
|
1051
1081
|
throw new Error(`Duplicate custom type ID: "${qualifiedId}"`);
|
|
1052
1082
|
}
|
|
1053
1083
|
typeMap.set(qualifiedId, type);
|
|
1084
|
+
for (const sourceTypeName of type.tsTypeNames ?? [type.typeName]) {
|
|
1085
|
+
if (typeNameMap.has(sourceTypeName)) {
|
|
1086
|
+
throw new Error(`Duplicate custom type source name: "${sourceTypeName}"`);
|
|
1087
|
+
}
|
|
1088
|
+
typeNameMap.set(sourceTypeName, {
|
|
1089
|
+
extensionId: ext.extensionId,
|
|
1090
|
+
registration: type
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
if (type.builtinConstraintBroadenings !== void 0) {
|
|
1094
|
+
for (const broadening of type.builtinConstraintBroadenings) {
|
|
1095
|
+
const key = `${qualifiedId}:${broadening.tagName}`;
|
|
1096
|
+
if (builtinBroadeningMap.has(key)) {
|
|
1097
|
+
throw new Error(`Duplicate built-in constraint broadening: "${key}"`);
|
|
1098
|
+
}
|
|
1099
|
+
builtinBroadeningMap.set(key, {
|
|
1100
|
+
extensionId: ext.extensionId,
|
|
1101
|
+
registration: broadening
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1054
1105
|
}
|
|
1055
1106
|
}
|
|
1056
1107
|
if (ext.constraints !== void 0) {
|
|
@@ -1062,6 +1113,17 @@ function createExtensionRegistry(extensions) {
|
|
|
1062
1113
|
constraintMap.set(qualifiedId, constraint);
|
|
1063
1114
|
}
|
|
1064
1115
|
}
|
|
1116
|
+
if (ext.constraintTags !== void 0) {
|
|
1117
|
+
for (const tag of ext.constraintTags) {
|
|
1118
|
+
if (constraintTagMap.has(tag.tagName)) {
|
|
1119
|
+
throw new Error(`Duplicate custom constraint tag: "@${tag.tagName}"`);
|
|
1120
|
+
}
|
|
1121
|
+
constraintTagMap.set(tag.tagName, {
|
|
1122
|
+
extensionId: ext.extensionId,
|
|
1123
|
+
registration: tag
|
|
1124
|
+
});
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1065
1127
|
if (ext.annotations !== void 0) {
|
|
1066
1128
|
for (const annotation of ext.annotations) {
|
|
1067
1129
|
const qualifiedId = `${ext.extensionId}/${annotation.annotationName}`;
|
|
@@ -1075,7 +1137,10 @@ function createExtensionRegistry(extensions) {
|
|
|
1075
1137
|
return {
|
|
1076
1138
|
extensions,
|
|
1077
1139
|
findType: (typeId) => typeMap.get(typeId),
|
|
1140
|
+
findTypeByName: (typeName) => typeNameMap.get(typeName),
|
|
1078
1141
|
findConstraint: (constraintId) => constraintMap.get(constraintId),
|
|
1142
|
+
findConstraintTag: (tagName) => constraintTagMap.get(tagName),
|
|
1143
|
+
findBuiltinConstraintBroadening: (typeId, tagName) => builtinBroadeningMap.get(`${typeId}:${tagName}`),
|
|
1079
1144
|
findAnnotation: (annotationId) => annotationMap.get(annotationId)
|
|
1080
1145
|
};
|
|
1081
1146
|
}
|
|
@@ -1143,85 +1208,17 @@ var jsonSchema7Schema = z3.lazy(
|
|
|
1143
1208
|
);
|
|
1144
1209
|
|
|
1145
1210
|
// src/analyzer/program.ts
|
|
1146
|
-
import * as
|
|
1211
|
+
import * as ts4 from "typescript";
|
|
1147
1212
|
import * as path from "path";
|
|
1148
|
-
function createProgramContext(filePath) {
|
|
1149
|
-
const absolutePath = path.resolve(filePath);
|
|
1150
|
-
const fileDir = path.dirname(absolutePath);
|
|
1151
|
-
const configPath = ts.findConfigFile(fileDir, ts.sys.fileExists.bind(ts.sys), "tsconfig.json");
|
|
1152
|
-
let compilerOptions;
|
|
1153
|
-
let fileNames;
|
|
1154
|
-
if (configPath) {
|
|
1155
|
-
const configFile = ts.readConfigFile(configPath, ts.sys.readFile.bind(ts.sys));
|
|
1156
|
-
if (configFile.error) {
|
|
1157
|
-
throw new Error(
|
|
1158
|
-
`Error reading tsconfig.json: ${ts.flattenDiagnosticMessageText(configFile.error.messageText, "\n")}`
|
|
1159
|
-
);
|
|
1160
|
-
}
|
|
1161
|
-
const parsed = ts.parseJsonConfigFileContent(
|
|
1162
|
-
configFile.config,
|
|
1163
|
-
ts.sys,
|
|
1164
|
-
path.dirname(configPath)
|
|
1165
|
-
);
|
|
1166
|
-
if (parsed.errors.length > 0) {
|
|
1167
|
-
const errorMessages = parsed.errors.map((e) => ts.flattenDiagnosticMessageText(e.messageText, "\n")).join("\n");
|
|
1168
|
-
throw new Error(`Error parsing tsconfig.json: ${errorMessages}`);
|
|
1169
|
-
}
|
|
1170
|
-
compilerOptions = parsed.options;
|
|
1171
|
-
fileNames = parsed.fileNames.includes(absolutePath) ? parsed.fileNames : [...parsed.fileNames, absolutePath];
|
|
1172
|
-
} else {
|
|
1173
|
-
compilerOptions = {
|
|
1174
|
-
target: ts.ScriptTarget.ES2022,
|
|
1175
|
-
module: ts.ModuleKind.NodeNext,
|
|
1176
|
-
moduleResolution: ts.ModuleResolutionKind.NodeNext,
|
|
1177
|
-
strict: true,
|
|
1178
|
-
skipLibCheck: true,
|
|
1179
|
-
declaration: true
|
|
1180
|
-
};
|
|
1181
|
-
fileNames = [absolutePath];
|
|
1182
|
-
}
|
|
1183
|
-
const program = ts.createProgram(fileNames, compilerOptions);
|
|
1184
|
-
const sourceFile = program.getSourceFile(absolutePath);
|
|
1185
|
-
if (!sourceFile) {
|
|
1186
|
-
throw new Error(`Could not find source file: ${absolutePath}`);
|
|
1187
|
-
}
|
|
1188
|
-
return {
|
|
1189
|
-
program,
|
|
1190
|
-
checker: program.getTypeChecker(),
|
|
1191
|
-
sourceFile
|
|
1192
|
-
};
|
|
1193
|
-
}
|
|
1194
|
-
function findNodeByName(sourceFile, name, predicate, getName) {
|
|
1195
|
-
let result = null;
|
|
1196
|
-
function visit(node) {
|
|
1197
|
-
if (result) return;
|
|
1198
|
-
if (predicate(node) && getName(node) === name) {
|
|
1199
|
-
result = node;
|
|
1200
|
-
return;
|
|
1201
|
-
}
|
|
1202
|
-
ts.forEachChild(node, visit);
|
|
1203
|
-
}
|
|
1204
|
-
visit(sourceFile);
|
|
1205
|
-
return result;
|
|
1206
|
-
}
|
|
1207
|
-
function findClassByName(sourceFile, className) {
|
|
1208
|
-
return findNodeByName(sourceFile, className, ts.isClassDeclaration, (n) => n.name?.text);
|
|
1209
|
-
}
|
|
1210
|
-
function findInterfaceByName(sourceFile, interfaceName) {
|
|
1211
|
-
return findNodeByName(sourceFile, interfaceName, ts.isInterfaceDeclaration, (n) => n.name.text);
|
|
1212
|
-
}
|
|
1213
|
-
function findTypeAliasByName(sourceFile, aliasName) {
|
|
1214
|
-
return findNodeByName(sourceFile, aliasName, ts.isTypeAliasDeclaration, (n) => n.name.text);
|
|
1215
|
-
}
|
|
1216
1213
|
|
|
1217
1214
|
// src/analyzer/class-analyzer.ts
|
|
1218
|
-
import * as
|
|
1215
|
+
import * as ts3 from "typescript";
|
|
1219
1216
|
|
|
1220
1217
|
// src/analyzer/jsdoc-constraints.ts
|
|
1221
|
-
import * as
|
|
1218
|
+
import * as ts2 from "typescript";
|
|
1222
1219
|
|
|
1223
1220
|
// src/analyzer/tsdoc-parser.ts
|
|
1224
|
-
import * as
|
|
1221
|
+
import * as ts from "typescript";
|
|
1225
1222
|
import {
|
|
1226
1223
|
TSDocParser,
|
|
1227
1224
|
TSDocConfiguration,
|
|
@@ -1261,7 +1258,7 @@ var LENGTH_CONSTRAINT_MAP = {
|
|
|
1261
1258
|
maxItems: "maxItems"
|
|
1262
1259
|
};
|
|
1263
1260
|
var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions", "defaultValue"]);
|
|
1264
|
-
function createFormSpecTSDocConfig() {
|
|
1261
|
+
function createFormSpecTSDocConfig(extensionTagNames = []) {
|
|
1265
1262
|
const config = new TSDocConfiguration();
|
|
1266
1263
|
for (const tagName of Object.keys(BUILTIN_CONSTRAINT_DEFINITIONS)) {
|
|
1267
1264
|
config.addTagDefinition(
|
|
@@ -1281,14 +1278,34 @@ function createFormSpecTSDocConfig() {
|
|
|
1281
1278
|
})
|
|
1282
1279
|
);
|
|
1283
1280
|
}
|
|
1281
|
+
for (const tagName of extensionTagNames) {
|
|
1282
|
+
config.addTagDefinition(
|
|
1283
|
+
new TSDocTagDefinition({
|
|
1284
|
+
tagName: "@" + tagName,
|
|
1285
|
+
syntaxKind: TSDocTagSyntaxKind.BlockTag,
|
|
1286
|
+
allowMultiple: true
|
|
1287
|
+
})
|
|
1288
|
+
);
|
|
1289
|
+
}
|
|
1284
1290
|
return config;
|
|
1285
1291
|
}
|
|
1286
|
-
var
|
|
1287
|
-
function getParser() {
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
+
var parserCache = /* @__PURE__ */ new Map();
|
|
1293
|
+
function getParser(options) {
|
|
1294
|
+
const extensionTagNames = [
|
|
1295
|
+
...options?.extensionRegistry?.extensions.flatMap(
|
|
1296
|
+
(extension) => (extension.constraintTags ?? []).map((tag) => tag.tagName)
|
|
1297
|
+
) ?? []
|
|
1298
|
+
].sort();
|
|
1299
|
+
const cacheKey = extensionTagNames.join("|");
|
|
1300
|
+
const existing = parserCache.get(cacheKey);
|
|
1301
|
+
if (existing) {
|
|
1302
|
+
return existing;
|
|
1303
|
+
}
|
|
1304
|
+
const parser = new TSDocParser(createFormSpecTSDocConfig(extensionTagNames));
|
|
1305
|
+
parserCache.set(cacheKey, parser);
|
|
1306
|
+
return parser;
|
|
1307
|
+
}
|
|
1308
|
+
function parseTSDocTags(node, file = "", options) {
|
|
1292
1309
|
const constraints = [];
|
|
1293
1310
|
const annotations = [];
|
|
1294
1311
|
let displayName;
|
|
@@ -1299,17 +1316,17 @@ function parseTSDocTags(node, file = "") {
|
|
|
1299
1316
|
let placeholderProvenance;
|
|
1300
1317
|
const sourceFile = node.getSourceFile();
|
|
1301
1318
|
const sourceText = sourceFile.getFullText();
|
|
1302
|
-
const commentRanges =
|
|
1319
|
+
const commentRanges = ts.getLeadingCommentRanges(sourceText, node.getFullStart());
|
|
1303
1320
|
if (commentRanges) {
|
|
1304
1321
|
for (const range of commentRanges) {
|
|
1305
|
-
if (range.kind !==
|
|
1322
|
+
if (range.kind !== ts.SyntaxKind.MultiLineCommentTrivia) {
|
|
1306
1323
|
continue;
|
|
1307
1324
|
}
|
|
1308
1325
|
const commentText = sourceText.substring(range.pos, range.end);
|
|
1309
1326
|
if (!commentText.startsWith("/**")) {
|
|
1310
1327
|
continue;
|
|
1311
1328
|
}
|
|
1312
|
-
const parser = getParser();
|
|
1329
|
+
const parser = getParser(options);
|
|
1313
1330
|
const parserContext = parser.parseRange(
|
|
1314
1331
|
TextRange.fromStringRange(sourceText, range.pos, range.end)
|
|
1315
1332
|
);
|
|
@@ -1320,26 +1337,31 @@ function parseTSDocTags(node, file = "") {
|
|
|
1320
1337
|
const text2 = extractBlockText(block).trim();
|
|
1321
1338
|
if (text2 === "") continue;
|
|
1322
1339
|
const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
displayName
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1340
|
+
switch (tagName) {
|
|
1341
|
+
case "displayName":
|
|
1342
|
+
if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
|
|
1343
|
+
displayName = text2;
|
|
1344
|
+
displayNameProvenance = provenance2;
|
|
1345
|
+
}
|
|
1346
|
+
break;
|
|
1347
|
+
case "format":
|
|
1348
|
+
annotations.push({
|
|
1349
|
+
kind: "annotation",
|
|
1350
|
+
annotationKind: "format",
|
|
1351
|
+
value: text2,
|
|
1352
|
+
provenance: provenance2
|
|
1353
|
+
});
|
|
1354
|
+
break;
|
|
1355
|
+
case "description":
|
|
1337
1356
|
description = text2;
|
|
1338
1357
|
descriptionProvenance = provenance2;
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1358
|
+
break;
|
|
1359
|
+
case "placeholder":
|
|
1360
|
+
if (placeholder === void 0) {
|
|
1361
|
+
placeholder = text2;
|
|
1362
|
+
placeholderProvenance = provenance2;
|
|
1363
|
+
}
|
|
1364
|
+
break;
|
|
1343
1365
|
}
|
|
1344
1366
|
continue;
|
|
1345
1367
|
}
|
|
@@ -1348,7 +1370,7 @@ function parseTSDocTags(node, file = "") {
|
|
|
1348
1370
|
const expectedType = isBuiltinConstraintName(tagName) ? BUILTIN_CONSTRAINT_DEFINITIONS[tagName] : void 0;
|
|
1349
1371
|
if (text === "" && expectedType !== "boolean") continue;
|
|
1350
1372
|
const provenance = provenanceForComment(range, sourceFile, file, tagName);
|
|
1351
|
-
const constraintNode = parseConstraintValue(tagName, text, provenance);
|
|
1373
|
+
const constraintNode = parseConstraintValue(tagName, text, provenance, options);
|
|
1352
1374
|
if (constraintNode) {
|
|
1353
1375
|
constraints.push(constraintNode);
|
|
1354
1376
|
}
|
|
@@ -1369,6 +1391,13 @@ function parseTSDocTags(node, file = "") {
|
|
|
1369
1391
|
descriptionProvenance = provenanceForComment(range, sourceFile, file, "remarks");
|
|
1370
1392
|
}
|
|
1371
1393
|
}
|
|
1394
|
+
if (description === void 0) {
|
|
1395
|
+
const summary = extractPlainText(docComment.summarySection).trim();
|
|
1396
|
+
if (summary !== "") {
|
|
1397
|
+
description = summary;
|
|
1398
|
+
descriptionProvenance = provenanceForComment(range, sourceFile, file, "summary");
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1372
1401
|
}
|
|
1373
1402
|
}
|
|
1374
1403
|
if (displayName !== void 0 && displayNameProvenance !== void 0) {
|
|
@@ -1395,7 +1424,7 @@ function parseTSDocTags(node, file = "") {
|
|
|
1395
1424
|
provenance: placeholderProvenance
|
|
1396
1425
|
});
|
|
1397
1426
|
}
|
|
1398
|
-
const jsDocTagsAll =
|
|
1427
|
+
const jsDocTagsAll = ts.getJSDocTags(node);
|
|
1399
1428
|
for (const tag of jsDocTagsAll) {
|
|
1400
1429
|
const tagName = normalizeConstraintTagName(tag.tagName.text);
|
|
1401
1430
|
if (!TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
@@ -1408,7 +1437,7 @@ function parseTSDocTags(node, file = "") {
|
|
|
1408
1437
|
annotations.push(defaultValueNode);
|
|
1409
1438
|
continue;
|
|
1410
1439
|
}
|
|
1411
|
-
const constraintNode = parseConstraintValue(tagName, text, provenance);
|
|
1440
|
+
const constraintNode = parseConstraintValue(tagName, text, provenance, options);
|
|
1412
1441
|
if (constraintNode) {
|
|
1413
1442
|
constraints.push(constraintNode);
|
|
1414
1443
|
}
|
|
@@ -1418,7 +1447,7 @@ function parseTSDocTags(node, file = "") {
|
|
|
1418
1447
|
function extractDisplayNameMetadata(node) {
|
|
1419
1448
|
let displayName;
|
|
1420
1449
|
const memberDisplayNames = /* @__PURE__ */ new Map();
|
|
1421
|
-
for (const tag of
|
|
1450
|
+
for (const tag of ts.getJSDocTags(node)) {
|
|
1422
1451
|
const tagName = normalizeConstraintTagName(tag.tagName.text);
|
|
1423
1452
|
if (tagName !== "displayName") continue;
|
|
1424
1453
|
const commentText = getTagCommentText(tag);
|
|
@@ -1439,11 +1468,11 @@ function extractDisplayNameMetadata(node) {
|
|
|
1439
1468
|
}
|
|
1440
1469
|
function extractPathTarget(text) {
|
|
1441
1470
|
const trimmed = text.trimStart();
|
|
1442
|
-
const match = /^:([a-zA-Z_]\w*)
|
|
1443
|
-
if (!match?.[1]
|
|
1471
|
+
const match = /^:([a-zA-Z_]\w*)(?:\s+([\s\S]*))?$/.exec(trimmed);
|
|
1472
|
+
if (!match?.[1]) return null;
|
|
1444
1473
|
return {
|
|
1445
1474
|
path: { segments: [match[1]] },
|
|
1446
|
-
remainingText: match[2]
|
|
1475
|
+
remainingText: match[2] ?? ""
|
|
1447
1476
|
};
|
|
1448
1477
|
}
|
|
1449
1478
|
function extractBlockText(block) {
|
|
@@ -1464,7 +1493,11 @@ function extractPlainText(node) {
|
|
|
1464
1493
|
}
|
|
1465
1494
|
return result;
|
|
1466
1495
|
}
|
|
1467
|
-
function parseConstraintValue(tagName, text, provenance) {
|
|
1496
|
+
function parseConstraintValue(tagName, text, provenance, options) {
|
|
1497
|
+
const customConstraint = parseExtensionConstraintValue(tagName, text, provenance, options);
|
|
1498
|
+
if (customConstraint) {
|
|
1499
|
+
return customConstraint;
|
|
1500
|
+
}
|
|
1468
1501
|
if (!isBuiltinConstraintName(tagName)) {
|
|
1469
1502
|
return null;
|
|
1470
1503
|
}
|
|
@@ -1569,6 +1602,83 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
1569
1602
|
provenance
|
|
1570
1603
|
};
|
|
1571
1604
|
}
|
|
1605
|
+
function parseExtensionConstraintValue(tagName, text, provenance, options) {
|
|
1606
|
+
const pathResult = extractPathTarget(text);
|
|
1607
|
+
const effectiveText = pathResult ? pathResult.remainingText : text;
|
|
1608
|
+
const path3 = pathResult?.path;
|
|
1609
|
+
const registry = options?.extensionRegistry;
|
|
1610
|
+
if (registry === void 0) {
|
|
1611
|
+
return null;
|
|
1612
|
+
}
|
|
1613
|
+
const directTag = registry.findConstraintTag(tagName);
|
|
1614
|
+
if (directTag !== void 0) {
|
|
1615
|
+
return makeCustomConstraintNode(
|
|
1616
|
+
directTag.extensionId,
|
|
1617
|
+
directTag.registration.constraintName,
|
|
1618
|
+
directTag.registration.parseValue(effectiveText),
|
|
1619
|
+
provenance,
|
|
1620
|
+
path3,
|
|
1621
|
+
registry
|
|
1622
|
+
);
|
|
1623
|
+
}
|
|
1624
|
+
if (!isBuiltinConstraintName(tagName)) {
|
|
1625
|
+
return null;
|
|
1626
|
+
}
|
|
1627
|
+
const broadenedTypeId = getBroadenedCustomTypeId(options?.fieldType);
|
|
1628
|
+
if (broadenedTypeId === void 0) {
|
|
1629
|
+
return null;
|
|
1630
|
+
}
|
|
1631
|
+
const broadened = registry.findBuiltinConstraintBroadening(broadenedTypeId, tagName);
|
|
1632
|
+
if (broadened === void 0) {
|
|
1633
|
+
return null;
|
|
1634
|
+
}
|
|
1635
|
+
return makeCustomConstraintNode(
|
|
1636
|
+
broadened.extensionId,
|
|
1637
|
+
broadened.registration.constraintName,
|
|
1638
|
+
broadened.registration.parseValue(effectiveText),
|
|
1639
|
+
provenance,
|
|
1640
|
+
path3,
|
|
1641
|
+
registry
|
|
1642
|
+
);
|
|
1643
|
+
}
|
|
1644
|
+
function getBroadenedCustomTypeId(fieldType) {
|
|
1645
|
+
if (fieldType?.kind === "custom") {
|
|
1646
|
+
return fieldType.typeId;
|
|
1647
|
+
}
|
|
1648
|
+
if (fieldType?.kind !== "union") {
|
|
1649
|
+
return void 0;
|
|
1650
|
+
}
|
|
1651
|
+
const customMembers = fieldType.members.filter(
|
|
1652
|
+
(member) => member.kind === "custom"
|
|
1653
|
+
);
|
|
1654
|
+
if (customMembers.length !== 1) {
|
|
1655
|
+
return void 0;
|
|
1656
|
+
}
|
|
1657
|
+
const nonCustomMembers = fieldType.members.filter((member) => member.kind !== "custom");
|
|
1658
|
+
const allOtherMembersAreNull = nonCustomMembers.every(
|
|
1659
|
+
(member) => member.kind === "primitive" && member.primitiveKind === "null"
|
|
1660
|
+
);
|
|
1661
|
+
const customMember = customMembers[0];
|
|
1662
|
+
return allOtherMembersAreNull && customMember !== void 0 ? customMember.typeId : void 0;
|
|
1663
|
+
}
|
|
1664
|
+
function makeCustomConstraintNode(extensionId, constraintName, payload, provenance, path3, registry) {
|
|
1665
|
+
const constraintId = `${extensionId}/${constraintName}`;
|
|
1666
|
+
const registration = registry.findConstraint(constraintId);
|
|
1667
|
+
if (registration === void 0) {
|
|
1668
|
+
throw new Error(
|
|
1669
|
+
`Custom TSDoc tag resolved to unregistered constraint "${constraintId}". Register the constraint before using its tag.`
|
|
1670
|
+
);
|
|
1671
|
+
}
|
|
1672
|
+
return {
|
|
1673
|
+
kind: "constraint",
|
|
1674
|
+
constraintKind: "custom",
|
|
1675
|
+
constraintId,
|
|
1676
|
+
payload,
|
|
1677
|
+
compositionRule: registration.compositionRule,
|
|
1678
|
+
...path3 && { path: path3 },
|
|
1679
|
+
provenance
|
|
1680
|
+
};
|
|
1681
|
+
}
|
|
1572
1682
|
function parseDefaultValueValue(text, provenance) {
|
|
1573
1683
|
const trimmed = text.trim();
|
|
1574
1684
|
let value;
|
|
@@ -1625,33 +1735,33 @@ function getTagCommentText(tag) {
|
|
|
1625
1735
|
if (typeof tag.comment === "string") {
|
|
1626
1736
|
return tag.comment;
|
|
1627
1737
|
}
|
|
1628
|
-
return
|
|
1738
|
+
return ts.getTextOfJSDocComment(tag.comment);
|
|
1629
1739
|
}
|
|
1630
1740
|
|
|
1631
1741
|
// src/analyzer/jsdoc-constraints.ts
|
|
1632
|
-
function extractJSDocConstraintNodes(node, file = "") {
|
|
1633
|
-
const result = parseTSDocTags(node, file);
|
|
1742
|
+
function extractJSDocConstraintNodes(node, file = "", options) {
|
|
1743
|
+
const result = parseTSDocTags(node, file, options);
|
|
1634
1744
|
return [...result.constraints];
|
|
1635
1745
|
}
|
|
1636
|
-
function extractJSDocAnnotationNodes(node, file = "") {
|
|
1637
|
-
const result = parseTSDocTags(node, file);
|
|
1746
|
+
function extractJSDocAnnotationNodes(node, file = "", options) {
|
|
1747
|
+
const result = parseTSDocTags(node, file, options);
|
|
1638
1748
|
return [...result.annotations];
|
|
1639
1749
|
}
|
|
1640
1750
|
function extractDefaultValueAnnotation(initializer, file = "") {
|
|
1641
1751
|
if (!initializer) return null;
|
|
1642
1752
|
let value;
|
|
1643
|
-
if (
|
|
1753
|
+
if (ts2.isStringLiteral(initializer)) {
|
|
1644
1754
|
value = initializer.text;
|
|
1645
|
-
} else if (
|
|
1755
|
+
} else if (ts2.isNumericLiteral(initializer)) {
|
|
1646
1756
|
value = Number(initializer.text);
|
|
1647
|
-
} else if (initializer.kind ===
|
|
1757
|
+
} else if (initializer.kind === ts2.SyntaxKind.TrueKeyword) {
|
|
1648
1758
|
value = true;
|
|
1649
|
-
} else if (initializer.kind ===
|
|
1759
|
+
} else if (initializer.kind === ts2.SyntaxKind.FalseKeyword) {
|
|
1650
1760
|
value = false;
|
|
1651
|
-
} else if (initializer.kind ===
|
|
1761
|
+
} else if (initializer.kind === ts2.SyntaxKind.NullKeyword) {
|
|
1652
1762
|
value = null;
|
|
1653
|
-
} else if (
|
|
1654
|
-
if (initializer.operator ===
|
|
1763
|
+
} else if (ts2.isPrefixUnaryExpression(initializer)) {
|
|
1764
|
+
if (initializer.operator === ts2.SyntaxKind.MinusToken && ts2.isNumericLiteral(initializer.operand)) {
|
|
1655
1765
|
value = -Number(initializer.operand.text);
|
|
1656
1766
|
}
|
|
1657
1767
|
}
|
|
@@ -1673,36 +1783,56 @@ function extractDefaultValueAnnotation(initializer, file = "") {
|
|
|
1673
1783
|
|
|
1674
1784
|
// src/analyzer/class-analyzer.ts
|
|
1675
1785
|
function isObjectType(type) {
|
|
1676
|
-
return !!(type.flags &
|
|
1786
|
+
return !!(type.flags & ts3.TypeFlags.Object);
|
|
1677
1787
|
}
|
|
1678
1788
|
function isTypeReference(type) {
|
|
1679
|
-
return !!(type.flags &
|
|
1789
|
+
return !!(type.flags & ts3.TypeFlags.Object) && !!(type.objectFlags & ts3.ObjectFlags.Reference);
|
|
1680
1790
|
}
|
|
1681
1791
|
var RESOLVING_TYPE_PLACEHOLDER = {
|
|
1682
1792
|
kind: "object",
|
|
1683
1793
|
properties: [],
|
|
1684
1794
|
additionalProperties: true
|
|
1685
1795
|
};
|
|
1686
|
-
function
|
|
1796
|
+
function makeParseOptions(extensionRegistry, fieldType) {
|
|
1797
|
+
if (extensionRegistry === void 0 && fieldType === void 0) {
|
|
1798
|
+
return void 0;
|
|
1799
|
+
}
|
|
1800
|
+
return {
|
|
1801
|
+
...extensionRegistry !== void 0 && { extensionRegistry },
|
|
1802
|
+
...fieldType !== void 0 && { fieldType }
|
|
1803
|
+
};
|
|
1804
|
+
}
|
|
1805
|
+
function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
|
|
1687
1806
|
const name = classDecl.name?.text ?? "AnonymousClass";
|
|
1688
1807
|
const fields = [];
|
|
1689
1808
|
const fieldLayouts = [];
|
|
1690
1809
|
const typeRegistry = {};
|
|
1691
|
-
const annotations = extractJSDocAnnotationNodes(
|
|
1810
|
+
const annotations = extractJSDocAnnotationNodes(
|
|
1811
|
+
classDecl,
|
|
1812
|
+
file,
|
|
1813
|
+
makeParseOptions(extensionRegistry)
|
|
1814
|
+
);
|
|
1692
1815
|
const visiting = /* @__PURE__ */ new Set();
|
|
1693
1816
|
const instanceMethods = [];
|
|
1694
1817
|
const staticMethods = [];
|
|
1695
1818
|
for (const member of classDecl.members) {
|
|
1696
|
-
if (
|
|
1697
|
-
const fieldNode = analyzeFieldToIR(
|
|
1819
|
+
if (ts3.isPropertyDeclaration(member)) {
|
|
1820
|
+
const fieldNode = analyzeFieldToIR(
|
|
1821
|
+
member,
|
|
1822
|
+
checker,
|
|
1823
|
+
file,
|
|
1824
|
+
typeRegistry,
|
|
1825
|
+
visiting,
|
|
1826
|
+
extensionRegistry
|
|
1827
|
+
);
|
|
1698
1828
|
if (fieldNode) {
|
|
1699
1829
|
fields.push(fieldNode);
|
|
1700
1830
|
fieldLayouts.push({});
|
|
1701
1831
|
}
|
|
1702
|
-
} else if (
|
|
1832
|
+
} else if (ts3.isMethodDeclaration(member)) {
|
|
1703
1833
|
const methodInfo = analyzeMethod(member, checker);
|
|
1704
1834
|
if (methodInfo) {
|
|
1705
|
-
const isStatic = member.modifiers?.some((m) => m.kind ===
|
|
1835
|
+
const isStatic = member.modifiers?.some((m) => m.kind === ts3.SyntaxKind.StaticKeyword);
|
|
1706
1836
|
if (isStatic) {
|
|
1707
1837
|
staticMethods.push(methodInfo);
|
|
1708
1838
|
} else {
|
|
@@ -1721,15 +1851,26 @@ function analyzeClassToIR(classDecl, checker, file = "") {
|
|
|
1721
1851
|
staticMethods
|
|
1722
1852
|
};
|
|
1723
1853
|
}
|
|
1724
|
-
function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
|
|
1854
|
+
function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegistry) {
|
|
1725
1855
|
const name = interfaceDecl.name.text;
|
|
1726
1856
|
const fields = [];
|
|
1727
1857
|
const typeRegistry = {};
|
|
1728
|
-
const annotations = extractJSDocAnnotationNodes(
|
|
1858
|
+
const annotations = extractJSDocAnnotationNodes(
|
|
1859
|
+
interfaceDecl,
|
|
1860
|
+
file,
|
|
1861
|
+
makeParseOptions(extensionRegistry)
|
|
1862
|
+
);
|
|
1729
1863
|
const visiting = /* @__PURE__ */ new Set();
|
|
1730
1864
|
for (const member of interfaceDecl.members) {
|
|
1731
|
-
if (
|
|
1732
|
-
const fieldNode = analyzeInterfacePropertyToIR(
|
|
1865
|
+
if (ts3.isPropertySignature(member)) {
|
|
1866
|
+
const fieldNode = analyzeInterfacePropertyToIR(
|
|
1867
|
+
member,
|
|
1868
|
+
checker,
|
|
1869
|
+
file,
|
|
1870
|
+
typeRegistry,
|
|
1871
|
+
visiting,
|
|
1872
|
+
extensionRegistry
|
|
1873
|
+
);
|
|
1733
1874
|
if (fieldNode) {
|
|
1734
1875
|
fields.push(fieldNode);
|
|
1735
1876
|
}
|
|
@@ -1746,11 +1887,11 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
|
|
|
1746
1887
|
staticMethods: []
|
|
1747
1888
|
};
|
|
1748
1889
|
}
|
|
1749
|
-
function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
1750
|
-
if (!
|
|
1890
|
+
function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry) {
|
|
1891
|
+
if (!ts3.isTypeLiteralNode(typeAlias.type)) {
|
|
1751
1892
|
const sourceFile = typeAlias.getSourceFile();
|
|
1752
1893
|
const { line } = sourceFile.getLineAndCharacterOfPosition(typeAlias.getStart());
|
|
1753
|
-
const kindDesc =
|
|
1894
|
+
const kindDesc = ts3.SyntaxKind[typeAlias.type.kind] ?? "unknown";
|
|
1754
1895
|
return {
|
|
1755
1896
|
ok: false,
|
|
1756
1897
|
error: `Type alias "${typeAlias.name.text}" at line ${String(line + 1)} is not an object type literal (found ${kindDesc})`
|
|
@@ -1759,11 +1900,22 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
|
1759
1900
|
const name = typeAlias.name.text;
|
|
1760
1901
|
const fields = [];
|
|
1761
1902
|
const typeRegistry = {};
|
|
1762
|
-
const annotations = extractJSDocAnnotationNodes(
|
|
1903
|
+
const annotations = extractJSDocAnnotationNodes(
|
|
1904
|
+
typeAlias,
|
|
1905
|
+
file,
|
|
1906
|
+
makeParseOptions(extensionRegistry)
|
|
1907
|
+
);
|
|
1763
1908
|
const visiting = /* @__PURE__ */ new Set();
|
|
1764
1909
|
for (const member of typeAlias.type.members) {
|
|
1765
|
-
if (
|
|
1766
|
-
const fieldNode = analyzeInterfacePropertyToIR(
|
|
1910
|
+
if (ts3.isPropertySignature(member)) {
|
|
1911
|
+
const fieldNode = analyzeInterfacePropertyToIR(
|
|
1912
|
+
member,
|
|
1913
|
+
checker,
|
|
1914
|
+
file,
|
|
1915
|
+
typeRegistry,
|
|
1916
|
+
visiting,
|
|
1917
|
+
extensionRegistry
|
|
1918
|
+
);
|
|
1767
1919
|
if (fieldNode) {
|
|
1768
1920
|
fields.push(fieldNode);
|
|
1769
1921
|
}
|
|
@@ -1782,22 +1934,36 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
|
1782
1934
|
}
|
|
1783
1935
|
};
|
|
1784
1936
|
}
|
|
1785
|
-
function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
|
|
1786
|
-
if (!
|
|
1937
|
+
function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1938
|
+
if (!ts3.isIdentifier(prop.name)) {
|
|
1787
1939
|
return null;
|
|
1788
1940
|
}
|
|
1789
1941
|
const name = prop.name.text;
|
|
1790
1942
|
const tsType = checker.getTypeAtLocation(prop);
|
|
1791
1943
|
const optional = prop.questionToken !== void 0;
|
|
1792
1944
|
const provenance = provenanceForNode(prop, file);
|
|
1793
|
-
let type = resolveTypeNode(
|
|
1945
|
+
let type = resolveTypeNode(
|
|
1946
|
+
tsType,
|
|
1947
|
+
checker,
|
|
1948
|
+
file,
|
|
1949
|
+
typeRegistry,
|
|
1950
|
+
visiting,
|
|
1951
|
+
prop,
|
|
1952
|
+
extensionRegistry
|
|
1953
|
+
);
|
|
1794
1954
|
const constraints = [];
|
|
1795
|
-
if (prop.type) {
|
|
1796
|
-
constraints.push(
|
|
1955
|
+
if (prop.type && !shouldEmitPrimitiveAliasDefinition(prop.type, checker)) {
|
|
1956
|
+
constraints.push(
|
|
1957
|
+
...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
|
|
1958
|
+
);
|
|
1797
1959
|
}
|
|
1798
|
-
constraints.push(
|
|
1960
|
+
constraints.push(
|
|
1961
|
+
...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
1962
|
+
);
|
|
1799
1963
|
let annotations = [];
|
|
1800
|
-
annotations.push(
|
|
1964
|
+
annotations.push(
|
|
1965
|
+
...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
1966
|
+
);
|
|
1801
1967
|
const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
|
|
1802
1968
|
if (defaultAnnotation && !annotations.some((a) => a.annotationKind === "defaultValue")) {
|
|
1803
1969
|
annotations.push(defaultAnnotation);
|
|
@@ -1813,22 +1979,36 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
|
|
|
1813
1979
|
provenance
|
|
1814
1980
|
};
|
|
1815
1981
|
}
|
|
1816
|
-
function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting) {
|
|
1817
|
-
if (!
|
|
1982
|
+
function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1983
|
+
if (!ts3.isIdentifier(prop.name)) {
|
|
1818
1984
|
return null;
|
|
1819
1985
|
}
|
|
1820
1986
|
const name = prop.name.text;
|
|
1821
1987
|
const tsType = checker.getTypeAtLocation(prop);
|
|
1822
1988
|
const optional = prop.questionToken !== void 0;
|
|
1823
1989
|
const provenance = provenanceForNode(prop, file);
|
|
1824
|
-
let type = resolveTypeNode(
|
|
1990
|
+
let type = resolveTypeNode(
|
|
1991
|
+
tsType,
|
|
1992
|
+
checker,
|
|
1993
|
+
file,
|
|
1994
|
+
typeRegistry,
|
|
1995
|
+
visiting,
|
|
1996
|
+
prop,
|
|
1997
|
+
extensionRegistry
|
|
1998
|
+
);
|
|
1825
1999
|
const constraints = [];
|
|
1826
|
-
if (prop.type) {
|
|
1827
|
-
constraints.push(
|
|
2000
|
+
if (prop.type && !shouldEmitPrimitiveAliasDefinition(prop.type, checker)) {
|
|
2001
|
+
constraints.push(
|
|
2002
|
+
...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
|
|
2003
|
+
);
|
|
1828
2004
|
}
|
|
1829
|
-
constraints.push(
|
|
2005
|
+
constraints.push(
|
|
2006
|
+
...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
2007
|
+
);
|
|
1830
2008
|
let annotations = [];
|
|
1831
|
-
annotations.push(
|
|
2009
|
+
annotations.push(
|
|
2010
|
+
...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
2011
|
+
);
|
|
1832
2012
|
({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
|
|
1833
2013
|
return {
|
|
1834
2014
|
kind: "field",
|
|
@@ -1902,20 +2082,94 @@ function parseEnumMemberDisplayName(value) {
|
|
|
1902
2082
|
if (label === "") return null;
|
|
1903
2083
|
return { value: match[1], label };
|
|
1904
2084
|
}
|
|
1905
|
-
function
|
|
1906
|
-
if (
|
|
2085
|
+
function resolveRegisteredCustomType(sourceNode, extensionRegistry, checker) {
|
|
2086
|
+
if (sourceNode === void 0 || extensionRegistry === void 0) {
|
|
2087
|
+
return null;
|
|
2088
|
+
}
|
|
2089
|
+
const typeNode = extractTypeNodeFromSource(sourceNode);
|
|
2090
|
+
if (typeNode === void 0) {
|
|
2091
|
+
return null;
|
|
2092
|
+
}
|
|
2093
|
+
return resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker);
|
|
2094
|
+
}
|
|
2095
|
+
function resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker) {
|
|
2096
|
+
if (ts3.isParenthesizedTypeNode(typeNode)) {
|
|
2097
|
+
return resolveRegisteredCustomTypeFromTypeNode(typeNode.type, extensionRegistry, checker);
|
|
2098
|
+
}
|
|
2099
|
+
const typeName = getTypeNodeRegistrationName(typeNode);
|
|
2100
|
+
if (typeName === null) {
|
|
2101
|
+
return null;
|
|
2102
|
+
}
|
|
2103
|
+
const registration = extensionRegistry.findTypeByName(typeName);
|
|
2104
|
+
if (registration !== void 0) {
|
|
2105
|
+
return {
|
|
2106
|
+
kind: "custom",
|
|
2107
|
+
typeId: `${registration.extensionId}/${registration.registration.typeName}`,
|
|
2108
|
+
payload: null
|
|
2109
|
+
};
|
|
2110
|
+
}
|
|
2111
|
+
if (ts3.isTypeReferenceNode(typeNode) && ts3.isIdentifier(typeNode.typeName)) {
|
|
2112
|
+
const aliasDecl = checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
2113
|
+
if (aliasDecl !== void 0) {
|
|
2114
|
+
return resolveRegisteredCustomTypeFromTypeNode(aliasDecl.type, extensionRegistry, checker);
|
|
2115
|
+
}
|
|
2116
|
+
}
|
|
2117
|
+
return null;
|
|
2118
|
+
}
|
|
2119
|
+
function extractTypeNodeFromSource(sourceNode) {
|
|
2120
|
+
if (ts3.isPropertyDeclaration(sourceNode) || ts3.isPropertySignature(sourceNode) || ts3.isParameter(sourceNode) || ts3.isTypeAliasDeclaration(sourceNode)) {
|
|
2121
|
+
return sourceNode.type;
|
|
2122
|
+
}
|
|
2123
|
+
if (ts3.isTypeNode(sourceNode)) {
|
|
2124
|
+
return sourceNode;
|
|
2125
|
+
}
|
|
2126
|
+
return void 0;
|
|
2127
|
+
}
|
|
2128
|
+
function getTypeNodeRegistrationName(typeNode) {
|
|
2129
|
+
if (ts3.isTypeReferenceNode(typeNode)) {
|
|
2130
|
+
return ts3.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : typeNode.typeName.right.text;
|
|
2131
|
+
}
|
|
2132
|
+
if (ts3.isParenthesizedTypeNode(typeNode)) {
|
|
2133
|
+
return getTypeNodeRegistrationName(typeNode.type);
|
|
2134
|
+
}
|
|
2135
|
+
if (typeNode.kind === ts3.SyntaxKind.BigIntKeyword || typeNode.kind === ts3.SyntaxKind.StringKeyword || typeNode.kind === ts3.SyntaxKind.NumberKeyword || typeNode.kind === ts3.SyntaxKind.BooleanKeyword) {
|
|
2136
|
+
return typeNode.getText();
|
|
2137
|
+
}
|
|
2138
|
+
return null;
|
|
2139
|
+
}
|
|
2140
|
+
function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
2141
|
+
const customType = resolveRegisteredCustomType(sourceNode, extensionRegistry, checker);
|
|
2142
|
+
if (customType) {
|
|
2143
|
+
return customType;
|
|
2144
|
+
}
|
|
2145
|
+
const primitiveAlias = tryResolveNamedPrimitiveAlias(
|
|
2146
|
+
type,
|
|
2147
|
+
checker,
|
|
2148
|
+
file,
|
|
2149
|
+
typeRegistry,
|
|
2150
|
+
visiting,
|
|
2151
|
+
sourceNode,
|
|
2152
|
+
extensionRegistry
|
|
2153
|
+
);
|
|
2154
|
+
if (primitiveAlias) {
|
|
2155
|
+
return primitiveAlias;
|
|
2156
|
+
}
|
|
2157
|
+
if (type.flags & ts3.TypeFlags.String) {
|
|
1907
2158
|
return { kind: "primitive", primitiveKind: "string" };
|
|
1908
2159
|
}
|
|
1909
|
-
if (type.flags &
|
|
2160
|
+
if (type.flags & ts3.TypeFlags.Number) {
|
|
1910
2161
|
return { kind: "primitive", primitiveKind: "number" };
|
|
1911
2162
|
}
|
|
1912
|
-
if (type.flags &
|
|
2163
|
+
if (type.flags & (ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral)) {
|
|
2164
|
+
return { kind: "primitive", primitiveKind: "bigint" };
|
|
2165
|
+
}
|
|
2166
|
+
if (type.flags & ts3.TypeFlags.Boolean) {
|
|
1913
2167
|
return { kind: "primitive", primitiveKind: "boolean" };
|
|
1914
2168
|
}
|
|
1915
|
-
if (type.flags &
|
|
2169
|
+
if (type.flags & ts3.TypeFlags.Null) {
|
|
1916
2170
|
return { kind: "primitive", primitiveKind: "null" };
|
|
1917
2171
|
}
|
|
1918
|
-
if (type.flags &
|
|
2172
|
+
if (type.flags & ts3.TypeFlags.Undefined) {
|
|
1919
2173
|
return { kind: "primitive", primitiveKind: "null" };
|
|
1920
2174
|
}
|
|
1921
2175
|
if (type.isStringLiteral()) {
|
|
@@ -1931,27 +2185,120 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
|
|
|
1931
2185
|
};
|
|
1932
2186
|
}
|
|
1933
2187
|
if (type.isUnion()) {
|
|
1934
|
-
return resolveUnionType(
|
|
2188
|
+
return resolveUnionType(
|
|
2189
|
+
type,
|
|
2190
|
+
checker,
|
|
2191
|
+
file,
|
|
2192
|
+
typeRegistry,
|
|
2193
|
+
visiting,
|
|
2194
|
+
sourceNode,
|
|
2195
|
+
extensionRegistry
|
|
2196
|
+
);
|
|
1935
2197
|
}
|
|
1936
2198
|
if (checker.isArrayType(type)) {
|
|
1937
|
-
return resolveArrayType(
|
|
2199
|
+
return resolveArrayType(
|
|
2200
|
+
type,
|
|
2201
|
+
checker,
|
|
2202
|
+
file,
|
|
2203
|
+
typeRegistry,
|
|
2204
|
+
visiting,
|
|
2205
|
+
sourceNode,
|
|
2206
|
+
extensionRegistry
|
|
2207
|
+
);
|
|
1938
2208
|
}
|
|
1939
2209
|
if (isObjectType(type)) {
|
|
1940
|
-
return resolveObjectType(type, checker, file, typeRegistry, visiting);
|
|
2210
|
+
return resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry);
|
|
1941
2211
|
}
|
|
1942
2212
|
return { kind: "primitive", primitiveKind: "string" };
|
|
1943
2213
|
}
|
|
1944
|
-
function
|
|
2214
|
+
function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
2215
|
+
if (!(type.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null))) {
|
|
2216
|
+
return null;
|
|
2217
|
+
}
|
|
2218
|
+
const aliasDecl = type.aliasSymbol?.declarations?.find(ts3.isTypeAliasDeclaration) ?? getReferencedTypeAliasDeclaration(sourceNode, checker);
|
|
2219
|
+
if (!aliasDecl) {
|
|
2220
|
+
return null;
|
|
2221
|
+
}
|
|
2222
|
+
const aliasName = aliasDecl.name.text;
|
|
2223
|
+
if (!typeRegistry[aliasName]) {
|
|
2224
|
+
const aliasType = checker.getTypeFromTypeNode(aliasDecl.type);
|
|
2225
|
+
const constraints = [
|
|
2226
|
+
...extractJSDocConstraintNodes(aliasDecl, file, makeParseOptions(extensionRegistry)),
|
|
2227
|
+
...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, extensionRegistry)
|
|
2228
|
+
];
|
|
2229
|
+
const annotations = extractJSDocAnnotationNodes(
|
|
2230
|
+
aliasDecl,
|
|
2231
|
+
file,
|
|
2232
|
+
makeParseOptions(extensionRegistry)
|
|
2233
|
+
);
|
|
2234
|
+
typeRegistry[aliasName] = {
|
|
2235
|
+
name: aliasName,
|
|
2236
|
+
type: resolveAliasedPrimitiveTarget(
|
|
2237
|
+
aliasType,
|
|
2238
|
+
checker,
|
|
2239
|
+
file,
|
|
2240
|
+
typeRegistry,
|
|
2241
|
+
visiting,
|
|
2242
|
+
extensionRegistry
|
|
2243
|
+
),
|
|
2244
|
+
...constraints.length > 0 && { constraints },
|
|
2245
|
+
...annotations.length > 0 && { annotations },
|
|
2246
|
+
provenance: provenanceForDeclaration(aliasDecl, file)
|
|
2247
|
+
};
|
|
2248
|
+
}
|
|
2249
|
+
return { kind: "reference", name: aliasName, typeArguments: [] };
|
|
2250
|
+
}
|
|
2251
|
+
function getReferencedTypeAliasDeclaration(sourceNode, checker) {
|
|
2252
|
+
const typeNode = sourceNode && (ts3.isPropertyDeclaration(sourceNode) || ts3.isPropertySignature(sourceNode) || ts3.isParameter(sourceNode)) ? sourceNode.type : void 0;
|
|
2253
|
+
if (!typeNode || !ts3.isTypeReferenceNode(typeNode)) {
|
|
2254
|
+
return void 0;
|
|
2255
|
+
}
|
|
2256
|
+
return checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
2257
|
+
}
|
|
2258
|
+
function shouldEmitPrimitiveAliasDefinition(typeNode, checker) {
|
|
2259
|
+
if (!ts3.isTypeReferenceNode(typeNode)) {
|
|
2260
|
+
return false;
|
|
2261
|
+
}
|
|
2262
|
+
const aliasDecl = checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
2263
|
+
if (!aliasDecl) {
|
|
2264
|
+
return false;
|
|
2265
|
+
}
|
|
2266
|
+
const resolved = checker.getTypeFromTypeNode(aliasDecl.type);
|
|
2267
|
+
return !!(resolved.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null));
|
|
2268
|
+
}
|
|
2269
|
+
function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2270
|
+
const nestedAliasDecl = type.aliasSymbol?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
2271
|
+
if (nestedAliasDecl !== void 0) {
|
|
2272
|
+
return resolveAliasedPrimitiveTarget(
|
|
2273
|
+
checker.getTypeFromTypeNode(nestedAliasDecl.type),
|
|
2274
|
+
checker,
|
|
2275
|
+
file,
|
|
2276
|
+
typeRegistry,
|
|
2277
|
+
visiting,
|
|
2278
|
+
extensionRegistry
|
|
2279
|
+
);
|
|
2280
|
+
}
|
|
2281
|
+
return resolveTypeNode(type, checker, file, typeRegistry, visiting, void 0, extensionRegistry);
|
|
2282
|
+
}
|
|
2283
|
+
function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
1945
2284
|
const typeName = getNamedTypeName(type);
|
|
1946
2285
|
const namedDecl = getNamedTypeDeclaration(type);
|
|
1947
2286
|
if (typeName && typeName in typeRegistry) {
|
|
1948
2287
|
return { kind: "reference", name: typeName, typeArguments: [] };
|
|
1949
2288
|
}
|
|
1950
2289
|
const allTypes = type.types;
|
|
2290
|
+
const unionMemberTypeNodes = extractUnionMemberTypeNodes(sourceNode, checker);
|
|
2291
|
+
const nonNullSourceNodes = unionMemberTypeNodes.filter(
|
|
2292
|
+
(memberTypeNode) => !isNullishTypeNode(resolveAliasedTypeNode(memberTypeNode, checker))
|
|
2293
|
+
);
|
|
1951
2294
|
const nonNullTypes = allTypes.filter(
|
|
1952
|
-
(
|
|
2295
|
+
(memberType) => !(memberType.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined))
|
|
1953
2296
|
);
|
|
1954
|
-
const
|
|
2297
|
+
const nonNullMembers = nonNullTypes.map((memberType, index) => ({
|
|
2298
|
+
memberType,
|
|
2299
|
+
sourceNode: nonNullSourceNodes.length === nonNullTypes.length ? nonNullSourceNodes[index] : void 0
|
|
2300
|
+
}));
|
|
2301
|
+
const hasNull = allTypes.some((t) => t.flags & ts3.TypeFlags.Null);
|
|
1955
2302
|
const memberDisplayNames = /* @__PURE__ */ new Map();
|
|
1956
2303
|
if (namedDecl) {
|
|
1957
2304
|
for (const [value, label] of extractDisplayNameMetadata(namedDecl).memberDisplayNames) {
|
|
@@ -1967,7 +2314,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
1967
2314
|
if (!typeName) {
|
|
1968
2315
|
return result;
|
|
1969
2316
|
}
|
|
1970
|
-
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
|
|
2317
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
1971
2318
|
typeRegistry[typeName] = {
|
|
1972
2319
|
name: typeName,
|
|
1973
2320
|
type: result,
|
|
@@ -1980,7 +2327,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
1980
2327
|
const displayName = memberDisplayNames.get(String(value));
|
|
1981
2328
|
return displayName !== void 0 ? { value, displayName } : { value };
|
|
1982
2329
|
});
|
|
1983
|
-
const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags &
|
|
2330
|
+
const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts3.TypeFlags.BooleanLiteral);
|
|
1984
2331
|
if (isBooleanUnion2) {
|
|
1985
2332
|
const boolNode = { kind: "primitive", primitiveKind: "boolean" };
|
|
1986
2333
|
const result = hasNull ? {
|
|
@@ -2015,14 +2362,15 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
2015
2362
|
} : enumNode;
|
|
2016
2363
|
return registerNamed(result);
|
|
2017
2364
|
}
|
|
2018
|
-
if (
|
|
2365
|
+
if (nonNullMembers.length === 1 && nonNullMembers[0]) {
|
|
2019
2366
|
const inner = resolveTypeNode(
|
|
2020
|
-
|
|
2367
|
+
nonNullMembers[0].memberType,
|
|
2021
2368
|
checker,
|
|
2022
2369
|
file,
|
|
2023
2370
|
typeRegistry,
|
|
2024
2371
|
visiting,
|
|
2025
|
-
sourceNode
|
|
2372
|
+
nonNullMembers[0].sourceNode ?? sourceNode,
|
|
2373
|
+
extensionRegistry
|
|
2026
2374
|
);
|
|
2027
2375
|
const result = hasNull ? {
|
|
2028
2376
|
kind: "union",
|
|
@@ -2030,29 +2378,54 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
2030
2378
|
} : inner;
|
|
2031
2379
|
return registerNamed(result);
|
|
2032
2380
|
}
|
|
2033
|
-
const members =
|
|
2034
|
-
(
|
|
2381
|
+
const members = nonNullMembers.map(
|
|
2382
|
+
({ memberType, sourceNode: memberSourceNode }) => resolveTypeNode(
|
|
2383
|
+
memberType,
|
|
2384
|
+
checker,
|
|
2385
|
+
file,
|
|
2386
|
+
typeRegistry,
|
|
2387
|
+
visiting,
|
|
2388
|
+
memberSourceNode ?? sourceNode,
|
|
2389
|
+
extensionRegistry
|
|
2390
|
+
)
|
|
2035
2391
|
);
|
|
2036
2392
|
if (hasNull) {
|
|
2037
2393
|
members.push({ kind: "primitive", primitiveKind: "null" });
|
|
2038
2394
|
}
|
|
2039
2395
|
return registerNamed({ kind: "union", members });
|
|
2040
2396
|
}
|
|
2041
|
-
function resolveArrayType(type, checker, file, typeRegistry, visiting) {
|
|
2397
|
+
function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
2042
2398
|
const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
|
|
2043
2399
|
const elementType = typeArgs?.[0];
|
|
2044
|
-
const
|
|
2400
|
+
const elementSourceNode = extractArrayElementTypeNode(sourceNode, checker);
|
|
2401
|
+
const items = elementType ? resolveTypeNode(
|
|
2402
|
+
elementType,
|
|
2403
|
+
checker,
|
|
2404
|
+
file,
|
|
2405
|
+
typeRegistry,
|
|
2406
|
+
visiting,
|
|
2407
|
+
elementSourceNode,
|
|
2408
|
+
extensionRegistry
|
|
2409
|
+
) : { kind: "primitive", primitiveKind: "string" };
|
|
2045
2410
|
return { kind: "array", items };
|
|
2046
2411
|
}
|
|
2047
|
-
function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
|
|
2412
|
+
function tryResolveRecordType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2048
2413
|
if (type.getProperties().length > 0) {
|
|
2049
2414
|
return null;
|
|
2050
2415
|
}
|
|
2051
|
-
const indexInfo = checker.getIndexInfoOfType(type,
|
|
2416
|
+
const indexInfo = checker.getIndexInfoOfType(type, ts3.IndexKind.String);
|
|
2052
2417
|
if (!indexInfo) {
|
|
2053
2418
|
return null;
|
|
2054
2419
|
}
|
|
2055
|
-
const valueType = resolveTypeNode(
|
|
2420
|
+
const valueType = resolveTypeNode(
|
|
2421
|
+
indexInfo.type,
|
|
2422
|
+
checker,
|
|
2423
|
+
file,
|
|
2424
|
+
typeRegistry,
|
|
2425
|
+
visiting,
|
|
2426
|
+
void 0,
|
|
2427
|
+
extensionRegistry
|
|
2428
|
+
);
|
|
2056
2429
|
return { kind: "record", valueType };
|
|
2057
2430
|
}
|
|
2058
2431
|
function typeNodeContainsReference(type, targetName) {
|
|
@@ -2080,7 +2453,7 @@ function typeNodeContainsReference(type, targetName) {
|
|
|
2080
2453
|
}
|
|
2081
2454
|
}
|
|
2082
2455
|
}
|
|
2083
|
-
function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
2456
|
+
function resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2084
2457
|
const typeName = getNamedTypeName(type);
|
|
2085
2458
|
const namedTypeName = typeName ?? void 0;
|
|
2086
2459
|
const namedDecl = getNamedTypeDeclaration(type);
|
|
@@ -2111,7 +2484,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
|
2111
2484
|
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
2112
2485
|
}
|
|
2113
2486
|
}
|
|
2114
|
-
const recordNode = tryResolveRecordType(
|
|
2487
|
+
const recordNode = tryResolveRecordType(
|
|
2488
|
+
type,
|
|
2489
|
+
checker,
|
|
2490
|
+
file,
|
|
2491
|
+
typeRegistry,
|
|
2492
|
+
visiting,
|
|
2493
|
+
extensionRegistry
|
|
2494
|
+
);
|
|
2115
2495
|
if (recordNode) {
|
|
2116
2496
|
visiting.delete(type);
|
|
2117
2497
|
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
@@ -2120,7 +2500,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
|
2120
2500
|
clearNamedTypeRegistration();
|
|
2121
2501
|
return recordNode;
|
|
2122
2502
|
}
|
|
2123
|
-
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
|
|
2503
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
2124
2504
|
typeRegistry[namedTypeName] = {
|
|
2125
2505
|
name: namedTypeName,
|
|
2126
2506
|
type: recordNode,
|
|
@@ -2132,19 +2512,27 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
|
2132
2512
|
return recordNode;
|
|
2133
2513
|
}
|
|
2134
2514
|
const properties = [];
|
|
2135
|
-
const fieldInfoMap = getNamedTypeFieldNodeInfoMap(
|
|
2515
|
+
const fieldInfoMap = getNamedTypeFieldNodeInfoMap(
|
|
2516
|
+
type,
|
|
2517
|
+
checker,
|
|
2518
|
+
file,
|
|
2519
|
+
typeRegistry,
|
|
2520
|
+
visiting,
|
|
2521
|
+
extensionRegistry
|
|
2522
|
+
);
|
|
2136
2523
|
for (const prop of type.getProperties()) {
|
|
2137
2524
|
const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
|
|
2138
2525
|
if (!declaration) continue;
|
|
2139
2526
|
const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
|
|
2140
|
-
const optional = !!(prop.flags &
|
|
2527
|
+
const optional = !!(prop.flags & ts3.SymbolFlags.Optional);
|
|
2141
2528
|
const propTypeNode = resolveTypeNode(
|
|
2142
2529
|
propType,
|
|
2143
2530
|
checker,
|
|
2144
2531
|
file,
|
|
2145
2532
|
typeRegistry,
|
|
2146
2533
|
visiting,
|
|
2147
|
-
declaration
|
|
2534
|
+
declaration,
|
|
2535
|
+
extensionRegistry
|
|
2148
2536
|
);
|
|
2149
2537
|
const fieldNodeInfo = fieldInfoMap?.get(prop.name);
|
|
2150
2538
|
properties.push({
|
|
@@ -2163,7 +2551,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
|
2163
2551
|
additionalProperties: true
|
|
2164
2552
|
};
|
|
2165
2553
|
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
2166
|
-
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
|
|
2554
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
2167
2555
|
typeRegistry[namedTypeName] = {
|
|
2168
2556
|
name: namedTypeName,
|
|
2169
2557
|
type: objectNode,
|
|
@@ -2174,19 +2562,26 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
|
2174
2562
|
}
|
|
2175
2563
|
return objectNode;
|
|
2176
2564
|
}
|
|
2177
|
-
function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting) {
|
|
2565
|
+
function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2178
2566
|
const symbols = [type.getSymbol(), type.aliasSymbol].filter(
|
|
2179
2567
|
(s) => s?.declarations != null && s.declarations.length > 0
|
|
2180
2568
|
);
|
|
2181
2569
|
for (const symbol of symbols) {
|
|
2182
2570
|
const declarations = symbol.declarations;
|
|
2183
2571
|
if (!declarations) continue;
|
|
2184
|
-
const classDecl = declarations.find(
|
|
2572
|
+
const classDecl = declarations.find(ts3.isClassDeclaration);
|
|
2185
2573
|
if (classDecl) {
|
|
2186
2574
|
const map = /* @__PURE__ */ new Map();
|
|
2187
2575
|
for (const member of classDecl.members) {
|
|
2188
|
-
if (
|
|
2189
|
-
const fieldNode = analyzeFieldToIR(
|
|
2576
|
+
if (ts3.isPropertyDeclaration(member) && ts3.isIdentifier(member.name)) {
|
|
2577
|
+
const fieldNode = analyzeFieldToIR(
|
|
2578
|
+
member,
|
|
2579
|
+
checker,
|
|
2580
|
+
file,
|
|
2581
|
+
typeRegistry,
|
|
2582
|
+
visiting,
|
|
2583
|
+
extensionRegistry
|
|
2584
|
+
);
|
|
2190
2585
|
if (fieldNode) {
|
|
2191
2586
|
map.set(fieldNode.name, {
|
|
2192
2587
|
constraints: [...fieldNode.constraints],
|
|
@@ -2198,28 +2593,86 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
2198
2593
|
}
|
|
2199
2594
|
return map;
|
|
2200
2595
|
}
|
|
2201
|
-
const interfaceDecl = declarations.find(
|
|
2596
|
+
const interfaceDecl = declarations.find(ts3.isInterfaceDeclaration);
|
|
2202
2597
|
if (interfaceDecl) {
|
|
2203
|
-
return buildFieldNodeInfoMap(
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2598
|
+
return buildFieldNodeInfoMap(
|
|
2599
|
+
interfaceDecl.members,
|
|
2600
|
+
checker,
|
|
2601
|
+
file,
|
|
2602
|
+
typeRegistry,
|
|
2603
|
+
visiting,
|
|
2604
|
+
extensionRegistry
|
|
2605
|
+
);
|
|
2606
|
+
}
|
|
2607
|
+
const typeAliasDecl = declarations.find(ts3.isTypeAliasDeclaration);
|
|
2608
|
+
if (typeAliasDecl && ts3.isTypeLiteralNode(typeAliasDecl.type)) {
|
|
2207
2609
|
return buildFieldNodeInfoMap(
|
|
2208
2610
|
typeAliasDecl.type.members,
|
|
2209
2611
|
checker,
|
|
2210
2612
|
file,
|
|
2211
2613
|
typeRegistry,
|
|
2212
|
-
visiting
|
|
2614
|
+
visiting,
|
|
2615
|
+
extensionRegistry
|
|
2213
2616
|
);
|
|
2214
2617
|
}
|
|
2215
2618
|
}
|
|
2216
2619
|
return null;
|
|
2217
2620
|
}
|
|
2218
|
-
function
|
|
2621
|
+
function extractArrayElementTypeNode(sourceNode, checker) {
|
|
2622
|
+
const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
|
|
2623
|
+
if (typeNode === void 0) {
|
|
2624
|
+
return void 0;
|
|
2625
|
+
}
|
|
2626
|
+
const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
|
|
2627
|
+
if (ts3.isArrayTypeNode(resolvedTypeNode)) {
|
|
2628
|
+
return resolvedTypeNode.elementType;
|
|
2629
|
+
}
|
|
2630
|
+
if (ts3.isTypeReferenceNode(resolvedTypeNode) && ts3.isIdentifier(resolvedTypeNode.typeName) && resolvedTypeNode.typeName.text === "Array" && resolvedTypeNode.typeArguments?.[0]) {
|
|
2631
|
+
return resolvedTypeNode.typeArguments[0];
|
|
2632
|
+
}
|
|
2633
|
+
return void 0;
|
|
2634
|
+
}
|
|
2635
|
+
function extractUnionMemberTypeNodes(sourceNode, checker) {
|
|
2636
|
+
const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
|
|
2637
|
+
if (!typeNode) {
|
|
2638
|
+
return [];
|
|
2639
|
+
}
|
|
2640
|
+
const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
|
|
2641
|
+
return ts3.isUnionTypeNode(resolvedTypeNode) ? [...resolvedTypeNode.types] : [];
|
|
2642
|
+
}
|
|
2643
|
+
function resolveAliasedTypeNode(typeNode, checker, visited = /* @__PURE__ */ new Set()) {
|
|
2644
|
+
if (ts3.isParenthesizedTypeNode(typeNode)) {
|
|
2645
|
+
return resolveAliasedTypeNode(typeNode.type, checker, visited);
|
|
2646
|
+
}
|
|
2647
|
+
if (!ts3.isTypeReferenceNode(typeNode) || !ts3.isIdentifier(typeNode.typeName)) {
|
|
2648
|
+
return typeNode;
|
|
2649
|
+
}
|
|
2650
|
+
const symbol = checker.getSymbolAtLocation(typeNode.typeName);
|
|
2651
|
+
const aliasDecl = symbol?.declarations?.find(ts3.isTypeAliasDeclaration);
|
|
2652
|
+
if (aliasDecl === void 0 || visited.has(aliasDecl)) {
|
|
2653
|
+
return typeNode;
|
|
2654
|
+
}
|
|
2655
|
+
visited.add(aliasDecl);
|
|
2656
|
+
return resolveAliasedTypeNode(aliasDecl.type, checker, visited);
|
|
2657
|
+
}
|
|
2658
|
+
function isNullishTypeNode(typeNode) {
|
|
2659
|
+
if (typeNode.kind === ts3.SyntaxKind.NullKeyword || typeNode.kind === ts3.SyntaxKind.UndefinedKeyword) {
|
|
2660
|
+
return true;
|
|
2661
|
+
}
|
|
2662
|
+
return ts3.isLiteralTypeNode(typeNode) && (typeNode.literal.kind === ts3.SyntaxKind.NullKeyword || typeNode.literal.kind === ts3.SyntaxKind.UndefinedKeyword);
|
|
2663
|
+
}
|
|
2664
|
+
function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
2219
2665
|
const map = /* @__PURE__ */ new Map();
|
|
2220
2666
|
for (const member of members) {
|
|
2221
|
-
if (
|
|
2222
|
-
const fieldNode = analyzeInterfacePropertyToIR(
|
|
2667
|
+
if (ts3.isPropertySignature(member)) {
|
|
2668
|
+
const fieldNode = analyzeInterfacePropertyToIR(
|
|
2669
|
+
member,
|
|
2670
|
+
checker,
|
|
2671
|
+
file,
|
|
2672
|
+
typeRegistry,
|
|
2673
|
+
visiting,
|
|
2674
|
+
extensionRegistry
|
|
2675
|
+
);
|
|
2223
2676
|
if (fieldNode) {
|
|
2224
2677
|
map.set(fieldNode.name, {
|
|
2225
2678
|
constraints: [...fieldNode.constraints],
|
|
@@ -2232,8 +2685,8 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting) {
|
|
|
2232
2685
|
return map;
|
|
2233
2686
|
}
|
|
2234
2687
|
var MAX_ALIAS_CHAIN_DEPTH = 8;
|
|
2235
|
-
function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
|
|
2236
|
-
if (!
|
|
2688
|
+
function extractTypeAliasConstraintNodes(typeNode, checker, file, extensionRegistry, depth = 0) {
|
|
2689
|
+
if (!ts3.isTypeReferenceNode(typeNode)) return [];
|
|
2237
2690
|
if (depth >= MAX_ALIAS_CHAIN_DEPTH) {
|
|
2238
2691
|
const aliasName = typeNode.typeName.getText();
|
|
2239
2692
|
throw new Error(
|
|
@@ -2242,11 +2695,26 @@ function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
|
|
|
2242
2695
|
}
|
|
2243
2696
|
const symbol = checker.getSymbolAtLocation(typeNode.typeName);
|
|
2244
2697
|
if (!symbol?.declarations) return [];
|
|
2245
|
-
const aliasDecl = symbol.declarations.find(
|
|
2698
|
+
const aliasDecl = symbol.declarations.find(ts3.isTypeAliasDeclaration);
|
|
2246
2699
|
if (!aliasDecl) return [];
|
|
2247
|
-
if (
|
|
2248
|
-
const
|
|
2249
|
-
|
|
2700
|
+
if (ts3.isTypeLiteralNode(aliasDecl.type)) return [];
|
|
2701
|
+
const aliasFieldType = resolveTypeNode(
|
|
2702
|
+
checker.getTypeAtLocation(aliasDecl.type),
|
|
2703
|
+
checker,
|
|
2704
|
+
file,
|
|
2705
|
+
{},
|
|
2706
|
+
/* @__PURE__ */ new Set(),
|
|
2707
|
+
aliasDecl.type,
|
|
2708
|
+
extensionRegistry
|
|
2709
|
+
);
|
|
2710
|
+
const constraints = extractJSDocConstraintNodes(
|
|
2711
|
+
aliasDecl,
|
|
2712
|
+
file,
|
|
2713
|
+
makeParseOptions(extensionRegistry, aliasFieldType)
|
|
2714
|
+
);
|
|
2715
|
+
constraints.push(
|
|
2716
|
+
...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, extensionRegistry, depth + 1)
|
|
2717
|
+
);
|
|
2250
2718
|
return constraints;
|
|
2251
2719
|
}
|
|
2252
2720
|
function provenanceForNode(node, file) {
|
|
@@ -2272,14 +2740,14 @@ function getNamedTypeName(type) {
|
|
|
2272
2740
|
const symbol = type.getSymbol();
|
|
2273
2741
|
if (symbol?.declarations) {
|
|
2274
2742
|
const decl = symbol.declarations[0];
|
|
2275
|
-
if (decl && (
|
|
2276
|
-
const name =
|
|
2743
|
+
if (decl && (ts3.isClassDeclaration(decl) || ts3.isInterfaceDeclaration(decl) || ts3.isTypeAliasDeclaration(decl))) {
|
|
2744
|
+
const name = ts3.isClassDeclaration(decl) ? decl.name?.text : decl.name.text;
|
|
2277
2745
|
if (name) return name;
|
|
2278
2746
|
}
|
|
2279
2747
|
}
|
|
2280
2748
|
const aliasSymbol = type.aliasSymbol;
|
|
2281
2749
|
if (aliasSymbol?.declarations) {
|
|
2282
|
-
const aliasDecl = aliasSymbol.declarations.find(
|
|
2750
|
+
const aliasDecl = aliasSymbol.declarations.find(ts3.isTypeAliasDeclaration);
|
|
2283
2751
|
if (aliasDecl) {
|
|
2284
2752
|
return aliasDecl.name.text;
|
|
2285
2753
|
}
|
|
@@ -2290,24 +2758,24 @@ function getNamedTypeDeclaration(type) {
|
|
|
2290
2758
|
const symbol = type.getSymbol();
|
|
2291
2759
|
if (symbol?.declarations) {
|
|
2292
2760
|
const decl = symbol.declarations[0];
|
|
2293
|
-
if (decl && (
|
|
2761
|
+
if (decl && (ts3.isClassDeclaration(decl) || ts3.isInterfaceDeclaration(decl) || ts3.isTypeAliasDeclaration(decl))) {
|
|
2294
2762
|
return decl;
|
|
2295
2763
|
}
|
|
2296
2764
|
}
|
|
2297
2765
|
const aliasSymbol = type.aliasSymbol;
|
|
2298
2766
|
if (aliasSymbol?.declarations) {
|
|
2299
|
-
return aliasSymbol.declarations.find(
|
|
2767
|
+
return aliasSymbol.declarations.find(ts3.isTypeAliasDeclaration);
|
|
2300
2768
|
}
|
|
2301
2769
|
return void 0;
|
|
2302
2770
|
}
|
|
2303
2771
|
function analyzeMethod(method, checker) {
|
|
2304
|
-
if (!
|
|
2772
|
+
if (!ts3.isIdentifier(method.name)) {
|
|
2305
2773
|
return null;
|
|
2306
2774
|
}
|
|
2307
2775
|
const name = method.name.text;
|
|
2308
2776
|
const parameters = [];
|
|
2309
2777
|
for (const param of method.parameters) {
|
|
2310
|
-
if (
|
|
2778
|
+
if (ts3.isIdentifier(param.name)) {
|
|
2311
2779
|
const paramInfo = analyzeParameter(param, checker);
|
|
2312
2780
|
parameters.push(paramInfo);
|
|
2313
2781
|
}
|
|
@@ -2318,7 +2786,7 @@ function analyzeMethod(method, checker) {
|
|
|
2318
2786
|
return { name, parameters, returnTypeNode, returnType };
|
|
2319
2787
|
}
|
|
2320
2788
|
function analyzeParameter(param, checker) {
|
|
2321
|
-
const name =
|
|
2789
|
+
const name = ts3.isIdentifier(param.name) ? param.name.text : "param";
|
|
2322
2790
|
const typeNode = param.type;
|
|
2323
2791
|
const type = checker.getTypeAtLocation(param);
|
|
2324
2792
|
const formSpecExportName = detectFormSpecReference(typeNode);
|
|
@@ -2327,67 +2795,966 @@ function analyzeParameter(param, checker) {
|
|
|
2327
2795
|
}
|
|
2328
2796
|
function detectFormSpecReference(typeNode) {
|
|
2329
2797
|
if (!typeNode) return null;
|
|
2330
|
-
if (!
|
|
2331
|
-
const typeName =
|
|
2798
|
+
if (!ts3.isTypeReferenceNode(typeNode)) return null;
|
|
2799
|
+
const typeName = ts3.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : ts3.isQualifiedName(typeNode.typeName) ? typeNode.typeName.right.text : null;
|
|
2332
2800
|
if (typeName !== "InferSchema" && typeName !== "InferFormSchema") return null;
|
|
2333
2801
|
const typeArg = typeNode.typeArguments?.[0];
|
|
2334
|
-
if (!typeArg || !
|
|
2335
|
-
if (
|
|
2802
|
+
if (!typeArg || !ts3.isTypeQueryNode(typeArg)) return null;
|
|
2803
|
+
if (ts3.isIdentifier(typeArg.exprName)) {
|
|
2336
2804
|
return typeArg.exprName.text;
|
|
2337
2805
|
}
|
|
2338
|
-
if (
|
|
2806
|
+
if (ts3.isQualifiedName(typeArg.exprName)) {
|
|
2339
2807
|
return typeArg.exprName.right.text;
|
|
2340
2808
|
}
|
|
2341
2809
|
return null;
|
|
2342
2810
|
}
|
|
2343
2811
|
|
|
2812
|
+
// src/analyzer/program.ts
|
|
2813
|
+
function createProgramContext(filePath) {
|
|
2814
|
+
const absolutePath = path.resolve(filePath);
|
|
2815
|
+
const fileDir = path.dirname(absolutePath);
|
|
2816
|
+
const configPath = ts4.findConfigFile(fileDir, ts4.sys.fileExists.bind(ts4.sys), "tsconfig.json");
|
|
2817
|
+
let compilerOptions;
|
|
2818
|
+
let fileNames;
|
|
2819
|
+
if (configPath) {
|
|
2820
|
+
const configFile = ts4.readConfigFile(configPath, ts4.sys.readFile.bind(ts4.sys));
|
|
2821
|
+
if (configFile.error) {
|
|
2822
|
+
throw new Error(
|
|
2823
|
+
`Error reading tsconfig.json: ${ts4.flattenDiagnosticMessageText(configFile.error.messageText, "\n")}`
|
|
2824
|
+
);
|
|
2825
|
+
}
|
|
2826
|
+
const parsed = ts4.parseJsonConfigFileContent(
|
|
2827
|
+
configFile.config,
|
|
2828
|
+
ts4.sys,
|
|
2829
|
+
path.dirname(configPath)
|
|
2830
|
+
);
|
|
2831
|
+
if (parsed.errors.length > 0) {
|
|
2832
|
+
const errorMessages = parsed.errors.map((e) => ts4.flattenDiagnosticMessageText(e.messageText, "\n")).join("\n");
|
|
2833
|
+
throw new Error(`Error parsing tsconfig.json: ${errorMessages}`);
|
|
2834
|
+
}
|
|
2835
|
+
compilerOptions = parsed.options;
|
|
2836
|
+
fileNames = parsed.fileNames.includes(absolutePath) ? parsed.fileNames : [...parsed.fileNames, absolutePath];
|
|
2837
|
+
} else {
|
|
2838
|
+
compilerOptions = {
|
|
2839
|
+
target: ts4.ScriptTarget.ES2022,
|
|
2840
|
+
module: ts4.ModuleKind.NodeNext,
|
|
2841
|
+
moduleResolution: ts4.ModuleResolutionKind.NodeNext,
|
|
2842
|
+
strict: true,
|
|
2843
|
+
skipLibCheck: true,
|
|
2844
|
+
declaration: true
|
|
2845
|
+
};
|
|
2846
|
+
fileNames = [absolutePath];
|
|
2847
|
+
}
|
|
2848
|
+
const program = ts4.createProgram(fileNames, compilerOptions);
|
|
2849
|
+
const sourceFile = program.getSourceFile(absolutePath);
|
|
2850
|
+
if (!sourceFile) {
|
|
2851
|
+
throw new Error(`Could not find source file: ${absolutePath}`);
|
|
2852
|
+
}
|
|
2853
|
+
return {
|
|
2854
|
+
program,
|
|
2855
|
+
checker: program.getTypeChecker(),
|
|
2856
|
+
sourceFile
|
|
2857
|
+
};
|
|
2858
|
+
}
|
|
2859
|
+
function findNodeByName(sourceFile, name, predicate, getName) {
|
|
2860
|
+
let result = null;
|
|
2861
|
+
function visit(node) {
|
|
2862
|
+
if (result) return;
|
|
2863
|
+
if (predicate(node) && getName(node) === name) {
|
|
2864
|
+
result = node;
|
|
2865
|
+
return;
|
|
2866
|
+
}
|
|
2867
|
+
ts4.forEachChild(node, visit);
|
|
2868
|
+
}
|
|
2869
|
+
visit(sourceFile);
|
|
2870
|
+
return result;
|
|
2871
|
+
}
|
|
2872
|
+
function findClassByName(sourceFile, className) {
|
|
2873
|
+
return findNodeByName(sourceFile, className, ts4.isClassDeclaration, (n) => n.name?.text);
|
|
2874
|
+
}
|
|
2875
|
+
function findInterfaceByName(sourceFile, interfaceName) {
|
|
2876
|
+
return findNodeByName(sourceFile, interfaceName, ts4.isInterfaceDeclaration, (n) => n.name.text);
|
|
2877
|
+
}
|
|
2878
|
+
function findTypeAliasByName(sourceFile, aliasName) {
|
|
2879
|
+
return findNodeByName(sourceFile, aliasName, ts4.isTypeAliasDeclaration, (n) => n.name.text);
|
|
2880
|
+
}
|
|
2881
|
+
function analyzeNamedTypeToIR(filePath, typeName, extensionRegistry) {
|
|
2882
|
+
const ctx = createProgramContext(filePath);
|
|
2883
|
+
const classDecl = findClassByName(ctx.sourceFile, typeName);
|
|
2884
|
+
if (classDecl !== null) {
|
|
2885
|
+
return analyzeClassToIR(classDecl, ctx.checker, filePath, extensionRegistry);
|
|
2886
|
+
}
|
|
2887
|
+
const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
|
|
2888
|
+
if (interfaceDecl !== null) {
|
|
2889
|
+
return analyzeInterfaceToIR(interfaceDecl, ctx.checker, filePath, extensionRegistry);
|
|
2890
|
+
}
|
|
2891
|
+
const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
|
|
2892
|
+
if (typeAlias !== null) {
|
|
2893
|
+
const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, filePath, extensionRegistry);
|
|
2894
|
+
if (result.ok) {
|
|
2895
|
+
return result.analysis;
|
|
2896
|
+
}
|
|
2897
|
+
throw new Error(result.error);
|
|
2898
|
+
}
|
|
2899
|
+
throw new Error(
|
|
2900
|
+
`Type "${typeName}" not found as a class, interface, or type alias in ${filePath}`
|
|
2901
|
+
);
|
|
2902
|
+
}
|
|
2903
|
+
|
|
2904
|
+
// src/validate/constraint-validator.ts
|
|
2905
|
+
import { normalizeConstraintTagName as normalizeConstraintTagName2 } from "@formspec/core";
|
|
2906
|
+
function addContradiction(ctx, message, primary, related) {
|
|
2907
|
+
ctx.diagnostics.push({
|
|
2908
|
+
code: "CONTRADICTING_CONSTRAINTS",
|
|
2909
|
+
message,
|
|
2910
|
+
severity: "error",
|
|
2911
|
+
primaryLocation: primary,
|
|
2912
|
+
relatedLocations: [related]
|
|
2913
|
+
});
|
|
2914
|
+
}
|
|
2915
|
+
function addTypeMismatch(ctx, message, primary) {
|
|
2916
|
+
ctx.diagnostics.push({
|
|
2917
|
+
code: "TYPE_MISMATCH",
|
|
2918
|
+
message,
|
|
2919
|
+
severity: "error",
|
|
2920
|
+
primaryLocation: primary,
|
|
2921
|
+
relatedLocations: []
|
|
2922
|
+
});
|
|
2923
|
+
}
|
|
2924
|
+
function addUnknownExtension(ctx, message, primary) {
|
|
2925
|
+
ctx.diagnostics.push({
|
|
2926
|
+
code: "UNKNOWN_EXTENSION",
|
|
2927
|
+
message,
|
|
2928
|
+
severity: "warning",
|
|
2929
|
+
primaryLocation: primary,
|
|
2930
|
+
relatedLocations: []
|
|
2931
|
+
});
|
|
2932
|
+
}
|
|
2933
|
+
function addUnknownPathTarget(ctx, message, primary) {
|
|
2934
|
+
ctx.diagnostics.push({
|
|
2935
|
+
code: "UNKNOWN_PATH_TARGET",
|
|
2936
|
+
message,
|
|
2937
|
+
severity: "error",
|
|
2938
|
+
primaryLocation: primary,
|
|
2939
|
+
relatedLocations: []
|
|
2940
|
+
});
|
|
2941
|
+
}
|
|
2942
|
+
function addConstraintBroadening(ctx, message, primary, related) {
|
|
2943
|
+
ctx.diagnostics.push({
|
|
2944
|
+
code: "CONSTRAINT_BROADENING",
|
|
2945
|
+
message,
|
|
2946
|
+
severity: "error",
|
|
2947
|
+
primaryLocation: primary,
|
|
2948
|
+
relatedLocations: [related]
|
|
2949
|
+
});
|
|
2950
|
+
}
|
|
2951
|
+
function getExtensionIdFromConstraintId(constraintId) {
|
|
2952
|
+
const separator = constraintId.lastIndexOf("/");
|
|
2953
|
+
if (separator <= 0) {
|
|
2954
|
+
return null;
|
|
2955
|
+
}
|
|
2956
|
+
return constraintId.slice(0, separator);
|
|
2957
|
+
}
|
|
2958
|
+
function findNumeric(constraints, constraintKind) {
|
|
2959
|
+
return constraints.find((c) => c.constraintKind === constraintKind);
|
|
2960
|
+
}
|
|
2961
|
+
function findLength(constraints, constraintKind) {
|
|
2962
|
+
return constraints.find((c) => c.constraintKind === constraintKind);
|
|
2963
|
+
}
|
|
2964
|
+
function findAllowedMembers(constraints) {
|
|
2965
|
+
return constraints.filter(
|
|
2966
|
+
(c) => c.constraintKind === "allowedMembers"
|
|
2967
|
+
);
|
|
2968
|
+
}
|
|
2969
|
+
function findConstConstraints(constraints) {
|
|
2970
|
+
return constraints.filter(
|
|
2971
|
+
(c) => c.constraintKind === "const"
|
|
2972
|
+
);
|
|
2973
|
+
}
|
|
2974
|
+
function jsonValueEquals(left, right) {
|
|
2975
|
+
if (left === right) {
|
|
2976
|
+
return true;
|
|
2977
|
+
}
|
|
2978
|
+
if (Array.isArray(left) || Array.isArray(right)) {
|
|
2979
|
+
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
|
|
2980
|
+
return false;
|
|
2981
|
+
}
|
|
2982
|
+
return left.every((item, index) => jsonValueEquals(item, right[index]));
|
|
2983
|
+
}
|
|
2984
|
+
if (isJsonObject(left) || isJsonObject(right)) {
|
|
2985
|
+
if (!isJsonObject(left) || !isJsonObject(right)) {
|
|
2986
|
+
return false;
|
|
2987
|
+
}
|
|
2988
|
+
const leftKeys = Object.keys(left).sort();
|
|
2989
|
+
const rightKeys = Object.keys(right).sort();
|
|
2990
|
+
if (leftKeys.length !== rightKeys.length) {
|
|
2991
|
+
return false;
|
|
2992
|
+
}
|
|
2993
|
+
return leftKeys.every((key, index) => {
|
|
2994
|
+
const rightKey = rightKeys[index];
|
|
2995
|
+
if (rightKey !== key) {
|
|
2996
|
+
return false;
|
|
2997
|
+
}
|
|
2998
|
+
const leftValue = left[key];
|
|
2999
|
+
const rightValue = right[rightKey];
|
|
3000
|
+
return leftValue !== void 0 && rightValue !== void 0 && jsonValueEquals(leftValue, rightValue);
|
|
3001
|
+
});
|
|
3002
|
+
}
|
|
3003
|
+
return false;
|
|
3004
|
+
}
|
|
3005
|
+
function isJsonObject(value) {
|
|
3006
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3007
|
+
}
|
|
3008
|
+
function isOrderedBoundConstraint(constraint) {
|
|
3009
|
+
return constraint.constraintKind === "minimum" || constraint.constraintKind === "exclusiveMinimum" || constraint.constraintKind === "minLength" || constraint.constraintKind === "minItems" || constraint.constraintKind === "maximum" || constraint.constraintKind === "exclusiveMaximum" || constraint.constraintKind === "maxLength" || constraint.constraintKind === "maxItems";
|
|
3010
|
+
}
|
|
3011
|
+
function pathKey(constraint) {
|
|
3012
|
+
return constraint.path?.segments.join(".") ?? "";
|
|
3013
|
+
}
|
|
3014
|
+
function orderedBoundFamily(kind) {
|
|
3015
|
+
switch (kind) {
|
|
3016
|
+
case "minimum":
|
|
3017
|
+
case "exclusiveMinimum":
|
|
3018
|
+
return "numeric-lower";
|
|
3019
|
+
case "maximum":
|
|
3020
|
+
case "exclusiveMaximum":
|
|
3021
|
+
return "numeric-upper";
|
|
3022
|
+
case "minLength":
|
|
3023
|
+
return "minLength";
|
|
3024
|
+
case "minItems":
|
|
3025
|
+
return "minItems";
|
|
3026
|
+
case "maxLength":
|
|
3027
|
+
return "maxLength";
|
|
3028
|
+
case "maxItems":
|
|
3029
|
+
return "maxItems";
|
|
3030
|
+
default: {
|
|
3031
|
+
const _exhaustive = kind;
|
|
3032
|
+
return _exhaustive;
|
|
3033
|
+
}
|
|
3034
|
+
}
|
|
3035
|
+
}
|
|
3036
|
+
function isNumericLowerKind(kind) {
|
|
3037
|
+
return kind === "minimum" || kind === "exclusiveMinimum";
|
|
3038
|
+
}
|
|
3039
|
+
function isNumericUpperKind(kind) {
|
|
3040
|
+
return kind === "maximum" || kind === "exclusiveMaximum";
|
|
3041
|
+
}
|
|
3042
|
+
function describeConstraintTag(constraint) {
|
|
3043
|
+
return `@${constraint.constraintKind}`;
|
|
3044
|
+
}
|
|
3045
|
+
function compareConstraintStrength(current, previous) {
|
|
3046
|
+
const family = orderedBoundFamily(current.constraintKind);
|
|
3047
|
+
if (family === "numeric-lower") {
|
|
3048
|
+
if (!isNumericLowerKind(current.constraintKind) || !isNumericLowerKind(previous.constraintKind)) {
|
|
3049
|
+
throw new Error("numeric-lower family received non-numeric lower-bound constraint");
|
|
3050
|
+
}
|
|
3051
|
+
if (current.value !== previous.value) {
|
|
3052
|
+
return current.value > previous.value ? 1 : -1;
|
|
3053
|
+
}
|
|
3054
|
+
if (current.constraintKind === "exclusiveMinimum" && previous.constraintKind === "minimum") {
|
|
3055
|
+
return 1;
|
|
3056
|
+
}
|
|
3057
|
+
if (current.constraintKind === "minimum" && previous.constraintKind === "exclusiveMinimum") {
|
|
3058
|
+
return -1;
|
|
3059
|
+
}
|
|
3060
|
+
return 0;
|
|
3061
|
+
}
|
|
3062
|
+
if (family === "numeric-upper") {
|
|
3063
|
+
if (!isNumericUpperKind(current.constraintKind) || !isNumericUpperKind(previous.constraintKind)) {
|
|
3064
|
+
throw new Error("numeric-upper family received non-numeric upper-bound constraint");
|
|
3065
|
+
}
|
|
3066
|
+
if (current.value !== previous.value) {
|
|
3067
|
+
return current.value < previous.value ? 1 : -1;
|
|
3068
|
+
}
|
|
3069
|
+
if (current.constraintKind === "exclusiveMaximum" && previous.constraintKind === "maximum") {
|
|
3070
|
+
return 1;
|
|
3071
|
+
}
|
|
3072
|
+
if (current.constraintKind === "maximum" && previous.constraintKind === "exclusiveMaximum") {
|
|
3073
|
+
return -1;
|
|
3074
|
+
}
|
|
3075
|
+
return 0;
|
|
3076
|
+
}
|
|
3077
|
+
switch (family) {
|
|
3078
|
+
case "minLength":
|
|
3079
|
+
case "minItems":
|
|
3080
|
+
if (current.value === previous.value) {
|
|
3081
|
+
return 0;
|
|
3082
|
+
}
|
|
3083
|
+
return current.value > previous.value ? 1 : -1;
|
|
3084
|
+
case "maxLength":
|
|
3085
|
+
case "maxItems":
|
|
3086
|
+
if (current.value === previous.value) {
|
|
3087
|
+
return 0;
|
|
3088
|
+
}
|
|
3089
|
+
return current.value < previous.value ? 1 : -1;
|
|
3090
|
+
default: {
|
|
3091
|
+
const _exhaustive = family;
|
|
3092
|
+
return _exhaustive;
|
|
3093
|
+
}
|
|
3094
|
+
}
|
|
3095
|
+
}
|
|
3096
|
+
function checkConstraintBroadening(ctx, fieldName, constraints) {
|
|
3097
|
+
const strongestByKey = /* @__PURE__ */ new Map();
|
|
3098
|
+
for (const constraint of constraints) {
|
|
3099
|
+
if (!isOrderedBoundConstraint(constraint)) {
|
|
3100
|
+
continue;
|
|
3101
|
+
}
|
|
3102
|
+
const key = `${orderedBoundFamily(constraint.constraintKind)}:${pathKey(constraint)}`;
|
|
3103
|
+
const previous = strongestByKey.get(key);
|
|
3104
|
+
if (previous === void 0) {
|
|
3105
|
+
strongestByKey.set(key, constraint);
|
|
3106
|
+
continue;
|
|
3107
|
+
}
|
|
3108
|
+
const strength = compareConstraintStrength(constraint, previous);
|
|
3109
|
+
if (strength < 0) {
|
|
3110
|
+
const displayFieldName = formatPathTargetFieldName(
|
|
3111
|
+
fieldName,
|
|
3112
|
+
constraint.path?.segments ?? []
|
|
3113
|
+
);
|
|
3114
|
+
addConstraintBroadening(
|
|
3115
|
+
ctx,
|
|
3116
|
+
`Field "${displayFieldName}": ${describeConstraintTag(constraint)} (${String(constraint.value)}) is broader than earlier ${describeConstraintTag(previous)} (${String(previous.value)}). Constraints can only narrow.`,
|
|
3117
|
+
constraint.provenance,
|
|
3118
|
+
previous.provenance
|
|
3119
|
+
);
|
|
3120
|
+
continue;
|
|
3121
|
+
}
|
|
3122
|
+
if (strength <= 0) {
|
|
3123
|
+
continue;
|
|
3124
|
+
}
|
|
3125
|
+
strongestByKey.set(key, constraint);
|
|
3126
|
+
}
|
|
3127
|
+
}
|
|
3128
|
+
function compareCustomConstraintStrength(current, previous) {
|
|
3129
|
+
const order = current.comparePayloads(current.constraint.payload, previous.constraint.payload);
|
|
3130
|
+
const equalPayloadTiebreaker = order === 0 ? compareSemanticInclusivity(current.role.inclusive, previous.role.inclusive) : order;
|
|
3131
|
+
switch (current.role.bound) {
|
|
3132
|
+
case "lower":
|
|
3133
|
+
return equalPayloadTiebreaker;
|
|
3134
|
+
case "upper":
|
|
3135
|
+
return equalPayloadTiebreaker === 0 ? 0 : -equalPayloadTiebreaker;
|
|
3136
|
+
case "exact":
|
|
3137
|
+
return order === 0 ? 0 : Number.NaN;
|
|
3138
|
+
default: {
|
|
3139
|
+
const _exhaustive = current.role.bound;
|
|
3140
|
+
return _exhaustive;
|
|
3141
|
+
}
|
|
3142
|
+
}
|
|
3143
|
+
}
|
|
3144
|
+
function compareSemanticInclusivity(currentInclusive, previousInclusive) {
|
|
3145
|
+
if (currentInclusive === previousInclusive) {
|
|
3146
|
+
return 0;
|
|
3147
|
+
}
|
|
3148
|
+
return currentInclusive ? -1 : 1;
|
|
3149
|
+
}
|
|
3150
|
+
function customConstraintsContradict(lower, upper) {
|
|
3151
|
+
const order = lower.comparePayloads(lower.constraint.payload, upper.constraint.payload);
|
|
3152
|
+
if (order > 0) {
|
|
3153
|
+
return true;
|
|
3154
|
+
}
|
|
3155
|
+
if (order < 0) {
|
|
3156
|
+
return false;
|
|
3157
|
+
}
|
|
3158
|
+
return !lower.role.inclusive || !upper.role.inclusive;
|
|
3159
|
+
}
|
|
3160
|
+
function describeCustomConstraintTag(constraint) {
|
|
3161
|
+
return constraint.provenance.tagName ?? constraint.constraintId;
|
|
3162
|
+
}
|
|
3163
|
+
function checkCustomConstraintSemantics(ctx, fieldName, constraints) {
|
|
3164
|
+
if (ctx.extensionRegistry === void 0) {
|
|
3165
|
+
return;
|
|
3166
|
+
}
|
|
3167
|
+
const strongestByKey = /* @__PURE__ */ new Map();
|
|
3168
|
+
const lowerByFamily = /* @__PURE__ */ new Map();
|
|
3169
|
+
const upperByFamily = /* @__PURE__ */ new Map();
|
|
3170
|
+
for (const constraint of constraints) {
|
|
3171
|
+
if (constraint.constraintKind !== "custom") {
|
|
3172
|
+
continue;
|
|
3173
|
+
}
|
|
3174
|
+
const registration = ctx.extensionRegistry.findConstraint(constraint.constraintId);
|
|
3175
|
+
if (registration?.comparePayloads === void 0 || registration.semanticRole === void 0) {
|
|
3176
|
+
continue;
|
|
3177
|
+
}
|
|
3178
|
+
const entry = {
|
|
3179
|
+
constraint,
|
|
3180
|
+
comparePayloads: registration.comparePayloads,
|
|
3181
|
+
role: registration.semanticRole
|
|
3182
|
+
};
|
|
3183
|
+
const familyKey = `${registration.semanticRole.family}:${pathKey(constraint)}`;
|
|
3184
|
+
const boundKey = `${familyKey}:${registration.semanticRole.bound}`;
|
|
3185
|
+
const previous = strongestByKey.get(boundKey);
|
|
3186
|
+
if (previous !== void 0) {
|
|
3187
|
+
const strength = compareCustomConstraintStrength(entry, previous);
|
|
3188
|
+
if (Number.isNaN(strength)) {
|
|
3189
|
+
addContradiction(
|
|
3190
|
+
ctx,
|
|
3191
|
+
`Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} conflicts with ${describeCustomConstraintTag(previous.constraint)}`,
|
|
3192
|
+
constraint.provenance,
|
|
3193
|
+
previous.constraint.provenance
|
|
3194
|
+
);
|
|
3195
|
+
continue;
|
|
3196
|
+
}
|
|
3197
|
+
if (strength < 0) {
|
|
3198
|
+
addConstraintBroadening(
|
|
3199
|
+
ctx,
|
|
3200
|
+
`Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} is broader than earlier ${describeCustomConstraintTag(previous.constraint)}. Constraints can only narrow.`,
|
|
3201
|
+
constraint.provenance,
|
|
3202
|
+
previous.constraint.provenance
|
|
3203
|
+
);
|
|
3204
|
+
continue;
|
|
3205
|
+
}
|
|
3206
|
+
if (strength > 0) {
|
|
3207
|
+
strongestByKey.set(boundKey, entry);
|
|
3208
|
+
}
|
|
3209
|
+
} else {
|
|
3210
|
+
strongestByKey.set(boundKey, entry);
|
|
3211
|
+
}
|
|
3212
|
+
if (registration.semanticRole.bound === "lower") {
|
|
3213
|
+
lowerByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
|
|
3214
|
+
} else if (registration.semanticRole.bound === "upper") {
|
|
3215
|
+
upperByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
|
|
3216
|
+
}
|
|
3217
|
+
}
|
|
3218
|
+
for (const [familyKey, lower] of lowerByFamily) {
|
|
3219
|
+
const upper = upperByFamily.get(familyKey);
|
|
3220
|
+
if (upper === void 0) {
|
|
3221
|
+
continue;
|
|
3222
|
+
}
|
|
3223
|
+
if (!customConstraintsContradict(lower, upper)) {
|
|
3224
|
+
continue;
|
|
3225
|
+
}
|
|
3226
|
+
addContradiction(
|
|
3227
|
+
ctx,
|
|
3228
|
+
`Field "${formatPathTargetFieldName(fieldName, lower.constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(lower.constraint)} contradicts ${describeCustomConstraintTag(upper.constraint)}`,
|
|
3229
|
+
lower.constraint.provenance,
|
|
3230
|
+
upper.constraint.provenance
|
|
3231
|
+
);
|
|
3232
|
+
}
|
|
3233
|
+
}
|
|
3234
|
+
function checkNumericContradictions(ctx, fieldName, constraints) {
|
|
3235
|
+
const min = findNumeric(constraints, "minimum");
|
|
3236
|
+
const max = findNumeric(constraints, "maximum");
|
|
3237
|
+
const exMin = findNumeric(constraints, "exclusiveMinimum");
|
|
3238
|
+
const exMax = findNumeric(constraints, "exclusiveMaximum");
|
|
3239
|
+
if (min !== void 0 && max !== void 0 && min.value > max.value) {
|
|
3240
|
+
addContradiction(
|
|
3241
|
+
ctx,
|
|
3242
|
+
`Field "${fieldName}": minimum (${String(min.value)}) is greater than maximum (${String(max.value)})`,
|
|
3243
|
+
min.provenance,
|
|
3244
|
+
max.provenance
|
|
3245
|
+
);
|
|
3246
|
+
}
|
|
3247
|
+
if (exMin !== void 0 && max !== void 0 && exMin.value >= max.value) {
|
|
3248
|
+
addContradiction(
|
|
3249
|
+
ctx,
|
|
3250
|
+
`Field "${fieldName}": exclusiveMinimum (${String(exMin.value)}) is greater than or equal to maximum (${String(max.value)})`,
|
|
3251
|
+
exMin.provenance,
|
|
3252
|
+
max.provenance
|
|
3253
|
+
);
|
|
3254
|
+
}
|
|
3255
|
+
if (min !== void 0 && exMax !== void 0 && min.value >= exMax.value) {
|
|
3256
|
+
addContradiction(
|
|
3257
|
+
ctx,
|
|
3258
|
+
`Field "${fieldName}": minimum (${String(min.value)}) is greater than or equal to exclusiveMaximum (${String(exMax.value)})`,
|
|
3259
|
+
min.provenance,
|
|
3260
|
+
exMax.provenance
|
|
3261
|
+
);
|
|
3262
|
+
}
|
|
3263
|
+
if (exMin !== void 0 && exMax !== void 0 && exMin.value >= exMax.value) {
|
|
3264
|
+
addContradiction(
|
|
3265
|
+
ctx,
|
|
3266
|
+
`Field "${fieldName}": exclusiveMinimum (${String(exMin.value)}) is greater than or equal to exclusiveMaximum (${String(exMax.value)})`,
|
|
3267
|
+
exMin.provenance,
|
|
3268
|
+
exMax.provenance
|
|
3269
|
+
);
|
|
3270
|
+
}
|
|
3271
|
+
}
|
|
3272
|
+
function checkLengthContradictions(ctx, fieldName, constraints) {
|
|
3273
|
+
const minLen = findLength(constraints, "minLength");
|
|
3274
|
+
const maxLen = findLength(constraints, "maxLength");
|
|
3275
|
+
if (minLen !== void 0 && maxLen !== void 0 && minLen.value > maxLen.value) {
|
|
3276
|
+
addContradiction(
|
|
3277
|
+
ctx,
|
|
3278
|
+
`Field "${fieldName}": minLength (${String(minLen.value)}) is greater than maxLength (${String(maxLen.value)})`,
|
|
3279
|
+
minLen.provenance,
|
|
3280
|
+
maxLen.provenance
|
|
3281
|
+
);
|
|
3282
|
+
}
|
|
3283
|
+
const minItems = findLength(constraints, "minItems");
|
|
3284
|
+
const maxItems = findLength(constraints, "maxItems");
|
|
3285
|
+
if (minItems !== void 0 && maxItems !== void 0 && minItems.value > maxItems.value) {
|
|
3286
|
+
addContradiction(
|
|
3287
|
+
ctx,
|
|
3288
|
+
`Field "${fieldName}": minItems (${String(minItems.value)}) is greater than maxItems (${String(maxItems.value)})`,
|
|
3289
|
+
minItems.provenance,
|
|
3290
|
+
maxItems.provenance
|
|
3291
|
+
);
|
|
3292
|
+
}
|
|
3293
|
+
}
|
|
3294
|
+
function checkAllowedMembersContradiction(ctx, fieldName, constraints) {
|
|
3295
|
+
const members = findAllowedMembers(constraints);
|
|
3296
|
+
if (members.length < 2) return;
|
|
3297
|
+
const firstSet = new Set(members[0]?.members ?? []);
|
|
3298
|
+
for (let i = 1; i < members.length; i++) {
|
|
3299
|
+
const current = members[i];
|
|
3300
|
+
if (current === void 0) continue;
|
|
3301
|
+
for (const m of firstSet) {
|
|
3302
|
+
if (!current.members.includes(m)) {
|
|
3303
|
+
firstSet.delete(m);
|
|
3304
|
+
}
|
|
3305
|
+
}
|
|
3306
|
+
}
|
|
3307
|
+
if (firstSet.size === 0) {
|
|
3308
|
+
const first = members[0];
|
|
3309
|
+
const second = members[1];
|
|
3310
|
+
if (first !== void 0 && second !== void 0) {
|
|
3311
|
+
addContradiction(
|
|
3312
|
+
ctx,
|
|
3313
|
+
`Field "${fieldName}": allowedMembers constraints have an empty intersection (no valid values remain)`,
|
|
3314
|
+
first.provenance,
|
|
3315
|
+
second.provenance
|
|
3316
|
+
);
|
|
3317
|
+
}
|
|
3318
|
+
}
|
|
3319
|
+
}
|
|
3320
|
+
function checkConstContradictions(ctx, fieldName, constraints) {
|
|
3321
|
+
const constConstraints = findConstConstraints(constraints);
|
|
3322
|
+
if (constConstraints.length < 2) return;
|
|
3323
|
+
const first = constConstraints[0];
|
|
3324
|
+
if (first === void 0) return;
|
|
3325
|
+
for (let i = 1; i < constConstraints.length; i++) {
|
|
3326
|
+
const current = constConstraints[i];
|
|
3327
|
+
if (current === void 0) continue;
|
|
3328
|
+
if (jsonValueEquals(first.value, current.value)) {
|
|
3329
|
+
continue;
|
|
3330
|
+
}
|
|
3331
|
+
addContradiction(
|
|
3332
|
+
ctx,
|
|
3333
|
+
`Field "${fieldName}": conflicting @const constraints require both ${JSON.stringify(first.value)} and ${JSON.stringify(current.value)}`,
|
|
3334
|
+
first.provenance,
|
|
3335
|
+
current.provenance
|
|
3336
|
+
);
|
|
3337
|
+
}
|
|
3338
|
+
}
|
|
3339
|
+
function typeLabel(type) {
|
|
3340
|
+
switch (type.kind) {
|
|
3341
|
+
case "primitive":
|
|
3342
|
+
return type.primitiveKind;
|
|
3343
|
+
case "enum":
|
|
3344
|
+
return "enum";
|
|
3345
|
+
case "array":
|
|
3346
|
+
return "array";
|
|
3347
|
+
case "object":
|
|
3348
|
+
return "object";
|
|
3349
|
+
case "record":
|
|
3350
|
+
return "record";
|
|
3351
|
+
case "union":
|
|
3352
|
+
return "union";
|
|
3353
|
+
case "reference":
|
|
3354
|
+
return `reference(${type.name})`;
|
|
3355
|
+
case "dynamic":
|
|
3356
|
+
return `dynamic(${type.dynamicKind})`;
|
|
3357
|
+
case "custom":
|
|
3358
|
+
return `custom(${type.typeId})`;
|
|
3359
|
+
default: {
|
|
3360
|
+
const _exhaustive = type;
|
|
3361
|
+
return String(_exhaustive);
|
|
3362
|
+
}
|
|
3363
|
+
}
|
|
3364
|
+
}
|
|
3365
|
+
function dereferenceType(ctx, type) {
|
|
3366
|
+
let current = type;
|
|
3367
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3368
|
+
while (current.kind === "reference") {
|
|
3369
|
+
if (seen.has(current.name)) {
|
|
3370
|
+
return current;
|
|
3371
|
+
}
|
|
3372
|
+
seen.add(current.name);
|
|
3373
|
+
const definition = ctx.typeRegistry[current.name];
|
|
3374
|
+
if (definition === void 0) {
|
|
3375
|
+
return current;
|
|
3376
|
+
}
|
|
3377
|
+
current = definition.type;
|
|
3378
|
+
}
|
|
3379
|
+
return current;
|
|
3380
|
+
}
|
|
3381
|
+
function collectReferencedTypeConstraints(ctx, type) {
|
|
3382
|
+
const collected = [];
|
|
3383
|
+
let current = type;
|
|
3384
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3385
|
+
while (current.kind === "reference") {
|
|
3386
|
+
if (seen.has(current.name)) {
|
|
3387
|
+
break;
|
|
3388
|
+
}
|
|
3389
|
+
seen.add(current.name);
|
|
3390
|
+
const definition = ctx.typeRegistry[current.name];
|
|
3391
|
+
if (definition === void 0) {
|
|
3392
|
+
break;
|
|
3393
|
+
}
|
|
3394
|
+
if (definition.constraints !== void 0) {
|
|
3395
|
+
collected.push(...definition.constraints);
|
|
3396
|
+
}
|
|
3397
|
+
current = definition.type;
|
|
3398
|
+
}
|
|
3399
|
+
return collected;
|
|
3400
|
+
}
|
|
3401
|
+
function resolvePathTargetType(ctx, type, segments) {
|
|
3402
|
+
const effectiveType = dereferenceType(ctx, type);
|
|
3403
|
+
if (segments.length === 0) {
|
|
3404
|
+
return { kind: "resolved", type: effectiveType };
|
|
3405
|
+
}
|
|
3406
|
+
if (effectiveType.kind === "array") {
|
|
3407
|
+
return resolvePathTargetType(ctx, effectiveType.items, segments);
|
|
3408
|
+
}
|
|
3409
|
+
if (effectiveType.kind === "object") {
|
|
3410
|
+
const [segment, ...rest] = segments;
|
|
3411
|
+
if (segment === void 0) {
|
|
3412
|
+
throw new Error("Invariant violation: object path traversal requires a segment");
|
|
3413
|
+
}
|
|
3414
|
+
const property = effectiveType.properties.find((prop) => prop.name === segment);
|
|
3415
|
+
if (property === void 0) {
|
|
3416
|
+
return { kind: "missing-property", segment };
|
|
3417
|
+
}
|
|
3418
|
+
return resolvePathTargetType(ctx, property.type, rest);
|
|
3419
|
+
}
|
|
3420
|
+
return { kind: "unresolvable", type: effectiveType };
|
|
3421
|
+
}
|
|
3422
|
+
function isNullType(type) {
|
|
3423
|
+
return type.kind === "primitive" && type.primitiveKind === "null";
|
|
3424
|
+
}
|
|
3425
|
+
function collectCustomConstraintCandidateTypes(ctx, type) {
|
|
3426
|
+
const effectiveType = dereferenceType(ctx, type);
|
|
3427
|
+
const candidates = [effectiveType];
|
|
3428
|
+
if (effectiveType.kind === "array") {
|
|
3429
|
+
candidates.push(...collectCustomConstraintCandidateTypes(ctx, effectiveType.items));
|
|
3430
|
+
}
|
|
3431
|
+
if (effectiveType.kind === "union") {
|
|
3432
|
+
const memberTypes = effectiveType.members.map((member) => dereferenceType(ctx, member));
|
|
3433
|
+
const nonNullMembers = memberTypes.filter((member) => !isNullType(member));
|
|
3434
|
+
if (nonNullMembers.length === 1 && nonNullMembers.length < memberTypes.length) {
|
|
3435
|
+
const [nullableMember] = nonNullMembers;
|
|
3436
|
+
if (nullableMember !== void 0) {
|
|
3437
|
+
candidates.push(...collectCustomConstraintCandidateTypes(ctx, nullableMember));
|
|
3438
|
+
}
|
|
3439
|
+
}
|
|
3440
|
+
}
|
|
3441
|
+
return candidates;
|
|
3442
|
+
}
|
|
3443
|
+
function formatPathTargetFieldName(fieldName, path3) {
|
|
3444
|
+
return path3.length === 0 ? fieldName : `${fieldName}.${path3.join(".")}`;
|
|
3445
|
+
}
|
|
3446
|
+
function checkConstraintOnType(ctx, fieldName, type, constraint) {
|
|
3447
|
+
const effectiveType = dereferenceType(ctx, type);
|
|
3448
|
+
const isNumber = effectiveType.kind === "primitive" && ["number", "integer", "bigint"].includes(effectiveType.primitiveKind);
|
|
3449
|
+
const isString = effectiveType.kind === "primitive" && effectiveType.primitiveKind === "string";
|
|
3450
|
+
const isArray = effectiveType.kind === "array";
|
|
3451
|
+
const isEnum = effectiveType.kind === "enum";
|
|
3452
|
+
const arrayItemType = effectiveType.kind === "array" ? dereferenceType(ctx, effectiveType.items) : void 0;
|
|
3453
|
+
const isStringArray = arrayItemType?.kind === "primitive" && arrayItemType.primitiveKind === "string";
|
|
3454
|
+
const label = typeLabel(effectiveType);
|
|
3455
|
+
const ck = constraint.constraintKind;
|
|
3456
|
+
switch (ck) {
|
|
3457
|
+
case "minimum":
|
|
3458
|
+
case "maximum":
|
|
3459
|
+
case "exclusiveMinimum":
|
|
3460
|
+
case "exclusiveMaximum":
|
|
3461
|
+
case "multipleOf": {
|
|
3462
|
+
if (!isNumber) {
|
|
3463
|
+
addTypeMismatch(
|
|
3464
|
+
ctx,
|
|
3465
|
+
`Field "${fieldName}": constraint "${ck}" is only valid on number fields, but field type is "${label}"`,
|
|
3466
|
+
constraint.provenance
|
|
3467
|
+
);
|
|
3468
|
+
}
|
|
3469
|
+
break;
|
|
3470
|
+
}
|
|
3471
|
+
case "minLength":
|
|
3472
|
+
case "maxLength":
|
|
3473
|
+
case "pattern": {
|
|
3474
|
+
if (!isString && !isStringArray) {
|
|
3475
|
+
addTypeMismatch(
|
|
3476
|
+
ctx,
|
|
3477
|
+
`Field "${fieldName}": constraint "${ck}" is only valid on string fields or string array items, but field type is "${label}"`,
|
|
3478
|
+
constraint.provenance
|
|
3479
|
+
);
|
|
3480
|
+
}
|
|
3481
|
+
break;
|
|
3482
|
+
}
|
|
3483
|
+
case "minItems":
|
|
3484
|
+
case "maxItems":
|
|
3485
|
+
case "uniqueItems": {
|
|
3486
|
+
if (!isArray) {
|
|
3487
|
+
addTypeMismatch(
|
|
3488
|
+
ctx,
|
|
3489
|
+
`Field "${fieldName}": constraint "${ck}" is only valid on array fields, but field type is "${label}"`,
|
|
3490
|
+
constraint.provenance
|
|
3491
|
+
);
|
|
3492
|
+
}
|
|
3493
|
+
break;
|
|
3494
|
+
}
|
|
3495
|
+
case "allowedMembers": {
|
|
3496
|
+
if (!isEnum) {
|
|
3497
|
+
addTypeMismatch(
|
|
3498
|
+
ctx,
|
|
3499
|
+
`Field "${fieldName}": constraint "allowedMembers" is only valid on enum fields, but field type is "${label}"`,
|
|
3500
|
+
constraint.provenance
|
|
3501
|
+
);
|
|
3502
|
+
}
|
|
3503
|
+
break;
|
|
3504
|
+
}
|
|
3505
|
+
case "const": {
|
|
3506
|
+
const isPrimitiveConstType = effectiveType.kind === "primitive" && ["string", "number", "integer", "bigint", "boolean", "null"].includes(
|
|
3507
|
+
effectiveType.primitiveKind
|
|
3508
|
+
) || effectiveType.kind === "enum";
|
|
3509
|
+
if (!isPrimitiveConstType) {
|
|
3510
|
+
addTypeMismatch(
|
|
3511
|
+
ctx,
|
|
3512
|
+
`Field "${fieldName}": constraint "const" is only valid on primitive or enum fields, but field type is "${label}"`,
|
|
3513
|
+
constraint.provenance
|
|
3514
|
+
);
|
|
3515
|
+
break;
|
|
3516
|
+
}
|
|
3517
|
+
if (effectiveType.kind === "primitive") {
|
|
3518
|
+
const valueType = constraint.value === null ? "null" : Array.isArray(constraint.value) ? "array" : typeof constraint.value;
|
|
3519
|
+
const expectedValueType = effectiveType.primitiveKind === "integer" || effectiveType.primitiveKind === "bigint" ? "number" : effectiveType.primitiveKind;
|
|
3520
|
+
if (valueType !== expectedValueType) {
|
|
3521
|
+
addTypeMismatch(
|
|
3522
|
+
ctx,
|
|
3523
|
+
`Field "${fieldName}": @const value type "${valueType}" is incompatible with field type "${effectiveType.primitiveKind}"`,
|
|
3524
|
+
constraint.provenance
|
|
3525
|
+
);
|
|
3526
|
+
}
|
|
3527
|
+
break;
|
|
3528
|
+
}
|
|
3529
|
+
const memberValues = effectiveType.members.map((member) => member.value);
|
|
3530
|
+
if (!memberValues.some((member) => jsonValueEquals(member, constraint.value))) {
|
|
3531
|
+
addTypeMismatch(
|
|
3532
|
+
ctx,
|
|
3533
|
+
`Field "${fieldName}": @const value ${JSON.stringify(constraint.value)} is not one of the enum members`,
|
|
3534
|
+
constraint.provenance
|
|
3535
|
+
);
|
|
3536
|
+
}
|
|
3537
|
+
break;
|
|
3538
|
+
}
|
|
3539
|
+
case "custom": {
|
|
3540
|
+
checkCustomConstraint(ctx, fieldName, effectiveType, constraint);
|
|
3541
|
+
break;
|
|
3542
|
+
}
|
|
3543
|
+
default: {
|
|
3544
|
+
const _exhaustive = constraint;
|
|
3545
|
+
throw new Error(
|
|
3546
|
+
`Unhandled constraint kind: ${_exhaustive.constraintKind}`
|
|
3547
|
+
);
|
|
3548
|
+
}
|
|
3549
|
+
}
|
|
3550
|
+
}
|
|
3551
|
+
function checkTypeApplicability(ctx, fieldName, type, constraints) {
|
|
3552
|
+
for (const constraint of constraints) {
|
|
3553
|
+
if (constraint.path) {
|
|
3554
|
+
const resolution = resolvePathTargetType(ctx, type, constraint.path.segments);
|
|
3555
|
+
const targetFieldName = formatPathTargetFieldName(fieldName, constraint.path.segments);
|
|
3556
|
+
if (resolution.kind === "missing-property") {
|
|
3557
|
+
addUnknownPathTarget(
|
|
3558
|
+
ctx,
|
|
3559
|
+
`Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" references unknown path segment "${resolution.segment}"`,
|
|
3560
|
+
constraint.provenance
|
|
3561
|
+
);
|
|
3562
|
+
continue;
|
|
3563
|
+
}
|
|
3564
|
+
if (resolution.kind === "unresolvable") {
|
|
3565
|
+
addTypeMismatch(
|
|
3566
|
+
ctx,
|
|
3567
|
+
`Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" is invalid because type "${typeLabel(resolution.type)}" cannot be traversed`,
|
|
3568
|
+
constraint.provenance
|
|
3569
|
+
);
|
|
3570
|
+
continue;
|
|
3571
|
+
}
|
|
3572
|
+
checkConstraintOnType(ctx, targetFieldName, resolution.type, constraint);
|
|
3573
|
+
continue;
|
|
3574
|
+
}
|
|
3575
|
+
checkConstraintOnType(ctx, fieldName, type, constraint);
|
|
3576
|
+
}
|
|
3577
|
+
}
|
|
3578
|
+
function checkCustomConstraint(ctx, fieldName, type, constraint) {
|
|
3579
|
+
if (ctx.extensionRegistry === void 0) return;
|
|
3580
|
+
const registration = ctx.extensionRegistry.findConstraint(constraint.constraintId);
|
|
3581
|
+
if (registration === void 0) {
|
|
3582
|
+
addUnknownExtension(
|
|
3583
|
+
ctx,
|
|
3584
|
+
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not registered in the extension registry`,
|
|
3585
|
+
constraint.provenance
|
|
3586
|
+
);
|
|
3587
|
+
return;
|
|
3588
|
+
}
|
|
3589
|
+
const candidateTypes = collectCustomConstraintCandidateTypes(ctx, type);
|
|
3590
|
+
const normalizedTagName = constraint.provenance.tagName === void 0 ? void 0 : normalizeConstraintTagName2(constraint.provenance.tagName.replace(/^@/, ""));
|
|
3591
|
+
if (normalizedTagName !== void 0) {
|
|
3592
|
+
const tagRegistration = ctx.extensionRegistry.findConstraintTag(normalizedTagName);
|
|
3593
|
+
const extensionId = getExtensionIdFromConstraintId(constraint.constraintId);
|
|
3594
|
+
if (extensionId !== null && tagRegistration?.extensionId === extensionId && tagRegistration.registration.constraintName === registration.constraintName && !candidateTypes.some(
|
|
3595
|
+
(candidateType) => tagRegistration.registration.isApplicableToType?.(candidateType) !== false
|
|
3596
|
+
)) {
|
|
3597
|
+
addTypeMismatch(
|
|
3598
|
+
ctx,
|
|
3599
|
+
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
3600
|
+
constraint.provenance
|
|
3601
|
+
);
|
|
3602
|
+
return;
|
|
3603
|
+
}
|
|
3604
|
+
}
|
|
3605
|
+
if (registration.applicableTypes === null) {
|
|
3606
|
+
if (!candidateTypes.some((candidateType) => registration.isApplicableToType?.(candidateType) !== false)) {
|
|
3607
|
+
addTypeMismatch(
|
|
3608
|
+
ctx,
|
|
3609
|
+
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
3610
|
+
constraint.provenance
|
|
3611
|
+
);
|
|
3612
|
+
}
|
|
3613
|
+
return;
|
|
3614
|
+
}
|
|
3615
|
+
const applicableTypes = registration.applicableTypes;
|
|
3616
|
+
const matchesApplicableType = candidateTypes.some(
|
|
3617
|
+
(candidateType) => applicableTypes.includes(candidateType.kind) && registration.isApplicableToType?.(candidateType) !== false
|
|
3618
|
+
);
|
|
3619
|
+
if (!matchesApplicableType) {
|
|
3620
|
+
addTypeMismatch(
|
|
3621
|
+
ctx,
|
|
3622
|
+
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
3623
|
+
constraint.provenance
|
|
3624
|
+
);
|
|
3625
|
+
}
|
|
3626
|
+
}
|
|
3627
|
+
function validateFieldNode(ctx, field) {
|
|
3628
|
+
validateConstraints(ctx, field.name, field.type, [
|
|
3629
|
+
...collectReferencedTypeConstraints(ctx, field.type),
|
|
3630
|
+
...field.constraints
|
|
3631
|
+
]);
|
|
3632
|
+
if (field.type.kind === "object") {
|
|
3633
|
+
for (const prop of field.type.properties) {
|
|
3634
|
+
validateObjectProperty(ctx, field.name, prop);
|
|
3635
|
+
}
|
|
3636
|
+
}
|
|
3637
|
+
}
|
|
3638
|
+
function validateObjectProperty(ctx, parentName, prop) {
|
|
3639
|
+
const qualifiedName = `${parentName}.${prop.name}`;
|
|
3640
|
+
validateConstraints(ctx, qualifiedName, prop.type, [
|
|
3641
|
+
...collectReferencedTypeConstraints(ctx, prop.type),
|
|
3642
|
+
...prop.constraints
|
|
3643
|
+
]);
|
|
3644
|
+
if (prop.type.kind === "object") {
|
|
3645
|
+
for (const nestedProp of prop.type.properties) {
|
|
3646
|
+
validateObjectProperty(ctx, qualifiedName, nestedProp);
|
|
3647
|
+
}
|
|
3648
|
+
}
|
|
3649
|
+
}
|
|
3650
|
+
function validateConstraints(ctx, name, type, constraints) {
|
|
3651
|
+
checkNumericContradictions(ctx, name, constraints);
|
|
3652
|
+
checkLengthContradictions(ctx, name, constraints);
|
|
3653
|
+
checkAllowedMembersContradiction(ctx, name, constraints);
|
|
3654
|
+
checkConstContradictions(ctx, name, constraints);
|
|
3655
|
+
checkConstraintBroadening(ctx, name, constraints);
|
|
3656
|
+
checkCustomConstraintSemantics(ctx, name, constraints);
|
|
3657
|
+
checkTypeApplicability(ctx, name, type, constraints);
|
|
3658
|
+
}
|
|
3659
|
+
function validateElement(ctx, element) {
|
|
3660
|
+
switch (element.kind) {
|
|
3661
|
+
case "field":
|
|
3662
|
+
validateFieldNode(ctx, element);
|
|
3663
|
+
break;
|
|
3664
|
+
case "group":
|
|
3665
|
+
for (const child of element.elements) {
|
|
3666
|
+
validateElement(ctx, child);
|
|
3667
|
+
}
|
|
3668
|
+
break;
|
|
3669
|
+
case "conditional":
|
|
3670
|
+
for (const child of element.elements) {
|
|
3671
|
+
validateElement(ctx, child);
|
|
3672
|
+
}
|
|
3673
|
+
break;
|
|
3674
|
+
default: {
|
|
3675
|
+
const _exhaustive = element;
|
|
3676
|
+
throw new Error(`Unhandled element kind: ${_exhaustive.kind}`);
|
|
3677
|
+
}
|
|
3678
|
+
}
|
|
3679
|
+
}
|
|
3680
|
+
function validateIR(ir, options) {
|
|
3681
|
+
const ctx = {
|
|
3682
|
+
diagnostics: [],
|
|
3683
|
+
extensionRegistry: options?.extensionRegistry,
|
|
3684
|
+
typeRegistry: ir.typeRegistry
|
|
3685
|
+
};
|
|
3686
|
+
for (const element of ir.elements) {
|
|
3687
|
+
validateElement(ctx, element);
|
|
3688
|
+
}
|
|
3689
|
+
return {
|
|
3690
|
+
diagnostics: ctx.diagnostics,
|
|
3691
|
+
valid: ctx.diagnostics.every((d) => d.severity !== "error")
|
|
3692
|
+
};
|
|
3693
|
+
}
|
|
3694
|
+
|
|
2344
3695
|
// src/generators/class-schema.ts
|
|
2345
|
-
function generateClassSchemas(analysis, source) {
|
|
3696
|
+
function generateClassSchemas(analysis, source, options) {
|
|
2346
3697
|
const ir = canonicalizeTSDoc(analysis, source);
|
|
3698
|
+
const validationResult = validateIR(ir, {
|
|
3699
|
+
...options?.extensionRegistry !== void 0 && {
|
|
3700
|
+
extensionRegistry: options.extensionRegistry
|
|
3701
|
+
},
|
|
3702
|
+
...options?.vendorPrefix !== void 0 && { vendorPrefix: options.vendorPrefix }
|
|
3703
|
+
});
|
|
3704
|
+
if (!validationResult.valid) {
|
|
3705
|
+
throw new Error(formatValidationError(validationResult.diagnostics));
|
|
3706
|
+
}
|
|
2347
3707
|
return {
|
|
2348
|
-
jsonSchema: generateJsonSchemaFromIR(ir),
|
|
3708
|
+
jsonSchema: generateJsonSchemaFromIR(ir, options),
|
|
2349
3709
|
uiSchema: generateUiSchemaFromIR(ir)
|
|
2350
3710
|
};
|
|
2351
3711
|
}
|
|
3712
|
+
function formatValidationError(diagnostics) {
|
|
3713
|
+
const lines = diagnostics.map((diagnostic) => {
|
|
3714
|
+
const primary = formatLocation(diagnostic.primaryLocation);
|
|
3715
|
+
const related = diagnostic.relatedLocations.length > 0 ? ` [related: ${diagnostic.relatedLocations.map(formatLocation).join(", ")}]` : "";
|
|
3716
|
+
return `${diagnostic.code}: ${diagnostic.message} (${primary})${related}`;
|
|
3717
|
+
});
|
|
3718
|
+
return `FormSpec validation failed:
|
|
3719
|
+
${lines.map((line) => `- ${line}`).join("\n")}`;
|
|
3720
|
+
}
|
|
3721
|
+
function formatLocation(location) {
|
|
3722
|
+
return `${location.file}:${String(location.line)}:${String(location.column)}`;
|
|
3723
|
+
}
|
|
2352
3724
|
function generateSchemasFromClass(options) {
|
|
2353
3725
|
const ctx = createProgramContext(options.filePath);
|
|
2354
3726
|
const classDecl = findClassByName(ctx.sourceFile, options.className);
|
|
2355
3727
|
if (!classDecl) {
|
|
2356
3728
|
throw new Error(`Class "${options.className}" not found in ${options.filePath}`);
|
|
2357
3729
|
}
|
|
2358
|
-
const analysis = analyzeClassToIR(
|
|
2359
|
-
|
|
3730
|
+
const analysis = analyzeClassToIR(
|
|
3731
|
+
classDecl,
|
|
3732
|
+
ctx.checker,
|
|
3733
|
+
options.filePath,
|
|
3734
|
+
options.extensionRegistry
|
|
3735
|
+
);
|
|
3736
|
+
return generateClassSchemas(
|
|
3737
|
+
analysis,
|
|
3738
|
+
{ file: options.filePath },
|
|
3739
|
+
{
|
|
3740
|
+
extensionRegistry: options.extensionRegistry,
|
|
3741
|
+
vendorPrefix: options.vendorPrefix
|
|
3742
|
+
}
|
|
3743
|
+
);
|
|
2360
3744
|
}
|
|
2361
3745
|
function generateSchemas(options) {
|
|
2362
|
-
const
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
const analysis = analyzeClassToIR(classDecl, ctx.checker, options.filePath);
|
|
2367
|
-
return generateClassSchemas(analysis, source);
|
|
2368
|
-
}
|
|
2369
|
-
const interfaceDecl = findInterfaceByName(ctx.sourceFile, options.typeName);
|
|
2370
|
-
if (interfaceDecl) {
|
|
2371
|
-
const analysis = analyzeInterfaceToIR(interfaceDecl, ctx.checker, options.filePath);
|
|
2372
|
-
return generateClassSchemas(analysis, source);
|
|
2373
|
-
}
|
|
2374
|
-
const typeAlias = findTypeAliasByName(ctx.sourceFile, options.typeName);
|
|
2375
|
-
if (typeAlias) {
|
|
2376
|
-
const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, options.filePath);
|
|
2377
|
-
if (result.ok) {
|
|
2378
|
-
return generateClassSchemas(result.analysis, source);
|
|
2379
|
-
}
|
|
2380
|
-
throw new Error(result.error);
|
|
2381
|
-
}
|
|
2382
|
-
throw new Error(
|
|
2383
|
-
`Type "${options.typeName}" not found as a class, interface, or type alias in ${options.filePath}`
|
|
3746
|
+
const analysis = analyzeNamedTypeToIR(
|
|
3747
|
+
options.filePath,
|
|
3748
|
+
options.typeName,
|
|
3749
|
+
options.extensionRegistry
|
|
2384
3750
|
);
|
|
3751
|
+
return generateClassSchemas(analysis, { file: options.filePath }, options);
|
|
2385
3752
|
}
|
|
2386
3753
|
|
|
2387
3754
|
// src/generators/mixed-authoring.ts
|
|
2388
3755
|
function buildMixedAuthoringSchemas(options) {
|
|
2389
3756
|
const { filePath, typeName, overlays, ...schemaOptions } = options;
|
|
2390
|
-
const analysis =
|
|
3757
|
+
const analysis = analyzeNamedTypeToIR(filePath, typeName, schemaOptions.extensionRegistry);
|
|
2391
3758
|
const composedAnalysis = composeAnalysisWithOverlays(analysis, overlays);
|
|
2392
3759
|
const ir = canonicalizeTSDoc(composedAnalysis, { file: filePath });
|
|
2393
3760
|
return {
|
|
@@ -2395,29 +3762,6 @@ function buildMixedAuthoringSchemas(options) {
|
|
|
2395
3762
|
uiSchema: generateUiSchemaFromIR(ir)
|
|
2396
3763
|
};
|
|
2397
3764
|
}
|
|
2398
|
-
function analyzeNamedType(filePath, typeName) {
|
|
2399
|
-
const ctx = createProgramContext(filePath);
|
|
2400
|
-
const source = { file: filePath };
|
|
2401
|
-
const classDecl = findClassByName(ctx.sourceFile, typeName);
|
|
2402
|
-
if (classDecl !== null) {
|
|
2403
|
-
return analyzeClassToIR(classDecl, ctx.checker, source.file);
|
|
2404
|
-
}
|
|
2405
|
-
const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
|
|
2406
|
-
if (interfaceDecl !== null) {
|
|
2407
|
-
return analyzeInterfaceToIR(interfaceDecl, ctx.checker, source.file);
|
|
2408
|
-
}
|
|
2409
|
-
const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
|
|
2410
|
-
if (typeAlias !== null) {
|
|
2411
|
-
const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, source.file);
|
|
2412
|
-
if (result.ok) {
|
|
2413
|
-
return result.analysis;
|
|
2414
|
-
}
|
|
2415
|
-
throw new Error(result.error);
|
|
2416
|
-
}
|
|
2417
|
-
throw new Error(
|
|
2418
|
-
`Type "${typeName}" not found as a class, interface, or type alias in ${filePath}`
|
|
2419
|
-
);
|
|
2420
|
-
}
|
|
2421
3765
|
function composeAnalysisWithOverlays(analysis, overlays) {
|
|
2422
3766
|
const overlayIR = canonicalizeChainDSL(overlays);
|
|
2423
3767
|
const overlayFields = collectOverlayFields(overlayIR.elements);
|
|
@@ -2487,7 +3831,7 @@ function assertSupportedOverlayField(baseField, overlayField) {
|
|
|
2487
3831
|
`Mixed-authoring overlay for "${baseField.name}" cannot define constraints; keep constraints on the static model`
|
|
2488
3832
|
);
|
|
2489
3833
|
}
|
|
2490
|
-
if (overlayField.required) {
|
|
3834
|
+
if (overlayField.required && !baseField.required) {
|
|
2491
3835
|
throw new Error(
|
|
2492
3836
|
`Mixed-authoring overlay for "${baseField.name}" cannot change requiredness; keep requiredness on the static model`
|
|
2493
3837
|
);
|
|
@@ -2577,7 +3921,7 @@ function mergeAnnotations(baseAnnotations, overlayAnnotations) {
|
|
|
2577
3921
|
const overlayOnly = overlayAnnotations.filter(
|
|
2578
3922
|
(annotation) => !baseKeys.has(annotationKey(annotation))
|
|
2579
3923
|
);
|
|
2580
|
-
return [...
|
|
3924
|
+
return [...baseAnnotations, ...overlayOnly];
|
|
2581
3925
|
}
|
|
2582
3926
|
function annotationKey(annotation) {
|
|
2583
3927
|
return annotation.annotationKind === "custom" ? `${annotation.annotationKind}:${annotation.annotationId}` : annotation.annotationKind;
|