Merge pull request #3393 from babel/cache

Move NodePath cache out of the AST
This commit is contained in:
Amjad Masad 2016-03-07 13:57:47 -08:00
commit 60d773f370
8 changed files with 64 additions and 46 deletions

View File

@ -0,0 +1,7 @@
export let path = new WeakMap();
export let scope = new WeakMap();
export function clear() {
path = new WeakMap();
scope = new WeakMap();
}

View File

@ -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<Symbol> = 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));
}
};

View File

@ -1 +0,0 @@
export const PATH_CACHE_KEY = "_paths"; //Symbol();

View File

@ -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++) {

View File

@ -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) {

View File

@ -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<Scope> = 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<Scope> = 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);
}
}
//

View File

@ -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]);
});
});
});

View File

@ -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;
}