@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@speclynx/apidom-traverse",
3
- "version": "4.10.1",
3
+ "version": "4.12.0",
4
4
  "description": "Traversal and visitor utilities for walking and transforming ApiDOM structures.",
5
5
  "keywords": [
6
6
  "apidom",
@@ -60,12 +60,12 @@
60
60
  "author": "Vladimir Gorej",
61
61
  "license": "Apache-2.0",
62
62
  "dependencies": {
63
- "@babel/runtime-corejs3": "^7.28.4",
64
- "@speclynx/apidom-datamodel": "4.10.1",
65
- "@speclynx/apidom-error": "4.10.1",
66
- "@speclynx/apidom-json-path": "4.10.1",
67
- "@speclynx/apidom-json-pointer": "4.10.1",
68
- "ramda-adjunct": "^6.0.0"
63
+ "@babel/runtime-corejs3": "^8.0.0",
64
+ "@speclynx/apidom-datamodel": "4.12.0",
65
+ "@speclynx/apidom-error": "4.12.0",
66
+ "@speclynx/apidom-json-path": "4.12.0",
67
+ "@speclynx/apidom-json-pointer": "4.12.0",
68
+ "ramda-adjunct": "^6.1.0"
69
69
  },
70
70
  "files": [
71
71
  "src/**/*.mjs",
@@ -77,5 +77,5 @@
77
77
  "README.md",
78
78
  "CHANGELOG.md"
79
79
  ],
80
- "gitHead": "fb6a0a6f3f5da22443865a3ee73503d0a269c511"
80
+ "gitHead": "7d5549ba93000f249ebbe1480ae01d1aaeb2ce15"
81
81
  }
package/src/Path.cjs CHANGED
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
 
3
- exports.__esModule = true;
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
4
6
  exports.Path = void 0;
5
7
  var _apidomDatamodel = require("@speclynx/apidom-datamodel");
6
8
  var _apidomJsonPointer = require("@speclynx/apidom-json-pointer");
@@ -55,6 +57,14 @@ class Path {
55
57
  */
56
58
  inList;
57
59
 
60
+ /**
61
+ * True when this node is a non-descending revisit of an already-visited node
62
+ * (only under skipVisited: 'enter-only'). Children are not traversed.
63
+ * Set on both the enter and the matching leave phase, so it is meaningful
64
+ * in either phase.
65
+ */
66
+ revisited = false;
67
+
58
68
  /**
59
69
  * Internal state for traversal control.
60
70
  */
package/src/Path.mjs CHANGED
@@ -52,6 +52,14 @@ export class Path {
52
52
  */
53
53
  inList;
54
54
 
55
+ /**
56
+ * True when this node is a non-descending revisit of an already-visited node
57
+ * (only under skipVisited: 'enter-only'). Children are not traversed.
58
+ * Set on both the enter and the matching leave phase, so it is meaningful
59
+ * in either phase.
60
+ */
61
+ revisited = false;
62
+
55
63
  /**
56
64
  * Internal state for traversal control.
57
65
  */
package/src/index.cjs CHANGED
@@ -1,37 +1,133 @@
1
1
  "use strict";
2
2
 
3
3
  var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault").default;
4
- exports.__esModule = true;
5
- exports.some = exports.reject = exports.parents = exports.mutateNode = exports.mergeVisitorsAsync = exports.mergeVisitors = exports.isNode = exports.getVisitFn = exports.getNodeType = exports.getNodePrimitiveType = exports.getNodeKeys = exports.forEach = exports.findAtOffset = exports.find = exports.filter = exports.cloneNode = void 0;
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ Object.defineProperty(exports, "Path", {
8
+ enumerable: true,
9
+ get: function () {
10
+ return _Path.Path;
11
+ }
12
+ });
13
+ Object.defineProperty(exports, "cloneNode", {
14
+ enumerable: true,
15
+ get: function () {
16
+ return _visitors.cloneNode;
17
+ }
18
+ });
19
+ Object.defineProperty(exports, "filter", {
20
+ enumerable: true,
21
+ get: function () {
22
+ return _filter.default;
23
+ }
24
+ });
25
+ Object.defineProperty(exports, "find", {
26
+ enumerable: true,
27
+ get: function () {
28
+ return _find.default;
29
+ }
30
+ });
31
+ Object.defineProperty(exports, "findAtOffset", {
32
+ enumerable: true,
33
+ get: function () {
34
+ return _findAtOffset.default;
35
+ }
36
+ });
37
+ Object.defineProperty(exports, "forEach", {
38
+ enumerable: true,
39
+ get: function () {
40
+ return _forEach.default;
41
+ }
42
+ });
43
+ Object.defineProperty(exports, "getNodeKeys", {
44
+ enumerable: true,
45
+ get: function () {
46
+ return _visitors.getNodeKeys;
47
+ }
48
+ });
49
+ Object.defineProperty(exports, "getNodePrimitiveType", {
50
+ enumerable: true,
51
+ get: function () {
52
+ return _visitors.getNodePrimitiveType;
53
+ }
54
+ });
55
+ Object.defineProperty(exports, "getNodeType", {
56
+ enumerable: true,
57
+ get: function () {
58
+ return _visitors.getNodeType;
59
+ }
60
+ });
61
+ Object.defineProperty(exports, "getVisitFn", {
62
+ enumerable: true,
63
+ get: function () {
64
+ return _visitors.getVisitFn;
65
+ }
66
+ });
67
+ Object.defineProperty(exports, "isNode", {
68
+ enumerable: true,
69
+ get: function () {
70
+ return _visitors.isNode;
71
+ }
72
+ });
73
+ Object.defineProperty(exports, "mergeVisitors", {
74
+ enumerable: true,
75
+ get: function () {
76
+ return _visitors.mergeVisitors;
77
+ }
78
+ });
79
+ Object.defineProperty(exports, "mergeVisitorsAsync", {
80
+ enumerable: true,
81
+ get: function () {
82
+ return _visitors.mergeVisitorsAsync;
83
+ }
84
+ });
85
+ Object.defineProperty(exports, "mutateNode", {
86
+ enumerable: true,
87
+ get: function () {
88
+ return _visitors.mutateNode;
89
+ }
90
+ });
91
+ Object.defineProperty(exports, "parents", {
92
+ enumerable: true,
93
+ get: function () {
94
+ return _parents.default;
95
+ }
96
+ });
97
+ Object.defineProperty(exports, "reject", {
98
+ enumerable: true,
99
+ get: function () {
100
+ return _reject.default;
101
+ }
102
+ });
103
+ Object.defineProperty(exports, "some", {
104
+ enumerable: true,
105
+ get: function () {
106
+ return _some.default;
107
+ }
108
+ });
109
+ Object.defineProperty(exports, "traverse", {
110
+ enumerable: true,
111
+ get: function () {
112
+ return _traversal.traverse;
113
+ }
114
+ });
115
+ Object.defineProperty(exports, "traverseAsync", {
116
+ enumerable: true,
117
+ get: function () {
118
+ return _traversal.traverseAsync;
119
+ }
120
+ });
6
121
  var _Path = require("./Path.cjs");
7
- exports.Path = _Path.Path;
8
122
  var _traversal = require("./traversal.cjs");
9
- exports.traverse = _traversal.traverse;
10
- exports.traverseAsync = _traversal.traverseAsync;
11
123
  var _visitors = require("./visitors.cjs");
12
- exports.getNodeType = _visitors.getNodeType;
13
- exports.getNodePrimitiveType = _visitors.getNodePrimitiveType;
14
- exports.isNode = _visitors.isNode;
15
- exports.cloneNode = _visitors.cloneNode;
16
- exports.mutateNode = _visitors.mutateNode;
17
- exports.getNodeKeys = _visitors.getNodeKeys;
18
- exports.getVisitFn = _visitors.getVisitFn;
19
- exports.mergeVisitors = _visitors.mergeVisitors;
20
- exports.mergeVisitorsAsync = _visitors.mergeVisitorsAsync;
21
124
  var _filter = _interopRequireDefault(require("./operations/filter.cjs"));
22
- exports.filter = _filter.default;
23
125
  var _find = _interopRequireDefault(require("./operations/find.cjs"));
24
- exports.find = _find.default;
25
126
  var _some = _interopRequireDefault(require("./operations/some.cjs"));
26
- exports.some = _some.default;
27
127
  var _reject = _interopRequireDefault(require("./operations/reject.cjs"));
28
- exports.reject = _reject.default;
29
128
  var _forEach = _interopRequireDefault(require("./operations/for-each.cjs"));
30
- exports.forEach = _forEach.default;
31
129
  var _parents = _interopRequireDefault(require("./operations/parents.cjs"));
32
- exports.parents = _parents.default;
33
130
  var _findAtOffset = _interopRequireDefault(require("./operations/find-at-offset.cjs"));
34
- exports.findAtOffset = _findAtOffset.default;
35
131
  // Wire up Path.prototype.traverse methods to avoid circular imports
36
132
  _Path.Path.prototype.traverse = function (visitor, options) {
37
133
  return (0, _traversal.traverse)(this.node, visitor, options);
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
 
3
- exports.__esModule = true;
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
4
6
  exports.default = void 0;
5
7
  var _traversal = require("../traversal.cjs");
6
8
  /**
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
 
3
- exports.__esModule = true;
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
4
6
  exports.default = void 0;
5
7
  var _apidomDatamodel = require("@speclynx/apidom-datamodel");
6
8
  var _traversal = require("../traversal.cjs");
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
 
3
- exports.__esModule = true;
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
4
6
  exports.default = void 0;
5
7
  var _traversal = require("../traversal.cjs");
6
8
  /**
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
 
3
- exports.__esModule = true;
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
4
6
  exports.default = void 0;
5
7
  var _traversal = require("../traversal.cjs");
6
8
  /**
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
 
3
- exports.__esModule = true;
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
4
6
  exports.default = void 0;
5
7
  var _traversal = require("../traversal.cjs");
6
8
  /**
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
2
 
3
3
  var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault").default;
4
- exports.__esModule = true;
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
5
7
  exports.default = void 0;
6
8
  var _filter = _interopRequireDefault(require("./filter.cjs"));
7
9
  /**
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
2
 
3
3
  var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault").default;
4
- exports.__esModule = true;
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
5
7
  exports.default = void 0;
6
8
  var _find = _interopRequireDefault(require("./find.cjs"));
7
9
  /**
package/src/traversal.cjs CHANGED
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
 
3
- exports.__esModule = true;
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
4
6
  exports.traverseAsync = exports.traverse = void 0;
5
7
  var _apidomError = require("@speclynx/apidom-error");
6
8
  var _ramdaAdjunct = require("ramda-adjunct");
@@ -12,6 +14,11 @@ var _visitors = require("./visitors.cjs");
12
14
  * SPDX-License-Identifier: MIT
13
15
  */
14
16
 
17
+ /**
18
+ * Controls handling of already-visited nodes during traversal.
19
+ * @public
20
+ */
21
+
15
22
  /**
16
23
  * Options for the traverse function.
17
24
  * @public
@@ -21,6 +28,11 @@ var _visitors = require("./visitors.cjs");
21
28
  // Internal types for generator
22
29
  // =============================================================================
23
30
 
31
+ const normalizeSkipVisited = v => {
32
+ if (v === true) return 'skip';
33
+ if (v === false || v === undefined) return 'never';
34
+ return v;
35
+ };
24
36
  // =============================================================================
25
37
  // Core generator
26
38
  // =============================================================================
@@ -38,7 +50,7 @@ function* traverseGenerator(root, visitor, options) {
38
50
  mutationFn
39
51
  } = options;
40
52
  const keyMapIsFunction = typeof keyMap === 'function';
41
- const visitedNodes = skipVisited ? new WeakSet() : null;
53
+ const visitedNodes = skipVisited !== 'never' ? new WeakSet() : null;
42
54
  let stack;
43
55
  let inArray = Array.isArray(root);
44
56
  let keys = [root];
@@ -53,6 +65,7 @@ function* traverseGenerator(root, visitor, options) {
53
65
  index += 1;
54
66
  const isLeaving = index === keys.length;
55
67
  let key;
68
+ let revisitNoDescend = false;
56
69
  const isEdited = isLeaving && edits.length !== 0;
57
70
  if (isLeaving) {
58
71
  key = ancestors.length === 0 ? undefined : currentPath?.key;
@@ -94,6 +107,7 @@ function* traverseGenerator(root, visitor, options) {
94
107
  edits = stack.edits;
95
108
  const parentInArray = stack.inArray;
96
109
  parentPath = stack.parentPath;
110
+ revisitNoDescend = stack.revisitNoDescend;
97
111
  stack = stack.prev;
98
112
 
99
113
  // Push the edited node to parent's edits for propagation up the tree
@@ -125,15 +139,22 @@ function* traverseGenerator(root, visitor, options) {
125
139
  }
126
140
 
127
141
  // Skip already-visited nodes (handles DAG structures from cloneShallow)
128
- if (skipVisited && !isLeaving) {
142
+ if (skipVisited !== 'never' && !isLeaving) {
129
143
  if (visitedNodes.has(node)) {
130
- continue;
144
+ if (skipVisited === 'enter-only') {
145
+ // fire enter/leave for this occurrence, but don't re-descend
146
+ revisitNoDescend = true;
147
+ } else {
148
+ continue;
149
+ }
150
+ } else {
151
+ visitedNodes.add(node);
131
152
  }
132
- visitedNodes.add(node);
133
153
  }
134
154
 
135
155
  // Always create Path for the current node (needed for parentPath chain)
136
156
  currentPath = new _Path.Path(node, parent, parentPath, key, inArray);
157
+ currentPath.revisited = revisitNoDescend;
137
158
  const visitFn = (0, _visitors.getVisitFn)(visitor, nodeTypeGetter(node), isLeaving);
138
159
  if (visitFn) {
139
160
  // Assign state to visitor
@@ -197,10 +218,13 @@ function* traverseGenerator(root, visitor, options) {
197
218
  keys,
198
219
  edits,
199
220
  parentPath,
221
+ revisitNoDescend,
200
222
  prev: stack
201
223
  };
202
224
  inArray = Array.isArray(node);
203
- if (inArray) {
225
+ if (revisitNoDescend) {
226
+ keys = [];
227
+ } else if (inArray) {
204
228
  keys = node;
205
229
  } else if (keyMapIsFunction) {
206
230
  keys = keyMap(node);
@@ -275,7 +299,7 @@ const traverse = (root, visitor, options = {}) => {
275
299
  nodePredicate: options.nodePredicate ?? _visitors.isNode,
276
300
  nodeCloneFn: options.nodeCloneFn ?? _visitors.cloneNode,
277
301
  detectCycles: options.detectCycles ?? true,
278
- skipVisited: options.skipVisited ?? false,
302
+ skipVisited: normalizeSkipVisited(options.skipVisited),
279
303
  mutable: options.mutable ?? false,
280
304
  mutationFn: options.mutationFn ?? _visitors.mutateNode
281
305
  };
@@ -308,7 +332,7 @@ const traverseAsync = async (root, visitor, options = {}) => {
308
332
  nodePredicate: options.nodePredicate ?? _visitors.isNode,
309
333
  nodeCloneFn: options.nodeCloneFn ?? _visitors.cloneNode,
310
334
  detectCycles: options.detectCycles ?? true,
311
- skipVisited: options.skipVisited ?? false,
335
+ skipVisited: normalizeSkipVisited(options.skipVisited),
312
336
  mutable: options.mutable ?? false,
313
337
  mutationFn: options.mutationFn ?? _visitors.mutateNode
314
338
  };
package/src/traversal.mjs CHANGED
@@ -8,6 +8,10 @@ import { ApiDOMStructuredError } from '@speclynx/apidom-error';
8
8
  import { isPromise } from 'ramda-adjunct';
9
9
  import { Path } from "./Path.mjs";
10
10
  import { getNodeType, isNode, cloneNode, mutateNode, getVisitFn, getNodeKeys } from "./visitors.mjs";
11
+ /**
12
+ * Controls handling of already-visited nodes during traversal.
13
+ * @public
14
+ */
11
15
  /**
12
16
  * Options for the traverse function.
13
17
  * @public
@@ -15,6 +19,12 @@ import { getNodeType, isNode, cloneNode, mutateNode, getVisitFn, getNodeKeys } f
15
19
  // =============================================================================
16
20
  // Internal types for generator
17
21
  // =============================================================================
22
+
23
+ const normalizeSkipVisited = v => {
24
+ if (v === true) return 'skip';
25
+ if (v === false || v === undefined) return 'never';
26
+ return v;
27
+ };
18
28
  // =============================================================================
19
29
  // Core generator
20
30
  // =============================================================================
@@ -32,7 +42,7 @@ function* traverseGenerator(root, visitor, options) {
32
42
  mutationFn
33
43
  } = options;
34
44
  const keyMapIsFunction = typeof keyMap === 'function';
35
- const visitedNodes = skipVisited ? new WeakSet() : null;
45
+ const visitedNodes = skipVisited !== 'never' ? new WeakSet() : null;
36
46
  let stack;
37
47
  let inArray = Array.isArray(root);
38
48
  let keys = [root];
@@ -47,6 +57,7 @@ function* traverseGenerator(root, visitor, options) {
47
57
  index += 1;
48
58
  const isLeaving = index === keys.length;
49
59
  let key;
60
+ let revisitNoDescend = false;
50
61
  const isEdited = isLeaving && edits.length !== 0;
51
62
  if (isLeaving) {
52
63
  key = ancestors.length === 0 ? undefined : currentPath?.key;
@@ -88,6 +99,7 @@ function* traverseGenerator(root, visitor, options) {
88
99
  edits = stack.edits;
89
100
  const parentInArray = stack.inArray;
90
101
  parentPath = stack.parentPath;
102
+ revisitNoDescend = stack.revisitNoDescend;
91
103
  stack = stack.prev;
92
104
 
93
105
  // Push the edited node to parent's edits for propagation up the tree
@@ -119,15 +131,22 @@ function* traverseGenerator(root, visitor, options) {
119
131
  }
120
132
 
121
133
  // Skip already-visited nodes (handles DAG structures from cloneShallow)
122
- if (skipVisited && !isLeaving) {
134
+ if (skipVisited !== 'never' && !isLeaving) {
123
135
  if (visitedNodes.has(node)) {
124
- continue;
136
+ if (skipVisited === 'enter-only') {
137
+ // fire enter/leave for this occurrence, but don't re-descend
138
+ revisitNoDescend = true;
139
+ } else {
140
+ continue;
141
+ }
142
+ } else {
143
+ visitedNodes.add(node);
125
144
  }
126
- visitedNodes.add(node);
127
145
  }
128
146
 
129
147
  // Always create Path for the current node (needed for parentPath chain)
130
148
  currentPath = new Path(node, parent, parentPath, key, inArray);
149
+ currentPath.revisited = revisitNoDescend;
131
150
  const visitFn = getVisitFn(visitor, nodeTypeGetter(node), isLeaving);
132
151
  if (visitFn) {
133
152
  // Assign state to visitor
@@ -191,10 +210,13 @@ function* traverseGenerator(root, visitor, options) {
191
210
  keys,
192
211
  edits,
193
212
  parentPath,
213
+ revisitNoDescend,
194
214
  prev: stack
195
215
  };
196
216
  inArray = Array.isArray(node);
197
- if (inArray) {
217
+ if (revisitNoDescend) {
218
+ keys = [];
219
+ } else if (inArray) {
198
220
  keys = node;
199
221
  } else if (keyMapIsFunction) {
200
222
  keys = keyMap(node);
@@ -269,7 +291,7 @@ export const traverse = (root, visitor, options = {}) => {
269
291
  nodePredicate: options.nodePredicate ?? isNode,
270
292
  nodeCloneFn: options.nodeCloneFn ?? cloneNode,
271
293
  detectCycles: options.detectCycles ?? true,
272
- skipVisited: options.skipVisited ?? false,
294
+ skipVisited: normalizeSkipVisited(options.skipVisited),
273
295
  mutable: options.mutable ?? false,
274
296
  mutationFn: options.mutationFn ?? mutateNode
275
297
  };
@@ -301,7 +323,7 @@ export const traverseAsync = async (root, visitor, options = {}) => {
301
323
  nodePredicate: options.nodePredicate ?? isNode,
302
324
  nodeCloneFn: options.nodeCloneFn ?? cloneNode,
303
325
  detectCycles: options.detectCycles ?? true,
304
- skipVisited: options.skipVisited ?? false,
326
+ skipVisited: normalizeSkipVisited(options.skipVisited),
305
327
  mutable: options.mutable ?? false,
306
328
  mutationFn: options.mutationFn ?? mutateNode
307
329
  };
package/src/visitors.cjs CHANGED
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
 
3
- exports.__esModule = true;
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
4
6
  exports.mutateNode = exports.mergeVisitorsAsync = exports.mergeVisitors = exports.isNode = exports.getVisitFn = exports.getNodeType = exports.getNodePrimitiveType = exports.getNodeKeys = exports.cloneNode = void 0;
5
7
  var _apidomError = require("@speclynx/apidom-error");
6
8
  var _apidomDatamodel = require("@speclynx/apidom-datamodel");
@@ -440,5 +442,7 @@ mergeVisitors[Symbol.for('nodejs.util.promisify.custom')] = mergeVisitorsAsync;
440
442
  * @internal
441
443
  */
442
444
  function createPathProxy(originalPath, currentNode) {
443
- return new _Path.Path(currentNode, originalPath.parent, originalPath.parentPath, originalPath.key, originalPath.inList);
445
+ const proxy = new _Path.Path(currentNode, originalPath.parent, originalPath.parentPath, originalPath.key, originalPath.inList);
446
+ proxy.revisited = originalPath.revisited;
447
+ return proxy;
444
448
  }
package/src/visitors.mjs CHANGED
@@ -427,5 +427,7 @@ mergeVisitors[Symbol.for('nodejs.util.promisify.custom')] = mergeVisitorsAsync;
427
427
  * @internal
428
428
  */
429
429
  function createPathProxy(originalPath, currentNode) {
430
- return new Path(currentNode, originalPath.parent, originalPath.parentPath, originalPath.key, originalPath.inList);
430
+ const proxy = new Path(currentNode, originalPath.parent, originalPath.parentPath, originalPath.key, originalPath.inList);
431
+ proxy.revisited = originalPath.revisited;
432
+ return proxy;
431
433
  }
@@ -196,6 +196,13 @@ export declare class Path<TNode = Element_2> {
196
196
  * Whether this node is inside an array in the parent.
197
197
  */
198
198
  readonly inList: boolean;
199
+ /**
200
+ * True when this node is a non-descending revisit of an already-visited node
201
+ * (only under skipVisited: 'enter-only'). Children are not traversed.
202
+ * Set on both the enter and the matching leave phase, so it is meaningful
203
+ * in either phase.
204
+ */
205
+ revisited: boolean;
199
206
  constructor(node: TNode, parent: TNode | undefined, parentPath: Path<TNode> | null, key: PropertyKey | undefined, inList: boolean);
200
207
  /**
201
208
  * Whether skip() was called on this path.
@@ -321,6 +328,17 @@ export declare class Path<TNode = Element_2> {
321
328
  */
322
329
  export declare const reject: <T extends Element_2>(element: T, predicate: (path: Path<Element_2>) => boolean) => Path<Element_2>[];
323
330
 
331
+ /**
332
+ * SPDX-FileCopyrightText: Copyright (c) GraphQL Contributors
333
+ *
334
+ * SPDX-License-Identifier: MIT
335
+ */
336
+ /**
337
+ * Controls handling of already-visited nodes during traversal.
338
+ * @public
339
+ */
340
+ export declare type SkipVisitedMode = 'never' | 'skip' | 'enter-only';
341
+
324
342
  /**
325
343
  * Tests whether at least one path's element passes the predicate.
326
344
  * @public
@@ -375,11 +393,6 @@ export declare const traverse: <TNode>(root: TNode, visitor: object, options?: T
375
393
  */
376
394
  export declare const traverseAsync: <TNode>(root: TNode, visitor: object, options?: TraverseOptions<TNode>) => Promise<TNode>;
377
395
 
378
- /**
379
- * SPDX-FileCopyrightText: Copyright (c) GraphQL Contributors
380
- *
381
- * SPDX-License-Identifier: MIT
382
- */
383
396
  /**
384
397
  * Options for the traverse function.
385
398
  * @public
@@ -412,13 +425,21 @@ export declare interface TraverseOptions<TNode> {
412
425
  */
413
426
  detectCycles?: boolean;
414
427
  /**
415
- * Whether to skip already-visited nodes. Defaults to false.
416
- * When true, uses a WeakSet to track visited nodes and skips any node
417
- * encountered a second time, regardless of the path taken to reach it.
428
+ * Controls handling of already-visited nodes (by identity, via a WeakSet).
429
+ * Defaults to 'never'.
430
+ * - 'never' : no skipping (default).
431
+ * - 'skip' : skip the node entirely on re-encounter (no enter/leave, no descent).
432
+ * - 'enter-only' : on re-encounter still fire enter/leave, but DO NOT descend into
433
+ * the (already-walked) subtree. Lets visitors observe every
434
+ * occurrence of a shared node while preserving the
435
+ * no-combinatorial-explosion guarantee.
418
436
  * Useful for traversing dereferenced trees that contain shared structure
419
437
  * from cloneShallow (DAG) which would otherwise cause combinatorial explosion.
438
+ *
439
+ * Legacy booleans are still accepted at runtime for backward compatibility:
440
+ * `false` maps to 'never' and `true` maps to 'skip'.
420
441
  */
421
- skipVisited?: boolean;
442
+ skipVisited?: SkipVisitedMode;
422
443
  /**
423
444
  * If true, edits modify the original tree in place.
424
445
  * If false (default), creates a new tree with changes applied.