@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/internals.cjs
CHANGED
|
@@ -117,11 +117,40 @@ function canonicalizeField(field) {
|
|
|
117
117
|
}
|
|
118
118
|
function canonicalizeTextField(field) {
|
|
119
119
|
const type = { kind: "primitive", primitiveKind: "string" };
|
|
120
|
+
const constraints = [];
|
|
121
|
+
if (field.minLength !== void 0) {
|
|
122
|
+
const c = {
|
|
123
|
+
kind: "constraint",
|
|
124
|
+
constraintKind: "minLength",
|
|
125
|
+
value: field.minLength,
|
|
126
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
127
|
+
};
|
|
128
|
+
constraints.push(c);
|
|
129
|
+
}
|
|
130
|
+
if (field.maxLength !== void 0) {
|
|
131
|
+
const c = {
|
|
132
|
+
kind: "constraint",
|
|
133
|
+
constraintKind: "maxLength",
|
|
134
|
+
value: field.maxLength,
|
|
135
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
136
|
+
};
|
|
137
|
+
constraints.push(c);
|
|
138
|
+
}
|
|
139
|
+
if (field.pattern !== void 0) {
|
|
140
|
+
const c = {
|
|
141
|
+
kind: "constraint",
|
|
142
|
+
constraintKind: "pattern",
|
|
143
|
+
pattern: field.pattern,
|
|
144
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
145
|
+
};
|
|
146
|
+
constraints.push(c);
|
|
147
|
+
}
|
|
120
148
|
return buildFieldNode(
|
|
121
149
|
field.name,
|
|
122
150
|
type,
|
|
123
151
|
field.required,
|
|
124
|
-
buildAnnotations(field.label, field.placeholder)
|
|
152
|
+
buildAnnotations(field.label, field.placeholder),
|
|
153
|
+
constraints
|
|
125
154
|
);
|
|
126
155
|
}
|
|
127
156
|
function canonicalizeNumberField(field) {
|
|
@@ -145,6 +174,15 @@ function canonicalizeNumberField(field) {
|
|
|
145
174
|
};
|
|
146
175
|
constraints.push(c);
|
|
147
176
|
}
|
|
177
|
+
if (field.multipleOf !== void 0) {
|
|
178
|
+
const c = {
|
|
179
|
+
kind: "constraint",
|
|
180
|
+
constraintKind: "multipleOf",
|
|
181
|
+
value: field.multipleOf,
|
|
182
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
183
|
+
};
|
|
184
|
+
constraints.push(c);
|
|
185
|
+
}
|
|
148
186
|
return buildFieldNode(
|
|
149
187
|
field.name,
|
|
150
188
|
type,
|
|
@@ -479,20 +517,31 @@ var import_core4 = require("@formspec/core");
|
|
|
479
517
|
var ts2 = __toESM(require("typescript"), 1);
|
|
480
518
|
var import_tsdoc = require("@microsoft/tsdoc");
|
|
481
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
|
|
482
531
|
var NUMERIC_CONSTRAINT_MAP = {
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
532
|
+
minimum: "minimum",
|
|
533
|
+
maximum: "maximum",
|
|
534
|
+
exclusiveMinimum: "exclusiveMinimum",
|
|
535
|
+
exclusiveMaximum: "exclusiveMaximum",
|
|
536
|
+
multipleOf: "multipleOf"
|
|
487
537
|
};
|
|
488
538
|
var LENGTH_CONSTRAINT_MAP = {
|
|
489
|
-
|
|
490
|
-
|
|
539
|
+
minLength: "minLength",
|
|
540
|
+
maxLength: "maxLength",
|
|
541
|
+
minItems: "minItems",
|
|
542
|
+
maxItems: "maxItems"
|
|
491
543
|
};
|
|
492
|
-
var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["
|
|
493
|
-
function isBuiltinConstraintName(tagName) {
|
|
494
|
-
return tagName in import_core3.BUILTIN_CONSTRAINT_DEFINITIONS;
|
|
495
|
-
}
|
|
544
|
+
var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions"]);
|
|
496
545
|
function createFormSpecTSDocConfig() {
|
|
497
546
|
const config = new import_tsdoc.TSDocConfiguration();
|
|
498
547
|
for (const tagName of Object.keys(import_core3.BUILTIN_CONSTRAINT_DEFINITIONS)) {
|
|
@@ -532,7 +581,7 @@ function parseTSDocTags(node, file = "") {
|
|
|
532
581
|
);
|
|
533
582
|
const docComment = parserContext.docComment;
|
|
534
583
|
for (const block of docComment.customBlocks) {
|
|
535
|
-
const tagName = block.blockTag.tagName.substring(1);
|
|
584
|
+
const tagName = (0, import_core3.normalizeConstraintTagName)(block.blockTag.tagName.substring(1));
|
|
536
585
|
if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
537
586
|
const text = extractBlockText(block).trim();
|
|
538
587
|
if (text === "") continue;
|
|
@@ -553,7 +602,7 @@ function parseTSDocTags(node, file = "") {
|
|
|
553
602
|
}
|
|
554
603
|
const jsDocTagsAll = ts2.getJSDocTags(node);
|
|
555
604
|
for (const tag of jsDocTagsAll) {
|
|
556
|
-
const tagName = tag.tagName.text;
|
|
605
|
+
const tagName = (0, import_core3.normalizeConstraintTagName)(tag.tagName.text);
|
|
557
606
|
if (!TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
558
607
|
const commentText = getTagCommentText(tag);
|
|
559
608
|
if (commentText === void 0 || commentText.trim() === "") continue;
|
|
@@ -601,6 +650,15 @@ function parseTSDocTags(node, file = "") {
|
|
|
601
650
|
}
|
|
602
651
|
return { constraints, annotations };
|
|
603
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
|
+
}
|
|
604
662
|
function extractBlockText(block) {
|
|
605
663
|
return extractPlainText(block.content);
|
|
606
664
|
}
|
|
@@ -620,12 +678,15 @@ function extractPlainText(node) {
|
|
|
620
678
|
return result;
|
|
621
679
|
}
|
|
622
680
|
function parseConstraintValue(tagName, text, provenance) {
|
|
623
|
-
if (!isBuiltinConstraintName(tagName)) {
|
|
681
|
+
if (!(0, import_core3.isBuiltinConstraintName)(tagName)) {
|
|
624
682
|
return null;
|
|
625
683
|
}
|
|
684
|
+
const pathResult = extractPathTarget(text);
|
|
685
|
+
const effectiveText = pathResult ? pathResult.remainingText : text;
|
|
686
|
+
const path2 = pathResult?.path;
|
|
626
687
|
const expectedType = import_core3.BUILTIN_CONSTRAINT_DEFINITIONS[tagName];
|
|
627
688
|
if (expectedType === "number") {
|
|
628
|
-
const value = Number(
|
|
689
|
+
const value = Number(effectiveText);
|
|
629
690
|
if (Number.isNaN(value)) {
|
|
630
691
|
return null;
|
|
631
692
|
}
|
|
@@ -635,6 +696,7 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
635
696
|
kind: "constraint",
|
|
636
697
|
constraintKind: numericKind,
|
|
637
698
|
value,
|
|
699
|
+
...path2 && { path: path2 },
|
|
638
700
|
provenance
|
|
639
701
|
};
|
|
640
702
|
}
|
|
@@ -644,42 +706,41 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
644
706
|
kind: "constraint",
|
|
645
707
|
constraintKind: lengthKind,
|
|
646
708
|
value,
|
|
709
|
+
...path2 && { path: path2 },
|
|
647
710
|
provenance
|
|
648
711
|
};
|
|
649
712
|
}
|
|
650
713
|
return null;
|
|
651
714
|
}
|
|
652
715
|
if (expectedType === "json") {
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
members.push(id);
|
|
666
|
-
}
|
|
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);
|
|
667
728
|
}
|
|
668
729
|
}
|
|
669
|
-
return {
|
|
670
|
-
kind: "constraint",
|
|
671
|
-
constraintKind: "allowedMembers",
|
|
672
|
-
members,
|
|
673
|
-
provenance
|
|
674
|
-
};
|
|
675
|
-
} catch {
|
|
676
|
-
return null;
|
|
677
730
|
}
|
|
731
|
+
return {
|
|
732
|
+
kind: "constraint",
|
|
733
|
+
constraintKind: "allowedMembers",
|
|
734
|
+
members,
|
|
735
|
+
...path2 && { path: path2 },
|
|
736
|
+
provenance
|
|
737
|
+
};
|
|
678
738
|
}
|
|
679
739
|
return {
|
|
680
740
|
kind: "constraint",
|
|
681
741
|
constraintKind: "pattern",
|
|
682
|
-
pattern:
|
|
742
|
+
pattern: effectiveText,
|
|
743
|
+
...path2 && { path: path2 },
|
|
683
744
|
provenance
|
|
684
745
|
};
|
|
685
746
|
}
|
|
@@ -1110,14 +1171,23 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting) {
|
|
|
1110
1171
|
}
|
|
1111
1172
|
return map;
|
|
1112
1173
|
}
|
|
1113
|
-
|
|
1174
|
+
var MAX_ALIAS_CHAIN_DEPTH = 8;
|
|
1175
|
+
function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
|
|
1114
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
|
+
}
|
|
1115
1183
|
const symbol = checker.getSymbolAtLocation(typeNode.typeName);
|
|
1116
1184
|
if (!symbol?.declarations) return [];
|
|
1117
1185
|
const aliasDecl = symbol.declarations.find(ts4.isTypeAliasDeclaration);
|
|
1118
1186
|
if (!aliasDecl) return [];
|
|
1119
1187
|
if (ts4.isTypeLiteralNode(aliasDecl.type)) return [];
|
|
1120
|
-
|
|
1188
|
+
const constraints = extractJSDocConstraintNodes(aliasDecl, file);
|
|
1189
|
+
constraints.push(...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, depth + 1));
|
|
1190
|
+
return constraints;
|
|
1121
1191
|
}
|
|
1122
1192
|
function provenanceForNode(node, file) {
|
|
1123
1193
|
const sourceFile = node.getSourceFile();
|
|
@@ -1239,8 +1309,70 @@ function collectFields(elements, properties, required, ctx) {
|
|
|
1239
1309
|
}
|
|
1240
1310
|
function generateFieldSchema(field, ctx) {
|
|
1241
1311
|
const schema = generateTypeNode(field.type, ctx);
|
|
1242
|
-
|
|
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);
|
|
1243
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
|
+
}
|
|
1244
1376
|
return schema;
|
|
1245
1377
|
}
|
|
1246
1378
|
function generateTypeNode(type, ctx) {
|
|
@@ -1687,14 +1819,10 @@ function addUnknownExtension(ctx, message, primary) {
|
|
|
1687
1819
|
});
|
|
1688
1820
|
}
|
|
1689
1821
|
function findNumeric(constraints, constraintKind) {
|
|
1690
|
-
return constraints.find(
|
|
1691
|
-
(c) => c.constraintKind === constraintKind
|
|
1692
|
-
);
|
|
1822
|
+
return constraints.find((c) => c.constraintKind === constraintKind);
|
|
1693
1823
|
}
|
|
1694
1824
|
function findLength(constraints, constraintKind) {
|
|
1695
|
-
return constraints.find(
|
|
1696
|
-
(c) => c.constraintKind === constraintKind
|
|
1697
|
-
);
|
|
1825
|
+
return constraints.find((c) => c.constraintKind === constraintKind);
|
|
1698
1826
|
}
|
|
1699
1827
|
function findAllowedMembers(constraints) {
|
|
1700
1828
|
return constraints.filter(
|
|
@@ -1818,6 +1946,17 @@ function checkTypeApplicability(ctx, fieldName, type, constraints) {
|
|
|
1818
1946
|
const isEnum = type.kind === "enum";
|
|
1819
1947
|
const label = typeLabel(type);
|
|
1820
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
|
+
}
|
|
1821
1960
|
const ck = constraint.constraintKind;
|
|
1822
1961
|
switch (ck) {
|
|
1823
1962
|
case "minimum":
|