1 | var types = require("./types");
|
2 | var NodePath = require("./node-path");
|
3 | var Printable = types.namedTypes.Printable;
|
4 | var isArray = types.builtInTypes.array;
|
5 | var isObject = types.builtInTypes.object;
|
6 | var isFunction = types.builtInTypes.function;
|
7 | var hasOwn = Object.prototype.hasOwnProperty;
|
8 | var undefined;
|
9 |
|
10 | function PathVisitor() {
|
11 | if (!(this instanceof PathVisitor)) {
|
12 | throw new Error(
|
13 | "PathVisitor constructor cannot be invoked without 'new'"
|
14 | );
|
15 | }
|
16 |
|
17 |
|
18 | this._reusableContextStack = [];
|
19 |
|
20 | this._methodNameTable = computeMethodNameTable(this);
|
21 | this._shouldVisitComments =
|
22 | hasOwn.call(this._methodNameTable, "Block") ||
|
23 | hasOwn.call(this._methodNameTable, "Line");
|
24 |
|
25 | this.Context = makeContextConstructor(this);
|
26 |
|
27 |
|
28 | this._visiting = false;
|
29 | this._changeReported = false;
|
30 | }
|
31 |
|
32 | function computeMethodNameTable(visitor) {
|
33 | var typeNames = Object.create(null);
|
34 |
|
35 | for (var methodName in visitor) {
|
36 | if (/^visit[A-Z]/.test(methodName)) {
|
37 | typeNames[methodName.slice("visit".length)] = true;
|
38 | }
|
39 | }
|
40 |
|
41 | var supertypeTable = types.computeSupertypeLookupTable(typeNames);
|
42 | var methodNameTable = Object.create(null);
|
43 |
|
44 | var typeNames = Object.keys(supertypeTable);
|
45 | var typeNameCount = typeNames.length;
|
46 | for (var i = 0; i < typeNameCount; ++i) {
|
47 | var typeName = typeNames[i];
|
48 | methodName = "visit" + supertypeTable[typeName];
|
49 | if (isFunction.check(visitor[methodName])) {
|
50 | methodNameTable[typeName] = methodName;
|
51 | }
|
52 | }
|
53 |
|
54 | return methodNameTable;
|
55 | }
|
56 |
|
57 | PathVisitor.fromMethodsObject = function fromMethodsObject(methods) {
|
58 | if (methods instanceof PathVisitor) {
|
59 | return methods;
|
60 | }
|
61 |
|
62 | if (!isObject.check(methods)) {
|
63 |
|
64 | return new PathVisitor;
|
65 | }
|
66 |
|
67 | function Visitor() {
|
68 | if (!(this instanceof Visitor)) {
|
69 | throw new Error(
|
70 | "Visitor constructor cannot be invoked without 'new'"
|
71 | );
|
72 | }
|
73 | PathVisitor.call(this);
|
74 | }
|
75 |
|
76 | var Vp = Visitor.prototype = Object.create(PVp);
|
77 | Vp.constructor = Visitor;
|
78 |
|
79 | extend(Vp, methods);
|
80 | extend(Visitor, PathVisitor);
|
81 |
|
82 | isFunction.assert(Visitor.fromMethodsObject);
|
83 | isFunction.assert(Visitor.visit);
|
84 |
|
85 | return new Visitor;
|
86 | };
|
87 |
|
88 | function extend(target, source) {
|
89 | for (var property in source) {
|
90 | if (hasOwn.call(source, property)) {
|
91 | target[property] = source[property];
|
92 | }
|
93 | }
|
94 |
|
95 | return target;
|
96 | }
|
97 |
|
98 | PathVisitor.visit = function visit(node, methods) {
|
99 | return PathVisitor.fromMethodsObject(methods).visit(node);
|
100 | };
|
101 |
|
102 | var PVp = PathVisitor.prototype;
|
103 |
|
104 | PVp.visit = function() {
|
105 | if (this._visiting) {
|
106 | throw new Error(
|
107 | "Recursively calling visitor.visit(path) resets visitor state. " +
|
108 | "Try this.visit(path) or this.traverse(path) instead."
|
109 | );
|
110 | }
|
111 |
|
112 |
|
113 | this._visiting = true;
|
114 | this._changeReported = false;
|
115 | this._abortRequested = false;
|
116 |
|
117 | var argc = arguments.length;
|
118 | var args = new Array(argc)
|
119 | for (var i = 0; i < argc; ++i) {
|
120 | args[i] = arguments[i];
|
121 | }
|
122 |
|
123 | if (!(args[0] instanceof NodePath)) {
|
124 | args[0] = new NodePath({ root: args[0] }).get("root");
|
125 | }
|
126 |
|
127 |
|
128 | this.reset.apply(this, args);
|
129 |
|
130 | try {
|
131 | var root = this.visitWithoutReset(args[0]);
|
132 | var didNotThrow = true;
|
133 | } finally {
|
134 | this._visiting = false;
|
135 |
|
136 | if (!didNotThrow && this._abortRequested) {
|
137 |
|
138 |
|
139 |
|
140 |
|
141 |
|
142 |
|
143 |
|
144 |
|
145 | return args[0].value;
|
146 | }
|
147 | }
|
148 |
|
149 | return root;
|
150 | };
|
151 |
|
152 | PVp.AbortRequest = function AbortRequest() {};
|
153 | PVp.abort = function() {
|
154 | var visitor = this;
|
155 | visitor._abortRequested = true;
|
156 | var request = new visitor.AbortRequest();
|
157 |
|
158 |
|
159 |
|
160 |
|
161 | request.cancel = function() {
|
162 | visitor._abortRequested = false;
|
163 | };
|
164 |
|
165 | throw request;
|
166 | };
|
167 |
|
168 | PVp.reset = function(path/*, additional arguments */) {
|
169 |
|
170 | };
|
171 |
|
172 | PVp.visitWithoutReset = function(path) {
|
173 | if (this instanceof this.Context) {
|
174 |
|
175 |
|
176 |
|
177 | return this.visitor.visitWithoutReset(path);
|
178 | }
|
179 |
|
180 | if (!(path instanceof NodePath)) {
|
181 | throw new Error("");
|
182 | }
|
183 |
|
184 | var value = path.value;
|
185 |
|
186 | var methodName = value &&
|
187 | typeof value === "object" &&
|
188 | typeof value.type === "string" &&
|
189 | this._methodNameTable[value.type];
|
190 |
|
191 | if (methodName) {
|
192 | var context = this.acquireContext(path);
|
193 | try {
|
194 | return context.invokeVisitorMethod(methodName);
|
195 | } finally {
|
196 | this.releaseContext(context);
|
197 | }
|
198 |
|
199 | } else {
|
200 |
|
201 |
|
202 | return visitChildren(path, this);
|
203 | }
|
204 | };
|
205 |
|
206 | function visitChildren(path, visitor) {
|
207 | if (!(path instanceof NodePath)) {
|
208 | throw new Error("");
|
209 | }
|
210 | if (!(visitor instanceof PathVisitor)) {
|
211 | throw new Error("");
|
212 | }
|
213 |
|
214 | var value = path.value;
|
215 |
|
216 | if (isArray.check(value)) {
|
217 | path.each(visitor.visitWithoutReset, visitor);
|
218 | } else if (!isObject.check(value)) {
|
219 |
|
220 | } else {
|
221 | var childNames = types.getFieldNames(value);
|
222 |
|
223 |
|
224 |
|
225 |
|
226 | if (visitor._shouldVisitComments &&
|
227 | value.comments &&
|
228 | childNames.indexOf("comments") < 0) {
|
229 | childNames.push("comments");
|
230 | }
|
231 |
|
232 | var childCount = childNames.length;
|
233 | var childPaths = [];
|
234 |
|
235 | for (var i = 0; i < childCount; ++i) {
|
236 | var childName = childNames[i];
|
237 | if (!hasOwn.call(value, childName)) {
|
238 | value[childName] = types.getFieldValue(value, childName);
|
239 | }
|
240 | childPaths.push(path.get(childName));
|
241 | }
|
242 |
|
243 | for (var i = 0; i < childCount; ++i) {
|
244 | visitor.visitWithoutReset(childPaths[i]);
|
245 | }
|
246 | }
|
247 |
|
248 | return path.value;
|
249 | }
|
250 |
|
251 | PVp.acquireContext = function(path) {
|
252 | if (this._reusableContextStack.length === 0) {
|
253 | return new this.Context(path);
|
254 | }
|
255 | return this._reusableContextStack.pop().reset(path);
|
256 | };
|
257 |
|
258 | PVp.releaseContext = function(context) {
|
259 | if (!(context instanceof this.Context)) {
|
260 | throw new Error("");
|
261 | }
|
262 | this._reusableContextStack.push(context);
|
263 | context.currentPath = null;
|
264 | };
|
265 |
|
266 | PVp.reportChanged = function() {
|
267 | this._changeReported = true;
|
268 | };
|
269 |
|
270 | PVp.wasChangeReported = function() {
|
271 | return this._changeReported;
|
272 | };
|
273 |
|
274 | function makeContextConstructor(visitor) {
|
275 | function Context(path) {
|
276 | if (!(this instanceof Context)) {
|
277 | throw new Error("");
|
278 | }
|
279 | if (!(this instanceof PathVisitor)) {
|
280 | throw new Error("");
|
281 | }
|
282 | if (!(path instanceof NodePath)) {
|
283 | throw new Error("");
|
284 | }
|
285 |
|
286 | Object.defineProperty(this, "visitor", {
|
287 | value: visitor,
|
288 | writable: false,
|
289 | enumerable: true,
|
290 | configurable: false
|
291 | });
|
292 |
|
293 | this.currentPath = path;
|
294 | this.needToCallTraverse = true;
|
295 |
|
296 | Object.seal(this);
|
297 | }
|
298 |
|
299 | if (!(visitor instanceof PathVisitor)) {
|
300 | throw new Error("");
|
301 | }
|
302 |
|
303 |
|
304 |
|
305 | var Cp = Context.prototype = Object.create(visitor);
|
306 |
|
307 | Cp.constructor = Context;
|
308 | extend(Cp, sharedContextProtoMethods);
|
309 |
|
310 | return Context;
|
311 | }
|
312 |
|
313 |
|
314 |
|
315 |
|
316 | var sharedContextProtoMethods = Object.create(null);
|
317 |
|
318 | sharedContextProtoMethods.reset =
|
319 | function reset(path) {
|
320 | if (!(this instanceof this.Context)) {
|
321 | throw new Error("");
|
322 | }
|
323 | if (!(path instanceof NodePath)) {
|
324 | throw new Error("");
|
325 | }
|
326 |
|
327 | this.currentPath = path;
|
328 | this.needToCallTraverse = true;
|
329 |
|
330 | return this;
|
331 | };
|
332 |
|
333 | sharedContextProtoMethods.invokeVisitorMethod =
|
334 | function invokeVisitorMethod(methodName) {
|
335 | if (!(this instanceof this.Context)) {
|
336 | throw new Error("");
|
337 | }
|
338 | if (!(this.currentPath instanceof NodePath)) {
|
339 | throw new Error("");
|
340 | }
|
341 |
|
342 | var result = this.visitor[methodName].call(this, this.currentPath);
|
343 |
|
344 | if (result === false) {
|
345 |
|
346 |
|
347 |
|
348 | this.needToCallTraverse = false;
|
349 |
|
350 | } else if (result !== undefined) {
|
351 |
|
352 |
|
353 | this.currentPath = this.currentPath.replace(result)[0];
|
354 |
|
355 | if (this.needToCallTraverse) {
|
356 |
|
357 |
|
358 | this.traverse(this.currentPath);
|
359 | }
|
360 | }
|
361 |
|
362 | if (this.needToCallTraverse !== false) {
|
363 | throw new Error(
|
364 | "Must either call this.traverse or return false in " + methodName
|
365 | );
|
366 | }
|
367 |
|
368 | var path = this.currentPath;
|
369 | return path && path.value;
|
370 | };
|
371 |
|
372 | sharedContextProtoMethods.traverse =
|
373 | function traverse(path, newVisitor) {
|
374 | if (!(this instanceof this.Context)) {
|
375 | throw new Error("");
|
376 | }
|
377 | if (!(path instanceof NodePath)) {
|
378 | throw new Error("");
|
379 | }
|
380 | if (!(this.currentPath instanceof NodePath)) {
|
381 | throw new Error("");
|
382 | }
|
383 |
|
384 | this.needToCallTraverse = false;
|
385 |
|
386 | return visitChildren(path, PathVisitor.fromMethodsObject(
|
387 | newVisitor || this.visitor
|
388 | ));
|
389 | };
|
390 |
|
391 | sharedContextProtoMethods.visit =
|
392 | function visit(path, newVisitor) {
|
393 | if (!(this instanceof this.Context)) {
|
394 | throw new Error("");
|
395 | }
|
396 | if (!(path instanceof NodePath)) {
|
397 | throw new Error("");
|
398 | }
|
399 | if (!(this.currentPath instanceof NodePath)) {
|
400 | throw new Error("");
|
401 | }
|
402 |
|
403 | this.needToCallTraverse = false;
|
404 |
|
405 | return PathVisitor.fromMethodsObject(
|
406 | newVisitor || this.visitor
|
407 | ).visitWithoutReset(path);
|
408 | };
|
409 |
|
410 | sharedContextProtoMethods.reportChanged = function reportChanged() {
|
411 | this.visitor.reportChanged();
|
412 | };
|
413 |
|
414 | sharedContextProtoMethods.abort = function abort() {
|
415 | this.needToCallTraverse = false;
|
416 | this.visitor.abort();
|
417 | };
|
418 |
|
419 | module.exports = PathVisitor;
|