From 375689a1ffb068118989312dd4322c885c99b24f Mon Sep 17 00:00:00 2001 From: Sebastian McKenzie Date: Sat, 16 May 2015 01:37:55 +0100 Subject: [PATCH] handle path contexts MUCH better, they're now only held during the traversal iteration and the previous context is released upon completion, also verify path keys and try and obtain a new one if invalid - fixes #1545 --- src/babel/transformation/file/index.js | 2 +- .../optimisation/react.constant-elements.js | 2 +- src/babel/traversal/context.js | 21 +++- src/babel/traversal/path/hoister.js | 2 +- src/babel/traversal/path/index.js | 113 +++++++++++++----- 5 files changed, 104 insertions(+), 36 deletions(-) diff --git a/src/babel/transformation/file/index.js b/src/babel/transformation/file/index.js index 5e149712bf..958a2cac43 100644 --- a/src/babel/transformation/file/index.js +++ b/src/babel/transformation/file/index.js @@ -463,7 +463,7 @@ export default class File { } _addAst(ast) { - this.path = TraversalPath.get(null, null, ast, ast, "program", this); + this.path = TraversalPath.get(null, ast, ast, "program", this).setContext(null, this); this.scope = this.path.scope; this.ast = ast; } diff --git a/src/babel/transformation/transformers/optimisation/react.constant-elements.js b/src/babel/transformation/transformers/optimisation/react.constant-elements.js index be28455c82..608d5c41c5 100644 --- a/src/babel/transformation/transformers/optimisation/react.constant-elements.js +++ b/src/babel/transformation/transformers/optimisation/react.constant-elements.js @@ -35,7 +35,7 @@ export function JSXElement(node, parent, scope, file) { this.traverse(immutabilityVisitor, state); if (state.isImmutable) { - return this.hoist(); + this.hoist(); } else { node._hoisted = true; } diff --git a/src/babel/traversal/context.js b/src/babel/traversal/context.js index 2241428dc5..e223d956c2 100644 --- a/src/babel/traversal/context.js +++ b/src/babel/traversal/context.js @@ -16,7 +16,9 @@ export default class TraversalContext { } create(node, obj, key) { - return TraversalPath.get(this.parentPath, this, node, obj, key); + var path = TraversalPath.get(this.parentPath, node, obj, key); + path.unshiftContext(this); + return path; } visitMultiple(nodes, node, key) { @@ -37,25 +39,32 @@ export default class TraversalContext { } // visit the queue - for (let i = 0; i < queue.length; i++) { - var path = queue[i]; + for (let path of (queue: Array)) { + path.update(); + if (visited.indexOf(path.node) >= 0) continue; visited.push(path.node); - path.setContext(this.parentPath, this, path.key); - if (path.visit()) { stop = true; break; } } + for (let path of (queue: Array)) { + path.shiftContext(); + } + + this.queue = null; + return stop; } visitSingle(node, key) { if (this.shouldVisit(node[key])) { - return this.create(node, node, key).visit(); + var path = this.create(node, node, key); + path.visit(); + path.shiftContext(); } } diff --git a/src/babel/traversal/path/hoister.js b/src/babel/traversal/path/hoister.js index 4ba7fb47e3..de68729b55 100644 --- a/src/babel/traversal/path/hoister.js +++ b/src/babel/traversal/path/hoister.js @@ -125,6 +125,6 @@ export default class PathHoister { uid = t.jSXExpressionContainer(uid); } - return uid; + this.path.replaceWith(uid); } } diff --git a/src/babel/traversal/path/index.js b/src/babel/traversal/path/index.js index ad03744313..bf505c2a2b 100644 --- a/src/babel/traversal/path/index.js +++ b/src/babel/traversal/path/index.js @@ -45,6 +45,7 @@ var hoistVariablesVisitor = explode({ export default class TraversalPath { constructor(parent, container) { this.container = container; + this.contexts = []; this.parent = parent; this.data = {}; } @@ -53,7 +54,7 @@ export default class TraversalPath { * Description */ - static get(parentPath: TraversalPath, context?: TraversalContext, parent, container, key, file?: File) { + static get(parentPath: TraversalPath, parent, container, key) { var targetNode = container[key]; var paths = container._paths = container._paths || []; var path; @@ -71,7 +72,7 @@ export default class TraversalPath { paths.push(path); } - path.setContext(parentPath, context, key, file); + path.setup(parentPath, key); return path; } @@ -178,8 +179,10 @@ export default class TraversalPath { */ queueNode(path) { - if (this.context && this.context.queue) { - this.context.queue.push(path); + for (var context of (this.contexts: Array)) { + if (context.queue) { + context.queue.push(path); + } } } @@ -225,7 +228,7 @@ export default class TraversalPath { paths.push(path); this.queueNode(path); } else { - paths.push(TraversalPath.get(this, null, node, this.container, to)); + paths.push(TraversalPath.get(this, node, this.container, to)); } } @@ -344,6 +347,8 @@ export default class TraversalPath { */ setScope(file?) { + if (this.opts && this.opts.noScope) return; + var target = this.context || this.parentPath; this.scope = TraversalPath.getScope(this, target && target.scope, file); } @@ -352,35 +357,87 @@ export default class TraversalPath { * Description */ - clearContext() { - this.context = null; - } - - /** - * Description - */ - - setContext(parentPath, context, key, file?) { + setContext(context, file) { this.shouldSkip = false; this.shouldStop = false; this.removed = false; - this.parentPath = parentPath || this.parentPath; - this.key = key; - if (context) { this.context = context; this.state = context.state; this.opts = context.opts; } - this.node = this.container[this.key]; - this.type = this.node && this.node.type; - var log = file && this.type === "Program"; if (log) file.log.debug("Start scope building"); this.setScope(file); if (log) file.log.debug("End scope building"); + + return this; + } + + /** + * Description + */ + + update() { + 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 + // not through our path APIs + + if (Array.isArray(this.container)) { + for (var i = 0; i < this.container.length; i++) { + if (this.container[i] === this.node) { + return this.setKey(i); + } + } + } else { + for (var key in this.container) { + if (this.container[key] === this.node) { + return this.setKey(key); + } + } + } + + throw new Error("Where did we go?!?!?!"); + } + + /** + * Description + */ + + shiftContext() { + this.contexts.shift(); + this.setContext(this.contexts[0]); + } + + /** + * Description + */ + + unshiftContext(context) { + this.contexts.unshift(context); + this.setContext(context); + } + + /** + * Description + */ + + setup(parentPath, key) { + this.parentPath = parentPath || this.parentPath; + this.setKey(key); + } + + /** + * Description + */ + + setKey(key) { + this.key = key; + this.node = this.container[this.key]; + this.type = this.node && this.node.type; } /** @@ -554,7 +611,7 @@ export default class TraversalPath { // doesn't matter, our nodes will be inserted anyway var container = this.node[containerKey]; - var path = TraversalPath.get(this, null, this.node, container, 0); + var path = TraversalPath.get(this, this.node, container, 0); return path.insertBefore(nodes); } @@ -571,7 +628,7 @@ export default class TraversalPath { var container = this.node[containerKey]; var i = container.length; - var path = TraversalPath.get(this, null, this.node, container, i); + var path = TraversalPath.get(this, this.node, container, i); return path.replaceWith(nodes, true); } @@ -767,9 +824,10 @@ export default class TraversalPath { var node = this.node; if (!node) return; + var previousType = this.type; + // call the function with the params (node, parent, scope, state) var replacement = fn.call(this, node, this.parent, this.scope, this.state); - var previousType = this.type; if (replacement) { this.replaceWith(replacement, true); @@ -777,7 +835,7 @@ export default class TraversalPath { if (this.shouldStop || this.shouldSkip || this.removed) return; - if (replacement && previousType !== this.type) { + if (previousType !== this.type) { this.queueNode(this); return; } @@ -831,7 +889,7 @@ export default class TraversalPath { */ getSibling(key) { - return TraversalPath.get(this.parentPath, null, this.parent, this.container, key, this.file); + return TraversalPath.get(this.parentPath, this.parent, this.container, key, this.file); } /** @@ -858,10 +916,10 @@ export default class TraversalPath { if (Array.isArray(container)) { // requested a container so give them all the paths return container.map((_, i) => { - return TraversalPath.get(this, null, node, container, i); + return TraversalPath.get(this, node, container, i).setContext(); }); } else { - return TraversalPath.get(this, null, node, node, key); + return TraversalPath.get(this, node, node, key).setContext(); } } @@ -1095,6 +1153,7 @@ export default class TraversalPath { */ traverse(visitor, state) { + if (!this.scope) console.log(this.contexts); traverse(this.node, visitor, this.scope, state, this); }