@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.js
CHANGED
|
@@ -71,11 +71,40 @@ function canonicalizeField(field) {
|
|
|
71
71
|
}
|
|
72
72
|
function canonicalizeTextField(field) {
|
|
73
73
|
const type = { kind: "primitive", primitiveKind: "string" };
|
|
74
|
+
const constraints = [];
|
|
75
|
+
if (field.minLength !== void 0) {
|
|
76
|
+
const c = {
|
|
77
|
+
kind: "constraint",
|
|
78
|
+
constraintKind: "minLength",
|
|
79
|
+
value: field.minLength,
|
|
80
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
81
|
+
};
|
|
82
|
+
constraints.push(c);
|
|
83
|
+
}
|
|
84
|
+
if (field.maxLength !== void 0) {
|
|
85
|
+
const c = {
|
|
86
|
+
kind: "constraint",
|
|
87
|
+
constraintKind: "maxLength",
|
|
88
|
+
value: field.maxLength,
|
|
89
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
90
|
+
};
|
|
91
|
+
constraints.push(c);
|
|
92
|
+
}
|
|
93
|
+
if (field.pattern !== void 0) {
|
|
94
|
+
const c = {
|
|
95
|
+
kind: "constraint",
|
|
96
|
+
constraintKind: "pattern",
|
|
97
|
+
pattern: field.pattern,
|
|
98
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
99
|
+
};
|
|
100
|
+
constraints.push(c);
|
|
101
|
+
}
|
|
74
102
|
return buildFieldNode(
|
|
75
103
|
field.name,
|
|
76
104
|
type,
|
|
77
105
|
field.required,
|
|
78
|
-
buildAnnotations(field.label, field.placeholder)
|
|
106
|
+
buildAnnotations(field.label, field.placeholder),
|
|
107
|
+
constraints
|
|
79
108
|
);
|
|
80
109
|
}
|
|
81
110
|
function canonicalizeNumberField(field) {
|
|
@@ -99,6 +128,15 @@ function canonicalizeNumberField(field) {
|
|
|
99
128
|
};
|
|
100
129
|
constraints.push(c);
|
|
101
130
|
}
|
|
131
|
+
if (field.multipleOf !== void 0) {
|
|
132
|
+
const c = {
|
|
133
|
+
kind: "constraint",
|
|
134
|
+
constraintKind: "multipleOf",
|
|
135
|
+
value: field.multipleOf,
|
|
136
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
137
|
+
};
|
|
138
|
+
constraints.push(c);
|
|
139
|
+
}
|
|
102
140
|
return buildFieldNode(
|
|
103
141
|
field.name,
|
|
104
142
|
type,
|
|
@@ -424,8 +462,70 @@ function collectFields(elements, properties, required, ctx) {
|
|
|
424
462
|
}
|
|
425
463
|
function generateFieldSchema(field, ctx) {
|
|
426
464
|
const schema = generateTypeNode(field.type, ctx);
|
|
427
|
-
|
|
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);
|
|
428
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
|
+
}
|
|
429
529
|
return schema;
|
|
430
530
|
}
|
|
431
531
|
function generateTypeNode(type, ctx) {
|
|
@@ -1032,6 +1132,20 @@ var init_program = __esm({
|
|
|
1032
1132
|
}
|
|
1033
1133
|
});
|
|
1034
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
|
+
|
|
1035
1149
|
// src/analyzer/tsdoc-parser.ts
|
|
1036
1150
|
import * as ts2 from "typescript";
|
|
1037
1151
|
import {
|
|
@@ -1044,11 +1158,10 @@ import {
|
|
|
1044
1158
|
TextRange
|
|
1045
1159
|
} from "@microsoft/tsdoc";
|
|
1046
1160
|
import {
|
|
1047
|
-
BUILTIN_CONSTRAINT_DEFINITIONS
|
|
1161
|
+
BUILTIN_CONSTRAINT_DEFINITIONS,
|
|
1162
|
+
normalizeConstraintTagName,
|
|
1163
|
+
isBuiltinConstraintName
|
|
1048
1164
|
} from "@formspec/core";
|
|
1049
|
-
function isBuiltinConstraintName(tagName) {
|
|
1050
|
-
return tagName in BUILTIN_CONSTRAINT_DEFINITIONS;
|
|
1051
|
-
}
|
|
1052
1165
|
function createFormSpecTSDocConfig() {
|
|
1053
1166
|
const config = new TSDocConfiguration();
|
|
1054
1167
|
for (const tagName of Object.keys(BUILTIN_CONSTRAINT_DEFINITIONS)) {
|
|
@@ -1087,7 +1200,7 @@ function parseTSDocTags(node, file = "") {
|
|
|
1087
1200
|
);
|
|
1088
1201
|
const docComment = parserContext.docComment;
|
|
1089
1202
|
for (const block of docComment.customBlocks) {
|
|
1090
|
-
const tagName = block.blockTag.tagName.substring(1);
|
|
1203
|
+
const tagName = normalizeConstraintTagName(block.blockTag.tagName.substring(1));
|
|
1091
1204
|
if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
1092
1205
|
const text = extractBlockText(block).trim();
|
|
1093
1206
|
if (text === "") continue;
|
|
@@ -1108,7 +1221,7 @@ function parseTSDocTags(node, file = "") {
|
|
|
1108
1221
|
}
|
|
1109
1222
|
const jsDocTagsAll = ts2.getJSDocTags(node);
|
|
1110
1223
|
for (const tag of jsDocTagsAll) {
|
|
1111
|
-
const tagName = tag.tagName.text;
|
|
1224
|
+
const tagName = normalizeConstraintTagName(tag.tagName.text);
|
|
1112
1225
|
if (!TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
1113
1226
|
const commentText = getTagCommentText(tag);
|
|
1114
1227
|
if (commentText === void 0 || commentText.trim() === "") continue;
|
|
@@ -1156,6 +1269,15 @@ function parseTSDocTags(node, file = "") {
|
|
|
1156
1269
|
}
|
|
1157
1270
|
return { constraints, annotations };
|
|
1158
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
|
+
}
|
|
1159
1281
|
function extractBlockText(block) {
|
|
1160
1282
|
return extractPlainText(block.content);
|
|
1161
1283
|
}
|
|
@@ -1178,9 +1300,12 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
1178
1300
|
if (!isBuiltinConstraintName(tagName)) {
|
|
1179
1301
|
return null;
|
|
1180
1302
|
}
|
|
1303
|
+
const pathResult = extractPathTarget(text);
|
|
1304
|
+
const effectiveText = pathResult ? pathResult.remainingText : text;
|
|
1305
|
+
const path4 = pathResult?.path;
|
|
1181
1306
|
const expectedType = BUILTIN_CONSTRAINT_DEFINITIONS[tagName];
|
|
1182
1307
|
if (expectedType === "number") {
|
|
1183
|
-
const value = Number(
|
|
1308
|
+
const value = Number(effectiveText);
|
|
1184
1309
|
if (Number.isNaN(value)) {
|
|
1185
1310
|
return null;
|
|
1186
1311
|
}
|
|
@@ -1190,6 +1315,7 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
1190
1315
|
kind: "constraint",
|
|
1191
1316
|
constraintKind: numericKind,
|
|
1192
1317
|
value,
|
|
1318
|
+
...path4 && { path: path4 },
|
|
1193
1319
|
provenance
|
|
1194
1320
|
};
|
|
1195
1321
|
}
|
|
@@ -1199,42 +1325,41 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
1199
1325
|
kind: "constraint",
|
|
1200
1326
|
constraintKind: lengthKind,
|
|
1201
1327
|
value,
|
|
1328
|
+
...path4 && { path: path4 },
|
|
1202
1329
|
provenance
|
|
1203
1330
|
};
|
|
1204
1331
|
}
|
|
1205
1332
|
return null;
|
|
1206
1333
|
}
|
|
1207
1334
|
if (expectedType === "json") {
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
members.push(id);
|
|
1221
|
-
}
|
|
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);
|
|
1222
1347
|
}
|
|
1223
1348
|
}
|
|
1224
|
-
return {
|
|
1225
|
-
kind: "constraint",
|
|
1226
|
-
constraintKind: "allowedMembers",
|
|
1227
|
-
members,
|
|
1228
|
-
provenance
|
|
1229
|
-
};
|
|
1230
|
-
} catch {
|
|
1231
|
-
return null;
|
|
1232
1349
|
}
|
|
1350
|
+
return {
|
|
1351
|
+
kind: "constraint",
|
|
1352
|
+
constraintKind: "allowedMembers",
|
|
1353
|
+
members,
|
|
1354
|
+
...path4 && { path: path4 },
|
|
1355
|
+
provenance
|
|
1356
|
+
};
|
|
1233
1357
|
}
|
|
1234
1358
|
return {
|
|
1235
1359
|
kind: "constraint",
|
|
1236
1360
|
constraintKind: "pattern",
|
|
1237
|
-
pattern:
|
|
1361
|
+
pattern: effectiveText,
|
|
1362
|
+
...path4 && { path: path4 },
|
|
1238
1363
|
provenance
|
|
1239
1364
|
};
|
|
1240
1365
|
}
|
|
@@ -1272,24 +1397,30 @@ var NUMERIC_CONSTRAINT_MAP, LENGTH_CONSTRAINT_MAP, TAGS_REQUIRING_RAW_TEXT, shar
|
|
|
1272
1397
|
var init_tsdoc_parser = __esm({
|
|
1273
1398
|
"src/analyzer/tsdoc-parser.ts"() {
|
|
1274
1399
|
"use strict";
|
|
1400
|
+
init_json_utils();
|
|
1275
1401
|
NUMERIC_CONSTRAINT_MAP = {
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1402
|
+
minimum: "minimum",
|
|
1403
|
+
maximum: "maximum",
|
|
1404
|
+
exclusiveMinimum: "exclusiveMinimum",
|
|
1405
|
+
exclusiveMaximum: "exclusiveMaximum",
|
|
1406
|
+
multipleOf: "multipleOf"
|
|
1280
1407
|
};
|
|
1281
1408
|
LENGTH_CONSTRAINT_MAP = {
|
|
1282
|
-
|
|
1283
|
-
|
|
1409
|
+
minLength: "minLength",
|
|
1410
|
+
maxLength: "maxLength",
|
|
1411
|
+
minItems: "minItems",
|
|
1412
|
+
maxItems: "maxItems"
|
|
1284
1413
|
};
|
|
1285
|
-
TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["
|
|
1414
|
+
TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions"]);
|
|
1286
1415
|
}
|
|
1287
1416
|
});
|
|
1288
1417
|
|
|
1289
1418
|
// src/analyzer/jsdoc-constraints.ts
|
|
1290
1419
|
import * as ts3 from "typescript";
|
|
1291
1420
|
import {
|
|
1292
|
-
BUILTIN_CONSTRAINT_DEFINITIONS as BUILTIN_CONSTRAINT_DEFINITIONS2
|
|
1421
|
+
BUILTIN_CONSTRAINT_DEFINITIONS as BUILTIN_CONSTRAINT_DEFINITIONS2,
|
|
1422
|
+
isBuiltinConstraintName as isBuiltinConstraintName2,
|
|
1423
|
+
normalizeConstraintTagName as normalizeConstraintTagName2
|
|
1293
1424
|
} from "@formspec/core";
|
|
1294
1425
|
function extractJSDocConstraintNodes(node, file = "") {
|
|
1295
1426
|
const result = parseTSDocTags(node, file);
|
|
@@ -1336,6 +1467,7 @@ var init_jsdoc_constraints = __esm({
|
|
|
1336
1467
|
"src/analyzer/jsdoc-constraints.ts"() {
|
|
1337
1468
|
"use strict";
|
|
1338
1469
|
init_tsdoc_parser();
|
|
1470
|
+
init_json_utils();
|
|
1339
1471
|
}
|
|
1340
1472
|
});
|
|
1341
1473
|
|
|
@@ -1693,14 +1825,22 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting) {
|
|
|
1693
1825
|
}
|
|
1694
1826
|
return map;
|
|
1695
1827
|
}
|
|
1696
|
-
function extractTypeAliasConstraintNodes(typeNode, checker, file) {
|
|
1828
|
+
function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
|
|
1697
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
|
+
}
|
|
1698
1836
|
const symbol = checker.getSymbolAtLocation(typeNode.typeName);
|
|
1699
1837
|
if (!symbol?.declarations) return [];
|
|
1700
1838
|
const aliasDecl = symbol.declarations.find(ts4.isTypeAliasDeclaration);
|
|
1701
1839
|
if (!aliasDecl) return [];
|
|
1702
1840
|
if (ts4.isTypeLiteralNode(aliasDecl.type)) return [];
|
|
1703
|
-
|
|
1841
|
+
const constraints = extractJSDocConstraintNodes(aliasDecl, file);
|
|
1842
|
+
constraints.push(...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, depth + 1));
|
|
1843
|
+
return constraints;
|
|
1704
1844
|
}
|
|
1705
1845
|
function provenanceForNode(node, file) {
|
|
1706
1846
|
const sourceFile = node.getSourceFile();
|
|
@@ -1773,10 +1913,12 @@ function detectFormSpecReference(typeNode) {
|
|
|
1773
1913
|
}
|
|
1774
1914
|
return null;
|
|
1775
1915
|
}
|
|
1916
|
+
var MAX_ALIAS_CHAIN_DEPTH;
|
|
1776
1917
|
var init_class_analyzer = __esm({
|
|
1777
1918
|
"src/analyzer/class-analyzer.ts"() {
|
|
1778
1919
|
"use strict";
|
|
1779
1920
|
init_jsdoc_constraints();
|
|
1921
|
+
MAX_ALIAS_CHAIN_DEPTH = 8;
|
|
1780
1922
|
}
|
|
1781
1923
|
});
|
|
1782
1924
|
|