@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/index.cjs
CHANGED
|
@@ -125,11 +125,40 @@ function canonicalizeField(field) {
|
|
|
125
125
|
}
|
|
126
126
|
function canonicalizeTextField(field) {
|
|
127
127
|
const type = { kind: "primitive", primitiveKind: "string" };
|
|
128
|
+
const constraints = [];
|
|
129
|
+
if (field.minLength !== void 0) {
|
|
130
|
+
const c = {
|
|
131
|
+
kind: "constraint",
|
|
132
|
+
constraintKind: "minLength",
|
|
133
|
+
value: field.minLength,
|
|
134
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
135
|
+
};
|
|
136
|
+
constraints.push(c);
|
|
137
|
+
}
|
|
138
|
+
if (field.maxLength !== void 0) {
|
|
139
|
+
const c = {
|
|
140
|
+
kind: "constraint",
|
|
141
|
+
constraintKind: "maxLength",
|
|
142
|
+
value: field.maxLength,
|
|
143
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
144
|
+
};
|
|
145
|
+
constraints.push(c);
|
|
146
|
+
}
|
|
147
|
+
if (field.pattern !== void 0) {
|
|
148
|
+
const c = {
|
|
149
|
+
kind: "constraint",
|
|
150
|
+
constraintKind: "pattern",
|
|
151
|
+
pattern: field.pattern,
|
|
152
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
153
|
+
};
|
|
154
|
+
constraints.push(c);
|
|
155
|
+
}
|
|
128
156
|
return buildFieldNode(
|
|
129
157
|
field.name,
|
|
130
158
|
type,
|
|
131
159
|
field.required,
|
|
132
|
-
buildAnnotations(field.label, field.placeholder)
|
|
160
|
+
buildAnnotations(field.label, field.placeholder),
|
|
161
|
+
constraints
|
|
133
162
|
);
|
|
134
163
|
}
|
|
135
164
|
function canonicalizeNumberField(field) {
|
|
@@ -153,6 +182,15 @@ function canonicalizeNumberField(field) {
|
|
|
153
182
|
};
|
|
154
183
|
constraints.push(c);
|
|
155
184
|
}
|
|
185
|
+
if (field.multipleOf !== void 0) {
|
|
186
|
+
const c = {
|
|
187
|
+
kind: "constraint",
|
|
188
|
+
constraintKind: "multipleOf",
|
|
189
|
+
value: field.multipleOf,
|
|
190
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
191
|
+
};
|
|
192
|
+
constraints.push(c);
|
|
193
|
+
}
|
|
156
194
|
return buildFieldNode(
|
|
157
195
|
field.name,
|
|
158
196
|
type,
|
|
@@ -452,8 +490,70 @@ function collectFields(elements, properties, required, ctx) {
|
|
|
452
490
|
}
|
|
453
491
|
function generateFieldSchema(field, ctx) {
|
|
454
492
|
const schema = generateTypeNode(field.type, ctx);
|
|
455
|
-
|
|
493
|
+
const directConstraints = [];
|
|
494
|
+
const pathConstraints = [];
|
|
495
|
+
for (const c of field.constraints) {
|
|
496
|
+
if (c.path) {
|
|
497
|
+
pathConstraints.push(c);
|
|
498
|
+
} else {
|
|
499
|
+
directConstraints.push(c);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
applyConstraints(schema, directConstraints);
|
|
456
503
|
applyAnnotations(schema, field.annotations);
|
|
504
|
+
if (pathConstraints.length === 0) {
|
|
505
|
+
return schema;
|
|
506
|
+
}
|
|
507
|
+
return applyPathTargetedConstraints(schema, pathConstraints);
|
|
508
|
+
}
|
|
509
|
+
function applyPathTargetedConstraints(schema, pathConstraints) {
|
|
510
|
+
if (schema.type === "array" && schema.items) {
|
|
511
|
+
schema.items = applyPathTargetedConstraints(schema.items, pathConstraints);
|
|
512
|
+
return schema;
|
|
513
|
+
}
|
|
514
|
+
const byTarget = /* @__PURE__ */ new Map();
|
|
515
|
+
for (const c of pathConstraints) {
|
|
516
|
+
const target = c.path?.segments[0];
|
|
517
|
+
if (!target) continue;
|
|
518
|
+
const group = byTarget.get(target) ?? [];
|
|
519
|
+
group.push(c);
|
|
520
|
+
byTarget.set(target, group);
|
|
521
|
+
}
|
|
522
|
+
const propertyOverrides = {};
|
|
523
|
+
for (const [target, constraints] of byTarget) {
|
|
524
|
+
const subSchema = {};
|
|
525
|
+
applyConstraints(subSchema, constraints);
|
|
526
|
+
propertyOverrides[target] = subSchema;
|
|
527
|
+
}
|
|
528
|
+
if (schema.$ref) {
|
|
529
|
+
const { $ref, ...rest } = schema;
|
|
530
|
+
const refPart = { $ref };
|
|
531
|
+
const overridePart = {
|
|
532
|
+
properties: propertyOverrides,
|
|
533
|
+
...rest
|
|
534
|
+
};
|
|
535
|
+
return { allOf: [refPart, overridePart] };
|
|
536
|
+
}
|
|
537
|
+
if (schema.type === "object" && schema.properties) {
|
|
538
|
+
const missingOverrides = {};
|
|
539
|
+
for (const [target, overrideSchema] of Object.entries(propertyOverrides)) {
|
|
540
|
+
if (schema.properties[target]) {
|
|
541
|
+
Object.assign(schema.properties[target], overrideSchema);
|
|
542
|
+
} else {
|
|
543
|
+
missingOverrides[target] = overrideSchema;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
if (Object.keys(missingOverrides).length === 0) {
|
|
547
|
+
return schema;
|
|
548
|
+
}
|
|
549
|
+
return {
|
|
550
|
+
allOf: [schema, { properties: missingOverrides }]
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
if (schema.allOf) {
|
|
554
|
+
schema.allOf = [...schema.allOf, { properties: propertyOverrides }];
|
|
555
|
+
return schema;
|
|
556
|
+
}
|
|
457
557
|
return schema;
|
|
458
558
|
}
|
|
459
559
|
function generateTypeNode(type, ctx) {
|
|
@@ -1028,20 +1128,31 @@ var import_core4 = require("@formspec/core");
|
|
|
1028
1128
|
var ts2 = __toESM(require("typescript"), 1);
|
|
1029
1129
|
var import_tsdoc = require("@microsoft/tsdoc");
|
|
1030
1130
|
var import_core3 = require("@formspec/core");
|
|
1131
|
+
|
|
1132
|
+
// src/analyzer/json-utils.ts
|
|
1133
|
+
function tryParseJson(text) {
|
|
1134
|
+
try {
|
|
1135
|
+
return JSON.parse(text);
|
|
1136
|
+
} catch {
|
|
1137
|
+
return null;
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
// src/analyzer/tsdoc-parser.ts
|
|
1031
1142
|
var NUMERIC_CONSTRAINT_MAP = {
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1143
|
+
minimum: "minimum",
|
|
1144
|
+
maximum: "maximum",
|
|
1145
|
+
exclusiveMinimum: "exclusiveMinimum",
|
|
1146
|
+
exclusiveMaximum: "exclusiveMaximum",
|
|
1147
|
+
multipleOf: "multipleOf"
|
|
1036
1148
|
};
|
|
1037
1149
|
var LENGTH_CONSTRAINT_MAP = {
|
|
1038
|
-
|
|
1039
|
-
|
|
1150
|
+
minLength: "minLength",
|
|
1151
|
+
maxLength: "maxLength",
|
|
1152
|
+
minItems: "minItems",
|
|
1153
|
+
maxItems: "maxItems"
|
|
1040
1154
|
};
|
|
1041
|
-
var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["
|
|
1042
|
-
function isBuiltinConstraintName(tagName) {
|
|
1043
|
-
return tagName in import_core3.BUILTIN_CONSTRAINT_DEFINITIONS;
|
|
1044
|
-
}
|
|
1155
|
+
var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions"]);
|
|
1045
1156
|
function createFormSpecTSDocConfig() {
|
|
1046
1157
|
const config = new import_tsdoc.TSDocConfiguration();
|
|
1047
1158
|
for (const tagName of Object.keys(import_core3.BUILTIN_CONSTRAINT_DEFINITIONS)) {
|
|
@@ -1081,7 +1192,7 @@ function parseTSDocTags(node, file = "") {
|
|
|
1081
1192
|
);
|
|
1082
1193
|
const docComment = parserContext.docComment;
|
|
1083
1194
|
for (const block of docComment.customBlocks) {
|
|
1084
|
-
const tagName = block.blockTag.tagName.substring(1);
|
|
1195
|
+
const tagName = (0, import_core3.normalizeConstraintTagName)(block.blockTag.tagName.substring(1));
|
|
1085
1196
|
if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
1086
1197
|
const text = extractBlockText(block).trim();
|
|
1087
1198
|
if (text === "") continue;
|
|
@@ -1102,7 +1213,7 @@ function parseTSDocTags(node, file = "") {
|
|
|
1102
1213
|
}
|
|
1103
1214
|
const jsDocTagsAll = ts2.getJSDocTags(node);
|
|
1104
1215
|
for (const tag of jsDocTagsAll) {
|
|
1105
|
-
const tagName = tag.tagName.text;
|
|
1216
|
+
const tagName = (0, import_core3.normalizeConstraintTagName)(tag.tagName.text);
|
|
1106
1217
|
if (!TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
1107
1218
|
const commentText = getTagCommentText(tag);
|
|
1108
1219
|
if (commentText === void 0 || commentText.trim() === "") continue;
|
|
@@ -1150,6 +1261,15 @@ function parseTSDocTags(node, file = "") {
|
|
|
1150
1261
|
}
|
|
1151
1262
|
return { constraints, annotations };
|
|
1152
1263
|
}
|
|
1264
|
+
function extractPathTarget(text) {
|
|
1265
|
+
const trimmed = text.trimStart();
|
|
1266
|
+
const match = /^:([a-zA-Z_]\w*)\s+([\s\S]*)$/.exec(trimmed);
|
|
1267
|
+
if (!match?.[1] || !match[2]) return null;
|
|
1268
|
+
return {
|
|
1269
|
+
path: { segments: [match[1]] },
|
|
1270
|
+
remainingText: match[2]
|
|
1271
|
+
};
|
|
1272
|
+
}
|
|
1153
1273
|
function extractBlockText(block) {
|
|
1154
1274
|
return extractPlainText(block.content);
|
|
1155
1275
|
}
|
|
@@ -1169,12 +1289,15 @@ function extractPlainText(node) {
|
|
|
1169
1289
|
return result;
|
|
1170
1290
|
}
|
|
1171
1291
|
function parseConstraintValue(tagName, text, provenance) {
|
|
1172
|
-
if (!isBuiltinConstraintName(tagName)) {
|
|
1292
|
+
if (!(0, import_core3.isBuiltinConstraintName)(tagName)) {
|
|
1173
1293
|
return null;
|
|
1174
1294
|
}
|
|
1295
|
+
const pathResult = extractPathTarget(text);
|
|
1296
|
+
const effectiveText = pathResult ? pathResult.remainingText : text;
|
|
1297
|
+
const path3 = pathResult?.path;
|
|
1175
1298
|
const expectedType = import_core3.BUILTIN_CONSTRAINT_DEFINITIONS[tagName];
|
|
1176
1299
|
if (expectedType === "number") {
|
|
1177
|
-
const value = Number(
|
|
1300
|
+
const value = Number(effectiveText);
|
|
1178
1301
|
if (Number.isNaN(value)) {
|
|
1179
1302
|
return null;
|
|
1180
1303
|
}
|
|
@@ -1184,6 +1307,7 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
1184
1307
|
kind: "constraint",
|
|
1185
1308
|
constraintKind: numericKind,
|
|
1186
1309
|
value,
|
|
1310
|
+
...path3 && { path: path3 },
|
|
1187
1311
|
provenance
|
|
1188
1312
|
};
|
|
1189
1313
|
}
|
|
@@ -1193,42 +1317,41 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
1193
1317
|
kind: "constraint",
|
|
1194
1318
|
constraintKind: lengthKind,
|
|
1195
1319
|
value,
|
|
1320
|
+
...path3 && { path: path3 },
|
|
1196
1321
|
provenance
|
|
1197
1322
|
};
|
|
1198
1323
|
}
|
|
1199
1324
|
return null;
|
|
1200
1325
|
}
|
|
1201
1326
|
if (expectedType === "json") {
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
members.push(id);
|
|
1215
|
-
}
|
|
1327
|
+
const parsed = tryParseJson(effectiveText);
|
|
1328
|
+
if (!Array.isArray(parsed)) {
|
|
1329
|
+
return null;
|
|
1330
|
+
}
|
|
1331
|
+
const members = [];
|
|
1332
|
+
for (const item of parsed) {
|
|
1333
|
+
if (typeof item === "string" || typeof item === "number") {
|
|
1334
|
+
members.push(item);
|
|
1335
|
+
} else if (typeof item === "object" && item !== null && "id" in item) {
|
|
1336
|
+
const id = item["id"];
|
|
1337
|
+
if (typeof id === "string" || typeof id === "number") {
|
|
1338
|
+
members.push(id);
|
|
1216
1339
|
}
|
|
1217
1340
|
}
|
|
1218
|
-
return {
|
|
1219
|
-
kind: "constraint",
|
|
1220
|
-
constraintKind: "allowedMembers",
|
|
1221
|
-
members,
|
|
1222
|
-
provenance
|
|
1223
|
-
};
|
|
1224
|
-
} catch {
|
|
1225
|
-
return null;
|
|
1226
1341
|
}
|
|
1342
|
+
return {
|
|
1343
|
+
kind: "constraint",
|
|
1344
|
+
constraintKind: "allowedMembers",
|
|
1345
|
+
members,
|
|
1346
|
+
...path3 && { path: path3 },
|
|
1347
|
+
provenance
|
|
1348
|
+
};
|
|
1227
1349
|
}
|
|
1228
1350
|
return {
|
|
1229
1351
|
kind: "constraint",
|
|
1230
1352
|
constraintKind: "pattern",
|
|
1231
|
-
pattern:
|
|
1353
|
+
pattern: effectiveText,
|
|
1354
|
+
...path3 && { path: path3 },
|
|
1232
1355
|
provenance
|
|
1233
1356
|
};
|
|
1234
1357
|
}
|
|
@@ -1659,14 +1782,23 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting) {
|
|
|
1659
1782
|
}
|
|
1660
1783
|
return map;
|
|
1661
1784
|
}
|
|
1662
|
-
|
|
1785
|
+
var MAX_ALIAS_CHAIN_DEPTH = 8;
|
|
1786
|
+
function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
|
|
1663
1787
|
if (!ts4.isTypeReferenceNode(typeNode)) return [];
|
|
1788
|
+
if (depth >= MAX_ALIAS_CHAIN_DEPTH) {
|
|
1789
|
+
const aliasName = typeNode.typeName.getText();
|
|
1790
|
+
throw new Error(
|
|
1791
|
+
`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.`
|
|
1792
|
+
);
|
|
1793
|
+
}
|
|
1664
1794
|
const symbol = checker.getSymbolAtLocation(typeNode.typeName);
|
|
1665
1795
|
if (!symbol?.declarations) return [];
|
|
1666
1796
|
const aliasDecl = symbol.declarations.find(ts4.isTypeAliasDeclaration);
|
|
1667
1797
|
if (!aliasDecl) return [];
|
|
1668
1798
|
if (ts4.isTypeLiteralNode(aliasDecl.type)) return [];
|
|
1669
|
-
|
|
1799
|
+
const constraints = extractJSDocConstraintNodes(aliasDecl, file);
|
|
1800
|
+
constraints.push(...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, depth + 1));
|
|
1801
|
+
return constraints;
|
|
1670
1802
|
}
|
|
1671
1803
|
function provenanceForNode(node, file) {
|
|
1672
1804
|
const sourceFile = node.getSourceFile();
|