@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/internals.cjs
CHANGED
|
@@ -517,20 +517,31 @@ var import_core4 = require("@formspec/core");
|
|
|
517
517
|
var ts2 = __toESM(require("typescript"), 1);
|
|
518
518
|
var import_tsdoc = require("@microsoft/tsdoc");
|
|
519
519
|
var import_core3 = require("@formspec/core");
|
|
520
|
+
|
|
521
|
+
// src/analyzer/json-utils.ts
|
|
522
|
+
function tryParseJson(text) {
|
|
523
|
+
try {
|
|
524
|
+
return JSON.parse(text);
|
|
525
|
+
} catch {
|
|
526
|
+
return null;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// src/analyzer/tsdoc-parser.ts
|
|
520
531
|
var NUMERIC_CONSTRAINT_MAP = {
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
532
|
+
minimum: "minimum",
|
|
533
|
+
maximum: "maximum",
|
|
534
|
+
exclusiveMinimum: "exclusiveMinimum",
|
|
535
|
+
exclusiveMaximum: "exclusiveMaximum",
|
|
536
|
+
multipleOf: "multipleOf"
|
|
525
537
|
};
|
|
526
538
|
var LENGTH_CONSTRAINT_MAP = {
|
|
527
|
-
|
|
528
|
-
|
|
539
|
+
minLength: "minLength",
|
|
540
|
+
maxLength: "maxLength",
|
|
541
|
+
minItems: "minItems",
|
|
542
|
+
maxItems: "maxItems"
|
|
529
543
|
};
|
|
530
|
-
var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["
|
|
531
|
-
function isBuiltinConstraintName(tagName) {
|
|
532
|
-
return tagName in import_core3.BUILTIN_CONSTRAINT_DEFINITIONS;
|
|
533
|
-
}
|
|
544
|
+
var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions"]);
|
|
534
545
|
function createFormSpecTSDocConfig() {
|
|
535
546
|
const config = new import_tsdoc.TSDocConfiguration();
|
|
536
547
|
for (const tagName of Object.keys(import_core3.BUILTIN_CONSTRAINT_DEFINITIONS)) {
|
|
@@ -570,7 +581,7 @@ function parseTSDocTags(node, file = "") {
|
|
|
570
581
|
);
|
|
571
582
|
const docComment = parserContext.docComment;
|
|
572
583
|
for (const block of docComment.customBlocks) {
|
|
573
|
-
const tagName = block.blockTag.tagName.substring(1);
|
|
584
|
+
const tagName = (0, import_core3.normalizeConstraintTagName)(block.blockTag.tagName.substring(1));
|
|
574
585
|
if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
575
586
|
const text = extractBlockText(block).trim();
|
|
576
587
|
if (text === "") continue;
|
|
@@ -591,7 +602,7 @@ function parseTSDocTags(node, file = "") {
|
|
|
591
602
|
}
|
|
592
603
|
const jsDocTagsAll = ts2.getJSDocTags(node);
|
|
593
604
|
for (const tag of jsDocTagsAll) {
|
|
594
|
-
const tagName = tag.tagName.text;
|
|
605
|
+
const tagName = (0, import_core3.normalizeConstraintTagName)(tag.tagName.text);
|
|
595
606
|
if (!TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
596
607
|
const commentText = getTagCommentText(tag);
|
|
597
608
|
if (commentText === void 0 || commentText.trim() === "") continue;
|
|
@@ -639,6 +650,15 @@ function parseTSDocTags(node, file = "") {
|
|
|
639
650
|
}
|
|
640
651
|
return { constraints, annotations };
|
|
641
652
|
}
|
|
653
|
+
function extractPathTarget(text) {
|
|
654
|
+
const trimmed = text.trimStart();
|
|
655
|
+
const match = /^:([a-zA-Z_]\w*)\s+([\s\S]*)$/.exec(trimmed);
|
|
656
|
+
if (!match?.[1] || !match[2]) return null;
|
|
657
|
+
return {
|
|
658
|
+
path: { segments: [match[1]] },
|
|
659
|
+
remainingText: match[2]
|
|
660
|
+
};
|
|
661
|
+
}
|
|
642
662
|
function extractBlockText(block) {
|
|
643
663
|
return extractPlainText(block.content);
|
|
644
664
|
}
|
|
@@ -658,12 +678,15 @@ function extractPlainText(node) {
|
|
|
658
678
|
return result;
|
|
659
679
|
}
|
|
660
680
|
function parseConstraintValue(tagName, text, provenance) {
|
|
661
|
-
if (!isBuiltinConstraintName(tagName)) {
|
|
681
|
+
if (!(0, import_core3.isBuiltinConstraintName)(tagName)) {
|
|
662
682
|
return null;
|
|
663
683
|
}
|
|
684
|
+
const pathResult = extractPathTarget(text);
|
|
685
|
+
const effectiveText = pathResult ? pathResult.remainingText : text;
|
|
686
|
+
const path2 = pathResult?.path;
|
|
664
687
|
const expectedType = import_core3.BUILTIN_CONSTRAINT_DEFINITIONS[tagName];
|
|
665
688
|
if (expectedType === "number") {
|
|
666
|
-
const value = Number(
|
|
689
|
+
const value = Number(effectiveText);
|
|
667
690
|
if (Number.isNaN(value)) {
|
|
668
691
|
return null;
|
|
669
692
|
}
|
|
@@ -673,6 +696,7 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
673
696
|
kind: "constraint",
|
|
674
697
|
constraintKind: numericKind,
|
|
675
698
|
value,
|
|
699
|
+
...path2 && { path: path2 },
|
|
676
700
|
provenance
|
|
677
701
|
};
|
|
678
702
|
}
|
|
@@ -682,42 +706,41 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
682
706
|
kind: "constraint",
|
|
683
707
|
constraintKind: lengthKind,
|
|
684
708
|
value,
|
|
709
|
+
...path2 && { path: path2 },
|
|
685
710
|
provenance
|
|
686
711
|
};
|
|
687
712
|
}
|
|
688
713
|
return null;
|
|
689
714
|
}
|
|
690
715
|
if (expectedType === "json") {
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
members.push(id);
|
|
704
|
-
}
|
|
716
|
+
const parsed = tryParseJson(effectiveText);
|
|
717
|
+
if (!Array.isArray(parsed)) {
|
|
718
|
+
return null;
|
|
719
|
+
}
|
|
720
|
+
const members = [];
|
|
721
|
+
for (const item of parsed) {
|
|
722
|
+
if (typeof item === "string" || typeof item === "number") {
|
|
723
|
+
members.push(item);
|
|
724
|
+
} else if (typeof item === "object" && item !== null && "id" in item) {
|
|
725
|
+
const id = item["id"];
|
|
726
|
+
if (typeof id === "string" || typeof id === "number") {
|
|
727
|
+
members.push(id);
|
|
705
728
|
}
|
|
706
729
|
}
|
|
707
|
-
return {
|
|
708
|
-
kind: "constraint",
|
|
709
|
-
constraintKind: "allowedMembers",
|
|
710
|
-
members,
|
|
711
|
-
provenance
|
|
712
|
-
};
|
|
713
|
-
} catch {
|
|
714
|
-
return null;
|
|
715
730
|
}
|
|
731
|
+
return {
|
|
732
|
+
kind: "constraint",
|
|
733
|
+
constraintKind: "allowedMembers",
|
|
734
|
+
members,
|
|
735
|
+
...path2 && { path: path2 },
|
|
736
|
+
provenance
|
|
737
|
+
};
|
|
716
738
|
}
|
|
717
739
|
return {
|
|
718
740
|
kind: "constraint",
|
|
719
741
|
constraintKind: "pattern",
|
|
720
|
-
pattern:
|
|
742
|
+
pattern: effectiveText,
|
|
743
|
+
...path2 && { path: path2 },
|
|
721
744
|
provenance
|
|
722
745
|
};
|
|
723
746
|
}
|
|
@@ -1148,14 +1171,23 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting) {
|
|
|
1148
1171
|
}
|
|
1149
1172
|
return map;
|
|
1150
1173
|
}
|
|
1151
|
-
|
|
1174
|
+
var MAX_ALIAS_CHAIN_DEPTH = 8;
|
|
1175
|
+
function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
|
|
1152
1176
|
if (!ts4.isTypeReferenceNode(typeNode)) return [];
|
|
1177
|
+
if (depth >= MAX_ALIAS_CHAIN_DEPTH) {
|
|
1178
|
+
const aliasName = typeNode.typeName.getText();
|
|
1179
|
+
throw new Error(
|
|
1180
|
+
`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.`
|
|
1181
|
+
);
|
|
1182
|
+
}
|
|
1153
1183
|
const symbol = checker.getSymbolAtLocation(typeNode.typeName);
|
|
1154
1184
|
if (!symbol?.declarations) return [];
|
|
1155
1185
|
const aliasDecl = symbol.declarations.find(ts4.isTypeAliasDeclaration);
|
|
1156
1186
|
if (!aliasDecl) return [];
|
|
1157
1187
|
if (ts4.isTypeLiteralNode(aliasDecl.type)) return [];
|
|
1158
|
-
|
|
1188
|
+
const constraints = extractJSDocConstraintNodes(aliasDecl, file);
|
|
1189
|
+
constraints.push(...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, depth + 1));
|
|
1190
|
+
return constraints;
|
|
1159
1191
|
}
|
|
1160
1192
|
function provenanceForNode(node, file) {
|
|
1161
1193
|
const sourceFile = node.getSourceFile();
|
|
@@ -1277,8 +1309,70 @@ function collectFields(elements, properties, required, ctx) {
|
|
|
1277
1309
|
}
|
|
1278
1310
|
function generateFieldSchema(field, ctx) {
|
|
1279
1311
|
const schema = generateTypeNode(field.type, ctx);
|
|
1280
|
-
|
|
1312
|
+
const directConstraints = [];
|
|
1313
|
+
const pathConstraints = [];
|
|
1314
|
+
for (const c of field.constraints) {
|
|
1315
|
+
if (c.path) {
|
|
1316
|
+
pathConstraints.push(c);
|
|
1317
|
+
} else {
|
|
1318
|
+
directConstraints.push(c);
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
applyConstraints(schema, directConstraints);
|
|
1281
1322
|
applyAnnotations(schema, field.annotations);
|
|
1323
|
+
if (pathConstraints.length === 0) {
|
|
1324
|
+
return schema;
|
|
1325
|
+
}
|
|
1326
|
+
return applyPathTargetedConstraints(schema, pathConstraints);
|
|
1327
|
+
}
|
|
1328
|
+
function applyPathTargetedConstraints(schema, pathConstraints) {
|
|
1329
|
+
if (schema.type === "array" && schema.items) {
|
|
1330
|
+
schema.items = applyPathTargetedConstraints(schema.items, pathConstraints);
|
|
1331
|
+
return schema;
|
|
1332
|
+
}
|
|
1333
|
+
const byTarget = /* @__PURE__ */ new Map();
|
|
1334
|
+
for (const c of pathConstraints) {
|
|
1335
|
+
const target = c.path?.segments[0];
|
|
1336
|
+
if (!target) continue;
|
|
1337
|
+
const group = byTarget.get(target) ?? [];
|
|
1338
|
+
group.push(c);
|
|
1339
|
+
byTarget.set(target, group);
|
|
1340
|
+
}
|
|
1341
|
+
const propertyOverrides = {};
|
|
1342
|
+
for (const [target, constraints] of byTarget) {
|
|
1343
|
+
const subSchema = {};
|
|
1344
|
+
applyConstraints(subSchema, constraints);
|
|
1345
|
+
propertyOverrides[target] = subSchema;
|
|
1346
|
+
}
|
|
1347
|
+
if (schema.$ref) {
|
|
1348
|
+
const { $ref, ...rest } = schema;
|
|
1349
|
+
const refPart = { $ref };
|
|
1350
|
+
const overridePart = {
|
|
1351
|
+
properties: propertyOverrides,
|
|
1352
|
+
...rest
|
|
1353
|
+
};
|
|
1354
|
+
return { allOf: [refPart, overridePart] };
|
|
1355
|
+
}
|
|
1356
|
+
if (schema.type === "object" && schema.properties) {
|
|
1357
|
+
const missingOverrides = {};
|
|
1358
|
+
for (const [target, overrideSchema] of Object.entries(propertyOverrides)) {
|
|
1359
|
+
if (schema.properties[target]) {
|
|
1360
|
+
Object.assign(schema.properties[target], overrideSchema);
|
|
1361
|
+
} else {
|
|
1362
|
+
missingOverrides[target] = overrideSchema;
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
if (Object.keys(missingOverrides).length === 0) {
|
|
1366
|
+
return schema;
|
|
1367
|
+
}
|
|
1368
|
+
return {
|
|
1369
|
+
allOf: [schema, { properties: missingOverrides }]
|
|
1370
|
+
};
|
|
1371
|
+
}
|
|
1372
|
+
if (schema.allOf) {
|
|
1373
|
+
schema.allOf = [...schema.allOf, { properties: propertyOverrides }];
|
|
1374
|
+
return schema;
|
|
1375
|
+
}
|
|
1282
1376
|
return schema;
|
|
1283
1377
|
}
|
|
1284
1378
|
function generateTypeNode(type, ctx) {
|
|
@@ -1725,14 +1819,10 @@ function addUnknownExtension(ctx, message, primary) {
|
|
|
1725
1819
|
});
|
|
1726
1820
|
}
|
|
1727
1821
|
function findNumeric(constraints, constraintKind) {
|
|
1728
|
-
return constraints.find(
|
|
1729
|
-
(c) => c.constraintKind === constraintKind
|
|
1730
|
-
);
|
|
1822
|
+
return constraints.find((c) => c.constraintKind === constraintKind);
|
|
1731
1823
|
}
|
|
1732
1824
|
function findLength(constraints, constraintKind) {
|
|
1733
|
-
return constraints.find(
|
|
1734
|
-
(c) => c.constraintKind === constraintKind
|
|
1735
|
-
);
|
|
1825
|
+
return constraints.find((c) => c.constraintKind === constraintKind);
|
|
1736
1826
|
}
|
|
1737
1827
|
function findAllowedMembers(constraints) {
|
|
1738
1828
|
return constraints.filter(
|
|
@@ -1856,6 +1946,17 @@ function checkTypeApplicability(ctx, fieldName, type, constraints) {
|
|
|
1856
1946
|
const isEnum = type.kind === "enum";
|
|
1857
1947
|
const label = typeLabel(type);
|
|
1858
1948
|
for (const constraint of constraints) {
|
|
1949
|
+
if (constraint.path) {
|
|
1950
|
+
const isTraversable = type.kind === "object" || type.kind === "array" || type.kind === "reference";
|
|
1951
|
+
if (!isTraversable) {
|
|
1952
|
+
addTypeMismatch(
|
|
1953
|
+
ctx,
|
|
1954
|
+
`Field "${fieldName}": path-targeted constraint "${constraint.constraintKind}" is invalid because type "${label}" cannot be traversed`,
|
|
1955
|
+
constraint.provenance
|
|
1956
|
+
);
|
|
1957
|
+
}
|
|
1958
|
+
continue;
|
|
1959
|
+
}
|
|
1859
1960
|
const ck = constraint.constraintKind;
|
|
1860
1961
|
switch (ck) {
|
|
1861
1962
|
case "minimum":
|