@formspec/build 0.1.0-alpha.13 → 0.1.0-alpha.14
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 +20 -20
- package/dist/__tests__/alias-chain-propagation.test.d.ts +9 -0
- package/dist/__tests__/alias-chain-propagation.test.d.ts.map +1 -0
- package/dist/__tests__/fixtures/alias-chains.d.ts +37 -0
- package/dist/__tests__/fixtures/alias-chains.d.ts.map +1 -0
- package/dist/__tests__/fixtures/example-a-builtins.d.ts +7 -7
- package/dist/__tests__/fixtures/example-interface-types.d.ts +17 -17
- package/dist/__tests__/json-utils.test.d.ts +5 -0
- package/dist/__tests__/json-utils.test.d.ts.map +1 -0
- package/dist/__tests__/path-target-parser.test.d.ts +9 -0
- package/dist/__tests__/path-target-parser.test.d.ts.map +1 -0
- package/dist/analyzer/class-analyzer.d.ts.map +1 -1
- package/dist/analyzer/jsdoc-constraints.d.ts +2 -2
- package/dist/analyzer/jsdoc-constraints.d.ts.map +1 -1
- package/dist/analyzer/json-utils.d.ts +22 -0
- package/dist/analyzer/json-utils.d.ts.map +1 -0
- package/dist/analyzer/tsdoc-parser.d.ts +18 -4
- package/dist/analyzer/tsdoc-parser.d.ts.map +1 -1
- package/dist/browser.cjs +76 -7
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.js +76 -7
- package/dist/browser.js.map +1 -1
- package/dist/build.d.ts +1 -0
- package/dist/cli.cjs +140 -41
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +145 -41
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +134 -40
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +139 -41
- package/dist/index.js.map +1 -1
- package/dist/internals.cjs +147 -46
- package/dist/internals.cjs.map +1 -1
- package/dist/internals.js +152 -47
- package/dist/internals.js.map +1 -1
- package/dist/json-schema/ir-generator.d.ts +1 -0
- 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
|
@@ -431,8 +431,70 @@ function collectFields(elements, properties, required, ctx) {
|
|
|
431
431
|
}
|
|
432
432
|
function generateFieldSchema(field, ctx) {
|
|
433
433
|
const schema = generateTypeNode(field.type, ctx);
|
|
434
|
-
|
|
434
|
+
const directConstraints = [];
|
|
435
|
+
const pathConstraints = [];
|
|
436
|
+
for (const c of field.constraints) {
|
|
437
|
+
if (c.path) {
|
|
438
|
+
pathConstraints.push(c);
|
|
439
|
+
} else {
|
|
440
|
+
directConstraints.push(c);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
applyConstraints(schema, directConstraints);
|
|
435
444
|
applyAnnotations(schema, field.annotations);
|
|
445
|
+
if (pathConstraints.length === 0) {
|
|
446
|
+
return schema;
|
|
447
|
+
}
|
|
448
|
+
return applyPathTargetedConstraints(schema, pathConstraints);
|
|
449
|
+
}
|
|
450
|
+
function applyPathTargetedConstraints(schema, pathConstraints) {
|
|
451
|
+
if (schema.type === "array" && schema.items) {
|
|
452
|
+
schema.items = applyPathTargetedConstraints(schema.items, pathConstraints);
|
|
453
|
+
return schema;
|
|
454
|
+
}
|
|
455
|
+
const byTarget = /* @__PURE__ */ new Map();
|
|
456
|
+
for (const c of pathConstraints) {
|
|
457
|
+
const target = c.path?.segments[0];
|
|
458
|
+
if (!target) continue;
|
|
459
|
+
const group = byTarget.get(target) ?? [];
|
|
460
|
+
group.push(c);
|
|
461
|
+
byTarget.set(target, group);
|
|
462
|
+
}
|
|
463
|
+
const propertyOverrides = {};
|
|
464
|
+
for (const [target, constraints] of byTarget) {
|
|
465
|
+
const subSchema = {};
|
|
466
|
+
applyConstraints(subSchema, constraints);
|
|
467
|
+
propertyOverrides[target] = subSchema;
|
|
468
|
+
}
|
|
469
|
+
if (schema.$ref) {
|
|
470
|
+
const { $ref, ...rest } = schema;
|
|
471
|
+
const refPart = { $ref };
|
|
472
|
+
const overridePart = {
|
|
473
|
+
properties: propertyOverrides,
|
|
474
|
+
...rest
|
|
475
|
+
};
|
|
476
|
+
return { allOf: [refPart, overridePart] };
|
|
477
|
+
}
|
|
478
|
+
if (schema.type === "object" && schema.properties) {
|
|
479
|
+
const missingOverrides = {};
|
|
480
|
+
for (const [target, overrideSchema] of Object.entries(propertyOverrides)) {
|
|
481
|
+
if (schema.properties[target]) {
|
|
482
|
+
Object.assign(schema.properties[target], overrideSchema);
|
|
483
|
+
} else {
|
|
484
|
+
missingOverrides[target] = overrideSchema;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
if (Object.keys(missingOverrides).length === 0) {
|
|
488
|
+
return schema;
|
|
489
|
+
}
|
|
490
|
+
return {
|
|
491
|
+
allOf: [schema, { properties: missingOverrides }]
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
if (schema.allOf) {
|
|
495
|
+
schema.allOf = [...schema.allOf, { properties: propertyOverrides }];
|
|
496
|
+
return schema;
|
|
497
|
+
}
|
|
436
498
|
return schema;
|
|
437
499
|
}
|
|
438
500
|
function generateTypeNode(type, ctx) {
|
|
@@ -1002,7 +1064,9 @@ import * as ts4 from "typescript";
|
|
|
1002
1064
|
// src/analyzer/jsdoc-constraints.ts
|
|
1003
1065
|
import * as ts3 from "typescript";
|
|
1004
1066
|
import {
|
|
1005
|
-
BUILTIN_CONSTRAINT_DEFINITIONS as BUILTIN_CONSTRAINT_DEFINITIONS2
|
|
1067
|
+
BUILTIN_CONSTRAINT_DEFINITIONS as BUILTIN_CONSTRAINT_DEFINITIONS2,
|
|
1068
|
+
isBuiltinConstraintName as isBuiltinConstraintName2,
|
|
1069
|
+
normalizeConstraintTagName as normalizeConstraintTagName2
|
|
1006
1070
|
} from "@formspec/core";
|
|
1007
1071
|
|
|
1008
1072
|
// src/analyzer/tsdoc-parser.ts
|
|
@@ -1017,22 +1081,35 @@ import {
|
|
|
1017
1081
|
TextRange
|
|
1018
1082
|
} from "@microsoft/tsdoc";
|
|
1019
1083
|
import {
|
|
1020
|
-
BUILTIN_CONSTRAINT_DEFINITIONS
|
|
1084
|
+
BUILTIN_CONSTRAINT_DEFINITIONS,
|
|
1085
|
+
normalizeConstraintTagName,
|
|
1086
|
+
isBuiltinConstraintName
|
|
1021
1087
|
} from "@formspec/core";
|
|
1088
|
+
|
|
1089
|
+
// src/analyzer/json-utils.ts
|
|
1090
|
+
function tryParseJson(text) {
|
|
1091
|
+
try {
|
|
1092
|
+
return JSON.parse(text);
|
|
1093
|
+
} catch {
|
|
1094
|
+
return null;
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
// src/analyzer/tsdoc-parser.ts
|
|
1022
1099
|
var NUMERIC_CONSTRAINT_MAP = {
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1100
|
+
minimum: "minimum",
|
|
1101
|
+
maximum: "maximum",
|
|
1102
|
+
exclusiveMinimum: "exclusiveMinimum",
|
|
1103
|
+
exclusiveMaximum: "exclusiveMaximum",
|
|
1104
|
+
multipleOf: "multipleOf"
|
|
1027
1105
|
};
|
|
1028
1106
|
var LENGTH_CONSTRAINT_MAP = {
|
|
1029
|
-
|
|
1030
|
-
|
|
1107
|
+
minLength: "minLength",
|
|
1108
|
+
maxLength: "maxLength",
|
|
1109
|
+
minItems: "minItems",
|
|
1110
|
+
maxItems: "maxItems"
|
|
1031
1111
|
};
|
|
1032
|
-
var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["
|
|
1033
|
-
function isBuiltinConstraintName(tagName) {
|
|
1034
|
-
return tagName in BUILTIN_CONSTRAINT_DEFINITIONS;
|
|
1035
|
-
}
|
|
1112
|
+
var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions"]);
|
|
1036
1113
|
function createFormSpecTSDocConfig() {
|
|
1037
1114
|
const config = new TSDocConfiguration();
|
|
1038
1115
|
for (const tagName of Object.keys(BUILTIN_CONSTRAINT_DEFINITIONS)) {
|
|
@@ -1072,7 +1149,7 @@ function parseTSDocTags(node, file = "") {
|
|
|
1072
1149
|
);
|
|
1073
1150
|
const docComment = parserContext.docComment;
|
|
1074
1151
|
for (const block of docComment.customBlocks) {
|
|
1075
|
-
const tagName = block.blockTag.tagName.substring(1);
|
|
1152
|
+
const tagName = normalizeConstraintTagName(block.blockTag.tagName.substring(1));
|
|
1076
1153
|
if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
1077
1154
|
const text = extractBlockText(block).trim();
|
|
1078
1155
|
if (text === "") continue;
|
|
@@ -1093,7 +1170,7 @@ function parseTSDocTags(node, file = "") {
|
|
|
1093
1170
|
}
|
|
1094
1171
|
const jsDocTagsAll = ts2.getJSDocTags(node);
|
|
1095
1172
|
for (const tag of jsDocTagsAll) {
|
|
1096
|
-
const tagName = tag.tagName.text;
|
|
1173
|
+
const tagName = normalizeConstraintTagName(tag.tagName.text);
|
|
1097
1174
|
if (!TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
1098
1175
|
const commentText = getTagCommentText(tag);
|
|
1099
1176
|
if (commentText === void 0 || commentText.trim() === "") continue;
|
|
@@ -1141,6 +1218,15 @@ function parseTSDocTags(node, file = "") {
|
|
|
1141
1218
|
}
|
|
1142
1219
|
return { constraints, annotations };
|
|
1143
1220
|
}
|
|
1221
|
+
function extractPathTarget(text) {
|
|
1222
|
+
const trimmed = text.trimStart();
|
|
1223
|
+
const match = /^:([a-zA-Z_]\w*)\s+([\s\S]*)$/.exec(trimmed);
|
|
1224
|
+
if (!match?.[1] || !match[2]) return null;
|
|
1225
|
+
return {
|
|
1226
|
+
path: { segments: [match[1]] },
|
|
1227
|
+
remainingText: match[2]
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1144
1230
|
function extractBlockText(block) {
|
|
1145
1231
|
return extractPlainText(block.content);
|
|
1146
1232
|
}
|
|
@@ -1163,9 +1249,12 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
1163
1249
|
if (!isBuiltinConstraintName(tagName)) {
|
|
1164
1250
|
return null;
|
|
1165
1251
|
}
|
|
1252
|
+
const pathResult = extractPathTarget(text);
|
|
1253
|
+
const effectiveText = pathResult ? pathResult.remainingText : text;
|
|
1254
|
+
const path3 = pathResult?.path;
|
|
1166
1255
|
const expectedType = BUILTIN_CONSTRAINT_DEFINITIONS[tagName];
|
|
1167
1256
|
if (expectedType === "number") {
|
|
1168
|
-
const value = Number(
|
|
1257
|
+
const value = Number(effectiveText);
|
|
1169
1258
|
if (Number.isNaN(value)) {
|
|
1170
1259
|
return null;
|
|
1171
1260
|
}
|
|
@@ -1175,6 +1264,7 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
1175
1264
|
kind: "constraint",
|
|
1176
1265
|
constraintKind: numericKind,
|
|
1177
1266
|
value,
|
|
1267
|
+
...path3 && { path: path3 },
|
|
1178
1268
|
provenance
|
|
1179
1269
|
};
|
|
1180
1270
|
}
|
|
@@ -1184,42 +1274,41 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
1184
1274
|
kind: "constraint",
|
|
1185
1275
|
constraintKind: lengthKind,
|
|
1186
1276
|
value,
|
|
1277
|
+
...path3 && { path: path3 },
|
|
1187
1278
|
provenance
|
|
1188
1279
|
};
|
|
1189
1280
|
}
|
|
1190
1281
|
return null;
|
|
1191
1282
|
}
|
|
1192
1283
|
if (expectedType === "json") {
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
members.push(id);
|
|
1206
|
-
}
|
|
1284
|
+
const parsed = tryParseJson(effectiveText);
|
|
1285
|
+
if (!Array.isArray(parsed)) {
|
|
1286
|
+
return null;
|
|
1287
|
+
}
|
|
1288
|
+
const members = [];
|
|
1289
|
+
for (const item of parsed) {
|
|
1290
|
+
if (typeof item === "string" || typeof item === "number") {
|
|
1291
|
+
members.push(item);
|
|
1292
|
+
} else if (typeof item === "object" && item !== null && "id" in item) {
|
|
1293
|
+
const id = item["id"];
|
|
1294
|
+
if (typeof id === "string" || typeof id === "number") {
|
|
1295
|
+
members.push(id);
|
|
1207
1296
|
}
|
|
1208
1297
|
}
|
|
1209
|
-
return {
|
|
1210
|
-
kind: "constraint",
|
|
1211
|
-
constraintKind: "allowedMembers",
|
|
1212
|
-
members,
|
|
1213
|
-
provenance
|
|
1214
|
-
};
|
|
1215
|
-
} catch {
|
|
1216
|
-
return null;
|
|
1217
1298
|
}
|
|
1299
|
+
return {
|
|
1300
|
+
kind: "constraint",
|
|
1301
|
+
constraintKind: "allowedMembers",
|
|
1302
|
+
members,
|
|
1303
|
+
...path3 && { path: path3 },
|
|
1304
|
+
provenance
|
|
1305
|
+
};
|
|
1218
1306
|
}
|
|
1219
1307
|
return {
|
|
1220
1308
|
kind: "constraint",
|
|
1221
1309
|
constraintKind: "pattern",
|
|
1222
|
-
pattern:
|
|
1310
|
+
pattern: effectiveText,
|
|
1311
|
+
...path3 && { path: path3 },
|
|
1223
1312
|
provenance
|
|
1224
1313
|
};
|
|
1225
1314
|
}
|
|
@@ -1650,14 +1739,23 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting) {
|
|
|
1650
1739
|
}
|
|
1651
1740
|
return map;
|
|
1652
1741
|
}
|
|
1653
|
-
|
|
1742
|
+
var MAX_ALIAS_CHAIN_DEPTH = 8;
|
|
1743
|
+
function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
|
|
1654
1744
|
if (!ts4.isTypeReferenceNode(typeNode)) return [];
|
|
1745
|
+
if (depth >= MAX_ALIAS_CHAIN_DEPTH) {
|
|
1746
|
+
const aliasName = typeNode.typeName.getText();
|
|
1747
|
+
throw new Error(
|
|
1748
|
+
`Type alias chain exceeds maximum depth of ${String(MAX_ALIAS_CHAIN_DEPTH)} at alias "${aliasName}" in ${file}. Simplify the alias chain or check for circular references.`
|
|
1749
|
+
);
|
|
1750
|
+
}
|
|
1655
1751
|
const symbol = checker.getSymbolAtLocation(typeNode.typeName);
|
|
1656
1752
|
if (!symbol?.declarations) return [];
|
|
1657
1753
|
const aliasDecl = symbol.declarations.find(ts4.isTypeAliasDeclaration);
|
|
1658
1754
|
if (!aliasDecl) return [];
|
|
1659
1755
|
if (ts4.isTypeLiteralNode(aliasDecl.type)) return [];
|
|
1660
|
-
|
|
1756
|
+
const constraints = extractJSDocConstraintNodes(aliasDecl, file);
|
|
1757
|
+
constraints.push(...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, depth + 1));
|
|
1758
|
+
return constraints;
|
|
1661
1759
|
}
|
|
1662
1760
|
function provenanceForNode(node, file) {
|
|
1663
1761
|
const sourceFile = node.getSourceFile();
|