@meteorjs/reify
Version:
Enable ECMAScript 2015 modules in Node today. No caveats. Full stop.
169 lines (140 loc) • 4.19 kB
JavaScript
;
// A simplified version of the AST traversal abstraction used by Recast:
// https://github.com/benjamn/recast/blob/master/lib/fast-path.js
const assert = require("assert");
const utils = require("./utils.js");
const periscopic = require('periscopic');
const brandKey = "reify-fast-path";
const brand = typeof Symbol === "function"
? Symbol.for(brandKey)
: brandKey;
class FastPath {
constructor(ast) {
assert.notStrictEqual(ast, null);
assert.strictEqual(typeof ast, "object");
this.stack = [ast];
this._scope = null;
}
static isInstance(value) {
return value !== null &&
typeof value === "object" &&
value[brand] === true;
}
static from(value) {
return this.isInstance(value) ? value : new this(value);
}
// Temporarily push properties named by string arguments given after the
// callback function onto this.stack, then call the callback with a
// reference to this (modified) FastPath object. Note that the stack will
// be restored to its original state after the callback is finished, so it
// is probably a mistake to retain a reference to the path.
call(callback/*, name1, name2, ... */) {
const s = this.stack;
const origLen = s.length;
const argCount = arguments.length;
let value = s[origLen - 1];
for (let i = 1; i < argCount; ++i) {
const name = arguments[i];
value = value[name];
s.push(name, value);
}
const result = callback(this);
s.length = origLen;
return result;
}
// Similar to FastPath.prototype.call, except that the value obtained by
// accessing this.getValue()[name1][name2]... should be array-like. The
// callback will be called with a reference to this path object for each
// element of the array.
each(callback/*, name1, name2, ... */) {
const s = this.stack;
const origLen = s.length;
const argCount = arguments.length;
let value = s[origLen - 1];
for (let i = 1; i < argCount; ++i) {
const name = arguments[i];
value = value[name];
s.push(name, value);
}
for (let i = 0; i < value.length; ++i) {
s.push(i, value[i]);
// If the callback needs to know the value of i, call path.getName(),
// assuming path is the parameter name.
callback(this);
s.length -= 2;
}
s.length = origLen;
}
getContainer() {
return getStackAt(this, 3);
}
getName() {
// The name of the current property is always the penultimate element of
// this.stack, and always a String. Since the name is always a string,
// null is a safe sentinel value to return if we do not know the name of
// the (root) value.
return getStackAt(this, 2);
}
getNode() {
return getNodeAt(this, 0);
}
getParentNode(depth) {
depth = depth || 1;
return getNodeAt(this, depth);
}
getValue() {
// The value of the current property is always the last element of
// this.stack.
return getStackAt(this, 1);
}
getScope() {
if (!this._scope) {
this._scope = periscopic.analyze(this.stack[0]);
}
let node;
let pos = 0;
while (node = getNodeAt(this, pos++)) {
let scope = this._scope.map.get(node);
if (scope) {
return scope;
}
}
}
replace(newValue) {
const s = this.stack;
const len = s.length;
const oldValue = this.getValue();
if (len > 2) {
const parent = s[len - 3];
const name = this.getName();
parent[name] = s[len - 1] = newValue;
}
return oldValue;
}
valueIsNode() {
return utils.isNodeLike(this.getValue());
}
};
Object.setPrototypeOf(FastPath.prototype, null);
Object.defineProperty(FastPath.prototype, brand, {
value: true,
enumerable: false,
writable: false,
configurable: false
});
function getNodeAt(path, pos) {
const s = path.stack;
for (let i = s.length - 1; i >= 0; i -= 2) {
const value = s[i];
if (utils.isNodeLike(value) && --pos < 0) {
return value;
}
}
return null;
}
function getStackAt(path, pos) {
const s = path.stack;
const len = s.length;
return len < pos ? null : s[len - pos];
}
module.exports = FastPath;