@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 +14 -0
- package/dist/apidom-traverse.browser.js +40 -8
- package/dist/apidom-traverse.browser.min.js +1 -1
- package/package.json +8 -8
- package/src/Path.cjs +11 -1
- package/src/Path.mjs +8 -0
- package/src/index.cjs +117 -21
- package/src/operations/filter.cjs +3 -1
- package/src/operations/find-at-offset.cjs +3 -1
- package/src/operations/find.cjs +3 -1
- package/src/operations/for-each.cjs +3 -1
- package/src/operations/parents.cjs +3 -1
- package/src/operations/reject.cjs +3 -1
- package/src/operations/some.cjs +3 -1
- package/src/traversal.cjs +32 -8
- package/src/traversal.mjs +29 -7
- package/src/visitors.cjs +6 -2
- package/src/visitors.mjs +3 -1
- package/types/apidom-traverse.d.ts +30 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@speclynx/apidom-traverse",
|
|
3
|
-
"version": "4.
|
|
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": "^
|
|
64
|
-
"@speclynx/apidom-datamodel": "4.
|
|
65
|
-
"@speclynx/apidom-error": "4.
|
|
66
|
-
"@speclynx/apidom-json-path": "4.
|
|
67
|
-
"@speclynx/apidom-json-pointer": "4.
|
|
68
|
-
"ramda-adjunct": "^6.
|
|
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": "
|
|
80
|
+
"gitHead": "7d5549ba93000f249ebbe1480ae01d1aaeb2ce15"
|
|
81
81
|
}
|
package/src/Path.cjs
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
exports
|
|
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
|
|
5
|
-
|
|
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);
|
package/src/operations/find.cjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault").default;
|
|
4
|
-
exports
|
|
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
|
/**
|
package/src/operations/some.cjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault").default;
|
|
4
|
-
exports
|
|
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
|
|
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
|
-
|
|
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 (
|
|
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
|
|
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
|
|
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
|
-
|
|
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 (
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
416
|
-
*
|
|
417
|
-
*
|
|
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?:
|
|
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.
|