add support for resyncing container on changes

This commit is contained in:
Sebastian McKenzie 2015-05-25 03:32:25 +01:00
parent a533042503
commit 8e2b743f7e
10 changed files with 125 additions and 32 deletions

View File

@ -462,7 +462,12 @@ export default class File {
}
_addAst(ast) {
this.path = NodePath.get(null, ast, ast, "program", this).setContext(null, this);
this.path = NodePath.get({
parentPath: null,
parent: ast,
container: ast,
key: "program"
}).setContext(null, this);
this.scope = this.path.scope;
this.ast = ast;
}

View File

@ -14,15 +14,21 @@ export default class TraversalContext {
return !!(this.opts.enter || this.opts.exit || this.opts[node.type] || (keys && keys.length));
}
create(node, obj, key) {
var path = NodePath.get(this.parentPath, node, obj, key);
create(node, obj, key, containerKey) {
var path = NodePath.get({
parentPath: this.parentPath,
parent: node,
container: obj,
key: key,
containerKey: containerKey
});
path.unshiftContext(this);
return path;
}
visitMultiple(nodes, node) {
visitMultiple(container, parent, containerKey) {
// nothing to traverse!
if (nodes.length === 0) return false;
if (container.length === 0) return false;
var visited = [];
@ -30,16 +36,16 @@ export default class TraversalContext {
var stop = false;
// build up initial queue
for (let i = 0; i < nodes.length; i++) {
var self = nodes[i];
for (let key = 0; key < container.length; key++) {
var self = container[key];
if (self && this.shouldVisit(self)) {
queue.push(this.create(node, nodes, i));
queue.push(this.create(parent, container, key, containerKey));
}
}
// visit the queue
for (let path of (queue: Array)) {
path.update();
path.resync();
if (visited.indexOf(path.node) >= 0) continue;
visited.push(path.node);

View File

@ -108,6 +108,7 @@ export function setScope(file?) {
var target = this.context || this.parentPath;
this.scope = NodePath.getScope(this, target && target.scope, file);
if (this.scope) this.scope.init();
}
/**
@ -137,7 +138,14 @@ export function setContext(context, file) {
* Description
*/
export function update() {
export function resync() {
if (this.removed) return;
this._resyncContainer();
this._resyncKey();
}
export function _resyncKey() {
if (this.node === this.container[this.key]) return;
// grrr, path key is out of sync. this is likely due to a modification to the AST
@ -160,6 +168,17 @@ export function update() {
throw new Error(messages.get("lostTrackNodePath"));
}
export function _resyncContainer() {
var containerKey = this.containerKey;
var parentPath = this.parentPath;
if (!containerKey || !parentPath) return;
var newContainer = parentPath.node[containerKey];
if (!newContainer || this.container === newContainer) return;
this.container = newContainer;
}
/**
* Description
*/

View File

@ -55,7 +55,13 @@ export function getCompletionRecords(): Array<NodePath> {
*/
export function getSibling(key) {
return NodePath.get(this.parentPath, this.parent, this.container, key, this.file);
return NodePath.get({
parentPath: this.parentPath,
parent: this.parent,
container: this.container,
containerKey: this.containerKey,
key: key
});
}
/**
@ -82,10 +88,21 @@ export function _getKey(key) {
if (Array.isArray(container)) {
// requested a container so give them all the paths
return container.map((_, i) => {
return NodePath.get(this, node, container, i).setContext();
return NodePath.get({
containerKey: key,
parentPath: this,
parent: node,
container: container,
key: i
}).setContext();
});
} else {
return NodePath.get(this, node, node, key).setContext();
return NodePath.get({
parentPath: this,
parent: node,
container: node,
key: key
}).setContext();
}
}

View File

@ -5,18 +5,19 @@ import Scope from "../scope";
import * as t from "../../types";
export default class NodePath {
constructor(parent, container) {
this.container = container;
this.contexts = [];
this.parent = parent;
this.data = {};
constructor(parent, container, containerKey) {
this.containerKey = containerKey;
this.container = container;
this.contexts = [];
this.parent = parent;
this.data = {};
}
/**
* Description
*/
static get(parentPath: NodePath, parent, container, key) {
static get({ parentPath, parent, container, containerKey, key }) {
var targetNode = container[key];
var paths = container._paths = container._paths || [];
var path;
@ -30,7 +31,7 @@ export default class NodePath {
}
if (!path) {
path = new NodePath(parent, container);
path = new NodePath(parent, container, containerKey);
paths.push(path);
}

View File

@ -40,11 +40,17 @@ export function _containerInsert(from, nodes) {
this.container.splice(to, 0, node);
if (this.context) {
var path = this.context.create(this.parent, this.container, to);
var path = this.context.create(this.parent, this.container, to, this.containerKey);
paths.push(path);
this.queueNode(path);
} else {
paths.push(NodePath.get(this, node, this.container, to));
paths.push(NodePath.get({
parentPath: this,
parent: node,
container: this.container,
containerKey: this.containerKey,
key: to
}));
}
}
@ -145,7 +151,13 @@ export function unshiftContainer(containerKey, nodes) {
// doesn't matter, our nodes will be inserted anyway
var container = this.node[containerKey];
var path = NodePath.get(this, this.node, container, 0);
var path = NodePath.get({
parentPath: this,
parent: this.node,
container: container,
containerKey,
key: 0
});
return path.insertBefore(nodes);
}
@ -162,7 +174,13 @@ export function pushContainer(containerKey, nodes) {
var container = this.node[containerKey];
var i = container.length;
var path = NodePath.get(this, this.node, container, i);
var path = NodePath.get({
parentPath: this,
parent: this.node,
container: container,
containerKey,
key: i
});
return path.replaceWith(nodes, true);
}

View File

@ -5,18 +5,23 @@ import * as removalHooks from "./lib/removal-hooks";
*/
export function remove() {
if (this._callRemovalHooks("pre")) return;
this.resync();
if (this._callRemovalHooks("pre")) {
this._markRemoved();
return;
}
this.shareCommentsWithSiblings();
this._remove();
this.removed = true;
this._markRemoved();
this._callRemovalHooks("post");
}
export function _callRemovalHooks(position) {
for (var fn of (removalHooks[position]: Array)) {
if (fn(this, this.parentPath)) return;
if (fn(this, this.parentPath)) return true;
}
}
@ -27,7 +32,11 @@ export function _remove() {
} else {
this.container[this.key] = null;
}
}
export function _markRemoved() {
this.node = null;
this.removed = true;
}
/**

View File

@ -35,6 +35,8 @@ var hoistVariablesVisitor = {
*/
export function replaceWithMultiple(nodes: Array<Object>) {
this.resync();
nodes = this._verifyNodeList(nodes);
t.inheritsComments(nodes[0], this.node);
this.node = this.container[this.key] = null;
@ -47,6 +49,8 @@ export function replaceWithMultiple(nodes: Array<Object>) {
*/
export function replaceWithSourceString(replacement) {
this.resync();
try {
replacement = `(${replacement})`;
replacement = parse(replacement);
@ -69,6 +73,8 @@ export function replaceWithSourceString(replacement) {
*/
export function replaceWith(replacement, whateverAllowed) {
this.resync();
if (this.removed) {
throw new Error("You can't replace this node, we've already removed it");
}
@ -129,6 +135,8 @@ export function replaceWith(replacement, whateverAllowed) {
*/
export function replaceExpressionWithStatements(nodes: Array) {
this.resync();
var toSequenceExpression = t.toSequenceExpression(nodes, this.scope);
if (toSequenceExpression) {
@ -167,6 +175,8 @@ export function replaceExpressionWithStatements(nodes: Array) {
*/
export function replaceInline(nodes) {
this.resync();
if (Array.isArray(nodes)) {
if (Array.isArray(this.container)) {
nodes = this._verifyNodeList(nodes);

View File

@ -146,8 +146,6 @@ export default class Scope {
this.parentBlock = path.parent;
this.block = path.node;
this.path = path;
this.crawl();
}
static globals = flatten([globals.builtin, globals.browser, globals.node].map(Object.keys));
@ -552,11 +550,21 @@ export default class Scope {
if (t.isIdentifier(node)) {
var bindingInfo = this.getBinding(node.name);
return bindingInfo && bindingInfo.constant;
} else if (t.isClass(node)) {
return !node.superClass;
} else {
return t.isPure(node);
}
}
/**
* Description
*/
init() {
if (!this.references) this.crawl();
}
/**
* Description
*/
@ -613,7 +621,7 @@ export default class Scope {
for (let param of (params: Array)) {
this.registerBinding("param", param);
}
this.traverse(path.get("body").node, blockVariableVisitor, this);
path.get("body").traverse(blockVariableVisitor, this);
}
// Program, Function - var variables

View File

@ -41,8 +41,8 @@
"SpreadProperty": ["UnaryLike"],
"SpreadElement": ["UnaryLike"],
"ClassDeclaration": ["Scopable", "Class", "Pure", "Statement", "Declaration"],
"ClassExpression": ["Scopable", "Class", "Pure", "Expression"],
"ClassDeclaration": ["Scopable", "Class", "Statement", "Declaration"],
"ClassExpression": ["Scopable", "Class", "Expression"],
"ForOfStatement": ["Scopable", "Statement", "For", "Loop"],
"ForInStatement": ["Scopable", "Statement", "For", "Loop"],