@speclynx/apidom-traverse 4.10.1 → 4.12.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/CHANGELOG.md CHANGED
@@ -3,6 +3,20 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # [4.12.0](https://github.com/speclynx/apidom/compare/v4.11.1...v4.12.0) (2026-06-23)
7
+
8
+ **Note:** Version bump only for package @speclynx/apidom-traverse
9
+
10
+ ## [4.11.1](https://github.com/speclynx/apidom/compare/v4.11.0...v4.11.1) (2026-06-10)
11
+
12
+ **Note:** Version bump only for package @speclynx/apidom-traverse
13
+
14
+ # [4.11.0](https://github.com/speclynx/apidom/compare/v4.10.1...v4.11.0) (2026-06-10)
15
+
16
+ ### Features
17
+
18
+ - **traverse:** skipVisited 'enter-only' mode and path.revisited ([#333](https://github.com/speclynx/apidom/issues/333)) ([fe944df](https://github.com/speclynx/apidom/commit/fe944df0eaab437a65d5761e6a3e14d490d2d3d6))
19
+
6
20
  ## [4.10.1](https://github.com/speclynx/apidom/compare/v4.10.0...v4.10.1) (2026-05-20)
7
21
 
8
22
  **Note:** Version bump only for package @speclynx/apidom-traverse
@@ -76,6 +76,14 @@ class Path {
76
76
  */
77
77
  inList;
78
78
 
79
+ /**
80
+ * True when this node is a non-descending revisit of an already-visited node
81
+ * (only under skipVisited: 'enter-only'). Children are not traversed.
82
+ * Set on both the enter and the matching leave phase, so it is meaningful
83
+ * in either phase.
84
+ */
85
+ revisited = false;
86
+
79
87
  /**
80
88
  * Internal state for traversal control.
81
89
  */
@@ -609,6 +617,11 @@ __webpack_require__.r(__webpack_exports__);
609
617
 
610
618
 
611
619
 
620
+ /**
621
+ * Controls handling of already-visited nodes during traversal.
622
+ * @public
623
+ */
624
+
612
625
  /**
613
626
  * Options for the traverse function.
614
627
  * @public
@@ -618,6 +631,11 @@ __webpack_require__.r(__webpack_exports__);
618
631
  // Internal types for generator
619
632
  // =============================================================================
620
633
 
634
+ const normalizeSkipVisited = v => {
635
+ if (v === true) return 'skip';
636
+ if (v === false || v === undefined) return 'never';
637
+ return v;
638
+ };
621
639
  // =============================================================================
622
640
  // Core generator
623
641
  // =============================================================================
@@ -635,7 +653,7 @@ function* traverseGenerator(root, visitor, options) {
635
653
  mutationFn
636
654
  } = options;
637
655
  const keyMapIsFunction = typeof keyMap === 'function';
638
- const visitedNodes = skipVisited ? new WeakSet() : null;
656
+ const visitedNodes = skipVisited !== 'never' ? new WeakSet() : null;
639
657
  let stack;
640
658
  let inArray = Array.isArray(root);
641
659
  let keys = [root];
@@ -650,6 +668,7 @@ function* traverseGenerator(root, visitor, options) {
650
668
  index += 1;
651
669
  const isLeaving = index === keys.length;
652
670
  let key;
671
+ let revisitNoDescend = false;
653
672
  const isEdited = isLeaving && edits.length !== 0;
654
673
  if (isLeaving) {
655
674
  key = ancestors.length === 0 ? undefined : currentPath?.key;
@@ -691,6 +710,7 @@ function* traverseGenerator(root, visitor, options) {
691
710
  edits = stack.edits;
692
711
  const parentInArray = stack.inArray;
693
712
  parentPath = stack.parentPath;
713
+ revisitNoDescend = stack.revisitNoDescend;
694
714
  stack = stack.prev;
695
715
 
696
716
  // Push the edited node to parent's edits for propagation up the tree
@@ -722,15 +742,22 @@ function* traverseGenerator(root, visitor, options) {
722
742
  }
723
743
 
724
744
  // Skip already-visited nodes (handles DAG structures from cloneShallow)
725
- if (skipVisited && !isLeaving) {
745
+ if (skipVisited !== 'never' && !isLeaving) {
726
746
  if (visitedNodes.has(node)) {
727
- continue;
747
+ if (skipVisited === 'enter-only') {
748
+ // fire enter/leave for this occurrence, but don't re-descend
749
+ revisitNoDescend = true;
750
+ } else {
751
+ continue;
752
+ }
753
+ } else {
754
+ visitedNodes.add(node);
728
755
  }
729
- visitedNodes.add(node);
730
756
  }
731
757
 
732
758
  // Always create Path for the current node (needed for parentPath chain)
733
759
  currentPath = new _Path_ts__WEBPACK_IMPORTED_MODULE_2__.Path(node, parent, parentPath, key, inArray);
760
+ currentPath.revisited = revisitNoDescend;
734
761
  const visitFn = (0,_visitors_ts__WEBPACK_IMPORTED_MODULE_3__.getVisitFn)(visitor, nodeTypeGetter(node), isLeaving);
735
762
  if (visitFn) {
736
763
  // Assign state to visitor
@@ -794,10 +821,13 @@ function* traverseGenerator(root, visitor, options) {
794
821
  keys,
795
822
  edits,
796
823
  parentPath,
824
+ revisitNoDescend,
797
825
  prev: stack
798
826
  };
799
827
  inArray = Array.isArray(node);
800
- if (inArray) {
828
+ if (revisitNoDescend) {
829
+ keys = [];
830
+ } else if (inArray) {
801
831
  keys = node;
802
832
  } else if (keyMapIsFunction) {
803
833
  keys = keyMap(node);
@@ -872,7 +902,7 @@ const traverse = (root, visitor, options = {}) => {
872
902
  nodePredicate: options.nodePredicate ?? _visitors_ts__WEBPACK_IMPORTED_MODULE_3__.isNode,
873
903
  nodeCloneFn: options.nodeCloneFn ?? _visitors_ts__WEBPACK_IMPORTED_MODULE_3__.cloneNode,
874
904
  detectCycles: options.detectCycles ?? true,
875
- skipVisited: options.skipVisited ?? false,
905
+ skipVisited: normalizeSkipVisited(options.skipVisited),
876
906
  mutable: options.mutable ?? false,
877
907
  mutationFn: options.mutationFn ?? _visitors_ts__WEBPACK_IMPORTED_MODULE_3__.mutateNode
878
908
  };
@@ -904,7 +934,7 @@ const traverseAsync = async (root, visitor, options = {}) => {
904
934
  nodePredicate: options.nodePredicate ?? _visitors_ts__WEBPACK_IMPORTED_MODULE_3__.isNode,
905
935
  nodeCloneFn: options.nodeCloneFn ?? _visitors_ts__WEBPACK_IMPORTED_MODULE_3__.cloneNode,
906
936
  detectCycles: options.detectCycles ?? true,
907
- skipVisited: options.skipVisited ?? false,
937
+ skipVisited: normalizeSkipVisited(options.skipVisited),
908
938
  mutable: options.mutable ?? false,
909
939
  mutationFn: options.mutationFn ?? _visitors_ts__WEBPACK_IMPORTED_MODULE_3__.mutateNode
910
940
  };
@@ -1374,7 +1404,9 @@ mergeVisitors[Symbol.for('nodejs.util.promisify.custom')] = mergeVisitorsAsync;
1374
1404
  * @internal
1375
1405
  */
1376
1406
  function createPathProxy(originalPath, currentNode) {
1377
- return new _Path_ts__WEBPACK_IMPORTED_MODULE_4__.Path(currentNode, originalPath.parent, originalPath.parentPath, originalPath.key, originalPath.inList);
1407
+ const proxy = new _Path_ts__WEBPACK_IMPORTED_MODULE_4__.Path(currentNode, originalPath.parent, originalPath.parentPath, originalPath.key, originalPath.inList);
1408
+ proxy.revisited = originalPath.revisited;
1409
+ return proxy;
1378
1410
  }
1379
1411
 
1380
1412
  /***/ },