@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.js CHANGED
@@ -462,8 +462,70 @@ function collectFields(elements, properties, required, ctx) {
462
462
  }
463
463
  function generateFieldSchema(field, ctx) {
464
464
  const schema = generateTypeNode(field.type, ctx);
465
- applyConstraints(schema, field.constraints);
465
+ const directConstraints = [];
466
+ const pathConstraints = [];
467
+ for (const c of field.constraints) {
468
+ if (c.path) {
469
+ pathConstraints.push(c);
470
+ } else {
471
+ directConstraints.push(c);
472
+ }
473
+ }
474
+ applyConstraints(schema, directConstraints);
466
475
  applyAnnotations(schema, field.annotations);
476
+ if (pathConstraints.length === 0) {
477
+ return schema;
478
+ }
479
+ return applyPathTargetedConstraints(schema, pathConstraints);
480
+ }
481
+ function applyPathTargetedConstraints(schema, pathConstraints) {
482
+ if (schema.type === "array" && schema.items) {
483
+ schema.items = applyPathTargetedConstraints(schema.items, pathConstraints);
484
+ return schema;
485
+ }
486
+ const byTarget = /* @__PURE__ */ new Map();
487
+ for (const c of pathConstraints) {
488
+ const target = c.path?.segments[0];
489
+ if (!target) continue;
490
+ const group = byTarget.get(target) ?? [];
491
+ group.push(c);
492
+ byTarget.set(target, group);
493
+ }
494
+ const propertyOverrides = {};
495
+ for (const [target, constraints] of byTarget) {
496
+ const subSchema = {};
497
+ applyConstraints(subSchema, constraints);
498
+ propertyOverrides[target] = subSchema;
499
+ }
500
+ if (schema.$ref) {
501
+ const { $ref, ...rest } = schema;
502
+ const refPart = { $ref };
503
+ const overridePart = {
504
+ properties: propertyOverrides,
505
+ ...rest
506
+ };
507
+ return { allOf: [refPart, overridePart] };
508
+ }
509
+ if (schema.type === "object" && schema.properties) {
510
+ const missingOverrides = {};
511
+ for (const [target, overrideSchema] of Object.entries(propertyOverrides)) {
512
+ if (schema.properties[target]) {
513
+ Object.assign(schema.properties[target], overrideSchema);
514
+ } else {
515
+ missingOverrides[target] = overrideSchema;
516
+ }
517
+ }
518
+ if (Object.keys(missingOverrides).length === 0) {
519
+ return schema;
520
+ }
521
+ return {
522
+ allOf: [schema, { properties: missingOverrides }]
523
+ };
524
+ }
525
+ if (schema.allOf) {
526
+ schema.allOf = [...schema.allOf, { properties: propertyOverrides }];
527
+ return schema;
528
+ }
467
529
  return schema;
468
530
  }
469
531
  function generateTypeNode(type, ctx) {
@@ -1070,6 +1132,20 @@ var init_program = __esm({
1070
1132
  }
1071
1133
  });
1072
1134
 
1135
+ // src/analyzer/json-utils.ts
1136
+ function tryParseJson(text) {
1137
+ try {
1138
+ return JSON.parse(text);
1139
+ } catch {
1140
+ return null;
1141
+ }
1142
+ }
1143
+ var init_json_utils = __esm({
1144
+ "src/analyzer/json-utils.ts"() {
1145
+ "use strict";
1146
+ }
1147
+ });
1148
+
1073
1149
  // src/analyzer/tsdoc-parser.ts
1074
1150
  import * as ts2 from "typescript";
1075
1151
  import {
@@ -1082,11 +1158,10 @@ import {
1082
1158
  TextRange
1083
1159
  } from "@microsoft/tsdoc";
1084
1160
  import {
1085
- BUILTIN_CONSTRAINT_DEFINITIONS
1161
+ BUILTIN_CONSTRAINT_DEFINITIONS,
1162
+ normalizeConstraintTagName,
1163
+ isBuiltinConstraintName
1086
1164
  } from "@formspec/core";
1087
- function isBuiltinConstraintName(tagName) {
1088
- return tagName in BUILTIN_CONSTRAINT_DEFINITIONS;
1089
- }
1090
1165
  function createFormSpecTSDocConfig() {
1091
1166
  const config = new TSDocConfiguration();
1092
1167
  for (const tagName of Object.keys(BUILTIN_CONSTRAINT_DEFINITIONS)) {
@@ -1125,7 +1200,7 @@ function parseTSDocTags(node, file = "") {
1125
1200
  );
1126
1201
  const docComment = parserContext.docComment;
1127
1202
  for (const block of docComment.customBlocks) {
1128
- const tagName = block.blockTag.tagName.substring(1);
1203
+ const tagName = normalizeConstraintTagName(block.blockTag.tagName.substring(1));
1129
1204
  if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
1130
1205
  const text = extractBlockText(block).trim();
1131
1206
  if (text === "") continue;
@@ -1146,7 +1221,7 @@ function parseTSDocTags(node, file = "") {
1146
1221
  }
1147
1222
  const jsDocTagsAll = ts2.getJSDocTags(node);
1148
1223
  for (const tag of jsDocTagsAll) {
1149
- const tagName = tag.tagName.text;
1224
+ const tagName = normalizeConstraintTagName(tag.tagName.text);
1150
1225
  if (!TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
1151
1226
  const commentText = getTagCommentText(tag);
1152
1227
  if (commentText === void 0 || commentText.trim() === "") continue;
@@ -1194,6 +1269,15 @@ function parseTSDocTags(node, file = "") {
1194
1269
  }
1195
1270
  return { constraints, annotations };
1196
1271
  }
1272
+ function extractPathTarget(text) {
1273
+ const trimmed = text.trimStart();
1274
+ const match = /^:([a-zA-Z_]\w*)\s+([\s\S]*)$/.exec(trimmed);
1275
+ if (!match?.[1] || !match[2]) return null;
1276
+ return {
1277
+ path: { segments: [match[1]] },
1278
+ remainingText: match[2]
1279
+ };
1280
+ }
1197
1281
  function extractBlockText(block) {
1198
1282
  return extractPlainText(block.content);
1199
1283
  }
@@ -1216,9 +1300,12 @@ function parseConstraintValue(tagName, text, provenance) {
1216
1300
  if (!isBuiltinConstraintName(tagName)) {
1217
1301
  return null;
1218
1302
  }
1303
+ const pathResult = extractPathTarget(text);
1304
+ const effectiveText = pathResult ? pathResult.remainingText : text;
1305
+ const path4 = pathResult?.path;
1219
1306
  const expectedType = BUILTIN_CONSTRAINT_DEFINITIONS[tagName];
1220
1307
  if (expectedType === "number") {
1221
- const value = Number(text);
1308
+ const value = Number(effectiveText);
1222
1309
  if (Number.isNaN(value)) {
1223
1310
  return null;
1224
1311
  }
@@ -1228,6 +1315,7 @@ function parseConstraintValue(tagName, text, provenance) {
1228
1315
  kind: "constraint",
1229
1316
  constraintKind: numericKind,
1230
1317
  value,
1318
+ ...path4 && { path: path4 },
1231
1319
  provenance
1232
1320
  };
1233
1321
  }
@@ -1237,42 +1325,41 @@ function parseConstraintValue(tagName, text, provenance) {
1237
1325
  kind: "constraint",
1238
1326
  constraintKind: lengthKind,
1239
1327
  value,
1328
+ ...path4 && { path: path4 },
1240
1329
  provenance
1241
1330
  };
1242
1331
  }
1243
1332
  return null;
1244
1333
  }
1245
1334
  if (expectedType === "json") {
1246
- try {
1247
- const parsed = JSON.parse(text);
1248
- if (!Array.isArray(parsed)) {
1249
- return null;
1250
- }
1251
- const members = [];
1252
- for (const item of parsed) {
1253
- if (typeof item === "string" || typeof item === "number") {
1254
- members.push(item);
1255
- } else if (typeof item === "object" && item !== null && "id" in item) {
1256
- const id = item["id"];
1257
- if (typeof id === "string" || typeof id === "number") {
1258
- members.push(id);
1259
- }
1335
+ const parsed = tryParseJson(effectiveText);
1336
+ if (!Array.isArray(parsed)) {
1337
+ return null;
1338
+ }
1339
+ const members = [];
1340
+ for (const item of parsed) {
1341
+ if (typeof item === "string" || typeof item === "number") {
1342
+ members.push(item);
1343
+ } else if (typeof item === "object" && item !== null && "id" in item) {
1344
+ const id = item["id"];
1345
+ if (typeof id === "string" || typeof id === "number") {
1346
+ members.push(id);
1260
1347
  }
1261
1348
  }
1262
- return {
1263
- kind: "constraint",
1264
- constraintKind: "allowedMembers",
1265
- members,
1266
- provenance
1267
- };
1268
- } catch {
1269
- return null;
1270
1349
  }
1350
+ return {
1351
+ kind: "constraint",
1352
+ constraintKind: "allowedMembers",
1353
+ members,
1354
+ ...path4 && { path: path4 },
1355
+ provenance
1356
+ };
1271
1357
  }
1272
1358
  return {
1273
1359
  kind: "constraint",
1274
1360
  constraintKind: "pattern",
1275
- pattern: text,
1361
+ pattern: effectiveText,
1362
+ ...path4 && { path: path4 },
1276
1363
  provenance
1277
1364
  };
1278
1365
  }
@@ -1310,24 +1397,30 @@ var NUMERIC_CONSTRAINT_MAP, LENGTH_CONSTRAINT_MAP, TAGS_REQUIRING_RAW_TEXT, shar
1310
1397
  var init_tsdoc_parser = __esm({
1311
1398
  "src/analyzer/tsdoc-parser.ts"() {
1312
1399
  "use strict";
1400
+ init_json_utils();
1313
1401
  NUMERIC_CONSTRAINT_MAP = {
1314
- Minimum: "minimum",
1315
- Maximum: "maximum",
1316
- ExclusiveMinimum: "exclusiveMinimum",
1317
- ExclusiveMaximum: "exclusiveMaximum"
1402
+ minimum: "minimum",
1403
+ maximum: "maximum",
1404
+ exclusiveMinimum: "exclusiveMinimum",
1405
+ exclusiveMaximum: "exclusiveMaximum",
1406
+ multipleOf: "multipleOf"
1318
1407
  };
1319
1408
  LENGTH_CONSTRAINT_MAP = {
1320
- MinLength: "minLength",
1321
- MaxLength: "maxLength"
1409
+ minLength: "minLength",
1410
+ maxLength: "maxLength",
1411
+ minItems: "minItems",
1412
+ maxItems: "maxItems"
1322
1413
  };
1323
- TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["Pattern", "EnumOptions"]);
1414
+ TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions"]);
1324
1415
  }
1325
1416
  });
1326
1417
 
1327
1418
  // src/analyzer/jsdoc-constraints.ts
1328
1419
  import * as ts3 from "typescript";
1329
1420
  import {
1330
- BUILTIN_CONSTRAINT_DEFINITIONS as BUILTIN_CONSTRAINT_DEFINITIONS2
1421
+ BUILTIN_CONSTRAINT_DEFINITIONS as BUILTIN_CONSTRAINT_DEFINITIONS2,
1422
+ isBuiltinConstraintName as isBuiltinConstraintName2,
1423
+ normalizeConstraintTagName as normalizeConstraintTagName2
1331
1424
  } from "@formspec/core";
1332
1425
  function extractJSDocConstraintNodes(node, file = "") {
1333
1426
  const result = parseTSDocTags(node, file);
@@ -1374,6 +1467,7 @@ var init_jsdoc_constraints = __esm({
1374
1467
  "src/analyzer/jsdoc-constraints.ts"() {
1375
1468
  "use strict";
1376
1469
  init_tsdoc_parser();
1470
+ init_json_utils();
1377
1471
  }
1378
1472
  });
1379
1473
 
@@ -1731,14 +1825,22 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting) {
1731
1825
  }
1732
1826
  return map;
1733
1827
  }
1734
- function extractTypeAliasConstraintNodes(typeNode, checker, file) {
1828
+ function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
1735
1829
  if (!ts4.isTypeReferenceNode(typeNode)) return [];
1830
+ if (depth >= MAX_ALIAS_CHAIN_DEPTH) {
1831
+ const aliasName = typeNode.typeName.getText();
1832
+ throw new Error(
1833
+ `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.`
1834
+ );
1835
+ }
1736
1836
  const symbol = checker.getSymbolAtLocation(typeNode.typeName);
1737
1837
  if (!symbol?.declarations) return [];
1738
1838
  const aliasDecl = symbol.declarations.find(ts4.isTypeAliasDeclaration);
1739
1839
  if (!aliasDecl) return [];
1740
1840
  if (ts4.isTypeLiteralNode(aliasDecl.type)) return [];
1741
- return extractJSDocConstraintNodes(aliasDecl, file);
1841
+ const constraints = extractJSDocConstraintNodes(aliasDecl, file);
1842
+ constraints.push(...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, depth + 1));
1843
+ return constraints;
1742
1844
  }
1743
1845
  function provenanceForNode(node, file) {
1744
1846
  const sourceFile = node.getSourceFile();
@@ -1811,10 +1913,12 @@ function detectFormSpecReference(typeNode) {
1811
1913
  }
1812
1914
  return null;
1813
1915
  }
1916
+ var MAX_ALIAS_CHAIN_DEPTH;
1814
1917
  var init_class_analyzer = __esm({
1815
1918
  "src/analyzer/class-analyzer.ts"() {
1816
1919
  "use strict";
1817
1920
  init_jsdoc_constraints();
1921
+ MAX_ALIAS_CHAIN_DEPTH = 8;
1818
1922
  }
1819
1923
  });
1820
1924