@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.js
CHANGED
|
@@ -66,11 +66,40 @@ function canonicalizeField(field) {
|
|
|
66
66
|
}
|
|
67
67
|
function canonicalizeTextField(field) {
|
|
68
68
|
const type = { kind: "primitive", primitiveKind: "string" };
|
|
69
|
+
const constraints = [];
|
|
70
|
+
if (field.minLength !== void 0) {
|
|
71
|
+
const c = {
|
|
72
|
+
kind: "constraint",
|
|
73
|
+
constraintKind: "minLength",
|
|
74
|
+
value: field.minLength,
|
|
75
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
76
|
+
};
|
|
77
|
+
constraints.push(c);
|
|
78
|
+
}
|
|
79
|
+
if (field.maxLength !== void 0) {
|
|
80
|
+
const c = {
|
|
81
|
+
kind: "constraint",
|
|
82
|
+
constraintKind: "maxLength",
|
|
83
|
+
value: field.maxLength,
|
|
84
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
85
|
+
};
|
|
86
|
+
constraints.push(c);
|
|
87
|
+
}
|
|
88
|
+
if (field.pattern !== void 0) {
|
|
89
|
+
const c = {
|
|
90
|
+
kind: "constraint",
|
|
91
|
+
constraintKind: "pattern",
|
|
92
|
+
pattern: field.pattern,
|
|
93
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
94
|
+
};
|
|
95
|
+
constraints.push(c);
|
|
96
|
+
}
|
|
69
97
|
return buildFieldNode(
|
|
70
98
|
field.name,
|
|
71
99
|
type,
|
|
72
100
|
field.required,
|
|
73
|
-
buildAnnotations(field.label, field.placeholder)
|
|
101
|
+
buildAnnotations(field.label, field.placeholder),
|
|
102
|
+
constraints
|
|
74
103
|
);
|
|
75
104
|
}
|
|
76
105
|
function canonicalizeNumberField(field) {
|
|
@@ -94,6 +123,15 @@ function canonicalizeNumberField(field) {
|
|
|
94
123
|
};
|
|
95
124
|
constraints.push(c);
|
|
96
125
|
}
|
|
126
|
+
if (field.multipleOf !== void 0) {
|
|
127
|
+
const c = {
|
|
128
|
+
kind: "constraint",
|
|
129
|
+
constraintKind: "multipleOf",
|
|
130
|
+
value: field.multipleOf,
|
|
131
|
+
provenance: CHAIN_DSL_PROVENANCE
|
|
132
|
+
};
|
|
133
|
+
constraints.push(c);
|
|
134
|
+
}
|
|
97
135
|
return buildFieldNode(
|
|
98
136
|
field.name,
|
|
99
137
|
type,
|
|
@@ -393,8 +431,70 @@ function collectFields(elements, properties, required, ctx) {
|
|
|
393
431
|
}
|
|
394
432
|
function generateFieldSchema(field, ctx) {
|
|
395
433
|
const schema = generateTypeNode(field.type, ctx);
|
|
396
|
-
|
|
434
|
+
const directConstraints = [];
|
|
435
|
+
const pathConstraints = [];
|
|
436
|
+
for (const c of field.constraints) {
|
|
437
|
+
if (c.path) {
|
|
438
|
+
pathConstraints.push(c);
|
|
439
|
+
} else {
|
|
440
|
+
directConstraints.push(c);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
applyConstraints(schema, directConstraints);
|
|
397
444
|
applyAnnotations(schema, field.annotations);
|
|
445
|
+
if (pathConstraints.length === 0) {
|
|
446
|
+
return schema;
|
|
447
|
+
}
|
|
448
|
+
return applyPathTargetedConstraints(schema, pathConstraints);
|
|
449
|
+
}
|
|
450
|
+
function applyPathTargetedConstraints(schema, pathConstraints) {
|
|
451
|
+
if (schema.type === "array" && schema.items) {
|
|
452
|
+
schema.items = applyPathTargetedConstraints(schema.items, pathConstraints);
|
|
453
|
+
return schema;
|
|
454
|
+
}
|
|
455
|
+
const byTarget = /* @__PURE__ */ new Map();
|
|
456
|
+
for (const c of pathConstraints) {
|
|
457
|
+
const target = c.path?.segments[0];
|
|
458
|
+
if (!target) continue;
|
|
459
|
+
const group = byTarget.get(target) ?? [];
|
|
460
|
+
group.push(c);
|
|
461
|
+
byTarget.set(target, group);
|
|
462
|
+
}
|
|
463
|
+
const propertyOverrides = {};
|
|
464
|
+
for (const [target, constraints] of byTarget) {
|
|
465
|
+
const subSchema = {};
|
|
466
|
+
applyConstraints(subSchema, constraints);
|
|
467
|
+
propertyOverrides[target] = subSchema;
|
|
468
|
+
}
|
|
469
|
+
if (schema.$ref) {
|
|
470
|
+
const { $ref, ...rest } = schema;
|
|
471
|
+
const refPart = { $ref };
|
|
472
|
+
const overridePart = {
|
|
473
|
+
properties: propertyOverrides,
|
|
474
|
+
...rest
|
|
475
|
+
};
|
|
476
|
+
return { allOf: [refPart, overridePart] };
|
|
477
|
+
}
|
|
478
|
+
if (schema.type === "object" && schema.properties) {
|
|
479
|
+
const missingOverrides = {};
|
|
480
|
+
for (const [target, overrideSchema] of Object.entries(propertyOverrides)) {
|
|
481
|
+
if (schema.properties[target]) {
|
|
482
|
+
Object.assign(schema.properties[target], overrideSchema);
|
|
483
|
+
} else {
|
|
484
|
+
missingOverrides[target] = overrideSchema;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
if (Object.keys(missingOverrides).length === 0) {
|
|
488
|
+
return schema;
|
|
489
|
+
}
|
|
490
|
+
return {
|
|
491
|
+
allOf: [schema, { properties: missingOverrides }]
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
if (schema.allOf) {
|
|
495
|
+
schema.allOf = [...schema.allOf, { properties: propertyOverrides }];
|
|
496
|
+
return schema;
|
|
497
|
+
}
|
|
398
498
|
return schema;
|
|
399
499
|
}
|
|
400
500
|
function generateTypeNode(type, ctx) {
|
|
@@ -964,7 +1064,9 @@ import * as ts4 from "typescript";
|
|
|
964
1064
|
// src/analyzer/jsdoc-constraints.ts
|
|
965
1065
|
import * as ts3 from "typescript";
|
|
966
1066
|
import {
|
|
967
|
-
BUILTIN_CONSTRAINT_DEFINITIONS as BUILTIN_CONSTRAINT_DEFINITIONS2
|
|
1067
|
+
BUILTIN_CONSTRAINT_DEFINITIONS as BUILTIN_CONSTRAINT_DEFINITIONS2,
|
|
1068
|
+
isBuiltinConstraintName as isBuiltinConstraintName2,
|
|
1069
|
+
normalizeConstraintTagName as normalizeConstraintTagName2
|
|
968
1070
|
} from "@formspec/core";
|
|
969
1071
|
|
|
970
1072
|
// src/analyzer/tsdoc-parser.ts
|
|
@@ -979,22 +1081,35 @@ import {
|
|
|
979
1081
|
TextRange
|
|
980
1082
|
} from "@microsoft/tsdoc";
|
|
981
1083
|
import {
|
|
982
|
-
BUILTIN_CONSTRAINT_DEFINITIONS
|
|
1084
|
+
BUILTIN_CONSTRAINT_DEFINITIONS,
|
|
1085
|
+
normalizeConstraintTagName,
|
|
1086
|
+
isBuiltinConstraintName
|
|
983
1087
|
} from "@formspec/core";
|
|
1088
|
+
|
|
1089
|
+
// src/analyzer/json-utils.ts
|
|
1090
|
+
function tryParseJson(text) {
|
|
1091
|
+
try {
|
|
1092
|
+
return JSON.parse(text);
|
|
1093
|
+
} catch {
|
|
1094
|
+
return null;
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
// src/analyzer/tsdoc-parser.ts
|
|
984
1099
|
var NUMERIC_CONSTRAINT_MAP = {
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1100
|
+
minimum: "minimum",
|
|
1101
|
+
maximum: "maximum",
|
|
1102
|
+
exclusiveMinimum: "exclusiveMinimum",
|
|
1103
|
+
exclusiveMaximum: "exclusiveMaximum",
|
|
1104
|
+
multipleOf: "multipleOf"
|
|
989
1105
|
};
|
|
990
1106
|
var LENGTH_CONSTRAINT_MAP = {
|
|
991
|
-
|
|
992
|
-
|
|
1107
|
+
minLength: "minLength",
|
|
1108
|
+
maxLength: "maxLength",
|
|
1109
|
+
minItems: "minItems",
|
|
1110
|
+
maxItems: "maxItems"
|
|
993
1111
|
};
|
|
994
|
-
var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["
|
|
995
|
-
function isBuiltinConstraintName(tagName) {
|
|
996
|
-
return tagName in BUILTIN_CONSTRAINT_DEFINITIONS;
|
|
997
|
-
}
|
|
1112
|
+
var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions"]);
|
|
998
1113
|
function createFormSpecTSDocConfig() {
|
|
999
1114
|
const config = new TSDocConfiguration();
|
|
1000
1115
|
for (const tagName of Object.keys(BUILTIN_CONSTRAINT_DEFINITIONS)) {
|
|
@@ -1034,7 +1149,7 @@ function parseTSDocTags(node, file = "") {
|
|
|
1034
1149
|
);
|
|
1035
1150
|
const docComment = parserContext.docComment;
|
|
1036
1151
|
for (const block of docComment.customBlocks) {
|
|
1037
|
-
const tagName = block.blockTag.tagName.substring(1);
|
|
1152
|
+
const tagName = normalizeConstraintTagName(block.blockTag.tagName.substring(1));
|
|
1038
1153
|
if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
1039
1154
|
const text = extractBlockText(block).trim();
|
|
1040
1155
|
if (text === "") continue;
|
|
@@ -1055,7 +1170,7 @@ function parseTSDocTags(node, file = "") {
|
|
|
1055
1170
|
}
|
|
1056
1171
|
const jsDocTagsAll = ts2.getJSDocTags(node);
|
|
1057
1172
|
for (const tag of jsDocTagsAll) {
|
|
1058
|
-
const tagName = tag.tagName.text;
|
|
1173
|
+
const tagName = normalizeConstraintTagName(tag.tagName.text);
|
|
1059
1174
|
if (!TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
|
|
1060
1175
|
const commentText = getTagCommentText(tag);
|
|
1061
1176
|
if (commentText === void 0 || commentText.trim() === "") continue;
|
|
@@ -1103,6 +1218,15 @@ function parseTSDocTags(node, file = "") {
|
|
|
1103
1218
|
}
|
|
1104
1219
|
return { constraints, annotations };
|
|
1105
1220
|
}
|
|
1221
|
+
function extractPathTarget(text) {
|
|
1222
|
+
const trimmed = text.trimStart();
|
|
1223
|
+
const match = /^:([a-zA-Z_]\w*)\s+([\s\S]*)$/.exec(trimmed);
|
|
1224
|
+
if (!match?.[1] || !match[2]) return null;
|
|
1225
|
+
return {
|
|
1226
|
+
path: { segments: [match[1]] },
|
|
1227
|
+
remainingText: match[2]
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1106
1230
|
function extractBlockText(block) {
|
|
1107
1231
|
return extractPlainText(block.content);
|
|
1108
1232
|
}
|
|
@@ -1125,9 +1249,12 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
1125
1249
|
if (!isBuiltinConstraintName(tagName)) {
|
|
1126
1250
|
return null;
|
|
1127
1251
|
}
|
|
1252
|
+
const pathResult = extractPathTarget(text);
|
|
1253
|
+
const effectiveText = pathResult ? pathResult.remainingText : text;
|
|
1254
|
+
const path3 = pathResult?.path;
|
|
1128
1255
|
const expectedType = BUILTIN_CONSTRAINT_DEFINITIONS[tagName];
|
|
1129
1256
|
if (expectedType === "number") {
|
|
1130
|
-
const value = Number(
|
|
1257
|
+
const value = Number(effectiveText);
|
|
1131
1258
|
if (Number.isNaN(value)) {
|
|
1132
1259
|
return null;
|
|
1133
1260
|
}
|
|
@@ -1137,6 +1264,7 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
1137
1264
|
kind: "constraint",
|
|
1138
1265
|
constraintKind: numericKind,
|
|
1139
1266
|
value,
|
|
1267
|
+
...path3 && { path: path3 },
|
|
1140
1268
|
provenance
|
|
1141
1269
|
};
|
|
1142
1270
|
}
|
|
@@ -1146,42 +1274,41 @@ function parseConstraintValue(tagName, text, provenance) {
|
|
|
1146
1274
|
kind: "constraint",
|
|
1147
1275
|
constraintKind: lengthKind,
|
|
1148
1276
|
value,
|
|
1277
|
+
...path3 && { path: path3 },
|
|
1149
1278
|
provenance
|
|
1150
1279
|
};
|
|
1151
1280
|
}
|
|
1152
1281
|
return null;
|
|
1153
1282
|
}
|
|
1154
1283
|
if (expectedType === "json") {
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
members.push(id);
|
|
1168
|
-
}
|
|
1284
|
+
const parsed = tryParseJson(effectiveText);
|
|
1285
|
+
if (!Array.isArray(parsed)) {
|
|
1286
|
+
return null;
|
|
1287
|
+
}
|
|
1288
|
+
const members = [];
|
|
1289
|
+
for (const item of parsed) {
|
|
1290
|
+
if (typeof item === "string" || typeof item === "number") {
|
|
1291
|
+
members.push(item);
|
|
1292
|
+
} else if (typeof item === "object" && item !== null && "id" in item) {
|
|
1293
|
+
const id = item["id"];
|
|
1294
|
+
if (typeof id === "string" || typeof id === "number") {
|
|
1295
|
+
members.push(id);
|
|
1169
1296
|
}
|
|
1170
1297
|
}
|
|
1171
|
-
return {
|
|
1172
|
-
kind: "constraint",
|
|
1173
|
-
constraintKind: "allowedMembers",
|
|
1174
|
-
members,
|
|
1175
|
-
provenance
|
|
1176
|
-
};
|
|
1177
|
-
} catch {
|
|
1178
|
-
return null;
|
|
1179
1298
|
}
|
|
1299
|
+
return {
|
|
1300
|
+
kind: "constraint",
|
|
1301
|
+
constraintKind: "allowedMembers",
|
|
1302
|
+
members,
|
|
1303
|
+
...path3 && { path: path3 },
|
|
1304
|
+
provenance
|
|
1305
|
+
};
|
|
1180
1306
|
}
|
|
1181
1307
|
return {
|
|
1182
1308
|
kind: "constraint",
|
|
1183
1309
|
constraintKind: "pattern",
|
|
1184
|
-
pattern:
|
|
1310
|
+
pattern: effectiveText,
|
|
1311
|
+
...path3 && { path: path3 },
|
|
1185
1312
|
provenance
|
|
1186
1313
|
};
|
|
1187
1314
|
}
|
|
@@ -1612,14 +1739,23 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting) {
|
|
|
1612
1739
|
}
|
|
1613
1740
|
return map;
|
|
1614
1741
|
}
|
|
1615
|
-
|
|
1742
|
+
var MAX_ALIAS_CHAIN_DEPTH = 8;
|
|
1743
|
+
function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
|
|
1616
1744
|
if (!ts4.isTypeReferenceNode(typeNode)) return [];
|
|
1745
|
+
if (depth >= MAX_ALIAS_CHAIN_DEPTH) {
|
|
1746
|
+
const aliasName = typeNode.typeName.getText();
|
|
1747
|
+
throw new Error(
|
|
1748
|
+
`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.`
|
|
1749
|
+
);
|
|
1750
|
+
}
|
|
1617
1751
|
const symbol = checker.getSymbolAtLocation(typeNode.typeName);
|
|
1618
1752
|
if (!symbol?.declarations) return [];
|
|
1619
1753
|
const aliasDecl = symbol.declarations.find(ts4.isTypeAliasDeclaration);
|
|
1620
1754
|
if (!aliasDecl) return [];
|
|
1621
1755
|
if (ts4.isTypeLiteralNode(aliasDecl.type)) return [];
|
|
1622
|
-
|
|
1756
|
+
const constraints = extractJSDocConstraintNodes(aliasDecl, file);
|
|
1757
|
+
constraints.push(...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, depth + 1));
|
|
1758
|
+
return constraints;
|
|
1623
1759
|
}
|
|
1624
1760
|
function provenanceForNode(node, file) {
|
|
1625
1761
|
const sourceFile = node.getSourceFile();
|