@formspec/build 0.1.0-alpha.12 → 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__/guards.test.d.ts +2 -0
- package/dist/__tests__/guards.test.d.ts.map +1 -0
- 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 +115 -8
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.js +115 -8
- package/dist/browser.js.map +1 -1
- package/dist/build.d.ts +1 -0
- package/dist/canonicalize/chain-dsl-canonicalizer.d.ts.map +1 -1
- package/dist/cli.cjs +179 -42
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +184 -42
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +173 -41
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +178 -42
- package/dist/index.js.map +1 -1
- package/dist/internals.cjs +186 -47
- package/dist/internals.cjs.map +1 -1
- package/dist/internals.js +191 -48
- 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
|
@@ -91,11 +91,40 @@ function canonicalizeField(field) {
|
|
|
91
91
|
}
|
|
92
92
|
function canonicalizeTextField(field) {
|
|
93
93
|
const type = { kind: "primitive", primitiveKind: "string" };
|
|
94
|
+
const constraints = [];
|
|
95
|
+
if (field.minLength !== void 0) {
|
|
96
|
+
const c = {
|
|
97
|
+
kind: "constraint",
|
|
98
|
+
constraintKind: "minLength",
|
|
99
|
+
value: field.minLength,
|
|
100
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
101
|
+
};
|
|
102
|
+
constraints.push(c);
|
|
103
|
+
}
|
|
104
|
+
if (field.maxLength !== void 0) {
|
|
105
|
+
const c = {
|
|
106
|
+
kind: "constraint",
|
|
107
|
+
constraintKind: "maxLength",
|
|
108
|
+
value: field.maxLength,
|
|
109
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
110
|
+
};
|
|
111
|
+
constraints.push(c);
|
|
112
|
+
}
|
|
113
|
+
if (field.pattern !== void 0) {
|
|
114
|
+
const c = {
|
|
115
|
+
kind: "constraint",
|
|
116
|
+
constraintKind: "pattern",
|
|
117
|
+
pattern: field.pattern,
|
|
118
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
119
|
+
};
|
|
120
|
+
constraints.push(c);
|
|
121
|
+
}
|
|
94
122
|
return buildFieldNode(
|
|
95
123
|
field.name,
|
|
96
124
|
type,
|
|
97
125
|
field.required,
|
|
98
|
-
buildAnnotations(field.label, field.placeholder)
|
|
126
|
+
buildAnnotations(field.label, field.placeholder),
|
|
127
|
+
constraints
|
|
99
128
|
);
|
|
100
129
|
}
|
|
101
130
|
function canonicalizeNumberField(field) {
|
|
@@ -119,6 +148,15 @@ function canonicalizeNumberField(field) {
|
|
|
119
148
|
};
|
|
120
149
|
constraints.push(c);
|
|
121
150
|
}
|
|
151
|
+
if (field.multipleOf !== void 0) {
|
|
152
|
+
const c = {
|
|
153
|
+
kind: "constraint",
|
|
154
|
+
constraintKind: "multipleOf",
|
|
155
|
+
value: field.multipleOf,
|
|
156
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
157
|
+
};
|
|
158
|
+
constraints.push(c);
|
|
159
|
+
}
|
|
122
160
|
return buildFieldNode(
|
|
123
161
|
field.name,
|
|
124
162
|
type,
|
|
@@ -446,8 +484,70 @@ function collectFields(elements, properties, required, ctx) {
|
|
|
446
484
|
}
|
|
447
485
|
function generateFieldSchema(field, ctx) {
|
|
448
486
|
const schema = generateTypeNode(field.type, ctx);
|
|
449
|
-
|
|
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);
|
|
450
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
|
+
}
|
|
451
551
|
return schema;
|
|
452
552
|
}
|
|
453
553
|
function generateTypeNode(type, ctx) {
|
|
@@ -1056,10 +1156,21 @@ var init_program = __esm({
|
|
|
1056
1156
|
}
|
|
1057
1157
|
});
|
|
1058
1158
|
|
|
1059
|
-
// src/analyzer/
|
|
1060
|
-
function
|
|
1061
|
-
|
|
1159
|
+
// src/analyzer/json-utils.ts
|
|
1160
|
+
function tryParseJson(text) {
|
|
1161
|
+
try {
|
|
1162
|
+
return JSON.parse(text);
|
|
1163
|
+
} catch {
|
|
1164
|
+
return null;
|
|
1165
|
+
}
|
|
1062
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
|
|
1063
1174
|
function createFormSpecTSDocConfig() {
|
|
1064
1175
|
const config = new import_tsdoc.TSDocConfiguration();
|
|
1065
1176
|
for (const tagName of Object.keys(import_core3.BUILTIN_CONSTRAINT_DEFINITIONS)) {
|
|
@@ -1098,7 +1209,7 @@ function parseTSDocTags(node, file = "") {
|
|
|
1098
1209
|
);
|
|
1099
1210
|
const docComment = parserContext.docComment;
|
|
1100
1211
|
for (const block of docComment.customBlocks) {
|
|
1101
|
-
const tagName = block.blockTag.tagName.substring(1);
|
|
1212
|
+
const tagName = (0, import_core3.normalizeConstraintTagName)(block.blockTag.tagName.substring(1));
|
|
1102
1213
|
if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
1103
1214
|
const text = extractBlockText(block).trim();
|
|
1104
1215
|
if (text === "") continue;
|
|
@@ -1119,7 +1230,7 @@ function parseTSDocTags(node, file = "") {
|
|
|
1119
1230
|
}
|
|
1120
1231
|
const jsDocTagsAll = ts2.getJSDocTags(node);
|
|
1121
1232
|
for (const tag of jsDocTagsAll) {
|
|
1122
|
-
const tagName = tag.tagName.text;
|
|
1233
|
+
const tagName = (0, import_core3.normalizeConstraintTagName)(tag.tagName.text);
|
|
1123
1234
|
if (!TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
1124
1235
|
const commentText = getTagCommentText(tag);
|
|
1125
1236
|
if (commentText === void 0 || commentText.trim() === "") continue;
|
|
@@ -1167,6 +1278,15 @@ function parseTSDocTags(node, file = "") {
|
|
|
1167
1278
|
}
|
|
1168
1279
|
return { constraints, annotations };
|
|
1169
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
|
+
}
|
|
1170
1290
|
function extractBlockText(block) {
|
|
1171
1291
|
return extractPlainText(block.content);
|
|
1172
1292
|
}
|
|
@@ -1186,12 +1306,15 @@ function extractPlainText(node) {
|
|
|
1186
1306
|
return result;
|
|
1187
1307
|
}
|
|
1188
1308
|
function parseConstraintValue(tagName, text, provenance) {
|
|
1189
|
-
if (!isBuiltinConstraintName(tagName)) {
|
|
1309
|
+
if (!(0, import_core3.isBuiltinConstraintName)(tagName)) {
|
|
1190
1310
|
return null;
|
|
1191
1311
|
}
|
|
1312
|
+
const pathResult = extractPathTarget(text);
|
|
1313
|
+
const effectiveText = pathResult ? pathResult.remainingText : text;
|
|
1314
|
+
const path4 = pathResult?.path;
|
|
1192
1315
|
const expectedType = import_core3.BUILTIN_CONSTRAINT_DEFINITIONS[tagName];
|
|
1193
1316
|
if (expectedType === "number") {
|
|
1194
|
-
const value = Number(
|
|
1317
|
+
const value = Number(effectiveText);
|
|
1195
1318
|
if (Number.isNaN(value)) {
|
|
1196
1319
|
return null;
|
|
1197
1320
|
}
|
|
@@ -1201,6 +1324,7 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
1201
1324
|
kind: "constraint",
|
|
1202
1325
|
constraintKind: numericKind,
|
|
1203
1326
|
value,
|
|
1327
|
+
...path4 && { path: path4 },
|
|
1204
1328
|
provenance
|
|
1205
1329
|
};
|
|
1206
1330
|
}
|
|
@@ -1210,42 +1334,41 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
1210
1334
|
kind: "constraint",
|
|
1211
1335
|
constraintKind: lengthKind,
|
|
1212
1336
|
value,
|
|
1337
|
+
...path4 && { path: path4 },
|
|
1213
1338
|
provenance
|
|
1214
1339
|
};
|
|
1215
1340
|
}
|
|
1216
1341
|
return null;
|
|
1217
1342
|
}
|
|
1218
1343
|
if (expectedType === "json") {
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
members.push(id);
|
|
1232
|
-
}
|
|
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);
|
|
1233
1356
|
}
|
|
1234
1357
|
}
|
|
1235
|
-
return {
|
|
1236
|
-
kind: "constraint",
|
|
1237
|
-
constraintKind: "allowedMembers",
|
|
1238
|
-
members,
|
|
1239
|
-
provenance
|
|
1240
|
-
};
|
|
1241
|
-
} catch {
|
|
1242
|
-
return null;
|
|
1243
1358
|
}
|
|
1359
|
+
return {
|
|
1360
|
+
kind: "constraint",
|
|
1361
|
+
constraintKind: "allowedMembers",
|
|
1362
|
+
members,
|
|
1363
|
+
...path4 && { path: path4 },
|
|
1364
|
+
provenance
|
|
1365
|
+
};
|
|
1244
1366
|
}
|
|
1245
1367
|
return {
|
|
1246
1368
|
kind: "constraint",
|
|
1247
1369
|
constraintKind: "pattern",
|
|
1248
|
-
pattern:
|
|
1370
|
+
pattern: effectiveText,
|
|
1371
|
+
...path4 && { path: path4 },
|
|
1249
1372
|
provenance
|
|
1250
1373
|
};
|
|
1251
1374
|
}
|
|
@@ -1286,17 +1409,21 @@ var init_tsdoc_parser = __esm({
|
|
|
1286
1409
|
ts2 = __toESM(require("typescript"), 1);
|
|
1287
1410
|
import_tsdoc = require("@microsoft/tsdoc");
|
|
1288
1411
|
import_core3 = require("@formspec/core");
|
|
1412
|
+
init_json_utils();
|
|
1289
1413
|
NUMERIC_CONSTRAINT_MAP = {
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1414
|
+
minimum: "minimum",
|
|
1415
|
+
maximum: "maximum",
|
|
1416
|
+
exclusiveMinimum: "exclusiveMinimum",
|
|
1417
|
+
exclusiveMaximum: "exclusiveMaximum",
|
|
1418
|
+
multipleOf: "multipleOf"
|
|
1294
1419
|
};
|
|
1295
1420
|
LENGTH_CONSTRAINT_MAP = {
|
|
1296
|
-
|
|
1297
|
-
|
|
1421
|
+
minLength: "minLength",
|
|
1422
|
+
maxLength: "maxLength",
|
|
1423
|
+
minItems: "minItems",
|
|
1424
|
+
maxItems: "maxItems"
|
|
1298
1425
|
};
|
|
1299
|
-
TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["
|
|
1426
|
+
TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions"]);
|
|
1300
1427
|
}
|
|
1301
1428
|
});
|
|
1302
1429
|
|
|
@@ -1349,6 +1476,7 @@ var init_jsdoc_constraints = __esm({
|
|
|
1349
1476
|
ts3 = __toESM(require("typescript"), 1);
|
|
1350
1477
|
import_core4 = require("@formspec/core");
|
|
1351
1478
|
init_tsdoc_parser();
|
|
1479
|
+
init_json_utils();
|
|
1352
1480
|
}
|
|
1353
1481
|
});
|
|
1354
1482
|
|
|
@@ -1705,14 +1833,22 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting) {
|
|
|
1705
1833
|
}
|
|
1706
1834
|
return map;
|
|
1707
1835
|
}
|
|
1708
|
-
function extractTypeAliasConstraintNodes(typeNode, checker, file) {
|
|
1836
|
+
function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
|
|
1709
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
|
+
}
|
|
1710
1844
|
const symbol = checker.getSymbolAtLocation(typeNode.typeName);
|
|
1711
1845
|
if (!symbol?.declarations) return [];
|
|
1712
1846
|
const aliasDecl = symbol.declarations.find(ts4.isTypeAliasDeclaration);
|
|
1713
1847
|
if (!aliasDecl) return [];
|
|
1714
1848
|
if (ts4.isTypeLiteralNode(aliasDecl.type)) return [];
|
|
1715
|
-
|
|
1849
|
+
const constraints = extractJSDocConstraintNodes(aliasDecl, file);
|
|
1850
|
+
constraints.push(...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, depth + 1));
|
|
1851
|
+
return constraints;
|
|
1716
1852
|
}
|
|
1717
1853
|
function provenanceForNode(node, file) {
|
|
1718
1854
|
const sourceFile = node.getSourceFile();
|
|
@@ -1785,12 +1921,13 @@ function detectFormSpecReference(typeNode) {
|
|
|
1785
1921
|
}
|
|
1786
1922
|
return null;
|
|
1787
1923
|
}
|
|
1788
|
-
var ts4;
|
|
1924
|
+
var ts4, MAX_ALIAS_CHAIN_DEPTH;
|
|
1789
1925
|
var init_class_analyzer = __esm({
|
|
1790
1926
|
"src/analyzer/class-analyzer.ts"() {
|
|
1791
1927
|
"use strict";
|
|
1792
1928
|
ts4 = __toESM(require("typescript"), 1);
|
|
1793
1929
|
init_jsdoc_constraints();
|
|
1930
|
+
MAX_ALIAS_CHAIN_DEPTH = 8;
|
|
1794
1931
|
}
|
|
1795
1932
|
});
|
|
1796
1933
|
|