You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
typesetting/pitfall/pdfkit/node_modules/ast-types/lib/path-visitor.js

371 lines
11 KiB
JavaScript

var assert = require("assert");
var types = require("./types");
var NodePath = require("./node-path");
var Printable = types.namedTypes.Printable;
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);
// Permanent state.
this._reusableContextStack = [];
this._methodNameTable = computeMethodNameTable(this);
this._shouldVisitComments =
hasOwn.call(this._methodNameTable, "Block") ||
hasOwn.call(this._methodNameTable, "Line");
this.Context = makeContextConstructor(this);
// State reset every time PathVisitor.prototype.visit is called.
this._visiting = false;
this._changeReported = false;
}
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);
var typeNames = Object.keys(supertypeTable);
var typeNameCount = typeNames.length;
for (var i = 0; i < typeNameCount; ++i) {
var typeName = typeNames[i];
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) {
return PathVisitor.fromMethodsObject(methods).visit(node);
};
var PVp = PathVisitor.prototype;
var recursiveVisitWarning = [
"Recursively calling visitor.visit(path) resets visitor state.",
"Try this.visit(path) or this.traverse(path) instead."
].join(" ");
PVp.visit = function() {
assert.ok(!this._visiting, recursiveVisitWarning);
// Private state that needs to be reset before every traversal.
this._visiting = true;
this._changeReported = false;
this._abortRequested = false;
var argc = arguments.length;
var args = new Array(argc)
for (var i = 0; i < argc; ++i) {
args[i] = arguments[i];
}
if (!(args[0] instanceof NodePath)) {
args[0] = new NodePath({ root: args[0] }).get("root");
}
// Called with the same arguments as .visit.
this.reset.apply(this, args);
try {
var root = this.visitWithoutReset(args[0]);
var didNotThrow = true;
} finally {
this._visiting = false;
if (!didNotThrow && this._abortRequested) {
// If this.visitWithoutReset threw an exception and
// this._abortRequested was set to true, return the root of
// the AST instead of letting the exception propagate, so that
// client code does not have to provide a try-catch block to
// intercept the AbortRequest exception. Other kinds of
// exceptions will propagate without being intercepted and
// rethrown by a catch block, so their stacks will accurately
// reflect the original throwing context.
return args[0].value;
}
}
return root;
};
PVp.AbortRequest = function AbortRequest() {};
PVp.abort = function() {
var visitor = this;
visitor._abortRequested = true;
var request = new visitor.AbortRequest();
// If you decide to catch this exception and stop it from propagating,
// make sure to call its cancel method to avoid silencing other
// exceptions that might be thrown later in the traversal.
request.cancel = function() {
visitor._abortRequested = false;
};
throw request;
};
PVp.reset = function(path/*, additional arguments */) {
// Empty stub; may be reassigned or overridden by subclasses.
};
PVp.visitWithoutReset = function(path) {
if (this instanceof this.Context) {
// Since this.Context.prototype === this, there's a chance we
// might accidentally call context.visitWithoutReset. If that
// happens, re-invoke the method against context.visitor.
return this.visitor.visitWithoutReset(path);
}
assert.ok(path instanceof NodePath);
var value = path.value;
var methodName = Printable.check(value) && this._methodNameTable[value.type];
if (methodName) {
var context = this.acquireContext(path);
try {
return context.invokeVisitorMethod(methodName);
} finally {
this.releaseContext(context);
}
} else {
// If there was no visitor method to call, visit the children of
// this node generically.
return 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.visitWithoutReset, visitor);
} else if (!isObject.check(value)) {
// No children to visit.
} else {
var childNames = types.getFieldNames(value);
// The .comments field of the Node type is hidden, so we only
// visit it if the visitor defines visitBlock or visitLine, and
// value.comments is defined.
if (visitor._shouldVisitComments &&
value.comments &&
childNames.indexOf("comments") < 0) {
childNames.push("comments");
}
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.visitWithoutReset(childPaths[i]);
}
}
return path.value;
}
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;
};
PVp.reportChanged = function() {
this._changeReported = true;
};
PVp.wasChangeReported = function() {
return this._changeReported;
};
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
);
var path = this.currentPath;
return path && path.value;
};
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;
return visitChildren(path, PathVisitor.fromMethodsObject(
newVisitor || this.visitor
));
};
sharedContextProtoMethods.visit =
function visit(path, newVisitor) {
assert.ok(this instanceof this.Context);
assert.ok(path instanceof NodePath);
assert.ok(this.currentPath instanceof NodePath);
this.needToCallTraverse = false;
return PathVisitor.fromMethodsObject(
newVisitor || this.visitor
).visitWithoutReset(path);
};
sharedContextProtoMethods.reportChanged = function reportChanged() {
this.visitor.reportChanged();
};
sharedContextProtoMethods.abort = function abort() {
this.needToCallTraverse = false;
this.visitor.abort();
};
module.exports = PathVisitor;