@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.js
CHANGED
|
@@ -462,8 +462,70 @@ function collectFields(elements, properties, required, ctx) {
|
|
|
462
462
|
}
|
|
463
463
|
function generateFieldSchema(field, ctx) {
|
|
464
464
|
const schema = generateTypeNode(field.type, ctx);
|
|
465
|
-
|
|
465
|
+
const directConstraints = [];
|
|
466
|
+
const pathConstraints = [];
|
|
467
|
+
for (const c of field.constraints) {
|
|
468
|
+
if (c.path) {
|
|
469
|
+
pathConstraints.push(c);
|
|
470
|
+
} else {
|
|
471
|
+
directConstraints.push(c);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
applyConstraints(schema, directConstraints);
|
|
466
475
|
applyAnnotations(schema, field.annotations);
|
|
476
|
+
if (pathConstraints.length === 0) {
|
|
477
|
+
return schema;
|
|
478
|
+
}
|
|
479
|
+
return applyPathTargetedConstraints(schema, pathConstraints);
|
|
480
|
+
}
|
|
481
|
+
function applyPathTargetedConstraints(schema, pathConstraints) {
|
|
482
|
+
if (schema.type === "array" && schema.items) {
|
|
483
|
+
schema.items = applyPathTargetedConstraints(schema.items, pathConstraints);
|
|
484
|
+
return schema;
|
|
485
|
+
}
|
|
486
|
+
const byTarget = /* @__PURE__ */ new Map();
|
|
487
|
+
for (const c of pathConstraints) {
|
|
488
|
+
const target = c.path?.segments[0];
|
|
489
|
+
if (!target) continue;
|
|
490
|
+
const group = byTarget.get(target) ?? [];
|
|
491
|
+
group.push(c);
|
|
492
|
+
byTarget.set(target, group);
|
|
493
|
+
}
|
|
494
|
+
const propertyOverrides = {};
|
|
495
|
+
for (const [target, constraints] of byTarget) {
|
|
496
|
+
const subSchema = {};
|
|
497
|
+
applyConstraints(subSchema, constraints);
|
|
498
|
+
propertyOverrides[target] = subSchema;
|
|
499
|
+
}
|
|
500
|
+
if (schema.$ref) {
|
|
501
|
+
const { $ref, ...rest } = schema;
|
|
502
|
+
const refPart = { $ref };
|
|
503
|
+
const overridePart = {
|
|
504
|
+
properties: propertyOverrides,
|
|
505
|
+
...rest
|
|
506
|
+
};
|
|
507
|
+
return { allOf: [refPart, overridePart] };
|
|
508
|
+
}
|
|
509
|
+
if (schema.type === "object" && schema.properties) {
|
|
510
|
+
const missingOverrides = {};
|
|
511
|
+
for (const [target, overrideSchema] of Object.entries(propertyOverrides)) {
|
|
512
|
+
if (schema.properties[target]) {
|
|
513
|
+
Object.assign(schema.properties[target], overrideSchema);
|
|
514
|
+
} else {
|
|
515
|
+
missingOverrides[target] = overrideSchema;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
if (Object.keys(missingOverrides).length === 0) {
|
|
519
|
+
return schema;
|
|
520
|
+
}
|
|
521
|
+
return {
|
|
522
|
+
allOf: [schema, { properties: missingOverrides }]
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
if (schema.allOf) {
|
|
526
|
+
schema.allOf = [...schema.allOf, { properties: propertyOverrides }];
|
|
527
|
+
return schema;
|
|
528
|
+
}
|
|
467
529
|
return schema;
|
|
468
530
|
}
|
|
469
531
|
function generateTypeNode(type, ctx) {
|
|
@@ -1070,6 +1132,20 @@ var init_program = __esm({
|
|
|
1070
1132
|
}
|
|
1071
1133
|
});
|
|
1072
1134
|
|
|
1135
|
+
// src/analyzer/json-utils.ts
|
|
1136
|
+
function tryParseJson(text) {
|
|
1137
|
+
try {
|
|
1138
|
+
return JSON.parse(text);
|
|
1139
|
+
} catch {
|
|
1140
|
+
return null;
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
var init_json_utils = __esm({
|
|
1144
|
+
"src/analyzer/json-utils.ts"() {
|
|
1145
|
+
"use strict";
|
|
1146
|
+
}
|
|
1147
|
+
});
|
|
1148
|
+
|
|
1073
1149
|
// src/analyzer/tsdoc-parser.ts
|
|
1074
1150
|
import * as ts2 from "typescript";
|
|
1075
1151
|
import {
|
|
@@ -1082,11 +1158,10 @@ import {
|
|
|
1082
1158
|
TextRange
|
|
1083
1159
|
} from "@microsoft/tsdoc";
|
|
1084
1160
|
import {
|
|
1085
|
-
BUILTIN_CONSTRAINT_DEFINITIONS
|
|
1161
|
+
BUILTIN_CONSTRAINT_DEFINITIONS,
|
|
1162
|
+
normalizeConstraintTagName,
|
|
1163
|
+
isBuiltinConstraintName
|
|
1086
1164
|
} from "@formspec/core";
|
|
1087
|
-
function isBuiltinConstraintName(tagName) {
|
|
1088
|
-
return tagName in BUILTIN_CONSTRAINT_DEFINITIONS;
|
|
1089
|
-
}
|
|
1090
1165
|
function createFormSpecTSDocConfig() {
|
|
1091
1166
|
const config = new TSDocConfiguration();
|
|
1092
1167
|
for (const tagName of Object.keys(BUILTIN_CONSTRAINT_DEFINITIONS)) {
|
|
@@ -1125,7 +1200,7 @@ function parseTSDocTags(node, file = "") {
|
|
|
1125
1200
|
);
|
|
1126
1201
|
const docComment = parserContext.docComment;
|
|
1127
1202
|
for (const block of docComment.customBlocks) {
|
|
1128
|
-
const tagName = block.blockTag.tagName.substring(1);
|
|
1203
|
+
const tagName = normalizeConstraintTagName(block.blockTag.tagName.substring(1));
|
|
1129
1204
|
if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
1130
1205
|
const text = extractBlockText(block).trim();
|
|
1131
1206
|
if (text === "") continue;
|
|
@@ -1146,7 +1221,7 @@ function parseTSDocTags(node, file = "") {
|
|
|
1146
1221
|
}
|
|
1147
1222
|
const jsDocTagsAll = ts2.getJSDocTags(node);
|
|
1148
1223
|
for (const tag of jsDocTagsAll) {
|
|
1149
|
-
const tagName = tag.tagName.text;
|
|
1224
|
+
const tagName = normalizeConstraintTagName(tag.tagName.text);
|
|
1150
1225
|
if (!TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
1151
1226
|
const commentText = getTagCommentText(tag);
|
|
1152
1227
|
if (commentText === void 0 || commentText.trim() === "") continue;
|
|
@@ -1194,6 +1269,15 @@ function parseTSDocTags(node, file = "") {
|
|
|
1194
1269
|
}
|
|
1195
1270
|
return { constraints, annotations };
|
|
1196
1271
|
}
|
|
1272
|
+
function extractPathTarget(text) {
|
|
1273
|
+
const trimmed = text.trimStart();
|
|
1274
|
+
const match = /^:([a-zA-Z_]\w*)\s+([\s\S]*)$/.exec(trimmed);
|
|
1275
|
+
if (!match?.[1] || !match[2]) return null;
|
|
1276
|
+
return {
|
|
1277
|
+
path: { segments: [match[1]] },
|
|
1278
|
+
remainingText: match[2]
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1197
1281
|
function extractBlockText(block) {
|
|
1198
1282
|
return extractPlainText(block.content);
|
|
1199
1283
|
}
|
|
@@ -1216,9 +1300,12 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
1216
1300
|
if (!isBuiltinConstraintName(tagName)) {
|
|
1217
1301
|
return null;
|
|
1218
1302
|
}
|
|
1303
|
+
const pathResult = extractPathTarget(text);
|
|
1304
|
+
const effectiveText = pathResult ? pathResult.remainingText : text;
|
|
1305
|
+
const path4 = pathResult?.path;
|
|
1219
1306
|
const expectedType = BUILTIN_CONSTRAINT_DEFINITIONS[tagName];
|
|
1220
1307
|
if (expectedType === "number") {
|
|
1221
|
-
const value = Number(
|
|
1308
|
+
const value = Number(effectiveText);
|
|
1222
1309
|
if (Number.isNaN(value)) {
|
|
1223
1310
|
return null;
|
|
1224
1311
|
}
|
|
@@ -1228,6 +1315,7 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
1228
1315
|
kind: "constraint",
|
|
1229
1316
|
constraintKind: numericKind,
|
|
1230
1317
|
value,
|
|
1318
|
+
...path4 && { path: path4 },
|
|
1231
1319
|
provenance
|
|
1232
1320
|
};
|
|
1233
1321
|
}
|
|
@@ -1237,42 +1325,41 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
1237
1325
|
kind: "constraint",
|
|
1238
1326
|
constraintKind: lengthKind,
|
|
1239
1327
|
value,
|
|
1328
|
+
...path4 && { path: path4 },
|
|
1240
1329
|
provenance
|
|
1241
1330
|
};
|
|
1242
1331
|
}
|
|
1243
1332
|
return null;
|
|
1244
1333
|
}
|
|
1245
1334
|
if (expectedType === "json") {
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
members.push(id);
|
|
1259
|
-
}
|
|
1335
|
+
const parsed = tryParseJson(effectiveText);
|
|
1336
|
+
if (!Array.isArray(parsed)) {
|
|
1337
|
+
return null;
|
|
1338
|
+
}
|
|
1339
|
+
const members = [];
|
|
1340
|
+
for (const item of parsed) {
|
|
1341
|
+
if (typeof item === "string" || typeof item === "number") {
|
|
1342
|
+
members.push(item);
|
|
1343
|
+
} else if (typeof item === "object" && item !== null && "id" in item) {
|
|
1344
|
+
const id = item["id"];
|
|
1345
|
+
if (typeof id === "string" || typeof id === "number") {
|
|
1346
|
+
members.push(id);
|
|
1260
1347
|
}
|
|
1261
1348
|
}
|
|
1262
|
-
return {
|
|
1263
|
-
kind: "constraint",
|
|
1264
|
-
constraintKind: "allowedMembers",
|
|
1265
|
-
members,
|
|
1266
|
-
provenance
|
|
1267
|
-
};
|
|
1268
|
-
} catch {
|
|
1269
|
-
return null;
|
|
1270
1349
|
}
|
|
1350
|
+
return {
|
|
1351
|
+
kind: "constraint",
|
|
1352
|
+
constraintKind: "allowedMembers",
|
|
1353
|
+
members,
|
|
1354
|
+
...path4 && { path: path4 },
|
|
1355
|
+
provenance
|
|
1356
|
+
};
|
|
1271
1357
|
}
|
|
1272
1358
|
return {
|
|
1273
1359
|
kind: "constraint",
|
|
1274
1360
|
constraintKind: "pattern",
|
|
1275
|
-
pattern:
|
|
1361
|
+
pattern: effectiveText,
|
|
1362
|
+
...path4 && { path: path4 },
|
|
1276
1363
|
provenance
|
|
1277
1364
|
};
|
|
1278
1365
|
}
|
|
@@ -1310,24 +1397,30 @@ var NUMERIC_CONSTRAINT_MAP, LENGTH_CONSTRAINT_MAP, TAGS_REQUIRING_RAW_TEXT, shar
|
|
|
1310
1397
|
var init_tsdoc_parser = __esm({
|
|
1311
1398
|
"src/analyzer/tsdoc-parser.ts"() {
|
|
1312
1399
|
"use strict";
|
|
1400
|
+
init_json_utils();
|
|
1313
1401
|
NUMERIC_CONSTRAINT_MAP = {
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1402
|
+
minimum: "minimum",
|
|
1403
|
+
maximum: "maximum",
|
|
1404
|
+
exclusiveMinimum: "exclusiveMinimum",
|
|
1405
|
+
exclusiveMaximum: "exclusiveMaximum",
|
|
1406
|
+
multipleOf: "multipleOf"
|
|
1318
1407
|
};
|
|
1319
1408
|
LENGTH_CONSTRAINT_MAP = {
|
|
1320
|
-
|
|
1321
|
-
|
|
1409
|
+
minLength: "minLength",
|
|
1410
|
+
maxLength: "maxLength",
|
|
1411
|
+
minItems: "minItems",
|
|
1412
|
+
maxItems: "maxItems"
|
|
1322
1413
|
};
|
|
1323
|
-
TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["
|
|
1414
|
+
TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions"]);
|
|
1324
1415
|
}
|
|
1325
1416
|
});
|
|
1326
1417
|
|
|
1327
1418
|
// src/analyzer/jsdoc-constraints.ts
|
|
1328
1419
|
import * as ts3 from "typescript";
|
|
1329
1420
|
import {
|
|
1330
|
-
BUILTIN_CONSTRAINT_DEFINITIONS as BUILTIN_CONSTRAINT_DEFINITIONS2
|
|
1421
|
+
BUILTIN_CONSTRAINT_DEFINITIONS as BUILTIN_CONSTRAINT_DEFINITIONS2,
|
|
1422
|
+
isBuiltinConstraintName as isBuiltinConstraintName2,
|
|
1423
|
+
normalizeConstraintTagName as normalizeConstraintTagName2
|
|
1331
1424
|
} from "@formspec/core";
|
|
1332
1425
|
function extractJSDocConstraintNodes(node, file = "") {
|
|
1333
1426
|
const result = parseTSDocTags(node, file);
|
|
@@ -1374,6 +1467,7 @@ var init_jsdoc_constraints = __esm({
|
|
|
1374
1467
|
"src/analyzer/jsdoc-constraints.ts"() {
|
|
1375
1468
|
"use strict";
|
|
1376
1469
|
init_tsdoc_parser();
|
|
1470
|
+
init_json_utils();
|
|
1377
1471
|
}
|
|
1378
1472
|
});
|
|
1379
1473
|
|
|
@@ -1731,14 +1825,22 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting) {
|
|
|
1731
1825
|
}
|
|
1732
1826
|
return map;
|
|
1733
1827
|
}
|
|
1734
|
-
function extractTypeAliasConstraintNodes(typeNode, checker, file) {
|
|
1828
|
+
function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
|
|
1735
1829
|
if (!ts4.isTypeReferenceNode(typeNode)) return [];
|
|
1830
|
+
if (depth >= MAX_ALIAS_CHAIN_DEPTH) {
|
|
1831
|
+
const aliasName = typeNode.typeName.getText();
|
|
1832
|
+
throw new Error(
|
|
1833
|
+
`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.`
|
|
1834
|
+
);
|
|
1835
|
+
}
|
|
1736
1836
|
const symbol = checker.getSymbolAtLocation(typeNode.typeName);
|
|
1737
1837
|
if (!symbol?.declarations) return [];
|
|
1738
1838
|
const aliasDecl = symbol.declarations.find(ts4.isTypeAliasDeclaration);
|
|
1739
1839
|
if (!aliasDecl) return [];
|
|
1740
1840
|
if (ts4.isTypeLiteralNode(aliasDecl.type)) return [];
|
|
1741
|
-
|
|
1841
|
+
const constraints = extractJSDocConstraintNodes(aliasDecl, file);
|
|
1842
|
+
constraints.push(...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, depth + 1));
|
|
1843
|
+
return constraints;
|
|
1742
1844
|
}
|
|
1743
1845
|
function provenanceForNode(node, file) {
|
|
1744
1846
|
const sourceFile = node.getSourceFile();
|
|
@@ -1811,10 +1913,12 @@ function detectFormSpecReference(typeNode) {
|
|
|
1811
1913
|
}
|
|
1812
1914
|
return null;
|
|
1813
1915
|
}
|
|
1916
|
+
var MAX_ALIAS_CHAIN_DEPTH;
|
|
1814
1917
|
var init_class_analyzer = __esm({
|
|
1815
1918
|
"src/analyzer/class-analyzer.ts"() {
|
|
1816
1919
|
"use strict";
|
|
1817
1920
|
init_jsdoc_constraints();
|
|
1921
|
+
MAX_ALIAS_CHAIN_DEPTH = 8;
|
|
1818
1922
|
}
|
|
1819
1923
|
});
|
|
1820
1924
|
|