@formspec/build 0.1.0-alpha.16 → 0.1.0-alpha.17
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/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/mixed-authoring-shipping-address.d.ts +1 -0
- package/dist/__tests__/fixtures/mixed-authoring-shipping-address.d.ts.map +1 -1
- 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/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/tsdoc-parser.d.ts +18 -2
- package/dist/analyzer/tsdoc-parser.d.ts.map +1 -1
- package/dist/browser.cjs +199 -4
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.js +199 -4
- package/dist/browser.js.map +1 -1
- package/dist/build.d.ts +28 -2
- package/dist/cli.cjs +547 -84
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +547 -84
- 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/index.cjs +546 -84
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +546 -84
- package/dist/index.js.map +1 -1
- package/dist/internals.cjs +645 -73
- package/dist/internals.cjs.map +1 -1
- package/dist/internals.js +643 -71
- package/dist/internals.js.map +1 -1
- package/dist/validate/constraint-validator.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/internals.js
CHANGED
|
@@ -503,7 +503,7 @@ var LENGTH_CONSTRAINT_MAP = {
|
|
|
503
503
|
maxItems: "maxItems"
|
|
504
504
|
};
|
|
505
505
|
var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions", "defaultValue"]);
|
|
506
|
-
function createFormSpecTSDocConfig() {
|
|
506
|
+
function createFormSpecTSDocConfig(extensionTagNames = []) {
|
|
507
507
|
const config = new TSDocConfiguration();
|
|
508
508
|
for (const tagName of Object.keys(BUILTIN_CONSTRAINT_DEFINITIONS)) {
|
|
509
509
|
config.addTagDefinition(
|
|
@@ -523,14 +523,34 @@ function createFormSpecTSDocConfig() {
|
|
|
523
523
|
})
|
|
524
524
|
);
|
|
525
525
|
}
|
|
526
|
+
for (const tagName of extensionTagNames) {
|
|
527
|
+
config.addTagDefinition(
|
|
528
|
+
new TSDocTagDefinition({
|
|
529
|
+
tagName: "@" + tagName,
|
|
530
|
+
syntaxKind: TSDocTagSyntaxKind.BlockTag,
|
|
531
|
+
allowMultiple: true
|
|
532
|
+
})
|
|
533
|
+
);
|
|
534
|
+
}
|
|
526
535
|
return config;
|
|
527
536
|
}
|
|
528
|
-
var
|
|
529
|
-
function getParser() {
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
537
|
+
var parserCache = /* @__PURE__ */ new Map();
|
|
538
|
+
function getParser(options) {
|
|
539
|
+
const extensionTagNames = [
|
|
540
|
+
...options?.extensionRegistry?.extensions.flatMap(
|
|
541
|
+
(extension) => (extension.constraintTags ?? []).map((tag) => tag.tagName)
|
|
542
|
+
) ?? []
|
|
543
|
+
].sort();
|
|
544
|
+
const cacheKey = extensionTagNames.join("|");
|
|
545
|
+
const existing = parserCache.get(cacheKey);
|
|
546
|
+
if (existing) {
|
|
547
|
+
return existing;
|
|
548
|
+
}
|
|
549
|
+
const parser = new TSDocParser(createFormSpecTSDocConfig(extensionTagNames));
|
|
550
|
+
parserCache.set(cacheKey, parser);
|
|
551
|
+
return parser;
|
|
552
|
+
}
|
|
553
|
+
function parseTSDocTags(node, file = "", options) {
|
|
534
554
|
const constraints = [];
|
|
535
555
|
const annotations = [];
|
|
536
556
|
let displayName;
|
|
@@ -551,7 +571,7 @@ function parseTSDocTags(node, file = "") {
|
|
|
551
571
|
if (!commentText.startsWith("/**")) {
|
|
552
572
|
continue;
|
|
553
573
|
}
|
|
554
|
-
const parser = getParser();
|
|
574
|
+
const parser = getParser(options);
|
|
555
575
|
const parserContext = parser.parseRange(
|
|
556
576
|
TextRange.fromStringRange(sourceText, range.pos, range.end)
|
|
557
577
|
);
|
|
@@ -590,7 +610,7 @@ function parseTSDocTags(node, file = "") {
|
|
|
590
610
|
const expectedType = isBuiltinConstraintName(tagName) ? BUILTIN_CONSTRAINT_DEFINITIONS[tagName] : void 0;
|
|
591
611
|
if (text === "" && expectedType !== "boolean") continue;
|
|
592
612
|
const provenance = provenanceForComment(range, sourceFile, file, tagName);
|
|
593
|
-
const constraintNode = parseConstraintValue(tagName, text, provenance);
|
|
613
|
+
const constraintNode = parseConstraintValue(tagName, text, provenance, options);
|
|
594
614
|
if (constraintNode) {
|
|
595
615
|
constraints.push(constraintNode);
|
|
596
616
|
}
|
|
@@ -650,7 +670,7 @@ function parseTSDocTags(node, file = "") {
|
|
|
650
670
|
annotations.push(defaultValueNode);
|
|
651
671
|
continue;
|
|
652
672
|
}
|
|
653
|
-
const constraintNode = parseConstraintValue(tagName, text, provenance);
|
|
673
|
+
const constraintNode = parseConstraintValue(tagName, text, provenance, options);
|
|
654
674
|
if (constraintNode) {
|
|
655
675
|
constraints.push(constraintNode);
|
|
656
676
|
}
|
|
@@ -706,7 +726,11 @@ function extractPlainText(node) {
|
|
|
706
726
|
}
|
|
707
727
|
return result;
|
|
708
728
|
}
|
|
709
|
-
function parseConstraintValue(tagName, text, provenance) {
|
|
729
|
+
function parseConstraintValue(tagName, text, provenance, options) {
|
|
730
|
+
const customConstraint = parseExtensionConstraintValue(tagName, text, provenance, options);
|
|
731
|
+
if (customConstraint) {
|
|
732
|
+
return customConstraint;
|
|
733
|
+
}
|
|
710
734
|
if (!isBuiltinConstraintName(tagName)) {
|
|
711
735
|
return null;
|
|
712
736
|
}
|
|
@@ -811,6 +835,83 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
811
835
|
provenance
|
|
812
836
|
};
|
|
813
837
|
}
|
|
838
|
+
function parseExtensionConstraintValue(tagName, text, provenance, options) {
|
|
839
|
+
const pathResult = extractPathTarget(text);
|
|
840
|
+
const effectiveText = pathResult ? pathResult.remainingText : text;
|
|
841
|
+
const path2 = pathResult?.path;
|
|
842
|
+
const registry = options?.extensionRegistry;
|
|
843
|
+
if (registry === void 0) {
|
|
844
|
+
return null;
|
|
845
|
+
}
|
|
846
|
+
const directTag = registry.findConstraintTag(tagName);
|
|
847
|
+
if (directTag !== void 0) {
|
|
848
|
+
return makeCustomConstraintNode(
|
|
849
|
+
directTag.extensionId,
|
|
850
|
+
directTag.registration.constraintName,
|
|
851
|
+
directTag.registration.parseValue(effectiveText),
|
|
852
|
+
provenance,
|
|
853
|
+
path2,
|
|
854
|
+
registry
|
|
855
|
+
);
|
|
856
|
+
}
|
|
857
|
+
if (!isBuiltinConstraintName(tagName)) {
|
|
858
|
+
return null;
|
|
859
|
+
}
|
|
860
|
+
const broadenedTypeId = getBroadenedCustomTypeId(options?.fieldType);
|
|
861
|
+
if (broadenedTypeId === void 0) {
|
|
862
|
+
return null;
|
|
863
|
+
}
|
|
864
|
+
const broadened = registry.findBuiltinConstraintBroadening(broadenedTypeId, tagName);
|
|
865
|
+
if (broadened === void 0) {
|
|
866
|
+
return null;
|
|
867
|
+
}
|
|
868
|
+
return makeCustomConstraintNode(
|
|
869
|
+
broadened.extensionId,
|
|
870
|
+
broadened.registration.constraintName,
|
|
871
|
+
broadened.registration.parseValue(effectiveText),
|
|
872
|
+
provenance,
|
|
873
|
+
path2,
|
|
874
|
+
registry
|
|
875
|
+
);
|
|
876
|
+
}
|
|
877
|
+
function getBroadenedCustomTypeId(fieldType) {
|
|
878
|
+
if (fieldType?.kind === "custom") {
|
|
879
|
+
return fieldType.typeId;
|
|
880
|
+
}
|
|
881
|
+
if (fieldType?.kind !== "union") {
|
|
882
|
+
return void 0;
|
|
883
|
+
}
|
|
884
|
+
const customMembers = fieldType.members.filter(
|
|
885
|
+
(member) => member.kind === "custom"
|
|
886
|
+
);
|
|
887
|
+
if (customMembers.length !== 1) {
|
|
888
|
+
return void 0;
|
|
889
|
+
}
|
|
890
|
+
const nonCustomMembers = fieldType.members.filter((member) => member.kind !== "custom");
|
|
891
|
+
const allOtherMembersAreNull = nonCustomMembers.every(
|
|
892
|
+
(member) => member.kind === "primitive" && member.primitiveKind === "null"
|
|
893
|
+
);
|
|
894
|
+
const customMember = customMembers[0];
|
|
895
|
+
return allOtherMembersAreNull && customMember !== void 0 ? customMember.typeId : void 0;
|
|
896
|
+
}
|
|
897
|
+
function makeCustomConstraintNode(extensionId, constraintName, payload, provenance, path2, registry) {
|
|
898
|
+
const constraintId = `${extensionId}/${constraintName}`;
|
|
899
|
+
const registration = registry.findConstraint(constraintId);
|
|
900
|
+
if (registration === void 0) {
|
|
901
|
+
throw new Error(
|
|
902
|
+
`Custom TSDoc tag resolved to unregistered constraint "${constraintId}". Register the constraint before using its tag.`
|
|
903
|
+
);
|
|
904
|
+
}
|
|
905
|
+
return {
|
|
906
|
+
kind: "constraint",
|
|
907
|
+
constraintKind: "custom",
|
|
908
|
+
constraintId,
|
|
909
|
+
payload,
|
|
910
|
+
compositionRule: registration.compositionRule,
|
|
911
|
+
...path2 && { path: path2 },
|
|
912
|
+
provenance
|
|
913
|
+
};
|
|
914
|
+
}
|
|
814
915
|
function parseDefaultValueValue(text, provenance) {
|
|
815
916
|
const trimmed = text.trim();
|
|
816
917
|
let value;
|
|
@@ -871,12 +972,12 @@ function getTagCommentText(tag) {
|
|
|
871
972
|
}
|
|
872
973
|
|
|
873
974
|
// src/analyzer/jsdoc-constraints.ts
|
|
874
|
-
function extractJSDocConstraintNodes(node, file = "") {
|
|
875
|
-
const result = parseTSDocTags(node, file);
|
|
975
|
+
function extractJSDocConstraintNodes(node, file = "", options) {
|
|
976
|
+
const result = parseTSDocTags(node, file, options);
|
|
876
977
|
return [...result.constraints];
|
|
877
978
|
}
|
|
878
|
-
function extractJSDocAnnotationNodes(node, file = "") {
|
|
879
|
-
const result = parseTSDocTags(node, file);
|
|
979
|
+
function extractJSDocAnnotationNodes(node, file = "", options) {
|
|
980
|
+
const result = parseTSDocTags(node, file, options);
|
|
880
981
|
return [...result.annotations];
|
|
881
982
|
}
|
|
882
983
|
function extractDefaultValueAnnotation(initializer, file = "") {
|
|
@@ -925,18 +1026,38 @@ var RESOLVING_TYPE_PLACEHOLDER = {
|
|
|
925
1026
|
properties: [],
|
|
926
1027
|
additionalProperties: true
|
|
927
1028
|
};
|
|
928
|
-
function
|
|
1029
|
+
function makeParseOptions(extensionRegistry, fieldType) {
|
|
1030
|
+
if (extensionRegistry === void 0 && fieldType === void 0) {
|
|
1031
|
+
return void 0;
|
|
1032
|
+
}
|
|
1033
|
+
return {
|
|
1034
|
+
...extensionRegistry !== void 0 && { extensionRegistry },
|
|
1035
|
+
...fieldType !== void 0 && { fieldType }
|
|
1036
|
+
};
|
|
1037
|
+
}
|
|
1038
|
+
function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
|
|
929
1039
|
const name = classDecl.name?.text ?? "AnonymousClass";
|
|
930
1040
|
const fields = [];
|
|
931
1041
|
const fieldLayouts = [];
|
|
932
1042
|
const typeRegistry = {};
|
|
933
|
-
const annotations = extractJSDocAnnotationNodes(
|
|
1043
|
+
const annotations = extractJSDocAnnotationNodes(
|
|
1044
|
+
classDecl,
|
|
1045
|
+
file,
|
|
1046
|
+
makeParseOptions(extensionRegistry)
|
|
1047
|
+
);
|
|
934
1048
|
const visiting = /* @__PURE__ */ new Set();
|
|
935
1049
|
const instanceMethods = [];
|
|
936
1050
|
const staticMethods = [];
|
|
937
1051
|
for (const member of classDecl.members) {
|
|
938
1052
|
if (ts4.isPropertyDeclaration(member)) {
|
|
939
|
-
const fieldNode = analyzeFieldToIR(
|
|
1053
|
+
const fieldNode = analyzeFieldToIR(
|
|
1054
|
+
member,
|
|
1055
|
+
checker,
|
|
1056
|
+
file,
|
|
1057
|
+
typeRegistry,
|
|
1058
|
+
visiting,
|
|
1059
|
+
extensionRegistry
|
|
1060
|
+
);
|
|
940
1061
|
if (fieldNode) {
|
|
941
1062
|
fields.push(fieldNode);
|
|
942
1063
|
fieldLayouts.push({});
|
|
@@ -963,15 +1084,26 @@ function analyzeClassToIR(classDecl, checker, file = "") {
|
|
|
963
1084
|
staticMethods
|
|
964
1085
|
};
|
|
965
1086
|
}
|
|
966
|
-
function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
|
|
1087
|
+
function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegistry) {
|
|
967
1088
|
const name = interfaceDecl.name.text;
|
|
968
1089
|
const fields = [];
|
|
969
1090
|
const typeRegistry = {};
|
|
970
|
-
const annotations = extractJSDocAnnotationNodes(
|
|
1091
|
+
const annotations = extractJSDocAnnotationNodes(
|
|
1092
|
+
interfaceDecl,
|
|
1093
|
+
file,
|
|
1094
|
+
makeParseOptions(extensionRegistry)
|
|
1095
|
+
);
|
|
971
1096
|
const visiting = /* @__PURE__ */ new Set();
|
|
972
1097
|
for (const member of interfaceDecl.members) {
|
|
973
1098
|
if (ts4.isPropertySignature(member)) {
|
|
974
|
-
const fieldNode = analyzeInterfacePropertyToIR(
|
|
1099
|
+
const fieldNode = analyzeInterfacePropertyToIR(
|
|
1100
|
+
member,
|
|
1101
|
+
checker,
|
|
1102
|
+
file,
|
|
1103
|
+
typeRegistry,
|
|
1104
|
+
visiting,
|
|
1105
|
+
extensionRegistry
|
|
1106
|
+
);
|
|
975
1107
|
if (fieldNode) {
|
|
976
1108
|
fields.push(fieldNode);
|
|
977
1109
|
}
|
|
@@ -988,7 +1120,7 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
|
|
|
988
1120
|
staticMethods: []
|
|
989
1121
|
};
|
|
990
1122
|
}
|
|
991
|
-
function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
1123
|
+
function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry) {
|
|
992
1124
|
if (!ts4.isTypeLiteralNode(typeAlias.type)) {
|
|
993
1125
|
const sourceFile = typeAlias.getSourceFile();
|
|
994
1126
|
const { line } = sourceFile.getLineAndCharacterOfPosition(typeAlias.getStart());
|
|
@@ -1001,11 +1133,22 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
|
1001
1133
|
const name = typeAlias.name.text;
|
|
1002
1134
|
const fields = [];
|
|
1003
1135
|
const typeRegistry = {};
|
|
1004
|
-
const annotations = extractJSDocAnnotationNodes(
|
|
1136
|
+
const annotations = extractJSDocAnnotationNodes(
|
|
1137
|
+
typeAlias,
|
|
1138
|
+
file,
|
|
1139
|
+
makeParseOptions(extensionRegistry)
|
|
1140
|
+
);
|
|
1005
1141
|
const visiting = /* @__PURE__ */ new Set();
|
|
1006
1142
|
for (const member of typeAlias.type.members) {
|
|
1007
1143
|
if (ts4.isPropertySignature(member)) {
|
|
1008
|
-
const fieldNode = analyzeInterfacePropertyToIR(
|
|
1144
|
+
const fieldNode = analyzeInterfacePropertyToIR(
|
|
1145
|
+
member,
|
|
1146
|
+
checker,
|
|
1147
|
+
file,
|
|
1148
|
+
typeRegistry,
|
|
1149
|
+
visiting,
|
|
1150
|
+
extensionRegistry
|
|
1151
|
+
);
|
|
1009
1152
|
if (fieldNode) {
|
|
1010
1153
|
fields.push(fieldNode);
|
|
1011
1154
|
}
|
|
@@ -1024,7 +1167,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
|
|
|
1024
1167
|
}
|
|
1025
1168
|
};
|
|
1026
1169
|
}
|
|
1027
|
-
function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
|
|
1170
|
+
function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1028
1171
|
if (!ts4.isIdentifier(prop.name)) {
|
|
1029
1172
|
return null;
|
|
1030
1173
|
}
|
|
@@ -1032,14 +1175,26 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
|
|
|
1032
1175
|
const tsType = checker.getTypeAtLocation(prop);
|
|
1033
1176
|
const optional = prop.questionToken !== void 0;
|
|
1034
1177
|
const provenance = provenanceForNode(prop, file);
|
|
1035
|
-
let type = resolveTypeNode(
|
|
1178
|
+
let type = resolveTypeNode(
|
|
1179
|
+
tsType,
|
|
1180
|
+
checker,
|
|
1181
|
+
file,
|
|
1182
|
+
typeRegistry,
|
|
1183
|
+
visiting,
|
|
1184
|
+
prop,
|
|
1185
|
+
extensionRegistry
|
|
1186
|
+
);
|
|
1036
1187
|
const constraints = [];
|
|
1037
1188
|
if (prop.type) {
|
|
1038
|
-
constraints.push(
|
|
1189
|
+
constraints.push(
|
|
1190
|
+
...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
|
|
1191
|
+
);
|
|
1039
1192
|
}
|
|
1040
|
-
constraints.push(...extractJSDocConstraintNodes(prop, file));
|
|
1193
|
+
constraints.push(...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type)));
|
|
1041
1194
|
let annotations = [];
|
|
1042
|
-
annotations.push(
|
|
1195
|
+
annotations.push(
|
|
1196
|
+
...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
1197
|
+
);
|
|
1043
1198
|
const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
|
|
1044
1199
|
if (defaultAnnotation && !annotations.some((a) => a.annotationKind === "defaultValue")) {
|
|
1045
1200
|
annotations.push(defaultAnnotation);
|
|
@@ -1055,7 +1210,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
|
|
|
1055
1210
|
provenance
|
|
1056
1211
|
};
|
|
1057
1212
|
}
|
|
1058
|
-
function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting) {
|
|
1213
|
+
function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1059
1214
|
if (!ts4.isIdentifier(prop.name)) {
|
|
1060
1215
|
return null;
|
|
1061
1216
|
}
|
|
@@ -1063,14 +1218,26 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
|
|
|
1063
1218
|
const tsType = checker.getTypeAtLocation(prop);
|
|
1064
1219
|
const optional = prop.questionToken !== void 0;
|
|
1065
1220
|
const provenance = provenanceForNode(prop, file);
|
|
1066
|
-
let type = resolveTypeNode(
|
|
1221
|
+
let type = resolveTypeNode(
|
|
1222
|
+
tsType,
|
|
1223
|
+
checker,
|
|
1224
|
+
file,
|
|
1225
|
+
typeRegistry,
|
|
1226
|
+
visiting,
|
|
1227
|
+
prop,
|
|
1228
|
+
extensionRegistry
|
|
1229
|
+
);
|
|
1067
1230
|
const constraints = [];
|
|
1068
1231
|
if (prop.type) {
|
|
1069
|
-
constraints.push(
|
|
1232
|
+
constraints.push(
|
|
1233
|
+
...extractTypeAliasConstraintNodes(prop.type, checker, file, extensionRegistry)
|
|
1234
|
+
);
|
|
1070
1235
|
}
|
|
1071
|
-
constraints.push(...extractJSDocConstraintNodes(prop, file));
|
|
1236
|
+
constraints.push(...extractJSDocConstraintNodes(prop, file, makeParseOptions(extensionRegistry, type)));
|
|
1072
1237
|
let annotations = [];
|
|
1073
|
-
annotations.push(
|
|
1238
|
+
annotations.push(
|
|
1239
|
+
...extractJSDocAnnotationNodes(prop, file, makeParseOptions(extensionRegistry, type))
|
|
1240
|
+
);
|
|
1074
1241
|
({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
|
|
1075
1242
|
return {
|
|
1076
1243
|
kind: "field",
|
|
@@ -1144,7 +1311,66 @@ function parseEnumMemberDisplayName(value) {
|
|
|
1144
1311
|
if (label === "") return null;
|
|
1145
1312
|
return { value: match[1], label };
|
|
1146
1313
|
}
|
|
1147
|
-
function
|
|
1314
|
+
function resolveRegisteredCustomType(sourceNode, extensionRegistry, checker) {
|
|
1315
|
+
if (sourceNode === void 0 || extensionRegistry === void 0) {
|
|
1316
|
+
return null;
|
|
1317
|
+
}
|
|
1318
|
+
const typeNode = extractTypeNodeFromSource(sourceNode);
|
|
1319
|
+
if (typeNode === void 0) {
|
|
1320
|
+
return null;
|
|
1321
|
+
}
|
|
1322
|
+
return resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker);
|
|
1323
|
+
}
|
|
1324
|
+
function resolveRegisteredCustomTypeFromTypeNode(typeNode, extensionRegistry, checker) {
|
|
1325
|
+
if (ts4.isParenthesizedTypeNode(typeNode)) {
|
|
1326
|
+
return resolveRegisteredCustomTypeFromTypeNode(typeNode.type, extensionRegistry, checker);
|
|
1327
|
+
}
|
|
1328
|
+
const typeName = getTypeNodeRegistrationName(typeNode);
|
|
1329
|
+
if (typeName === null) {
|
|
1330
|
+
return null;
|
|
1331
|
+
}
|
|
1332
|
+
const registration = extensionRegistry.findTypeByName(typeName);
|
|
1333
|
+
if (registration !== void 0) {
|
|
1334
|
+
return {
|
|
1335
|
+
kind: "custom",
|
|
1336
|
+
typeId: `${registration.extensionId}/${registration.registration.typeName}`,
|
|
1337
|
+
payload: null
|
|
1338
|
+
};
|
|
1339
|
+
}
|
|
1340
|
+
if (ts4.isTypeReferenceNode(typeNode) && ts4.isIdentifier(typeNode.typeName)) {
|
|
1341
|
+
const aliasDecl = checker.getSymbolAtLocation(typeNode.typeName)?.declarations?.find(ts4.isTypeAliasDeclaration);
|
|
1342
|
+
if (aliasDecl !== void 0) {
|
|
1343
|
+
return resolveRegisteredCustomTypeFromTypeNode(aliasDecl.type, extensionRegistry, checker);
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
return null;
|
|
1347
|
+
}
|
|
1348
|
+
function extractTypeNodeFromSource(sourceNode) {
|
|
1349
|
+
if (ts4.isPropertyDeclaration(sourceNode) || ts4.isPropertySignature(sourceNode) || ts4.isParameter(sourceNode) || ts4.isTypeAliasDeclaration(sourceNode)) {
|
|
1350
|
+
return sourceNode.type;
|
|
1351
|
+
}
|
|
1352
|
+
if (ts4.isTypeNode(sourceNode)) {
|
|
1353
|
+
return sourceNode;
|
|
1354
|
+
}
|
|
1355
|
+
return void 0;
|
|
1356
|
+
}
|
|
1357
|
+
function getTypeNodeRegistrationName(typeNode) {
|
|
1358
|
+
if (ts4.isTypeReferenceNode(typeNode)) {
|
|
1359
|
+
return ts4.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : typeNode.typeName.right.text;
|
|
1360
|
+
}
|
|
1361
|
+
if (ts4.isParenthesizedTypeNode(typeNode)) {
|
|
1362
|
+
return getTypeNodeRegistrationName(typeNode.type);
|
|
1363
|
+
}
|
|
1364
|
+
if (typeNode.kind === ts4.SyntaxKind.BigIntKeyword || typeNode.kind === ts4.SyntaxKind.StringKeyword || typeNode.kind === ts4.SyntaxKind.NumberKeyword || typeNode.kind === ts4.SyntaxKind.BooleanKeyword) {
|
|
1365
|
+
return typeNode.getText();
|
|
1366
|
+
}
|
|
1367
|
+
return null;
|
|
1368
|
+
}
|
|
1369
|
+
function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
1370
|
+
const customType = resolveRegisteredCustomType(sourceNode, extensionRegistry, checker);
|
|
1371
|
+
if (customType) {
|
|
1372
|
+
return customType;
|
|
1373
|
+
}
|
|
1148
1374
|
if (type.flags & ts4.TypeFlags.String) {
|
|
1149
1375
|
return { kind: "primitive", primitiveKind: "string" };
|
|
1150
1376
|
}
|
|
@@ -1173,26 +1399,50 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
|
|
|
1173
1399
|
};
|
|
1174
1400
|
}
|
|
1175
1401
|
if (type.isUnion()) {
|
|
1176
|
-
return resolveUnionType(
|
|
1402
|
+
return resolveUnionType(
|
|
1403
|
+
type,
|
|
1404
|
+
checker,
|
|
1405
|
+
file,
|
|
1406
|
+
typeRegistry,
|
|
1407
|
+
visiting,
|
|
1408
|
+
sourceNode,
|
|
1409
|
+
extensionRegistry
|
|
1410
|
+
);
|
|
1177
1411
|
}
|
|
1178
1412
|
if (checker.isArrayType(type)) {
|
|
1179
|
-
return resolveArrayType(
|
|
1413
|
+
return resolveArrayType(
|
|
1414
|
+
type,
|
|
1415
|
+
checker,
|
|
1416
|
+
file,
|
|
1417
|
+
typeRegistry,
|
|
1418
|
+
visiting,
|
|
1419
|
+
sourceNode,
|
|
1420
|
+
extensionRegistry
|
|
1421
|
+
);
|
|
1180
1422
|
}
|
|
1181
1423
|
if (isObjectType(type)) {
|
|
1182
|
-
return resolveObjectType(type, checker, file, typeRegistry, visiting);
|
|
1424
|
+
return resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry);
|
|
1183
1425
|
}
|
|
1184
1426
|
return { kind: "primitive", primitiveKind: "string" };
|
|
1185
1427
|
}
|
|
1186
|
-
function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode) {
|
|
1428
|
+
function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
1187
1429
|
const typeName = getNamedTypeName(type);
|
|
1188
1430
|
const namedDecl = getNamedTypeDeclaration(type);
|
|
1189
1431
|
if (typeName && typeName in typeRegistry) {
|
|
1190
1432
|
return { kind: "reference", name: typeName, typeArguments: [] };
|
|
1191
1433
|
}
|
|
1192
1434
|
const allTypes = type.types;
|
|
1435
|
+
const unionMemberTypeNodes = extractUnionMemberTypeNodes(sourceNode, checker);
|
|
1436
|
+
const nonNullSourceNodes = unionMemberTypeNodes.filter(
|
|
1437
|
+
(memberTypeNode) => !isNullishTypeNode(resolveAliasedTypeNode(memberTypeNode, checker))
|
|
1438
|
+
);
|
|
1193
1439
|
const nonNullTypes = allTypes.filter(
|
|
1194
|
-
(
|
|
1440
|
+
(memberType) => !(memberType.flags & (ts4.TypeFlags.Null | ts4.TypeFlags.Undefined))
|
|
1195
1441
|
);
|
|
1442
|
+
const nonNullMembers = nonNullTypes.map((memberType, index) => ({
|
|
1443
|
+
memberType,
|
|
1444
|
+
sourceNode: nonNullSourceNodes.length === nonNullTypes.length ? nonNullSourceNodes[index] : void 0
|
|
1445
|
+
}));
|
|
1196
1446
|
const hasNull = allTypes.some((t) => t.flags & ts4.TypeFlags.Null);
|
|
1197
1447
|
const memberDisplayNames = /* @__PURE__ */ new Map();
|
|
1198
1448
|
if (namedDecl) {
|
|
@@ -1209,7 +1459,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
1209
1459
|
if (!typeName) {
|
|
1210
1460
|
return result;
|
|
1211
1461
|
}
|
|
1212
|
-
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
|
|
1462
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
1213
1463
|
typeRegistry[typeName] = {
|
|
1214
1464
|
name: typeName,
|
|
1215
1465
|
type: result,
|
|
@@ -1257,14 +1507,15 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
1257
1507
|
} : enumNode;
|
|
1258
1508
|
return registerNamed(result);
|
|
1259
1509
|
}
|
|
1260
|
-
if (
|
|
1510
|
+
if (nonNullMembers.length === 1 && nonNullMembers[0]) {
|
|
1261
1511
|
const inner = resolveTypeNode(
|
|
1262
|
-
|
|
1512
|
+
nonNullMembers[0].memberType,
|
|
1263
1513
|
checker,
|
|
1264
1514
|
file,
|
|
1265
1515
|
typeRegistry,
|
|
1266
1516
|
visiting,
|
|
1267
|
-
sourceNode
|
|
1517
|
+
nonNullMembers[0].sourceNode ?? sourceNode,
|
|
1518
|
+
extensionRegistry
|
|
1268
1519
|
);
|
|
1269
1520
|
const result = hasNull ? {
|
|
1270
1521
|
kind: "union",
|
|
@@ -1272,21 +1523,38 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
|
|
|
1272
1523
|
} : inner;
|
|
1273
1524
|
return registerNamed(result);
|
|
1274
1525
|
}
|
|
1275
|
-
const members =
|
|
1276
|
-
(
|
|
1526
|
+
const members = nonNullMembers.map(
|
|
1527
|
+
({ memberType, sourceNode: memberSourceNode }) => resolveTypeNode(
|
|
1528
|
+
memberType,
|
|
1529
|
+
checker,
|
|
1530
|
+
file,
|
|
1531
|
+
typeRegistry,
|
|
1532
|
+
visiting,
|
|
1533
|
+
memberSourceNode ?? sourceNode,
|
|
1534
|
+
extensionRegistry
|
|
1535
|
+
)
|
|
1277
1536
|
);
|
|
1278
1537
|
if (hasNull) {
|
|
1279
1538
|
members.push({ kind: "primitive", primitiveKind: "null" });
|
|
1280
1539
|
}
|
|
1281
1540
|
return registerNamed({ kind: "union", members });
|
|
1282
1541
|
}
|
|
1283
|
-
function resolveArrayType(type, checker, file, typeRegistry, visiting) {
|
|
1542
|
+
function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry) {
|
|
1284
1543
|
const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
|
|
1285
1544
|
const elementType = typeArgs?.[0];
|
|
1286
|
-
const
|
|
1545
|
+
const elementSourceNode = extractArrayElementTypeNode(sourceNode, checker);
|
|
1546
|
+
const items = elementType ? resolveTypeNode(
|
|
1547
|
+
elementType,
|
|
1548
|
+
checker,
|
|
1549
|
+
file,
|
|
1550
|
+
typeRegistry,
|
|
1551
|
+
visiting,
|
|
1552
|
+
elementSourceNode,
|
|
1553
|
+
extensionRegistry
|
|
1554
|
+
) : { kind: "primitive", primitiveKind: "string" };
|
|
1287
1555
|
return { kind: "array", items };
|
|
1288
1556
|
}
|
|
1289
|
-
function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
|
|
1557
|
+
function tryResolveRecordType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1290
1558
|
if (type.getProperties().length > 0) {
|
|
1291
1559
|
return null;
|
|
1292
1560
|
}
|
|
@@ -1294,7 +1562,15 @@ function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
|
|
|
1294
1562
|
if (!indexInfo) {
|
|
1295
1563
|
return null;
|
|
1296
1564
|
}
|
|
1297
|
-
const valueType = resolveTypeNode(
|
|
1565
|
+
const valueType = resolveTypeNode(
|
|
1566
|
+
indexInfo.type,
|
|
1567
|
+
checker,
|
|
1568
|
+
file,
|
|
1569
|
+
typeRegistry,
|
|
1570
|
+
visiting,
|
|
1571
|
+
void 0,
|
|
1572
|
+
extensionRegistry
|
|
1573
|
+
);
|
|
1298
1574
|
return { kind: "record", valueType };
|
|
1299
1575
|
}
|
|
1300
1576
|
function typeNodeContainsReference(type, targetName) {
|
|
@@ -1322,7 +1598,7 @@ function typeNodeContainsReference(type, targetName) {
|
|
|
1322
1598
|
}
|
|
1323
1599
|
}
|
|
1324
1600
|
}
|
|
1325
|
-
function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
1601
|
+
function resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1326
1602
|
const typeName = getNamedTypeName(type);
|
|
1327
1603
|
const namedTypeName = typeName ?? void 0;
|
|
1328
1604
|
const namedDecl = getNamedTypeDeclaration(type);
|
|
@@ -1353,7 +1629,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
|
1353
1629
|
return { kind: "reference", name: namedTypeName, typeArguments: [] };
|
|
1354
1630
|
}
|
|
1355
1631
|
}
|
|
1356
|
-
const recordNode = tryResolveRecordType(
|
|
1632
|
+
const recordNode = tryResolveRecordType(
|
|
1633
|
+
type,
|
|
1634
|
+
checker,
|
|
1635
|
+
file,
|
|
1636
|
+
typeRegistry,
|
|
1637
|
+
visiting,
|
|
1638
|
+
extensionRegistry
|
|
1639
|
+
);
|
|
1357
1640
|
if (recordNode) {
|
|
1358
1641
|
visiting.delete(type);
|
|
1359
1642
|
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
@@ -1362,7 +1645,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
|
1362
1645
|
clearNamedTypeRegistration();
|
|
1363
1646
|
return recordNode;
|
|
1364
1647
|
}
|
|
1365
|
-
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
|
|
1648
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
1366
1649
|
typeRegistry[namedTypeName] = {
|
|
1367
1650
|
name: namedTypeName,
|
|
1368
1651
|
type: recordNode,
|
|
@@ -1374,7 +1657,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
|
1374
1657
|
return recordNode;
|
|
1375
1658
|
}
|
|
1376
1659
|
const properties = [];
|
|
1377
|
-
const fieldInfoMap = getNamedTypeFieldNodeInfoMap(
|
|
1660
|
+
const fieldInfoMap = getNamedTypeFieldNodeInfoMap(
|
|
1661
|
+
type,
|
|
1662
|
+
checker,
|
|
1663
|
+
file,
|
|
1664
|
+
typeRegistry,
|
|
1665
|
+
visiting,
|
|
1666
|
+
extensionRegistry
|
|
1667
|
+
);
|
|
1378
1668
|
for (const prop of type.getProperties()) {
|
|
1379
1669
|
const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
|
|
1380
1670
|
if (!declaration) continue;
|
|
@@ -1386,7 +1676,8 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
|
1386
1676
|
file,
|
|
1387
1677
|
typeRegistry,
|
|
1388
1678
|
visiting,
|
|
1389
|
-
declaration
|
|
1679
|
+
declaration,
|
|
1680
|
+
extensionRegistry
|
|
1390
1681
|
);
|
|
1391
1682
|
const fieldNodeInfo = fieldInfoMap?.get(prop.name);
|
|
1392
1683
|
properties.push({
|
|
@@ -1405,7 +1696,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
|
1405
1696
|
additionalProperties: true
|
|
1406
1697
|
};
|
|
1407
1698
|
if (namedTypeName !== void 0 && shouldRegisterNamedType) {
|
|
1408
|
-
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
|
|
1699
|
+
const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
|
|
1409
1700
|
typeRegistry[namedTypeName] = {
|
|
1410
1701
|
name: namedTypeName,
|
|
1411
1702
|
type: objectNode,
|
|
@@ -1416,7 +1707,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
|
|
|
1416
1707
|
}
|
|
1417
1708
|
return objectNode;
|
|
1418
1709
|
}
|
|
1419
|
-
function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting) {
|
|
1710
|
+
function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1420
1711
|
const symbols = [type.getSymbol(), type.aliasSymbol].filter(
|
|
1421
1712
|
(s) => s?.declarations != null && s.declarations.length > 0
|
|
1422
1713
|
);
|
|
@@ -1428,7 +1719,14 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
1428
1719
|
const map = /* @__PURE__ */ new Map();
|
|
1429
1720
|
for (const member of classDecl.members) {
|
|
1430
1721
|
if (ts4.isPropertyDeclaration(member) && ts4.isIdentifier(member.name)) {
|
|
1431
|
-
const fieldNode = analyzeFieldToIR(
|
|
1722
|
+
const fieldNode = analyzeFieldToIR(
|
|
1723
|
+
member,
|
|
1724
|
+
checker,
|
|
1725
|
+
file,
|
|
1726
|
+
typeRegistry,
|
|
1727
|
+
visiting,
|
|
1728
|
+
extensionRegistry
|
|
1729
|
+
);
|
|
1432
1730
|
if (fieldNode) {
|
|
1433
1731
|
map.set(fieldNode.name, {
|
|
1434
1732
|
constraints: [...fieldNode.constraints],
|
|
@@ -1442,7 +1740,14 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
1442
1740
|
}
|
|
1443
1741
|
const interfaceDecl = declarations.find(ts4.isInterfaceDeclaration);
|
|
1444
1742
|
if (interfaceDecl) {
|
|
1445
|
-
return buildFieldNodeInfoMap(
|
|
1743
|
+
return buildFieldNodeInfoMap(
|
|
1744
|
+
interfaceDecl.members,
|
|
1745
|
+
checker,
|
|
1746
|
+
file,
|
|
1747
|
+
typeRegistry,
|
|
1748
|
+
visiting,
|
|
1749
|
+
extensionRegistry
|
|
1750
|
+
);
|
|
1446
1751
|
}
|
|
1447
1752
|
const typeAliasDecl = declarations.find(ts4.isTypeAliasDeclaration);
|
|
1448
1753
|
if (typeAliasDecl && ts4.isTypeLiteralNode(typeAliasDecl.type)) {
|
|
@@ -1451,17 +1756,68 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
|
|
|
1451
1756
|
checker,
|
|
1452
1757
|
file,
|
|
1453
1758
|
typeRegistry,
|
|
1454
|
-
visiting
|
|
1759
|
+
visiting,
|
|
1760
|
+
extensionRegistry
|
|
1455
1761
|
);
|
|
1456
1762
|
}
|
|
1457
1763
|
}
|
|
1458
1764
|
return null;
|
|
1459
1765
|
}
|
|
1460
|
-
function
|
|
1766
|
+
function extractArrayElementTypeNode(sourceNode, checker) {
|
|
1767
|
+
const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
|
|
1768
|
+
if (typeNode === void 0) {
|
|
1769
|
+
return void 0;
|
|
1770
|
+
}
|
|
1771
|
+
const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
|
|
1772
|
+
if (ts4.isArrayTypeNode(resolvedTypeNode)) {
|
|
1773
|
+
return resolvedTypeNode.elementType;
|
|
1774
|
+
}
|
|
1775
|
+
if (ts4.isTypeReferenceNode(resolvedTypeNode) && ts4.isIdentifier(resolvedTypeNode.typeName) && resolvedTypeNode.typeName.text === "Array" && resolvedTypeNode.typeArguments?.[0]) {
|
|
1776
|
+
return resolvedTypeNode.typeArguments[0];
|
|
1777
|
+
}
|
|
1778
|
+
return void 0;
|
|
1779
|
+
}
|
|
1780
|
+
function extractUnionMemberTypeNodes(sourceNode, checker) {
|
|
1781
|
+
const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
|
|
1782
|
+
if (!typeNode) {
|
|
1783
|
+
return [];
|
|
1784
|
+
}
|
|
1785
|
+
const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
|
|
1786
|
+
return ts4.isUnionTypeNode(resolvedTypeNode) ? [...resolvedTypeNode.types] : [];
|
|
1787
|
+
}
|
|
1788
|
+
function resolveAliasedTypeNode(typeNode, checker, visited = /* @__PURE__ */ new Set()) {
|
|
1789
|
+
if (ts4.isParenthesizedTypeNode(typeNode)) {
|
|
1790
|
+
return resolveAliasedTypeNode(typeNode.type, checker, visited);
|
|
1791
|
+
}
|
|
1792
|
+
if (!ts4.isTypeReferenceNode(typeNode) || !ts4.isIdentifier(typeNode.typeName)) {
|
|
1793
|
+
return typeNode;
|
|
1794
|
+
}
|
|
1795
|
+
const symbol = checker.getSymbolAtLocation(typeNode.typeName);
|
|
1796
|
+
const aliasDecl = symbol?.declarations?.find(ts4.isTypeAliasDeclaration);
|
|
1797
|
+
if (aliasDecl === void 0 || visited.has(aliasDecl)) {
|
|
1798
|
+
return typeNode;
|
|
1799
|
+
}
|
|
1800
|
+
visited.add(aliasDecl);
|
|
1801
|
+
return resolveAliasedTypeNode(aliasDecl.type, checker, visited);
|
|
1802
|
+
}
|
|
1803
|
+
function isNullishTypeNode(typeNode) {
|
|
1804
|
+
if (typeNode.kind === ts4.SyntaxKind.NullKeyword || typeNode.kind === ts4.SyntaxKind.UndefinedKeyword) {
|
|
1805
|
+
return true;
|
|
1806
|
+
}
|
|
1807
|
+
return ts4.isLiteralTypeNode(typeNode) && (typeNode.literal.kind === ts4.SyntaxKind.NullKeyword || typeNode.literal.kind === ts4.SyntaxKind.UndefinedKeyword);
|
|
1808
|
+
}
|
|
1809
|
+
function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, extensionRegistry) {
|
|
1461
1810
|
const map = /* @__PURE__ */ new Map();
|
|
1462
1811
|
for (const member of members) {
|
|
1463
1812
|
if (ts4.isPropertySignature(member)) {
|
|
1464
|
-
const fieldNode = analyzeInterfacePropertyToIR(
|
|
1813
|
+
const fieldNode = analyzeInterfacePropertyToIR(
|
|
1814
|
+
member,
|
|
1815
|
+
checker,
|
|
1816
|
+
file,
|
|
1817
|
+
typeRegistry,
|
|
1818
|
+
visiting,
|
|
1819
|
+
extensionRegistry
|
|
1820
|
+
);
|
|
1465
1821
|
if (fieldNode) {
|
|
1466
1822
|
map.set(fieldNode.name, {
|
|
1467
1823
|
constraints: [...fieldNode.constraints],
|
|
@@ -1474,7 +1830,7 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting) {
|
|
|
1474
1830
|
return map;
|
|
1475
1831
|
}
|
|
1476
1832
|
var MAX_ALIAS_CHAIN_DEPTH = 8;
|
|
1477
|
-
function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
|
|
1833
|
+
function extractTypeAliasConstraintNodes(typeNode, checker, file, extensionRegistry, depth = 0) {
|
|
1478
1834
|
if (!ts4.isTypeReferenceNode(typeNode)) return [];
|
|
1479
1835
|
if (depth >= MAX_ALIAS_CHAIN_DEPTH) {
|
|
1480
1836
|
const aliasName = typeNode.typeName.getText();
|
|
@@ -1487,8 +1843,29 @@ function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
|
|
|
1487
1843
|
const aliasDecl = symbol.declarations.find(ts4.isTypeAliasDeclaration);
|
|
1488
1844
|
if (!aliasDecl) return [];
|
|
1489
1845
|
if (ts4.isTypeLiteralNode(aliasDecl.type)) return [];
|
|
1490
|
-
const
|
|
1491
|
-
|
|
1846
|
+
const aliasFieldType = resolveTypeNode(
|
|
1847
|
+
checker.getTypeAtLocation(aliasDecl.type),
|
|
1848
|
+
checker,
|
|
1849
|
+
file,
|
|
1850
|
+
{},
|
|
1851
|
+
/* @__PURE__ */ new Set(),
|
|
1852
|
+
aliasDecl.type,
|
|
1853
|
+
extensionRegistry
|
|
1854
|
+
);
|
|
1855
|
+
const constraints = extractJSDocConstraintNodes(
|
|
1856
|
+
aliasDecl,
|
|
1857
|
+
file,
|
|
1858
|
+
makeParseOptions(extensionRegistry, aliasFieldType)
|
|
1859
|
+
);
|
|
1860
|
+
constraints.push(
|
|
1861
|
+
...extractTypeAliasConstraintNodes(
|
|
1862
|
+
aliasDecl.type,
|
|
1863
|
+
checker,
|
|
1864
|
+
file,
|
|
1865
|
+
extensionRegistry,
|
|
1866
|
+
depth + 1
|
|
1867
|
+
)
|
|
1868
|
+
);
|
|
1492
1869
|
return constraints;
|
|
1493
1870
|
}
|
|
1494
1871
|
function provenanceForNode(node, file) {
|
|
@@ -1975,7 +2352,12 @@ function applyCustomConstraint(schema, constraint, ctx) {
|
|
|
1975
2352
|
`Cannot generate JSON Schema for custom constraint "${constraint.constraintId}" without a matching extension registration`
|
|
1976
2353
|
);
|
|
1977
2354
|
}
|
|
1978
|
-
|
|
2355
|
+
assignVendorPrefixedExtensionKeywords(
|
|
2356
|
+
schema,
|
|
2357
|
+
registration.toJsonSchema(constraint.payload, ctx.vendorPrefix),
|
|
2358
|
+
ctx.vendorPrefix,
|
|
2359
|
+
`custom constraint "${constraint.constraintId}"`
|
|
2360
|
+
);
|
|
1979
2361
|
}
|
|
1980
2362
|
function applyCustomAnnotation(schema, annotation, ctx) {
|
|
1981
2363
|
const registration = ctx.extensionRegistry?.findAnnotation(annotation.annotationId);
|
|
@@ -1987,7 +2369,22 @@ function applyCustomAnnotation(schema, annotation, ctx) {
|
|
|
1987
2369
|
if (registration.toJsonSchema === void 0) {
|
|
1988
2370
|
return;
|
|
1989
2371
|
}
|
|
1990
|
-
|
|
2372
|
+
assignVendorPrefixedExtensionKeywords(
|
|
2373
|
+
schema,
|
|
2374
|
+
registration.toJsonSchema(annotation.value, ctx.vendorPrefix),
|
|
2375
|
+
ctx.vendorPrefix,
|
|
2376
|
+
`custom annotation "${annotation.annotationId}"`
|
|
2377
|
+
);
|
|
2378
|
+
}
|
|
2379
|
+
function assignVendorPrefixedExtensionKeywords(schema, extensionSchema, vendorPrefix, source) {
|
|
2380
|
+
for (const [key, value] of Object.entries(extensionSchema)) {
|
|
2381
|
+
if (!key.startsWith(`${vendorPrefix}-`)) {
|
|
2382
|
+
throw new Error(
|
|
2383
|
+
`Cannot apply ${source}: extension hooks may only emit "${vendorPrefix}-*" JSON Schema keywords`
|
|
2384
|
+
);
|
|
2385
|
+
}
|
|
2386
|
+
schema[key] = value;
|
|
2387
|
+
}
|
|
1991
2388
|
}
|
|
1992
2389
|
|
|
1993
2390
|
// src/ui-schema/schema.ts
|
|
@@ -2214,15 +2611,16 @@ function generateUiSchemaFromIR(ir) {
|
|
|
2214
2611
|
}
|
|
2215
2612
|
|
|
2216
2613
|
// src/generators/class-schema.ts
|
|
2217
|
-
function generateClassSchemas(analysis, source) {
|
|
2614
|
+
function generateClassSchemas(analysis, source, options) {
|
|
2218
2615
|
const ir = canonicalizeTSDoc(analysis, source);
|
|
2219
2616
|
return {
|
|
2220
|
-
jsonSchema: generateJsonSchemaFromIR(ir),
|
|
2617
|
+
jsonSchema: generateJsonSchemaFromIR(ir, options),
|
|
2221
2618
|
uiSchema: generateUiSchemaFromIR(ir)
|
|
2222
2619
|
};
|
|
2223
2620
|
}
|
|
2224
2621
|
|
|
2225
2622
|
// src/validate/constraint-validator.ts
|
|
2623
|
+
import { normalizeConstraintTagName as normalizeConstraintTagName2 } from "@formspec/core";
|
|
2226
2624
|
function addContradiction(ctx, message, primary, related) {
|
|
2227
2625
|
ctx.diagnostics.push({
|
|
2228
2626
|
code: "CONTRADICTING_CONSTRAINTS",
|
|
@@ -2268,6 +2666,13 @@ function addConstraintBroadening(ctx, message, primary, related) {
|
|
|
2268
2666
|
relatedLocations: [related]
|
|
2269
2667
|
});
|
|
2270
2668
|
}
|
|
2669
|
+
function getExtensionIdFromConstraintId(constraintId) {
|
|
2670
|
+
const separator = constraintId.lastIndexOf("/");
|
|
2671
|
+
if (separator <= 0) {
|
|
2672
|
+
return null;
|
|
2673
|
+
}
|
|
2674
|
+
return constraintId.slice(0, separator);
|
|
2675
|
+
}
|
|
2271
2676
|
function findNumeric(constraints, constraintKind) {
|
|
2272
2677
|
return constraints.find((c) => c.constraintKind === constraintKind);
|
|
2273
2678
|
}
|
|
@@ -2438,6 +2843,112 @@ function checkConstraintBroadening(ctx, fieldName, constraints) {
|
|
|
2438
2843
|
strongestByKey.set(key, constraint);
|
|
2439
2844
|
}
|
|
2440
2845
|
}
|
|
2846
|
+
function compareCustomConstraintStrength(current, previous) {
|
|
2847
|
+
const order = current.comparePayloads(current.constraint.payload, previous.constraint.payload);
|
|
2848
|
+
const equalPayloadTiebreaker = order === 0 ? compareSemanticInclusivity(current.role.inclusive, previous.role.inclusive) : order;
|
|
2849
|
+
switch (current.role.bound) {
|
|
2850
|
+
case "lower":
|
|
2851
|
+
return equalPayloadTiebreaker;
|
|
2852
|
+
case "upper":
|
|
2853
|
+
return equalPayloadTiebreaker === 0 ? 0 : -equalPayloadTiebreaker;
|
|
2854
|
+
case "exact":
|
|
2855
|
+
return order === 0 ? 0 : Number.NaN;
|
|
2856
|
+
default: {
|
|
2857
|
+
const _exhaustive = current.role.bound;
|
|
2858
|
+
return _exhaustive;
|
|
2859
|
+
}
|
|
2860
|
+
}
|
|
2861
|
+
}
|
|
2862
|
+
function compareSemanticInclusivity(currentInclusive, previousInclusive) {
|
|
2863
|
+
if (currentInclusive === previousInclusive) {
|
|
2864
|
+
return 0;
|
|
2865
|
+
}
|
|
2866
|
+
return currentInclusive ? -1 : 1;
|
|
2867
|
+
}
|
|
2868
|
+
function customConstraintsContradict(lower, upper) {
|
|
2869
|
+
const order = lower.comparePayloads(lower.constraint.payload, upper.constraint.payload);
|
|
2870
|
+
if (order > 0) {
|
|
2871
|
+
return true;
|
|
2872
|
+
}
|
|
2873
|
+
if (order < 0) {
|
|
2874
|
+
return false;
|
|
2875
|
+
}
|
|
2876
|
+
return !lower.role.inclusive || !upper.role.inclusive;
|
|
2877
|
+
}
|
|
2878
|
+
function describeCustomConstraintTag(constraint) {
|
|
2879
|
+
return constraint.provenance.tagName ?? constraint.constraintId;
|
|
2880
|
+
}
|
|
2881
|
+
function checkCustomConstraintSemantics(ctx, fieldName, constraints) {
|
|
2882
|
+
if (ctx.extensionRegistry === void 0) {
|
|
2883
|
+
return;
|
|
2884
|
+
}
|
|
2885
|
+
const strongestByKey = /* @__PURE__ */ new Map();
|
|
2886
|
+
const lowerByFamily = /* @__PURE__ */ new Map();
|
|
2887
|
+
const upperByFamily = /* @__PURE__ */ new Map();
|
|
2888
|
+
for (const constraint of constraints) {
|
|
2889
|
+
if (constraint.constraintKind !== "custom") {
|
|
2890
|
+
continue;
|
|
2891
|
+
}
|
|
2892
|
+
const registration = ctx.extensionRegistry.findConstraint(constraint.constraintId);
|
|
2893
|
+
if (registration?.comparePayloads === void 0 || registration.semanticRole === void 0) {
|
|
2894
|
+
continue;
|
|
2895
|
+
}
|
|
2896
|
+
const entry = {
|
|
2897
|
+
constraint,
|
|
2898
|
+
comparePayloads: registration.comparePayloads,
|
|
2899
|
+
role: registration.semanticRole
|
|
2900
|
+
};
|
|
2901
|
+
const familyKey = `${registration.semanticRole.family}:${pathKey(constraint)}`;
|
|
2902
|
+
const boundKey = `${familyKey}:${registration.semanticRole.bound}`;
|
|
2903
|
+
const previous = strongestByKey.get(boundKey);
|
|
2904
|
+
if (previous !== void 0) {
|
|
2905
|
+
const strength = compareCustomConstraintStrength(entry, previous);
|
|
2906
|
+
if (Number.isNaN(strength)) {
|
|
2907
|
+
addContradiction(
|
|
2908
|
+
ctx,
|
|
2909
|
+
`Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} conflicts with ${describeCustomConstraintTag(previous.constraint)}`,
|
|
2910
|
+
constraint.provenance,
|
|
2911
|
+
previous.constraint.provenance
|
|
2912
|
+
);
|
|
2913
|
+
continue;
|
|
2914
|
+
}
|
|
2915
|
+
if (strength < 0) {
|
|
2916
|
+
addConstraintBroadening(
|
|
2917
|
+
ctx,
|
|
2918
|
+
`Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} is broader than earlier ${describeCustomConstraintTag(previous.constraint)}. Constraints can only narrow.`,
|
|
2919
|
+
constraint.provenance,
|
|
2920
|
+
previous.constraint.provenance
|
|
2921
|
+
);
|
|
2922
|
+
continue;
|
|
2923
|
+
}
|
|
2924
|
+
if (strength > 0) {
|
|
2925
|
+
strongestByKey.set(boundKey, entry);
|
|
2926
|
+
}
|
|
2927
|
+
} else {
|
|
2928
|
+
strongestByKey.set(boundKey, entry);
|
|
2929
|
+
}
|
|
2930
|
+
if (registration.semanticRole.bound === "lower") {
|
|
2931
|
+
lowerByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
|
|
2932
|
+
} else if (registration.semanticRole.bound === "upper") {
|
|
2933
|
+
upperByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
|
|
2934
|
+
}
|
|
2935
|
+
}
|
|
2936
|
+
for (const [familyKey, lower] of lowerByFamily) {
|
|
2937
|
+
const upper = upperByFamily.get(familyKey);
|
|
2938
|
+
if (upper === void 0) {
|
|
2939
|
+
continue;
|
|
2940
|
+
}
|
|
2941
|
+
if (!customConstraintsContradict(lower, upper)) {
|
|
2942
|
+
continue;
|
|
2943
|
+
}
|
|
2944
|
+
addContradiction(
|
|
2945
|
+
ctx,
|
|
2946
|
+
`Field "${formatPathTargetFieldName(fieldName, lower.constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(lower.constraint)} contradicts ${describeCustomConstraintTag(upper.constraint)}`,
|
|
2947
|
+
lower.constraint.provenance,
|
|
2948
|
+
upper.constraint.provenance
|
|
2949
|
+
);
|
|
2950
|
+
}
|
|
2951
|
+
}
|
|
2441
2952
|
function checkNumericContradictions(ctx, fieldName, constraints) {
|
|
2442
2953
|
const min = findNumeric(constraints, "minimum");
|
|
2443
2954
|
const max = findNumeric(constraints, "maximum");
|
|
@@ -2749,8 +3260,30 @@ function checkCustomConstraint(ctx, fieldName, type, constraint) {
|
|
|
2749
3260
|
);
|
|
2750
3261
|
return;
|
|
2751
3262
|
}
|
|
2752
|
-
|
|
2753
|
-
if (
|
|
3263
|
+
const normalizedTagName = constraint.provenance.tagName === void 0 ? void 0 : normalizeConstraintTagName2(constraint.provenance.tagName.replace(/^@/, ""));
|
|
3264
|
+
if (normalizedTagName !== void 0) {
|
|
3265
|
+
const tagRegistration = ctx.extensionRegistry.findConstraintTag(normalizedTagName);
|
|
3266
|
+
const extensionId = getExtensionIdFromConstraintId(constraint.constraintId);
|
|
3267
|
+
if (extensionId !== null && tagRegistration?.extensionId === extensionId && tagRegistration.registration.constraintName === registration.constraintName && tagRegistration.registration.isApplicableToType?.(type) === false) {
|
|
3268
|
+
addTypeMismatch(
|
|
3269
|
+
ctx,
|
|
3270
|
+
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
3271
|
+
constraint.provenance
|
|
3272
|
+
);
|
|
3273
|
+
return;
|
|
3274
|
+
}
|
|
3275
|
+
}
|
|
3276
|
+
if (registration.applicableTypes === null) {
|
|
3277
|
+
if (registration.isApplicableToType?.(type) === false) {
|
|
3278
|
+
addTypeMismatch(
|
|
3279
|
+
ctx,
|
|
3280
|
+
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
3281
|
+
constraint.provenance
|
|
3282
|
+
);
|
|
3283
|
+
}
|
|
3284
|
+
return;
|
|
3285
|
+
}
|
|
3286
|
+
if (!registration.applicableTypes.includes(type.kind) || registration.isApplicableToType?.(type) === false) {
|
|
2754
3287
|
addTypeMismatch(
|
|
2755
3288
|
ctx,
|
|
2756
3289
|
`Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
|
|
@@ -2781,6 +3314,7 @@ function validateConstraints(ctx, name, type, constraints) {
|
|
|
2781
3314
|
checkAllowedMembersContradiction(ctx, name, constraints);
|
|
2782
3315
|
checkConstContradictions(ctx, name, constraints);
|
|
2783
3316
|
checkConstraintBroadening(ctx, name, constraints);
|
|
3317
|
+
checkCustomConstraintSemantics(ctx, name, constraints);
|
|
2784
3318
|
checkTypeApplicability(ctx, name, type, constraints);
|
|
2785
3319
|
}
|
|
2786
3320
|
function validateElement(ctx, element) {
|
|
@@ -2822,7 +3356,10 @@ function validateIR(ir, options) {
|
|
|
2822
3356
|
// src/extensions/registry.ts
|
|
2823
3357
|
function createExtensionRegistry(extensions) {
|
|
2824
3358
|
const typeMap = /* @__PURE__ */ new Map();
|
|
3359
|
+
const typeNameMap = /* @__PURE__ */ new Map();
|
|
2825
3360
|
const constraintMap = /* @__PURE__ */ new Map();
|
|
3361
|
+
const constraintTagMap = /* @__PURE__ */ new Map();
|
|
3362
|
+
const builtinBroadeningMap = /* @__PURE__ */ new Map();
|
|
2826
3363
|
const annotationMap = /* @__PURE__ */ new Map();
|
|
2827
3364
|
for (const ext of extensions) {
|
|
2828
3365
|
if (ext.types !== void 0) {
|
|
@@ -2832,6 +3369,27 @@ function createExtensionRegistry(extensions) {
|
|
|
2832
3369
|
throw new Error(`Duplicate custom type ID: "${qualifiedId}"`);
|
|
2833
3370
|
}
|
|
2834
3371
|
typeMap.set(qualifiedId, type);
|
|
3372
|
+
for (const sourceTypeName of type.tsTypeNames ?? [type.typeName]) {
|
|
3373
|
+
if (typeNameMap.has(sourceTypeName)) {
|
|
3374
|
+
throw new Error(`Duplicate custom type source name: "${sourceTypeName}"`);
|
|
3375
|
+
}
|
|
3376
|
+
typeNameMap.set(sourceTypeName, {
|
|
3377
|
+
extensionId: ext.extensionId,
|
|
3378
|
+
registration: type
|
|
3379
|
+
});
|
|
3380
|
+
}
|
|
3381
|
+
if (type.builtinConstraintBroadenings !== void 0) {
|
|
3382
|
+
for (const broadening of type.builtinConstraintBroadenings) {
|
|
3383
|
+
const key = `${qualifiedId}:${broadening.tagName}`;
|
|
3384
|
+
if (builtinBroadeningMap.has(key)) {
|
|
3385
|
+
throw new Error(`Duplicate built-in constraint broadening: "${key}"`);
|
|
3386
|
+
}
|
|
3387
|
+
builtinBroadeningMap.set(key, {
|
|
3388
|
+
extensionId: ext.extensionId,
|
|
3389
|
+
registration: broadening
|
|
3390
|
+
});
|
|
3391
|
+
}
|
|
3392
|
+
}
|
|
2835
3393
|
}
|
|
2836
3394
|
}
|
|
2837
3395
|
if (ext.constraints !== void 0) {
|
|
@@ -2843,6 +3401,17 @@ function createExtensionRegistry(extensions) {
|
|
|
2843
3401
|
constraintMap.set(qualifiedId, constraint);
|
|
2844
3402
|
}
|
|
2845
3403
|
}
|
|
3404
|
+
if (ext.constraintTags !== void 0) {
|
|
3405
|
+
for (const tag of ext.constraintTags) {
|
|
3406
|
+
if (constraintTagMap.has(tag.tagName)) {
|
|
3407
|
+
throw new Error(`Duplicate custom constraint tag: "@${tag.tagName}"`);
|
|
3408
|
+
}
|
|
3409
|
+
constraintTagMap.set(tag.tagName, {
|
|
3410
|
+
extensionId: ext.extensionId,
|
|
3411
|
+
registration: tag
|
|
3412
|
+
});
|
|
3413
|
+
}
|
|
3414
|
+
}
|
|
2846
3415
|
if (ext.annotations !== void 0) {
|
|
2847
3416
|
for (const annotation of ext.annotations) {
|
|
2848
3417
|
const qualifiedId = `${ext.extensionId}/${annotation.annotationName}`;
|
|
@@ -2856,7 +3425,10 @@ function createExtensionRegistry(extensions) {
|
|
|
2856
3425
|
return {
|
|
2857
3426
|
extensions,
|
|
2858
3427
|
findType: (typeId) => typeMap.get(typeId),
|
|
3428
|
+
findTypeByName: (typeName) => typeNameMap.get(typeName),
|
|
2859
3429
|
findConstraint: (constraintId) => constraintMap.get(constraintId),
|
|
3430
|
+
findConstraintTag: (tagName) => constraintTagMap.get(tagName),
|
|
3431
|
+
findBuiltinConstraintBroadening: (typeId, tagName) => builtinBroadeningMap.get(`${typeId}:${tagName}`),
|
|
2860
3432
|
findAnnotation: (annotationId) => annotationMap.get(annotationId)
|
|
2861
3433
|
};
|
|
2862
3434
|
}
|