@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/cli.cjs
CHANGED
|
@@ -484,8 +484,70 @@ function collectFields(elements, properties, required, ctx) {
|
|
|
484
484
|
}
|
|
485
485
|
function generateFieldSchema(field, ctx) {
|
|
486
486
|
const schema = generateTypeNode(field.type, ctx);
|
|
487
|
-
|
|
487
|
+
const directConstraints = [];
|
|
488
|
+
const pathConstraints = [];
|
|
489
|
+
for (const c of field.constraints) {
|
|
490
|
+
if (c.path) {
|
|
491
|
+
pathConstraints.push(c);
|
|
492
|
+
} else {
|
|
493
|
+
directConstraints.push(c);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
applyConstraints(schema, directConstraints);
|
|
488
497
|
applyAnnotations(schema, field.annotations);
|
|
498
|
+
if (pathConstraints.length === 0) {
|
|
499
|
+
return schema;
|
|
500
|
+
}
|
|
501
|
+
return applyPathTargetedConstraints(schema, pathConstraints);
|
|
502
|
+
}
|
|
503
|
+
function applyPathTargetedConstraints(schema, pathConstraints) {
|
|
504
|
+
if (schema.type === "array" && schema.items) {
|
|
505
|
+
schema.items = applyPathTargetedConstraints(schema.items, pathConstraints);
|
|
506
|
+
return schema;
|
|
507
|
+
}
|
|
508
|
+
const byTarget = /* @__PURE__ */ new Map();
|
|
509
|
+
for (const c of pathConstraints) {
|
|
510
|
+
const target = c.path?.segments[0];
|
|
511
|
+
if (!target) continue;
|
|
512
|
+
const group = byTarget.get(target) ?? [];
|
|
513
|
+
group.push(c);
|
|
514
|
+
byTarget.set(target, group);
|
|
515
|
+
}
|
|
516
|
+
const propertyOverrides = {};
|
|
517
|
+
for (const [target, constraints] of byTarget) {
|
|
518
|
+
const subSchema = {};
|
|
519
|
+
applyConstraints(subSchema, constraints);
|
|
520
|
+
propertyOverrides[target] = subSchema;
|
|
521
|
+
}
|
|
522
|
+
if (schema.$ref) {
|
|
523
|
+
const { $ref, ...rest } = schema;
|
|
524
|
+
const refPart = { $ref };
|
|
525
|
+
const overridePart = {
|
|
526
|
+
properties: propertyOverrides,
|
|
527
|
+
...rest
|
|
528
|
+
};
|
|
529
|
+
return { allOf: [refPart, overridePart] };
|
|
530
|
+
}
|
|
531
|
+
if (schema.type === "object" && schema.properties) {
|
|
532
|
+
const missingOverrides = {};
|
|
533
|
+
for (const [target, overrideSchema] of Object.entries(propertyOverrides)) {
|
|
534
|
+
if (schema.properties[target]) {
|
|
535
|
+
Object.assign(schema.properties[target], overrideSchema);
|
|
536
|
+
} else {
|
|
537
|
+
missingOverrides[target] = overrideSchema;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
if (Object.keys(missingOverrides).length === 0) {
|
|
541
|
+
return schema;
|
|
542
|
+
}
|
|
543
|
+
return {
|
|
544
|
+
allOf: [schema, { properties: missingOverrides }]
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
if (schema.allOf) {
|
|
548
|
+
schema.allOf = [...schema.allOf, { properties: propertyOverrides }];
|
|
549
|
+
return schema;
|
|
550
|
+
}
|
|
489
551
|
return schema;
|
|
490
552
|
}
|
|
491
553
|
function generateTypeNode(type, ctx) {
|
|
@@ -1094,10 +1156,21 @@ var init_program = __esm({
|
|
|
1094
1156
|
}
|
|
1095
1157
|
});
|
|
1096
1158
|
|
|
1097
|
-
// src/analyzer/
|
|
1098
|
-
function
|
|
1099
|
-
|
|
1159
|
+
// src/analyzer/json-utils.ts
|
|
1160
|
+
function tryParseJson(text) {
|
|
1161
|
+
try {
|
|
1162
|
+
return JSON.parse(text);
|
|
1163
|
+
} catch {
|
|
1164
|
+
return null;
|
|
1165
|
+
}
|
|
1100
1166
|
}
|
|
1167
|
+
var init_json_utils = __esm({
|
|
1168
|
+
"src/analyzer/json-utils.ts"() {
|
|
1169
|
+
"use strict";
|
|
1170
|
+
}
|
|
1171
|
+
});
|
|
1172
|
+
|
|
1173
|
+
// src/analyzer/tsdoc-parser.ts
|
|
1101
1174
|
function createFormSpecTSDocConfig() {
|
|
1102
1175
|
const config = new import_tsdoc.TSDocConfiguration();
|
|
1103
1176
|
for (const tagName of Object.keys(import_core3.BUILTIN_CONSTRAINT_DEFINITIONS)) {
|
|
@@ -1136,7 +1209,7 @@ function parseTSDocTags(node, file = "") {
|
|
|
1136
1209
|
);
|
|
1137
1210
|
const docComment = parserContext.docComment;
|
|
1138
1211
|
for (const block of docComment.customBlocks) {
|
|
1139
|
-
const tagName = block.blockTag.tagName.substring(1);
|
|
1212
|
+
const tagName = (0, import_core3.normalizeConstraintTagName)(block.blockTag.tagName.substring(1));
|
|
1140
1213
|
if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
1141
1214
|
const text = extractBlockText(block).trim();
|
|
1142
1215
|
if (text === "") continue;
|
|
@@ -1157,7 +1230,7 @@ function parseTSDocTags(node, file = "") {
|
|
|
1157
1230
|
}
|
|
1158
1231
|
const jsDocTagsAll = ts2.getJSDocTags(node);
|
|
1159
1232
|
for (const tag of jsDocTagsAll) {
|
|
1160
|
-
const tagName = tag.tagName.text;
|
|
1233
|
+
const tagName = (0, import_core3.normalizeConstraintTagName)(tag.tagName.text);
|
|
1161
1234
|
if (!TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
1162
1235
|
const commentText = getTagCommentText(tag);
|
|
1163
1236
|
if (commentText === void 0 || commentText.trim() === "") continue;
|
|
@@ -1205,6 +1278,15 @@ function parseTSDocTags(node, file = "") {
|
|
|
1205
1278
|
}
|
|
1206
1279
|
return { constraints, annotations };
|
|
1207
1280
|
}
|
|
1281
|
+
function extractPathTarget(text) {
|
|
1282
|
+
const trimmed = text.trimStart();
|
|
1283
|
+
const match = /^:([a-zA-Z_]\w*)\s+([\s\S]*)$/.exec(trimmed);
|
|
1284
|
+
if (!match?.[1] || !match[2]) return null;
|
|
1285
|
+
return {
|
|
1286
|
+
path: { segments: [match[1]] },
|
|
1287
|
+
remainingText: match[2]
|
|
1288
|
+
};
|
|
1289
|
+
}
|
|
1208
1290
|
function extractBlockText(block) {
|
|
1209
1291
|
return extractPlainText(block.content);
|
|
1210
1292
|
}
|
|
@@ -1224,12 +1306,15 @@ function extractPlainText(node) {
|
|
|
1224
1306
|
return result;
|
|
1225
1307
|
}
|
|
1226
1308
|
function parseConstraintValue(tagName, text, provenance) {
|
|
1227
|
-
if (!isBuiltinConstraintName(tagName)) {
|
|
1309
|
+
if (!(0, import_core3.isBuiltinConstraintName)(tagName)) {
|
|
1228
1310
|
return null;
|
|
1229
1311
|
}
|
|
1312
|
+
const pathResult = extractPathTarget(text);
|
|
1313
|
+
const effectiveText = pathResult ? pathResult.remainingText : text;
|
|
1314
|
+
const path4 = pathResult?.path;
|
|
1230
1315
|
const expectedType = import_core3.BUILTIN_CONSTRAINT_DEFINITIONS[tagName];
|
|
1231
1316
|
if (expectedType === "number") {
|
|
1232
|
-
const value = Number(
|
|
1317
|
+
const value = Number(effectiveText);
|
|
1233
1318
|
if (Number.isNaN(value)) {
|
|
1234
1319
|
return null;
|
|
1235
1320
|
}
|
|
@@ -1239,6 +1324,7 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
1239
1324
|
kind: "constraint",
|
|
1240
1325
|
constraintKind: numericKind,
|
|
1241
1326
|
value,
|
|
1327
|
+
...path4 && { path: path4 },
|
|
1242
1328
|
provenance
|
|
1243
1329
|
};
|
|
1244
1330
|
}
|
|
@@ -1248,42 +1334,41 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
1248
1334
|
kind: "constraint",
|
|
1249
1335
|
constraintKind: lengthKind,
|
|
1250
1336
|
value,
|
|
1337
|
+
...path4 && { path: path4 },
|
|
1251
1338
|
provenance
|
|
1252
1339
|
};
|
|
1253
1340
|
}
|
|
1254
1341
|
return null;
|
|
1255
1342
|
}
|
|
1256
1343
|
if (expectedType === "json") {
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
members.push(id);
|
|
1270
|
-
}
|
|
1344
|
+
const parsed = tryParseJson(effectiveText);
|
|
1345
|
+
if (!Array.isArray(parsed)) {
|
|
1346
|
+
return null;
|
|
1347
|
+
}
|
|
1348
|
+
const members = [];
|
|
1349
|
+
for (const item of parsed) {
|
|
1350
|
+
if (typeof item === "string" || typeof item === "number") {
|
|
1351
|
+
members.push(item);
|
|
1352
|
+
} else if (typeof item === "object" && item !== null && "id" in item) {
|
|
1353
|
+
const id = item["id"];
|
|
1354
|
+
if (typeof id === "string" || typeof id === "number") {
|
|
1355
|
+
members.push(id);
|
|
1271
1356
|
}
|
|
1272
1357
|
}
|
|
1273
|
-
return {
|
|
1274
|
-
kind: "constraint",
|
|
1275
|
-
constraintKind: "allowedMembers",
|
|
1276
|
-
members,
|
|
1277
|
-
provenance
|
|
1278
|
-
};
|
|
1279
|
-
} catch {
|
|
1280
|
-
return null;
|
|
1281
1358
|
}
|
|
1359
|
+
return {
|
|
1360
|
+
kind: "constraint",
|
|
1361
|
+
constraintKind: "allowedMembers",
|
|
1362
|
+
members,
|
|
1363
|
+
...path4 && { path: path4 },
|
|
1364
|
+
provenance
|
|
1365
|
+
};
|
|
1282
1366
|
}
|
|
1283
1367
|
return {
|
|
1284
1368
|
kind: "constraint",
|
|
1285
1369
|
constraintKind: "pattern",
|
|
1286
|
-
pattern:
|
|
1370
|
+
pattern: effectiveText,
|
|
1371
|
+
...path4 && { path: path4 },
|
|
1287
1372
|
provenance
|
|
1288
1373
|
};
|
|
1289
1374
|
}
|
|
@@ -1324,17 +1409,21 @@ var init_tsdoc_parser = __esm({
|
|
|
1324
1409
|
ts2 = __toESM(require("typescript"), 1);
|
|
1325
1410
|
import_tsdoc = require("@microsoft/tsdoc");
|
|
1326
1411
|
import_core3 = require("@formspec/core");
|
|
1412
|
+
init_json_utils();
|
|
1327
1413
|
NUMERIC_CONSTRAINT_MAP = {
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1414
|
+
minimum: "minimum",
|
|
1415
|
+
maximum: "maximum",
|
|
1416
|
+
exclusiveMinimum: "exclusiveMinimum",
|
|
1417
|
+
exclusiveMaximum: "exclusiveMaximum",
|
|
1418
|
+
multipleOf: "multipleOf"
|
|
1332
1419
|
};
|
|
1333
1420
|
LENGTH_CONSTRAINT_MAP = {
|
|
1334
|
-
|
|
1335
|
-
|
|
1421
|
+
minLength: "minLength",
|
|
1422
|
+
maxLength: "maxLength",
|
|
1423
|
+
minItems: "minItems",
|
|
1424
|
+
maxItems: "maxItems"
|
|
1336
1425
|
};
|
|
1337
|
-
TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["
|
|
1426
|
+
TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions"]);
|
|
1338
1427
|
}
|
|
1339
1428
|
});
|
|
1340
1429
|
|
|
@@ -1387,6 +1476,7 @@ var init_jsdoc_constraints = __esm({
|
|
|
1387
1476
|
ts3 = __toESM(require("typescript"), 1);
|
|
1388
1477
|
import_core4 = require("@formspec/core");
|
|
1389
1478
|
init_tsdoc_parser();
|
|
1479
|
+
init_json_utils();
|
|
1390
1480
|
}
|
|
1391
1481
|
});
|
|
1392
1482
|
|
|
@@ -1743,14 +1833,22 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting) {
|
|
|
1743
1833
|
}
|
|
1744
1834
|
return map;
|
|
1745
1835
|
}
|
|
1746
|
-
function extractTypeAliasConstraintNodes(typeNode, checker, file) {
|
|
1836
|
+
function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
|
|
1747
1837
|
if (!ts4.isTypeReferenceNode(typeNode)) return [];
|
|
1838
|
+
if (depth >= MAX_ALIAS_CHAIN_DEPTH) {
|
|
1839
|
+
const aliasName = typeNode.typeName.getText();
|
|
1840
|
+
throw new Error(
|
|
1841
|
+
`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.`
|
|
1842
|
+
);
|
|
1843
|
+
}
|
|
1748
1844
|
const symbol = checker.getSymbolAtLocation(typeNode.typeName);
|
|
1749
1845
|
if (!symbol?.declarations) return [];
|
|
1750
1846
|
const aliasDecl = symbol.declarations.find(ts4.isTypeAliasDeclaration);
|
|
1751
1847
|
if (!aliasDecl) return [];
|
|
1752
1848
|
if (ts4.isTypeLiteralNode(aliasDecl.type)) return [];
|
|
1753
|
-
|
|
1849
|
+
const constraints = extractJSDocConstraintNodes(aliasDecl, file);
|
|
1850
|
+
constraints.push(...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, depth + 1));
|
|
1851
|
+
return constraints;
|
|
1754
1852
|
}
|
|
1755
1853
|
function provenanceForNode(node, file) {
|
|
1756
1854
|
const sourceFile = node.getSourceFile();
|
|
@@ -1823,12 +1921,13 @@ function detectFormSpecReference(typeNode) {
|
|
|
1823
1921
|
}
|
|
1824
1922
|
return null;
|
|
1825
1923
|
}
|
|
1826
|
-
var ts4;
|
|
1924
|
+
var ts4, MAX_ALIAS_CHAIN_DEPTH;
|
|
1827
1925
|
var init_class_analyzer = __esm({
|
|
1828
1926
|
"src/analyzer/class-analyzer.ts"() {
|
|
1829
1927
|
"use strict";
|
|
1830
1928
|
ts4 = __toESM(require("typescript"), 1);
|
|
1831
1929
|
init_jsdoc_constraints();
|
|
1930
|
+
MAX_ALIAS_CHAIN_DEPTH = 8;
|
|
1832
1931
|
}
|
|
1833
1932
|
});
|
|
1834
1933
|
|