@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.
Files changed (39) 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__/json-utils.test.d.ts +5 -0
  9. package/dist/__tests__/json-utils.test.d.ts.map +1 -0
  10. package/dist/__tests__/path-target-parser.test.d.ts +9 -0
  11. package/dist/__tests__/path-target-parser.test.d.ts.map +1 -0
  12. package/dist/analyzer/class-analyzer.d.ts.map +1 -1
  13. package/dist/analyzer/jsdoc-constraints.d.ts +2 -2
  14. package/dist/analyzer/jsdoc-constraints.d.ts.map +1 -1
  15. package/dist/analyzer/json-utils.d.ts +22 -0
  16. package/dist/analyzer/json-utils.d.ts.map +1 -0
  17. package/dist/analyzer/tsdoc-parser.d.ts +18 -4
  18. package/dist/analyzer/tsdoc-parser.d.ts.map +1 -1
  19. package/dist/browser.cjs +76 -7
  20. package/dist/browser.cjs.map +1 -1
  21. package/dist/browser.js +76 -7
  22. package/dist/browser.js.map +1 -1
  23. package/dist/build.d.ts +1 -0
  24. package/dist/cli.cjs +140 -41
  25. package/dist/cli.cjs.map +1 -1
  26. package/dist/cli.js +145 -41
  27. package/dist/cli.js.map +1 -1
  28. package/dist/index.cjs +134 -40
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.js +139 -41
  31. package/dist/index.js.map +1 -1
  32. package/dist/internals.cjs +147 -46
  33. package/dist/internals.cjs.map +1 -1
  34. package/dist/internals.js +152 -47
  35. package/dist/internals.js.map +1 -1
  36. package/dist/json-schema/ir-generator.d.ts +1 -0
  37. package/dist/json-schema/ir-generator.d.ts.map +1 -1
  38. package/dist/validate/constraint-validator.d.ts.map +1 -1
  39. package/package.json +3 -3
package/dist/cli.cjs CHANGED
@@ -484,8 +484,70 @@ function collectFields(elements, properties, required, ctx) {
484
484
  }
485
485
  function generateFieldSchema(field, ctx) {
486
486
  const schema = generateTypeNode(field.type, ctx);
487
- applyConstraints(schema, field.constraints);
487
+ const directConstraints = [];
488
+ const pathConstraints = [];
489
+ for (const c of field.constraints) {
490
+ if (c.path) {
491
+ pathConstraints.push(c);
492
+ } else {
493
+ directConstraints.push(c);
494
+ }
495
+ }
496
+ applyConstraints(schema, directConstraints);
488
497
  applyAnnotations(schema, field.annotations);
498
+ if (pathConstraints.length === 0) {
499
+ return schema;
500
+ }
501
+ return applyPathTargetedConstraints(schema, pathConstraints);
502
+ }
503
+ function applyPathTargetedConstraints(schema, pathConstraints) {
504
+ if (schema.type === "array" && schema.items) {
505
+ schema.items = applyPathTargetedConstraints(schema.items, pathConstraints);
506
+ return schema;
507
+ }
508
+ const byTarget = /* @__PURE__ */ new Map();
509
+ for (const c of pathConstraints) {
510
+ const target = c.path?.segments[0];
511
+ if (!target) continue;
512
+ const group = byTarget.get(target) ?? [];
513
+ group.push(c);
514
+ byTarget.set(target, group);
515
+ }
516
+ const propertyOverrides = {};
517
+ for (const [target, constraints] of byTarget) {
518
+ const subSchema = {};
519
+ applyConstraints(subSchema, constraints);
520
+ propertyOverrides[target] = subSchema;
521
+ }
522
+ if (schema.$ref) {
523
+ const { $ref, ...rest } = schema;
524
+ const refPart = { $ref };
525
+ const overridePart = {
526
+ properties: propertyOverrides,
527
+ ...rest
528
+ };
529
+ return { allOf: [refPart, overridePart] };
530
+ }
531
+ if (schema.type === "object" && schema.properties) {
532
+ const missingOverrides = {};
533
+ for (const [target, overrideSchema] of Object.entries(propertyOverrides)) {
534
+ if (schema.properties[target]) {
535
+ Object.assign(schema.properties[target], overrideSchema);
536
+ } else {
537
+ missingOverrides[target] = overrideSchema;
538
+ }
539
+ }
540
+ if (Object.keys(missingOverrides).length === 0) {
541
+ return schema;
542
+ }
543
+ return {
544
+ allOf: [schema, { properties: missingOverrides }]
545
+ };
546
+ }
547
+ if (schema.allOf) {
548
+ schema.allOf = [...schema.allOf, { properties: propertyOverrides }];
549
+ return schema;
550
+ }
489
551
  return schema;
490
552
  }
491
553
  function generateTypeNode(type, ctx) {
@@ -1094,10 +1156,21 @@ var init_program = __esm({
1094
1156
  }
1095
1157
  });
1096
1158
 
1097
- // src/analyzer/tsdoc-parser.ts
1098
- function isBuiltinConstraintName(tagName) {
1099
- return tagName in import_core3.BUILTIN_CONSTRAINT_DEFINITIONS;
1159
+ // src/analyzer/json-utils.ts
1160
+ function tryParseJson(text) {
1161
+ try {
1162
+ return JSON.parse(text);
1163
+ } catch {
1164
+ return null;
1165
+ }
1100
1166
  }
1167
+ var init_json_utils = __esm({
1168
+ "src/analyzer/json-utils.ts"() {
1169
+ "use strict";
1170
+ }
1171
+ });
1172
+
1173
+ // src/analyzer/tsdoc-parser.ts
1101
1174
  function createFormSpecTSDocConfig() {
1102
1175
  const config = new import_tsdoc.TSDocConfiguration();
1103
1176
  for (const tagName of Object.keys(import_core3.BUILTIN_CONSTRAINT_DEFINITIONS)) {
@@ -1136,7 +1209,7 @@ function parseTSDocTags(node, file = "") {
1136
1209
  );
1137
1210
  const docComment = parserContext.docComment;
1138
1211
  for (const block of docComment.customBlocks) {
1139
- const tagName = block.blockTag.tagName.substring(1);
1212
+ const tagName = (0, import_core3.normalizeConstraintTagName)(block.blockTag.tagName.substring(1));
1140
1213
  if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
1141
1214
  const text = extractBlockText(block).trim();
1142
1215
  if (text === "") continue;
@@ -1157,7 +1230,7 @@ function parseTSDocTags(node, file = "") {
1157
1230
  }
1158
1231
  const jsDocTagsAll = ts2.getJSDocTags(node);
1159
1232
  for (const tag of jsDocTagsAll) {
1160
- const tagName = tag.tagName.text;
1233
+ const tagName = (0, import_core3.normalizeConstraintTagName)(tag.tagName.text);
1161
1234
  if (!TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
1162
1235
  const commentText = getTagCommentText(tag);
1163
1236
  if (commentText === void 0 || commentText.trim() === "") continue;
@@ -1205,6 +1278,15 @@ function parseTSDocTags(node, file = "") {
1205
1278
  }
1206
1279
  return { constraints, annotations };
1207
1280
  }
1281
+ function extractPathTarget(text) {
1282
+ const trimmed = text.trimStart();
1283
+ const match = /^:([a-zA-Z_]\w*)\s+([\s\S]*)$/.exec(trimmed);
1284
+ if (!match?.[1] || !match[2]) return null;
1285
+ return {
1286
+ path: { segments: [match[1]] },
1287
+ remainingText: match[2]
1288
+ };
1289
+ }
1208
1290
  function extractBlockText(block) {
1209
1291
  return extractPlainText(block.content);
1210
1292
  }
@@ -1224,12 +1306,15 @@ function extractPlainText(node) {
1224
1306
  return result;
1225
1307
  }
1226
1308
  function parseConstraintValue(tagName, text, provenance) {
1227
- if (!isBuiltinConstraintName(tagName)) {
1309
+ if (!(0, import_core3.isBuiltinConstraintName)(tagName)) {
1228
1310
  return null;
1229
1311
  }
1312
+ const pathResult = extractPathTarget(text);
1313
+ const effectiveText = pathResult ? pathResult.remainingText : text;
1314
+ const path4 = pathResult?.path;
1230
1315
  const expectedType = import_core3.BUILTIN_CONSTRAINT_DEFINITIONS[tagName];
1231
1316
  if (expectedType === "number") {
1232
- const value = Number(text);
1317
+ const value = Number(effectiveText);
1233
1318
  if (Number.isNaN(value)) {
1234
1319
  return null;
1235
1320
  }
@@ -1239,6 +1324,7 @@ function parseConstraintValue(tagName, text, provenance) {
1239
1324
  kind: "constraint",
1240
1325
  constraintKind: numericKind,
1241
1326
  value,
1327
+ ...path4 && { path: path4 },
1242
1328
  provenance
1243
1329
  };
1244
1330
  }
@@ -1248,42 +1334,41 @@ function parseConstraintValue(tagName, text, provenance) {
1248
1334
  kind: "constraint",
1249
1335
  constraintKind: lengthKind,
1250
1336
  value,
1337
+ ...path4 && { path: path4 },
1251
1338
  provenance
1252
1339
  };
1253
1340
  }
1254
1341
  return null;
1255
1342
  }
1256
1343
  if (expectedType === "json") {
1257
- try {
1258
- const parsed = JSON.parse(text);
1259
- if (!Array.isArray(parsed)) {
1260
- return null;
1261
- }
1262
- const members = [];
1263
- for (const item of parsed) {
1264
- if (typeof item === "string" || typeof item === "number") {
1265
- members.push(item);
1266
- } else if (typeof item === "object" && item !== null && "id" in item) {
1267
- const id = item["id"];
1268
- if (typeof id === "string" || typeof id === "number") {
1269
- members.push(id);
1270
- }
1344
+ const parsed = tryParseJson(effectiveText);
1345
+ if (!Array.isArray(parsed)) {
1346
+ return null;
1347
+ }
1348
+ const members = [];
1349
+ for (const item of parsed) {
1350
+ if (typeof item === "string" || typeof item === "number") {
1351
+ members.push(item);
1352
+ } else if (typeof item === "object" && item !== null && "id" in item) {
1353
+ const id = item["id"];
1354
+ if (typeof id === "string" || typeof id === "number") {
1355
+ members.push(id);
1271
1356
  }
1272
1357
  }
1273
- return {
1274
- kind: "constraint",
1275
- constraintKind: "allowedMembers",
1276
- members,
1277
- provenance
1278
- };
1279
- } catch {
1280
- return null;
1281
1358
  }
1359
+ return {
1360
+ kind: "constraint",
1361
+ constraintKind: "allowedMembers",
1362
+ members,
1363
+ ...path4 && { path: path4 },
1364
+ provenance
1365
+ };
1282
1366
  }
1283
1367
  return {
1284
1368
  kind: "constraint",
1285
1369
  constraintKind: "pattern",
1286
- pattern: text,
1370
+ pattern: effectiveText,
1371
+ ...path4 && { path: path4 },
1287
1372
  provenance
1288
1373
  };
1289
1374
  }
@@ -1324,17 +1409,21 @@ var init_tsdoc_parser = __esm({
1324
1409
  ts2 = __toESM(require("typescript"), 1);
1325
1410
  import_tsdoc = require("@microsoft/tsdoc");
1326
1411
  import_core3 = require("@formspec/core");
1412
+ init_json_utils();
1327
1413
  NUMERIC_CONSTRAINT_MAP = {
1328
- Minimum: "minimum",
1329
- Maximum: "maximum",
1330
- ExclusiveMinimum: "exclusiveMinimum",
1331
- ExclusiveMaximum: "exclusiveMaximum"
1414
+ minimum: "minimum",
1415
+ maximum: "maximum",
1416
+ exclusiveMinimum: "exclusiveMinimum",
1417
+ exclusiveMaximum: "exclusiveMaximum",
1418
+ multipleOf: "multipleOf"
1332
1419
  };
1333
1420
  LENGTH_CONSTRAINT_MAP = {
1334
- MinLength: "minLength",
1335
- MaxLength: "maxLength"
1421
+ minLength: "minLength",
1422
+ maxLength: "maxLength",
1423
+ minItems: "minItems",
1424
+ maxItems: "maxItems"
1336
1425
  };
1337
- TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["Pattern", "EnumOptions"]);
1426
+ TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions"]);
1338
1427
  }
1339
1428
  });
1340
1429
 
@@ -1387,6 +1476,7 @@ var init_jsdoc_constraints = __esm({
1387
1476
  ts3 = __toESM(require("typescript"), 1);
1388
1477
  import_core4 = require("@formspec/core");
1389
1478
  init_tsdoc_parser();
1479
+ init_json_utils();
1390
1480
  }
1391
1481
  });
1392
1482
 
@@ -1743,14 +1833,22 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting) {
1743
1833
  }
1744
1834
  return map;
1745
1835
  }
1746
- function extractTypeAliasConstraintNodes(typeNode, checker, file) {
1836
+ function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
1747
1837
  if (!ts4.isTypeReferenceNode(typeNode)) return [];
1838
+ if (depth >= MAX_ALIAS_CHAIN_DEPTH) {
1839
+ const aliasName = typeNode.typeName.getText();
1840
+ throw new Error(
1841
+ `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.`
1842
+ );
1843
+ }
1748
1844
  const symbol = checker.getSymbolAtLocation(typeNode.typeName);
1749
1845
  if (!symbol?.declarations) return [];
1750
1846
  const aliasDecl = symbol.declarations.find(ts4.isTypeAliasDeclaration);
1751
1847
  if (!aliasDecl) return [];
1752
1848
  if (ts4.isTypeLiteralNode(aliasDecl.type)) return [];
1753
- return extractJSDocConstraintNodes(aliasDecl, file);
1849
+ const constraints = extractJSDocConstraintNodes(aliasDecl, file);
1850
+ constraints.push(...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, depth + 1));
1851
+ return constraints;
1754
1852
  }
1755
1853
  function provenanceForNode(node, file) {
1756
1854
  const sourceFile = node.getSourceFile();
@@ -1823,12 +1921,13 @@ function detectFormSpecReference(typeNode) {
1823
1921
  }
1824
1922
  return null;
1825
1923
  }
1826
- var ts4;
1924
+ var ts4, MAX_ALIAS_CHAIN_DEPTH;
1827
1925
  var init_class_analyzer = __esm({
1828
1926
  "src/analyzer/class-analyzer.ts"() {
1829
1927
  "use strict";
1830
1928
  ts4 = __toESM(require("typescript"), 1);
1831
1929
  init_jsdoc_constraints();
1930
+ MAX_ALIAS_CHAIN_DEPTH = 8;
1832
1931
  }
1833
1932
  });
1834
1933