diff --git a/packages/babel-traverse/src/cache.js b/packages/babel-traverse/src/cache.js new file mode 100644 index 0000000000..08a771a5fc --- /dev/null +++ b/packages/babel-traverse/src/cache.js @@ -0,0 +1,7 @@ +export let path = new WeakMap(); +export let scope = new WeakMap(); + +export function clear() { + path = new WeakMap(); + scope = new WeakMap(); +} diff --git a/packages/babel-traverse/src/index.js b/packages/babel-traverse/src/index.js index 01a48112c4..8821cb510f 100644 --- a/packages/babel-traverse/src/index.js +++ b/packages/babel-traverse/src/index.js @@ -5,6 +5,7 @@ import * as visitors from "./visitors"; import * as messages from "babel-messages"; import includes from "lodash/collection/includes"; import * as t from "babel-types"; +import * as cache from "./cache"; export { default as NodePath } from "./path"; export { default as Scope } from "./scope"; @@ -87,6 +88,8 @@ traverse.clearNode = function (node) { if (key[0] === "_" && node[key] != null) node[key] = undefined; } + cache.path.delete(node); + let syms: Array = Object.getOwnPropertySymbols(node); for (let sym of syms) { node[sym] = null; @@ -124,3 +127,13 @@ traverse.hasType = function (tree: Object, scope: Object, type: Object, blacklis return state.has; }; + +traverse.clearCache = function() { + cache.clear(); +}; + +traverse.copyCache = function(source, destination) { + if (cache.path.has(source)) { + cache.path.set(destination, cache.path.get(source)); + } +}; diff --git a/packages/babel-traverse/src/path/constants.js b/packages/babel-traverse/src/path/constants.js deleted file mode 100644 index 11431edfe4..0000000000 --- a/packages/babel-traverse/src/path/constants.js +++ /dev/null @@ -1 +0,0 @@ -export const PATH_CACHE_KEY = "_paths"; //Symbol(); diff --git a/packages/babel-traverse/src/path/index.js b/packages/babel-traverse/src/path/index.js index 4fa06c9b30..63f971a5b3 100644 --- a/packages/babel-traverse/src/path/index.js +++ b/packages/babel-traverse/src/path/index.js @@ -4,12 +4,12 @@ import type Hub from "../hub"; import type TraversalContext from "../context"; import * as virtualTypes from "./lib/virtual-types"; import buildDebug from "debug"; -import { PATH_CACHE_KEY } from "./constants"; import invariant from "invariant"; import traverse from "../index"; import assign from "lodash/object/assign"; import Scope from "../scope"; import * as t from "babel-types"; +import { path as pathCache } from "../cache"; let debug = buildDebug("babel"); @@ -69,7 +69,11 @@ export default class NodePath { let targetNode = container[key]; - let paths = parent[PATH_CACHE_KEY] = parent[PATH_CACHE_KEY] || []; + let paths = pathCache.get(parent) || []; + if (!pathCache.has(parent)) { + pathCache.set(parent, paths); + } + let path; for (let i = 0; i < paths.length; i++) { diff --git a/packages/babel-traverse/src/path/modification.js b/packages/babel-traverse/src/path/modification.js index b2827613a3..6465207ebf 100644 --- a/packages/babel-traverse/src/path/modification.js +++ b/packages/babel-traverse/src/path/modification.js @@ -1,7 +1,7 @@ /* eslint max-len: 0 */ // This file contains methods that modify the path/node in some ways. -import { PATH_CACHE_KEY } from "./constants"; +import { path as pathCache } from "../cache"; import PathHoister from "./lib/hoister"; import NodePath from "./index"; import * as t from "babel-types"; @@ -136,7 +136,7 @@ export function insertAfter(nodes) { export function updateSiblingKeys(fromIndex, incrementBy) { if (!this.parent) return; - let paths = this.parent[PATH_CACHE_KEY]; + let paths = pathCache.get(this.parent); for (let i = 0; i < paths.length; i++) { let path = paths[i]; if (path.key >= fromIndex) { diff --git a/packages/babel-traverse/src/scope/index.js b/packages/babel-traverse/src/scope/index.js index 05c492f2d4..084100c815 100644 --- a/packages/babel-traverse/src/scope/index.js +++ b/packages/babel-traverse/src/scope/index.js @@ -10,59 +10,25 @@ import * as messages from "babel-messages"; import Binding from "./binding"; import globals from "globals"; import * as t from "babel-types"; - -// - -const CACHE_SINGLE_KEY = Symbol(); -const CACHE_MULTIPLE_KEY = Symbol(); +import { scope as scopeCache } from "../cache"; /** * To avoid creating a new Scope instance for each traversal, we maintain a cache on the * node itself containing all scopes it has been associated with. - * - * We also optimise for the case of there being only a single scope associated with a node. */ function getCache(node, parentScope, self) { - let singleCache = node[CACHE_SINGLE_KEY]; + let scopes: Array = scopeCache.get(node) || []; - if (singleCache) { - // we've only ever associated one scope with this node so let's check it - if (matchesParent(singleCache, parentScope)) { - return singleCache; - } - } else if (!node[CACHE_MULTIPLE_KEY]) { - // no scope has ever been associated with this node - node[CACHE_SINGLE_KEY] = self; - return; - } - - // looks like we have either a single scope association that was never matched or - // multiple assocations, let's find the right one! - return getCacheMultiple(node, parentScope, self, singleCache); -} - -function matchesParent(scope, parentScope) { - if (scope.parent === parentScope) { - return true; - } -} - -function getCacheMultiple(node, parentScope, self, singleCache) { - let scopes: Array = node[CACHE_MULTIPLE_KEY] = node[CACHE_MULTIPLE_KEY] || []; - - if (singleCache) { - // we have a scope assocation miss so push it onto our scopes - scopes.push(singleCache); - node[CACHE_SINGLE_KEY] = null; - } - - // loop through and check each scope to see if it matches our parent for (let scope of scopes) { - if (matchesParent(scope, parentScope)) return scope; + if (scope.parent === parentScope) return scope; } scopes.push(self); + + if (!scopeCache.has(node)) { + scopeCache.set(node, scopes); + } } // diff --git a/packages/babel-traverse/test/traverse.js b/packages/babel-traverse/test/traverse.js index b2cc301171..1e4f59b1f8 100644 --- a/packages/babel-traverse/test/traverse.js +++ b/packages/babel-traverse/test/traverse.js @@ -123,4 +123,26 @@ suite("traverse", function () { assert.ok(!traverse.hasType(ast, null, "ArrowFunctionExpression")); }); + + test("clearCache", function () { + var paths = []; + traverse(ast, { + enter: function (path) { + paths.push(path); + } + }); + + traverse.clearCache(); + + var paths2 = []; + traverse(ast, { + enter: function (path) { + paths2.push(path); + } + }); + + paths2.forEach(function (p, i) { + assert.notStrictEqual(p, paths[i]); + }); + }); }); diff --git a/packages/babel-types/src/index.js b/packages/babel-types/src/index.js index e3c7f578ee..ea4f785c12 100644 --- a/packages/babel-types/src/index.js +++ b/packages/babel-types/src/index.js @@ -372,6 +372,12 @@ function _inheritComments(key, child, parent) { } } + +// Can't use import because of cyclic dependency between babel-traverse +// and this module (babel-types). This require needs to appear after +// we export the TYPES constant. +const traverse = require("babel-traverse").default; + /** * Inherit all contextual properties from `parent` node to `child` node. */ @@ -397,6 +403,7 @@ export function inherits(child: Object, parent: Object): Object { } t.inheritsComments(child, parent); + traverse.copyCache(parent, child); return child; }