@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.
Files changed (42) hide show
  1. package/README.md +20 -20
  2. package/dist/__tests__/alias-chain-propagation.test.d.ts +9 -0
  3. package/dist/__tests__/alias-chain-propagation.test.d.ts.map +1 -0
  4. package/dist/__tests__/fixtures/alias-chains.d.ts +37 -0
  5. package/dist/__tests__/fixtures/alias-chains.d.ts.map +1 -0
  6. package/dist/__tests__/fixtures/example-a-builtins.d.ts +7 -7
  7. package/dist/__tests__/fixtures/example-interface-types.d.ts +17 -17
  8. package/dist/__tests__/guards.test.d.ts +2 -0
  9. package/dist/__tests__/guards.test.d.ts.map +1 -0
  10. package/dist/__tests__/json-utils.test.d.ts +5 -0
  11. package/dist/__tests__/json-utils.test.d.ts.map +1 -0
  12. package/dist/__tests__/path-target-parser.test.d.ts +9 -0
  13. package/dist/__tests__/path-target-parser.test.d.ts.map +1 -0
  14. package/dist/analyzer/class-analyzer.d.ts.map +1 -1
  15. package/dist/analyzer/jsdoc-constraints.d.ts +2 -2
  16. package/dist/analyzer/jsdoc-constraints.d.ts.map +1 -1
  17. package/dist/analyzer/json-utils.d.ts +22 -0
  18. package/dist/analyzer/json-utils.d.ts.map +1 -0
  19. package/dist/analyzer/tsdoc-parser.d.ts +18 -4
  20. package/dist/analyzer/tsdoc-parser.d.ts.map +1 -1
  21. package/dist/browser.cjs +115 -8
  22. package/dist/browser.cjs.map +1 -1
  23. package/dist/browser.js +115 -8
  24. package/dist/browser.js.map +1 -1
  25. package/dist/build.d.ts +1 -0
  26. package/dist/canonicalize/chain-dsl-canonicalizer.d.ts.map +1 -1
  27. package/dist/cli.cjs +179 -42
  28. package/dist/cli.cjs.map +1 -1
  29. package/dist/cli.js +184 -42
  30. package/dist/cli.js.map +1 -1
  31. package/dist/index.cjs +173 -41
  32. package/dist/index.cjs.map +1 -1
  33. package/dist/index.js +178 -42
  34. package/dist/index.js.map +1 -1
  35. package/dist/internals.cjs +186 -47
  36. package/dist/internals.cjs.map +1 -1
  37. package/dist/internals.js +191 -48
  38. package/dist/internals.js.map +1 -1
  39. package/dist/json-schema/ir-generator.d.ts +1 -0
  40. package/dist/json-schema/ir-generator.d.ts.map +1 -1
  41. package/dist/validate/constraint-validator.d.ts.map +1 -1
  42. 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
- applyConstraints(schema, field.constraints);
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
- Minimum: "minimum",
986
- Maximum: "maximum",
987
- ExclusiveMinimum: "exclusiveMinimum",
988
- ExclusiveMaximum: "exclusiveMaximum"
1100
+ minimum: "minimum",
1101
+ maximum: "maximum",
1102
+ exclusiveMinimum: "exclusiveMinimum",
1103
+ exclusiveMaximum: "exclusiveMaximum",
1104
+ multipleOf: "multipleOf"
989
1105
  };
990
1106
  var LENGTH_CONSTRAINT_MAP = {
991
- MinLength: "minLength",
992
- MaxLength: "maxLength"
1107
+ minLength: "minLength",
1108
+ maxLength: "maxLength",
1109
+ minItems: "minItems",
1110
+ maxItems: "maxItems"
993
1111
  };
994
- var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["Pattern", "EnumOptions"]);
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(text);
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
- try {
1156
- const parsed = JSON.parse(text);
1157
- if (!Array.isArray(parsed)) {
1158
- return null;
1159
- }
1160
- const members = [];
1161
- for (const item of parsed) {
1162
- if (typeof item === "string" || typeof item === "number") {
1163
- members.push(item);
1164
- } else if (typeof item === "object" && item !== null && "id" in item) {
1165
- const id = item["id"];
1166
- if (typeof id === "string" || typeof id === "number") {
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: text,
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
- function extractTypeAliasConstraintNodes(typeNode, checker, file) {
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
- return extractJSDocConstraintNodes(aliasDecl, file);
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();