var assert = require(“assert”); var types = require(“./types”); var NodePath = require(“./node-path”); var Node = types.namedTypes.Node; var isArray = types.builtInTypes.array; var isObject = types.builtInTypes.object; var isFunction = types.builtInTypes.function; var hasOwn = Object.prototype.hasOwnProperty; var undefined;
function PathVisitor() {
assert.ok(this instanceof PathVisitor); this._reusableContextStack = []; this._methodNameTable = computeMethodNameTable(this); this.Context = makeContextConstructor(this);
}
function computeMethodNameTable(visitor) {
var typeNames = Object.create(null); for (var methodName in visitor) { if (/^visit[A-Z]/.test(methodName)) { typeNames[methodName.slice("visit".length)] = true; } } var supertypeTable = types.computeSupertypeLookupTable(typeNames); var methodNameTable = Object.create(null); for (var typeName in supertypeTable) { if (hasOwn.call(supertypeTable, typeName)) { methodName = "visit" + supertypeTable[typeName]; if (isFunction.check(visitor[methodName])) { methodNameTable[typeName] = methodName; } } } return methodNameTable;
}
PathVisitor.fromMethodsObject = function fromMethodsObject(methods) {
if (methods instanceof PathVisitor) { return methods; } if (!isObject.check(methods)) { // An empty visitor? return new PathVisitor; } function Visitor() { assert.ok(this instanceof Visitor); PathVisitor.call(this); } var Vp = Visitor.prototype = Object.create(PVp); Vp.constructor = Visitor; extend(Vp, methods); extend(Visitor, PathVisitor); isFunction.assert(Visitor.fromMethodsObject); isFunction.assert(Visitor.visit); return new Visitor;
};
function extend(target, source) {
for (var property in source) { if (hasOwn.call(source, property)) { target[property] = source[property]; } } return target;
}
PathVisitor.visit = function visit(node, methods) {
var visitor = PathVisitor.fromMethodsObject(methods); if (node instanceof NodePath) { visitor.visit(node); return node.value; } var rootPath = new NodePath({ root: node }); visitor.visit(rootPath.get("root")); return rootPath.value.root;
};
var PVp = PathVisitor.prototype;
PVp.visit = function(path) {
if (this instanceof this.Context) { // If we somehow end up calling context.visit, then we need to // re-invoke the .visit method against context.visitor. return this.visitor.visit(path); } assert.ok(path instanceof NodePath); var value = path.value; var methodName = Node.check(value) && this._methodNameTable[value.type]; if (methodName) { var context = this.acquireContext(path); try { context.invokeVisitorMethod(methodName); } finally { this.releaseContext(context); } } else { // If there was no visitor method to call, visit the children of // this node generically. visitChildren(path, this); }
};
function visitChildren(path, visitor) {
assert.ok(path instanceof NodePath); assert.ok(visitor instanceof PathVisitor); var value = path.value; if (isArray.check(value)) { path.each(visitor.visit, visitor); } else if (!isObject.check(value)) { // No children to visit. } else { var childNames = types.getFieldNames(value); var childCount = childNames.length; var childPaths = []; for (var i = 0; i < childCount; ++i) { var childName = childNames[i]; if (!hasOwn.call(value, childName)) { value[childName] = types.getFieldValue(value, childName); } childPaths.push(path.get(childName)); } for (var i = 0; i < childCount; ++i) { visitor.visit(childPaths[i]); } }
}
PVp.acquireContext = function(path) {
if (this._reusableContextStack.length === 0) { return new this.Context(path); } return this._reusableContextStack.pop().reset(path);
};
PVp.releaseContext = function(context) {
assert.ok(context instanceof this.Context); this._reusableContextStack.push(context); context.currentPath = null;
};
function makeContextConstructor(visitor) {
function Context(path) { assert.ok(this instanceof Context); assert.ok(this instanceof PathVisitor); assert.ok(path instanceof NodePath); Object.defineProperty(this, "visitor", { value: visitor, writable: false, enumerable: true, configurable: false }); this.currentPath = path; this.needToCallTraverse = true; Object.seal(this); } assert.ok(visitor instanceof PathVisitor); // Note that the visitor object is the prototype of Context.prototype, // so all visitor methods are inherited by context objects. var Cp = Context.prototype = Object.create(visitor); Cp.constructor = Context; extend(Cp, sharedContextProtoMethods); return Context;
}
// Every PathVisitor has a different this.Context constructor and // this.Context.prototype object, but those prototypes can all use the // same reset, invokeVisitorMethod, and traverse function objects. var sharedContextProtoMethods = Object.create(null);
sharedContextProtoMethods.reset = function reset(path) {
assert.ok(this instanceof this.Context); assert.ok(path instanceof NodePath); this.currentPath = path; this.needToCallTraverse = true; return this;
};
sharedContextProtoMethods.invokeVisitorMethod = function invokeVisitorMethod(methodName) {
assert.ok(this instanceof this.Context); assert.ok(this.currentPath instanceof NodePath); var result = this.visitor[methodName].call(this, this.currentPath); if (result === false) { // Visitor methods return false to indicate that they have handled // their own traversal needs, and we should not complain if // this.needToCallTraverse is still true. this.needToCallTraverse = false; } else if (result !== undefined) { // Any other non-undefined value returned from the visitor method // is interpreted as a replacement value. this.currentPath = this.currentPath.replace(result)[0]; if (this.needToCallTraverse) { // If this.traverse still hasn't been called, visit the // children of the replacement node. this.traverse(this.currentPath); } } assert.strictEqual( this.needToCallTraverse, false, "Must either call this.traverse or return false in " + methodName );
};
sharedContextProtoMethods.traverse = function traverse(path, newVisitor) {
assert.ok(this instanceof this.Context); assert.ok(path instanceof NodePath); assert.ok(this.currentPath instanceof NodePath); this.needToCallTraverse = false; visitChildren(path, PathVisitor.fromMethodsObject( newVisitor || this.visitor ));
};
module.exports = PathVisitor;