forked from babel/babel
/
context.js
163 lines (132 loc) · 3.86 KB
/
context.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
import NodePath from "./path";
import * as t from "@babel/types";
const testing = process.env.NODE_ENV === "test";
export default class TraversalContext {
constructor(scope, opts, state, parentPath) {
this.parentPath = parentPath;
this.scope = scope;
this.state = state;
this.opts = opts;
}
declare parentPath: NodePath;
declare scope;
declare state;
declare opts;
queue: ?Array<NodePath> = null;
/**
* This method does a simple check to determine whether or not we really need to attempt
* visit a node. This will prevent us from constructing a NodePath.
*/
shouldVisit(node): boolean {
const opts = this.opts;
if (opts.enter || opts.exit) return true;
// check if we have a visitor for this node
if (opts[node.type]) return true;
// check if we're going to traverse into this node
const keys: ?Array<string> = t.VISITOR_KEYS[node.type];
if (!keys?.length) return false;
// we need to traverse into this node so ensure that it has children to traverse into!
for (const key of keys) {
if (node[key]) return true;
}
return false;
}
create(node, obj, key, listKey): NodePath {
// We don't need to `.setContext()` here, since `.visitQueue()` already
// calls `.pushContext`.
return NodePath.get({
parentPath: this.parentPath,
parent: node,
container: obj,
key: key,
listKey,
});
}
maybeQueue(path, notPriority?: boolean) {
if (this.trap) {
throw new Error("Infinite cycle detected");
}
if (this.queue) {
if (notPriority) {
this.queue.push(path);
} else {
this.priorityQueue.push(path);
}
}
}
visitMultiple(container, parent, listKey) {
// nothing to traverse!
if (container.length === 0) return false;
const queue = [];
// build up initial queue
for (let key = 0; key < container.length; key++) {
const node = container[key];
if (node && this.shouldVisit(node)) {
queue.push(this.create(parent, container, key, listKey));
}
}
return this.visitQueue(queue);
}
visitSingle(node, key): boolean {
if (this.shouldVisit(node[key])) {
return this.visitQueue([this.create(node, node, key)]);
} else {
return false;
}
}
visitQueue(queue: Array<NodePath>) {
// set queue
this.queue = queue;
this.priorityQueue = [];
const visited = new WeakSet();
let stop = false;
// visit the queue
for (const path of queue) {
path.resync();
if (
path.contexts.length === 0 ||
path.contexts[path.contexts.length - 1] !== this
) {
// The context might already have been pushed when this path was inserted and queued.
// If we always re-pushed here, we could get duplicates and risk leaving contexts
// on the stack after the traversal has completed, which could break things.
path.pushContext(this);
}
// this path no longer belongs to the tree
if (path.key === null) continue;
if (testing && queue.length >= 10_000) {
this.trap = true;
}
// ensure we don't visit the same node twice
const { node } = path;
if (visited.has(node)) continue;
if (node) visited.add(node);
if (path.visit()) {
stop = true;
break;
}
if (this.priorityQueue.length) {
stop = this.visitQueue(this.priorityQueue);
this.priorityQueue = [];
this.queue = queue;
if (stop) break;
}
}
// clear queue
for (const path of queue) {
path.popContext();
}
// clear queue
this.queue = null;
return stop;
}
visit(node, key) {
const nodes = node[key];
if (!nodes) return false;
if (Array.isArray(nodes)) {
return this.visitMultiple(nodes, node, key);
} else {
return this.visitSingle(node, key);
}
}
}