@kubb/ast 5.18.7 → 5.19.0

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/dist/index.cjs CHANGED
@@ -46,11 +46,37 @@ let node_crypto = require("node:crypto");
46
46
  * `const refs = memo(refCache, node, () => collectRefs(node))`
47
47
  */
48
48
  function memo(store, key, compute) {
49
- if (store.has(key)) return store.get(key);
49
+ const cached = store.get(key);
50
+ if (cached !== void 0 || store.has(key)) return cached;
50
51
  const value = compute();
51
52
  store.set(key, value);
52
53
  return value;
53
54
  }
55
+ function createStore({ weak, max } = {}) {
56
+ if (weak) return /* @__PURE__ */ new WeakMap();
57
+ const entry = /* @__PURE__ */ new Map();
58
+ if (max === void 0) return entry;
59
+ const set = (key, value) => {
60
+ if (entry.size >= max) entry.clear();
61
+ entry.set(key, value);
62
+ };
63
+ if (max !== 1) return {
64
+ has: (key) => entry.has(key),
65
+ get: (key) => entry.get(key),
66
+ set
67
+ };
68
+ const touch = (key) => {
69
+ if (!entry.has(key)) return false;
70
+ set(key, entry.get(key));
71
+ return true;
72
+ };
73
+ return {
74
+ has: touch,
75
+ get: (key) => touch(key) ? entry.get(key) : void 0,
76
+ set
77
+ };
78
+ }
79
+ memo.create = createStore;
54
80
  // src/constants.ts
55
81
  /**
56
82
  * Node kind discriminators used by every AST node (for example `node.kind === nodeKinds.text`).
@@ -321,14 +347,17 @@ function walkSchema(node, through, visit) {
321
347
  * ```
322
348
  */
323
349
  function getSchemaLiteralValues$1(node) {
324
- const values = /* @__PURE__ */ new Set();
325
- walkSchema(node, "union", (current) => {
326
- if (current.type !== "enum") return;
327
- for (const { value } of current.namedEnumValues ?? current.enumValues?.map((value) => ({ value })) ?? []) values.add(value);
350
+ return memo(literalValues, node, () => {
351
+ const values = /* @__PURE__ */ new Set();
352
+ walkSchema(node, "union", (current) => {
353
+ if (current.type !== "enum") return;
354
+ for (const { value } of current.namedEnumValues ?? current.enumValues?.map((value) => ({ value })) ?? []) values.add(value);
355
+ });
356
+ return Object.freeze([...values]);
328
357
  });
329
- return [...values];
330
358
  }
331
359
  __name(getSchemaLiteralValues$1, "getSchemaLiteralValues");
360
+ const literalValues = memo.create({ weak: true });
332
361
  function collectPropertyNames(node) {
333
362
  const names = /* @__PURE__ */ new Set();
334
363
  walkSchema(node, "intersection", (current) => {
@@ -477,24 +506,6 @@ function isNode(value) {
477
506
  function isNodeRecord(value) {
478
507
  return typeof value === "object" && value !== null && !Array.isArray(value);
479
508
  }
480
- /**
481
- * Returns the immediate traversable children of `node`.
482
- *
483
- * @example
484
- * ```ts
485
- * const children = getChildren(operationNode, true)
486
- * // returns parameters, the request body, and responses
487
- * ```
488
- */
489
- function getChildren(node, recurse) {
490
- if (node.kind === nodeKinds$1.schema && !recurse) return [];
491
- const children = [];
492
- visitChildNodes(node, (child) => {
493
- children.push(child);
494
- return false;
495
- });
496
- return children;
497
- }
498
509
  function isNeverPrinted({ node, key }) {
499
510
  if (node.kind !== nodeKinds$1.schema) return false;
500
511
  if (key === "contains" && (node.type === "array" || node.type === "tuple")) return node.contains?.min === 0 && node.contains.max === void 0;
@@ -502,41 +513,63 @@ function isNeverPrinted({ node, key }) {
502
513
  return false;
503
514
  }
504
515
  /**
516
+ * Child keys of a schema walked with `checks: true`: the shape keys, then {@link schemaCheckKeys}.
517
+ */
518
+ const SCHEMA_CHECK_VISITOR_KEYS = [...VISITOR_KEYS.Schema, ...schemaCheckKeys];
519
+ function visitOnce(child, seen, visit) {
520
+ if (seen.first === void 0) {
521
+ seen.first = child;
522
+ return visit(child);
523
+ }
524
+ if (seen.first === child || seen.rest?.has(child)) return false;
525
+ (seen.rest ??= /* @__PURE__ */ new Set()).add(child);
526
+ return visit(child);
527
+ }
528
+ function visitRecord(value, seen, visit) {
529
+ for (const name in value) {
530
+ if (!Object.hasOwn(value, name)) continue;
531
+ const item = value[name];
532
+ if (isNode(item) && visitOnce(item, seen, visit)) return true;
533
+ }
534
+ return false;
535
+ }
536
+ /**
505
537
  * Calls `visit` for each direct child node and stops at the first call that returns `true`.
506
538
  * With `checks: true`, a schema's {@link schemaCheckKeys} fields are walked too, except checks that cannot fail.
507
539
  */
508
540
  function visitChildNodes(node, visit, { checks = false } = {}) {
509
- const keys = checks && node.kind === nodeKinds$1.schema ? [...VISITOR_KEYS[node.kind] ?? [], ...schemaCheckKeys] : VISITOR_KEYS[node.kind];
541
+ const withChecks = checks && node.kind === nodeKinds$1.schema;
542
+ const keys = withChecks ? SCHEMA_CHECK_VISITOR_KEYS : VISITOR_KEYS[node.kind];
510
543
  if (!keys) return false;
511
544
  const record = node;
512
- const seen = /* @__PURE__ */ new Set();
513
- const visitOnce = (child) => !seen.has(child) && (seen.add(child), visit(child));
514
- const visitRecord = (value) => {
515
- for (const name in value) {
516
- if (!Object.hasOwn(value, name)) continue;
517
- const item = value[name];
518
- if (isNode(item) && visitOnce(item)) return true;
519
- }
520
- return false;
521
- };
545
+ let seen;
522
546
  for (const key of keys) {
523
- if (checks && isNeverPrinted({
547
+ const value = record[key];
548
+ if (value === void 0 || value === null) continue;
549
+ if (withChecks && isNeverPrinted({
524
550
  node,
525
551
  key
526
552
  })) continue;
527
- const value = record[key];
553
+ seen ??= {
554
+ first: void 0,
555
+ rest: void 0
556
+ };
528
557
  if (Array.isArray(value)) {
529
- for (const item of value) if (isNode(item) ? visitOnce(item) : isNodeRecord(item) && visitRecord(item)) return true;
558
+ for (const item of value) if (isNode(item) ? visitOnce(item, seen, visit) : isNodeRecord(item) && visitRecord(item, seen, visit)) return true;
530
559
  continue;
531
560
  }
532
561
  if (isNode(value)) {
533
- if (visitOnce(value)) return true;
562
+ if (visitOnce(value, seen, visit)) return true;
534
563
  continue;
535
564
  }
536
- if (isNodeRecord(value) && visitRecord(value)) return true;
565
+ if (isNodeRecord(value) && visitRecord(value, seen, visit)) return true;
537
566
  }
538
567
  return false;
539
568
  }
569
+ const notFound = {
570
+ found: void 0,
571
+ low: Number.POSITIVE_INFINITY
572
+ };
540
573
  /**
541
574
  * Finds the first schema that matches `predicate` in depth-first order. Set `followRefs` to include resolved ref targets.
542
575
  *
@@ -547,28 +580,68 @@ function visitChildNodes(node, visit, { checks = false } = {}) {
547
580
  * })
548
581
  * ```
549
582
  */
550
- function findSchema$1(node, { predicate, followRefs = false }) {
551
- const seen = followRefs ? /* @__PURE__ */ new WeakSet() : void 0;
552
- const walk = (current) => {
553
- if (current.kind === nodeKinds$1.schema) {
554
- if (predicate(current)) return current;
555
- if (seen) {
556
- if (seen.has(current)) return void 0;
557
- seen.add(current);
558
- if (current.type === "ref" && current.schema) {
559
- const found = walk(current.schema);
560
- if (found) return found;
561
- }
562
- }
563
- }
564
- let found;
583
+ function findSchema$1(node, { predicate, followRefs = false, cache }) {
584
+ const state = followRefs ? /* @__PURE__ */ new Map() : void 0;
585
+ const pending = [];
586
+ let order = 0;
587
+ const walkChildren = (current, initial) => {
588
+ let result = initial;
565
589
  visitChildNodes(current, (child) => {
566
- found = walk(child);
567
- return found !== void 0;
590
+ const next = walk(child);
591
+ result = next.found || next.low < result.low ? next : result;
592
+ return next.found !== void 0;
568
593
  });
569
- if (found) return found;
594
+ return result;
595
+ };
596
+ const walkSchema = (current) => {
597
+ const target = state && current.type === "ref" && current.schema ? walk(current.schema) : notFound;
598
+ if (target.found) return target;
599
+ return walkChildren(current, target);
570
600
  };
571
- return walk(node);
601
+ const walk = (current) => {
602
+ if (current.kind !== nodeKinds$1.schema) return walkChildren(current, notFound);
603
+ const cached = cache?.get(current);
604
+ if (cached === null) return notFound;
605
+ if (cached) return {
606
+ found: cached,
607
+ low: Number.POSITIVE_INFINITY
608
+ };
609
+ if (predicate(current)) {
610
+ cache?.set(current, current);
611
+ return {
612
+ found: current,
613
+ low: Number.POSITIVE_INFINITY
614
+ };
615
+ }
616
+ const known = state?.get(current);
617
+ if (known !== void 0) return {
618
+ found: void 0,
619
+ low: known
620
+ };
621
+ const index = order++;
622
+ const start = pending.length;
623
+ state?.set(current, index);
624
+ const result = walkSchema(current);
625
+ if (result.found) {
626
+ cache?.set(current, result.found);
627
+ return result;
628
+ }
629
+ if (!state) {
630
+ cache?.set(current, null);
631
+ return notFound;
632
+ }
633
+ if (result.low < index) {
634
+ state.set(current, result.low);
635
+ pending.push(current);
636
+ return result;
637
+ }
638
+ for (const done of [...pending.splice(start), current]) {
639
+ state.set(done, Number.POSITIVE_INFINITY);
640
+ cache?.set(done, null);
641
+ }
642
+ return notFound;
643
+ };
644
+ return walk(node).found;
572
645
  }
573
646
  __name(findSchema$1, "findSchema");
574
647
  /**
@@ -632,15 +705,16 @@ function transformChildren(node, visitor, recurse) {
632
705
  if (!keys) return node;
633
706
  const record = node;
634
707
  let updates;
635
- const done = /* @__PURE__ */ new Map();
708
+ let done;
636
709
  const transformOnce = (child) => {
710
+ done ??= /* @__PURE__ */ new Map();
637
711
  const next = done.get(child) ?? transformNode(child, visitor, recurse, node);
638
712
  done.set(child, next);
639
713
  return next;
640
714
  };
641
715
  for (const key of keys) {
642
- if (!(key in record)) continue;
643
716
  const value = record[key];
717
+ if (value === void 0 || value === null) continue;
644
718
  if (Array.isArray(value)) {
645
719
  let mapped;
646
720
  for (const [i, item] of value.entries()) {
@@ -697,7 +771,11 @@ __name(collect$1, "collect");
697
771
  function collectInto(node, visitor, recurse, parent, result) {
698
772
  const v = applyVisitor(node, visitor, parent);
699
773
  if (v != null) result.push(v);
700
- for (const child of getChildren(node, recurse)) collectInto(child, visitor, recurse, node, result);
774
+ if (node.kind === nodeKinds$1.schema && !recurse) return;
775
+ visitChildNodes(node, (child) => {
776
+ collectInto(child, visitor, recurse, node, result);
777
+ return false;
778
+ });
701
779
  }
702
780
  // src/utils/refs.ts
703
781
  /**
@@ -706,21 +784,17 @@ function collectInto(node, visitor, recurse, parent, result) {
706
784
  * (RFC 6901), so `a~1b~0c` names the component `a/b~c`.
707
785
  */
708
786
  function refName$1(ref) {
709
- const cached = refNames.get(ref);
710
- if (cached !== void 0) return cached;
711
- const segment = ref.split("/").at(-1) ?? ref;
712
- let decoded = segment;
713
- try {
714
- decoded = decodeURIComponent(segment);
715
- } catch {}
716
- const name = decoded.replaceAll("~1", "/").replaceAll("~0", "~");
717
- if (refNames.size >= memoLimit) refNames.clear();
718
- refNames.set(ref, name);
719
- return name;
787
+ return memo(refNames, ref, () => {
788
+ const segment = ref.split("/").at(-1) ?? ref;
789
+ let decoded = segment;
790
+ try {
791
+ decoded = decodeURIComponent(segment);
792
+ } catch {}
793
+ return decoded.replaceAll("~1", "/").replaceAll("~0", "~");
794
+ });
720
795
  }
721
796
  __name(refName$1, "refName");
722
- const refNames = /* @__PURE__ */ new Map();
723
- const memoLimit = 1e5;
797
+ const refNames = memo.create({ max: 1e5 });
724
798
  /**
725
799
  * Resolves the emitted name of the schema a ref node points at. Prefers `targetName` (set when
726
800
  * the referenced schema was renamed, e.g. to break a collision), then the last segment of `ref`,
@@ -806,11 +880,11 @@ function isBareRef$1(node, keysToOmit) {
806
880
  }
807
881
  __name(isBareRef$1, "isBareRef");
808
882
  // src/utils/schemaGraph.ts
809
- const refEntries = /* @__PURE__ */ new WeakMap();
810
- const checkRefEntries = /* @__PURE__ */ new WeakMap();
811
- const schemaRefs = /* @__PURE__ */ new WeakMap();
812
- const importedRefNames = /* @__PURE__ */ new WeakMap();
813
- const importedCheckRefNames = /* @__PURE__ */ new WeakMap();
883
+ const refEntries = memo.create({ weak: true });
884
+ const checkRefEntries = memo.create({ weak: true });
885
+ const schemaRefs = memo.create({ weak: true });
886
+ const importedRefNames = memo.create({ weak: true });
887
+ const importedCheckRefNames = memo.create({ weak: true });
814
888
  /**
815
889
  * Walks a node's subtree once and lists every ref it holds, in traversal order, as the resolved
816
890
  * name plus whether the ref carries a `$ref` pointer. Memoized per node and per `checks` mode, so
@@ -1032,27 +1106,25 @@ function getBaseURL(server) {
1032
1106
  if (!server || server.variables || server.url.includes("{") || !server.url.includes("://")) return void 0;
1033
1107
  return URL.canParse(server.url) ? server.url : void 0;
1034
1108
  }
1035
- const parametersByNode = /* @__PURE__ */ new WeakMap();
1109
+ const parametersByNode = memo.create({ weak: true });
1036
1110
  /** The operation's parameters by location, with a `querystring` parameter in `query`. A repeated name keeps its first parameter. */
1037
1111
  function groupParameters(node) {
1038
- const cached = parametersByNode.get(node);
1039
- if (cached) return cached;
1040
- const group = (...locations) => {
1041
- const seen = /* @__PURE__ */ new Set();
1042
- return locations.flatMap((location) => node.parameters.filter((param) => {
1043
- if (param.in !== location || seen.has(param.name)) return false;
1044
- seen.add(param.name);
1045
- return true;
1046
- }));
1047
- };
1048
- const groups = {
1049
- path: group("path"),
1050
- query: group("query", "querystring"),
1051
- header: group("header"),
1052
- cookie: group("cookie")
1053
- };
1054
- parametersByNode.set(node, groups);
1055
- return groups;
1112
+ return memo(parametersByNode, node, () => {
1113
+ const group = (...locations) => {
1114
+ const seen = /* @__PURE__ */ new Set();
1115
+ return locations.flatMap((location) => node.parameters.filter((param) => {
1116
+ if (param.in !== location || seen.has(param.name)) return false;
1117
+ seen.add(param.name);
1118
+ return true;
1119
+ }));
1120
+ };
1121
+ return {
1122
+ path: group("path"),
1123
+ query: group("query", "querystring"),
1124
+ header: group("header"),
1125
+ cookie: group("cookie")
1126
+ };
1127
+ });
1056
1128
  }
1057
1129
  // src/nodes/schema.ts
1058
1130
  /**
@@ -1458,12 +1530,11 @@ function combineImports(imports, exports, source) {
1458
1530
  const exportedNames = new Set(exports.flatMap((e) => Array.isArray(e.name) ? e.name : e.name ? [e.name] : []));
1459
1531
  const identifiers = source && imports.length > INDEX_ABOVE_IMPORTS ? new Set(source.match(IDENTIFIER_RUN)) : null;
1460
1532
  const isUsed = (importName) => !source || identifiers?.has(importName) || source.includes(importName) || exportedNames.has(importName);
1461
- const importNameMemo = /* @__PURE__ */ new Map();
1533
+ const importNameMemo = memo.create();
1462
1534
  const canonicalizeName = (n) => {
1463
1535
  if (typeof n === "string") return n;
1464
1536
  const key = `${n.propertyName}:${n.name ?? ""}`;
1465
- if (!importNameMemo.has(key)) importNameMemo.set(key, n);
1466
- return importNameMemo.get(key);
1537
+ return memo(importNameMemo, key, () => n);
1467
1538
  };
1468
1539
  const pathsWithUsedNamedImport = /* @__PURE__ */ new Set();
1469
1540
  for (const node of imports) if (Array.isArray(node.name) && node.name.some((item) => isUsed(importLocalName(item)))) pathsWithUsedNamedImport.add(node.path);