@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.js
CHANGED
|
@@ -461,7 +461,9 @@ import * as ts4 from "typescript";
|
|
|
461
461
|
// src/analyzer/jsdoc-constraints.ts
|
|
462
462
|
import * as ts3 from "typescript";
|
|
463
463
|
import {
|
|
464
|
-
BUILTIN_CONSTRAINT_DEFINITIONS as BUILTIN_CONSTRAINT_DEFINITIONS2
|
|
464
|
+
BUILTIN_CONSTRAINT_DEFINITIONS as BUILTIN_CONSTRAINT_DEFINITIONS2,
|
|
465
|
+
isBuiltinConstraintName as isBuiltinConstraintName2,
|
|
466
|
+
normalizeConstraintTagName as normalizeConstraintTagName2
|
|
465
467
|
} from "@formspec/core";
|
|
466
468
|
|
|
467
469
|
// src/analyzer/tsdoc-parser.ts
|
|
@@ -476,22 +478,35 @@ import {
|
|
|
476
478
|
TextRange
|
|
477
479
|
} from "@microsoft/tsdoc";
|
|
478
480
|
import {
|
|
479
|
-
BUILTIN_CONSTRAINT_DEFINITIONS
|
|
481
|
+
BUILTIN_CONSTRAINT_DEFINITIONS,
|
|
482
|
+
normalizeConstraintTagName,
|
|
483
|
+
isBuiltinConstraintName
|
|
480
484
|
} from "@formspec/core";
|
|
485
|
+
|
|
486
|
+
// src/analyzer/json-utils.ts
|
|
487
|
+
function tryParseJson(text) {
|
|
488
|
+
try {
|
|
489
|
+
return JSON.parse(text);
|
|
490
|
+
} catch {
|
|
491
|
+
return null;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// src/analyzer/tsdoc-parser.ts
|
|
481
496
|
var NUMERIC_CONSTRAINT_MAP = {
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
497
|
+
minimum: "minimum",
|
|
498
|
+
maximum: "maximum",
|
|
499
|
+
exclusiveMinimum: "exclusiveMinimum",
|
|
500
|
+
exclusiveMaximum: "exclusiveMaximum",
|
|
501
|
+
multipleOf: "multipleOf"
|
|
486
502
|
};
|
|
487
503
|
var LENGTH_CONSTRAINT_MAP = {
|
|
488
|
-
|
|
489
|
-
|
|
504
|
+
minLength: "minLength",
|
|
505
|
+
maxLength: "maxLength",
|
|
506
|
+
minItems: "minItems",
|
|
507
|
+
maxItems: "maxItems"
|
|
490
508
|
};
|
|
491
|
-
var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["
|
|
492
|
-
function isBuiltinConstraintName(tagName) {
|
|
493
|
-
return tagName in BUILTIN_CONSTRAINT_DEFINITIONS;
|
|
494
|
-
}
|
|
509
|
+
var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions"]);
|
|
495
510
|
function createFormSpecTSDocConfig() {
|
|
496
511
|
const config = new TSDocConfiguration();
|
|
497
512
|
for (const tagName of Object.keys(BUILTIN_CONSTRAINT_DEFINITIONS)) {
|
|
@@ -531,7 +546,7 @@ function parseTSDocTags(node, file = "") {
|
|
|
531
546
|
);
|
|
532
547
|
const docComment = parserContext.docComment;
|
|
533
548
|
for (const block of docComment.customBlocks) {
|
|
534
|
-
const tagName = block.blockTag.tagName.substring(1);
|
|
549
|
+
const tagName = normalizeConstraintTagName(block.blockTag.tagName.substring(1));
|
|
535
550
|
if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
536
551
|
const text = extractBlockText(block).trim();
|
|
537
552
|
if (text === "") continue;
|
|
@@ -552,7 +567,7 @@ function parseTSDocTags(node, file = "") {
|
|
|
552
567
|
}
|
|
553
568
|
const jsDocTagsAll = ts2.getJSDocTags(node);
|
|
554
569
|
for (const tag of jsDocTagsAll) {
|
|
555
|
-
const tagName = tag.tagName.text;
|
|
570
|
+
const tagName = normalizeConstraintTagName(tag.tagName.text);
|
|
556
571
|
if (!TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
557
572
|
const commentText = getTagCommentText(tag);
|
|
558
573
|
if (commentText === void 0 || commentText.trim() === "") continue;
|
|
@@ -600,6 +615,15 @@ function parseTSDocTags(node, file = "") {
|
|
|
600
615
|
}
|
|
601
616
|
return { constraints, annotations };
|
|
602
617
|
}
|
|
618
|
+
function extractPathTarget(text) {
|
|
619
|
+
const trimmed = text.trimStart();
|
|
620
|
+
const match = /^:([a-zA-Z_]\w*)\s+([\s\S]*)$/.exec(trimmed);
|
|
621
|
+
if (!match?.[1] || !match[2]) return null;
|
|
622
|
+
return {
|
|
623
|
+
path: { segments: [match[1]] },
|
|
624
|
+
remainingText: match[2]
|
|
625
|
+
};
|
|
626
|
+
}
|
|
603
627
|
function extractBlockText(block) {
|
|
604
628
|
return extractPlainText(block.content);
|
|
605
629
|
}
|
|
@@ -622,9 +646,12 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
622
646
|
if (!isBuiltinConstraintName(tagName)) {
|
|
623
647
|
return null;
|
|
624
648
|
}
|
|
649
|
+
const pathResult = extractPathTarget(text);
|
|
650
|
+
const effectiveText = pathResult ? pathResult.remainingText : text;
|
|
651
|
+
const path2 = pathResult?.path;
|
|
625
652
|
const expectedType = BUILTIN_CONSTRAINT_DEFINITIONS[tagName];
|
|
626
653
|
if (expectedType === "number") {
|
|
627
|
-
const value = Number(
|
|
654
|
+
const value = Number(effectiveText);
|
|
628
655
|
if (Number.isNaN(value)) {
|
|
629
656
|
return null;
|
|
630
657
|
}
|
|
@@ -634,6 +661,7 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
634
661
|
kind: "constraint",
|
|
635
662
|
constraintKind: numericKind,
|
|
636
663
|
value,
|
|
664
|
+
...path2 && { path: path2 },
|
|
637
665
|
provenance
|
|
638
666
|
};
|
|
639
667
|
}
|
|
@@ -643,42 +671,41 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
643
671
|
kind: "constraint",
|
|
644
672
|
constraintKind: lengthKind,
|
|
645
673
|
value,
|
|
674
|
+
...path2 && { path: path2 },
|
|
646
675
|
provenance
|
|
647
676
|
};
|
|
648
677
|
}
|
|
649
678
|
return null;
|
|
650
679
|
}
|
|
651
680
|
if (expectedType === "json") {
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
members.push(id);
|
|
665
|
-
}
|
|
681
|
+
const parsed = tryParseJson(effectiveText);
|
|
682
|
+
if (!Array.isArray(parsed)) {
|
|
683
|
+
return null;
|
|
684
|
+
}
|
|
685
|
+
const members = [];
|
|
686
|
+
for (const item of parsed) {
|
|
687
|
+
if (typeof item === "string" || typeof item === "number") {
|
|
688
|
+
members.push(item);
|
|
689
|
+
} else if (typeof item === "object" && item !== null && "id" in item) {
|
|
690
|
+
const id = item["id"];
|
|
691
|
+
if (typeof id === "string" || typeof id === "number") {
|
|
692
|
+
members.push(id);
|
|
666
693
|
}
|
|
667
694
|
}
|
|
668
|
-
return {
|
|
669
|
-
kind: "constraint",
|
|
670
|
-
constraintKind: "allowedMembers",
|
|
671
|
-
members,
|
|
672
|
-
provenance
|
|
673
|
-
};
|
|
674
|
-
} catch {
|
|
675
|
-
return null;
|
|
676
695
|
}
|
|
696
|
+
return {
|
|
697
|
+
kind: "constraint",
|
|
698
|
+
constraintKind: "allowedMembers",
|
|
699
|
+
members,
|
|
700
|
+
...path2 && { path: path2 },
|
|
701
|
+
provenance
|
|
702
|
+
};
|
|
677
703
|
}
|
|
678
704
|
return {
|
|
679
705
|
kind: "constraint",
|
|
680
706
|
constraintKind: "pattern",
|
|
681
|
-
pattern:
|
|
707
|
+
pattern: effectiveText,
|
|
708
|
+
...path2 && { path: path2 },
|
|
682
709
|
provenance
|
|
683
710
|
};
|
|
684
711
|
}
|
|
@@ -1109,14 +1136,23 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting) {
|
|
|
1109
1136
|
}
|
|
1110
1137
|
return map;
|
|
1111
1138
|
}
|
|
1112
|
-
|
|
1139
|
+
var MAX_ALIAS_CHAIN_DEPTH = 8;
|
|
1140
|
+
function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
|
|
1113
1141
|
if (!ts4.isTypeReferenceNode(typeNode)) return [];
|
|
1142
|
+
if (depth >= MAX_ALIAS_CHAIN_DEPTH) {
|
|
1143
|
+
const aliasName = typeNode.typeName.getText();
|
|
1144
|
+
throw new Error(
|
|
1145
|
+
`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.`
|
|
1146
|
+
);
|
|
1147
|
+
}
|
|
1114
1148
|
const symbol = checker.getSymbolAtLocation(typeNode.typeName);
|
|
1115
1149
|
if (!symbol?.declarations) return [];
|
|
1116
1150
|
const aliasDecl = symbol.declarations.find(ts4.isTypeAliasDeclaration);
|
|
1117
1151
|
if (!aliasDecl) return [];
|
|
1118
1152
|
if (ts4.isTypeLiteralNode(aliasDecl.type)) return [];
|
|
1119
|
-
|
|
1153
|
+
const constraints = extractJSDocConstraintNodes(aliasDecl, file);
|
|
1154
|
+
constraints.push(...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, depth + 1));
|
|
1155
|
+
return constraints;
|
|
1120
1156
|
}
|
|
1121
1157
|
function provenanceForNode(node, file) {
|
|
1122
1158
|
const sourceFile = node.getSourceFile();
|
|
@@ -1238,8 +1274,70 @@ function collectFields(elements, properties, required, ctx) {
|
|
|
1238
1274
|
}
|
|
1239
1275
|
function generateFieldSchema(field, ctx) {
|
|
1240
1276
|
const schema = generateTypeNode(field.type, ctx);
|
|
1241
|
-
|
|
1277
|
+
const directConstraints = [];
|
|
1278
|
+
const pathConstraints = [];
|
|
1279
|
+
for (const c of field.constraints) {
|
|
1280
|
+
if (c.path) {
|
|
1281
|
+
pathConstraints.push(c);
|
|
1282
|
+
} else {
|
|
1283
|
+
directConstraints.push(c);
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
applyConstraints(schema, directConstraints);
|
|
1242
1287
|
applyAnnotations(schema, field.annotations);
|
|
1288
|
+
if (pathConstraints.length === 0) {
|
|
1289
|
+
return schema;
|
|
1290
|
+
}
|
|
1291
|
+
return applyPathTargetedConstraints(schema, pathConstraints);
|
|
1292
|
+
}
|
|
1293
|
+
function applyPathTargetedConstraints(schema, pathConstraints) {
|
|
1294
|
+
if (schema.type === "array" && schema.items) {
|
|
1295
|
+
schema.items = applyPathTargetedConstraints(schema.items, pathConstraints);
|
|
1296
|
+
return schema;
|
|
1297
|
+
}
|
|
1298
|
+
const byTarget = /* @__PURE__ */ new Map();
|
|
1299
|
+
for (const c of pathConstraints) {
|
|
1300
|
+
const target = c.path?.segments[0];
|
|
1301
|
+
if (!target) continue;
|
|
1302
|
+
const group = byTarget.get(target) ?? [];
|
|
1303
|
+
group.push(c);
|
|
1304
|
+
byTarget.set(target, group);
|
|
1305
|
+
}
|
|
1306
|
+
const propertyOverrides = {};
|
|
1307
|
+
for (const [target, constraints] of byTarget) {
|
|
1308
|
+
const subSchema = {};
|
|
1309
|
+
applyConstraints(subSchema, constraints);
|
|
1310
|
+
propertyOverrides[target] = subSchema;
|
|
1311
|
+
}
|
|
1312
|
+
if (schema.$ref) {
|
|
1313
|
+
const { $ref, ...rest } = schema;
|
|
1314
|
+
const refPart = { $ref };
|
|
1315
|
+
const overridePart = {
|
|
1316
|
+
properties: propertyOverrides,
|
|
1317
|
+
...rest
|
|
1318
|
+
};
|
|
1319
|
+
return { allOf: [refPart, overridePart] };
|
|
1320
|
+
}
|
|
1321
|
+
if (schema.type === "object" && schema.properties) {
|
|
1322
|
+
const missingOverrides = {};
|
|
1323
|
+
for (const [target, overrideSchema] of Object.entries(propertyOverrides)) {
|
|
1324
|
+
if (schema.properties[target]) {
|
|
1325
|
+
Object.assign(schema.properties[target], overrideSchema);
|
|
1326
|
+
} else {
|
|
1327
|
+
missingOverrides[target] = overrideSchema;
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
if (Object.keys(missingOverrides).length === 0) {
|
|
1331
|
+
return schema;
|
|
1332
|
+
}
|
|
1333
|
+
return {
|
|
1334
|
+
allOf: [schema, { properties: missingOverrides }]
|
|
1335
|
+
};
|
|
1336
|
+
}
|
|
1337
|
+
if (schema.allOf) {
|
|
1338
|
+
schema.allOf = [...schema.allOf, { properties: propertyOverrides }];
|
|
1339
|
+
return schema;
|
|
1340
|
+
}
|
|
1243
1341
|
return schema;
|
|
1244
1342
|
}
|
|
1245
1343
|
function generateTypeNode(type, ctx) {
|
|
@@ -1686,14 +1784,10 @@ function addUnknownExtension(ctx, message, primary) {
|
|
|
1686
1784
|
});
|
|
1687
1785
|
}
|
|
1688
1786
|
function findNumeric(constraints, constraintKind) {
|
|
1689
|
-
return constraints.find(
|
|
1690
|
-
(c) => c.constraintKind === constraintKind
|
|
1691
|
-
);
|
|
1787
|
+
return constraints.find((c) => c.constraintKind === constraintKind);
|
|
1692
1788
|
}
|
|
1693
1789
|
function findLength(constraints, constraintKind) {
|
|
1694
|
-
return constraints.find(
|
|
1695
|
-
(c) => c.constraintKind === constraintKind
|
|
1696
|
-
);
|
|
1790
|
+
return constraints.find((c) => c.constraintKind === constraintKind);
|
|
1697
1791
|
}
|
|
1698
1792
|
function findAllowedMembers(constraints) {
|
|
1699
1793
|
return constraints.filter(
|
|
@@ -1817,6 +1911,17 @@ function checkTypeApplicability(ctx, fieldName, type, constraints) {
|
|
|
1817
1911
|
const isEnum = type.kind === "enum";
|
|
1818
1912
|
const label = typeLabel(type);
|
|
1819
1913
|
for (const constraint of constraints) {
|
|
1914
|
+
if (constraint.path) {
|
|
1915
|
+
const isTraversable = type.kind === "object" || type.kind === "array" || type.kind === "reference";
|
|
1916
|
+
if (!isTraversable) {
|
|
1917
|
+
addTypeMismatch(
|
|
1918
|
+
ctx,
|
|
1919
|
+
`Field "${fieldName}": path-targeted constraint "${constraint.constraintKind}" is invalid because type "${label}" cannot be traversed`,
|
|
1920
|
+
constraint.provenance
|
|
1921
|
+
);
|
|
1922
|
+
}
|
|
1923
|
+
continue;
|
|
1924
|
+
}
|
|
1820
1925
|
const ck = constraint.constraintKind;
|
|
1821
1926
|
switch (ck) {
|
|
1822
1927
|
case "minimum":
|