Skip to content

Commit

Permalink
Speed and memory
Browse files Browse the repository at this point in the history
  • Loading branch information
eoftedal committed Feb 15, 2024
1 parent 5f82790 commit 65d14de
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 71 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ lib
node_modules
**/.DS_Store

tmp
tmp/
coverage/
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ function resolveBinding(path: NodePath<Babel.Node>) : NodePath<Babel.Node> | und
const name = path.node.name;
if (name == undefined || typeof name != "string") return undefined;
//const binding = path.scope.getBinding(name);
const binding = getBinding(path.scope, name);
const binding = getBinding(path.scopeId, name);
if (!binding) return undefined;
log.debug("THIS IS THE BINDING", binding);
return binding.path;
Expand Down Expand Up @@ -427,7 +427,7 @@ function travHandle<T extends Record<string, QNode>>(queries: T, root: NodePath<
state.matches.pop();
state.functionCalls.pop();
}
}, root.scope, state, root);
}, root.scopeId, state, root);
return results;
}

Expand Down
175 changes: 107 additions & 68 deletions src/traverse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,33 +15,12 @@ export type Binding = {

export type Scope = {
bindings: Record<string, Binding>;
parentScope?: Scope;
parentScopeId?: number;
id: number;
hasEntries: boolean;
};

let scopeId = 0;

function createScope(parentScope?: Scope) {
const id = scopeId++;
const bindings: Record<string, Binding> = {};
return {
bindings,
id,
parentScope
}
}
export function getBinding(scope: Scope, name: string) {
const s = scope.bindings[name];
if (s) return s;
if (scope.parentScope) {
return getBinding(scope.parentScope, name);
}
return undefined;
}
function setBinding(scope: Scope, name: string, binding: Binding) {
scope.bindings[name] = binding;
}

const scopes: Array<Scope | number> = new Array(100000);

const voidFn = () => {};

Expand All @@ -52,7 +31,7 @@ export type NodePath<T> = {
parentKey?: string;
stop: () => void;
get(key: string): NodePath<Babel.Node>[];
scope: Scope,
scopeId: number;
shouldStop: boolean;
};

Expand All @@ -61,66 +40,123 @@ type Visitor<T> = {
exit?: (path: NodePath<Babel.Node>, state: T) => void;
}

function createNodePath(node: Babel.Node, key: string | undefined, parentKey: string | undefined, scope: Scope | undefined, nodePath?: NodePath<Babel.Node>) : NodePath<Babel.Node> {

let scopeIdCounter = 0;
let removedScopes = 0;

function createScope(parentScopeId?: number): number {
const id = scopeIdCounter++;
/*const bindings: Record<string, Binding> = {};
const s: Scope = {
bindings,
id,
parentScopeId,
hasEntries: false
}*/
scopes[id] = parentScopeId ?? -1;
return id;
}

export function getBinding(scopeId: number, name: string) {
const scope = scopes[scopeId];
if (typeof scope == "number") {
if (scope == -1) return undefined;
return getBinding(scope, name);
}
const s = scope.bindings[name];
if (s) return s;
if (scope.parentScopeId) {
return getBinding(scope.parentScopeId, name);
}
return undefined;
}
function setBinding(scopeId: number, name: string, binding: Binding) {
let scope: Scope;
const s = scopes[scopeId];
if (typeof s == "number") {
scope = {
bindings: {},
id: scopeId,
parentScopeId: s == -1 ? undefined : s,
hasEntries: false
};
scopes[scopeId] = scope;
} else {
scope = s;
}
scope.bindings[name] = binding;
scope.hasEntries = true;
}



let pathsCreated = 0;

function createNodePath(node: Babel.Node, key: string | undefined, parentKey: string | undefined, scopeId: number | undefined, nodePath?: NodePath<Babel.Node>) : NodePath<Babel.Node> {
if (node.extra && node.extra["babel-q-path"]) {
const path = node.extra["babel-q-path"] as NodePath<Babel.Node>;
path.key = key;
path.parentKey = parentKey;
path.parentPath = nodePath;
return path;
}
const finalScope: Scope = ((node.extra && node.extra["scope"]) ? node.extra["scope"] as Scope : scope) ?? createScope();
const finalScope: number = ((node.extra && node.extra["scopeId"]) ? node.extra["scopeId"] as number : scopeId) ?? createScope();

const path = {
node,
scope: finalScope,
shouldStop: false,
stop: voidFn,
get: (key: string) => {
if (key in node) {
const r = (node as unknown as Record<string, unknown>)[key];
if (Array.isArray(r)) {
return r.map((n, i) => createNodePath(n, i.toString(), key, scope, nodePath));
} else if (r != undefined) {
return [createNodePath(r as Babel.Node, key, key, scope, nodePath)];
}
node,
scopeId: finalScope,
shouldStop: false,
stop: voidFn,
get: (key: string) => {
if (key in node) {
const r = (node as unknown as Record<string, unknown>)[key];
if (Array.isArray(r)) {
return r.map((n, i) => createNodePath(n, i.toString(), key, scopeId, nodePath));
} else if (r != undefined) {
return [createNodePath(r as Babel.Node, key, key, scopeId, nodePath)];
}
return [];
},
parentPath: nodePath,
key,
parentKey
}
path.stop = () => { path.shouldStop = true; };
return path;
}
return [];
},
parentPath: nodePath,
key,
parentKey
}
path.stop = () => { path.shouldStop = true; };
if (t.isNode(node)) {
node.extra = node.extra ?? {};
node.extra["babel-q-path"] = path;
}
pathsCreated++;
return path;
}


function registerBinding(node: Babel.Node, parentNode: Babel.Node, grandParentNode: Babel.Node | undefined, scope: Scope) {
function registerBinding(node: Babel.Node, parentNode: Babel.Node, grandParentNode: Babel.Node | undefined, scopeId: number) {
if (t.isBinding(node, parentNode, grandParentNode) && !t.isMemberExpression(node)) {
if (t.isIdentifier(node) && !t.isAssignmentExpression(parentNode)) {
//A bit of a hack here as well. Needs some further investigation
if (t.isFunctionDeclaration(parentNode) || t.isFunctionExpression(parentNode) || t.isScope(node, parentNode)) {
setBinding(scope, node.name, { path: createNodePath(node, undefined, undefined, scope) });
setBinding(scopeId, node.name, { path: createNodePath(node, undefined, undefined, scopeId) });
} else {
setBinding(scope, node.name, { path: createNodePath(parentNode, undefined, undefined, scope) });
setBinding(scopeId, node.name, { path: createNodePath(parentNode, undefined, undefined, scopeId) });
}
}
}
}

function registerBindings(node: Babel.Node, parentNode: Babel.Node, grandParentNode: Babel.Node | undefined, scope: Scope) {
function registerBindings(node: Babel.Node, parentNode: Babel.Node, grandParentNode: Babel.Node | undefined, scopeId: number) {
if (typeof node == "object" && node != null) {
node.extra = node.extra ?? {};
if (node.extra["scope"]) return;
node.extra["scope"] = scope;
if (node.extra["scopeId"]) return;
node.extra["scopeId"] = scopeId;
}
const keys = t.VISITOR_KEYS[node.type];

let childScope = scope;
let childScopeId = scopeId;
// This is also buggy. Need to investigate what creates a new scope
if (t.isScopable(node) || t.isExportSpecifier(node)) {
childScope = createScope(scope);
childScopeId = createScope(scopeId);
}
for (const key of keys) {
const childNodes = node[key as keyof Babel.Node];
Expand All @@ -129,12 +165,15 @@ function registerBindings(node: Babel.Node, parentNode: Babel.Node, grandParentN
if (isNode(child)) {
// This feels like a hack. Need to figure out how to make this work
// for other types of scopes as well (classes, etc.)
const s = key == "id" ? scope : childScope;
const s = key == "id" ? scopeId : childScopeId;
registerBinding(child, node, parentNode, s);
registerBindings(child, node, parentNode, s);
}
});

}
if (childScopeId != scopeId && typeof scopes[childScopeId] == "number") { // Scope has not been populated
scopes[childScopeId] = scopes[scopeId];
removedScopes++;
}
}

Expand All @@ -146,21 +185,21 @@ function isNode(candidate: unknown): candidate is Babel.Node {
function traverseInner<T>(
node: Babel.Node,
visitor: Visitor<T>,
scope: Scope | undefined,
scopeId: number | undefined,
state: T,
path?: NodePath<Babel.Node>
) {
const nodePath = path ?? createNodePath(node, undefined, undefined, scope);
const nodePath = path ?? createNodePath(node, undefined, undefined, scopeId);
const keys = t.VISITOR_KEYS[node.type];

if (nodePath.parentPath) registerBindings(nodePath.node, nodePath.parentPath.node, nodePath.parentPath.parentPath?.node, nodePath.scope);
if (nodePath.parentPath) registerBindings(nodePath.node, nodePath.parentPath.node, nodePath.parentPath.parentPath?.node, nodePath.scopeId);

for (const key of keys) {
const childNodes = node[key as keyof Babel.Node];
const children = Array.isArray(childNodes) ? childNodes : childNodes ? [childNodes] : [];
const nodePaths = children.map((child, i) => {
if (isNode(child)) {
return createNodePath(child, key, Array.isArray(childNodes) ? i.toString() : key, nodePath.scope, nodePath);
return createNodePath(child, key, Array.isArray(childNodes) ? i.toString() : key, nodePath.scopeId, nodePath);
}
return undefined;
}).filter(x => x != undefined) as NodePath<Babel.Node>[];
Expand All @@ -171,7 +210,7 @@ function traverseInner<T>(
nodePath.shouldStop = true;
return;
}
traverseInner(childPath.node, visitor, nodePath.scope, state, childPath);
traverseInner(childPath.node, visitor, nodePath.scopeId, state, childPath);
if (visitor.exit) visitor.exit(childPath, state);

if (childPath.shouldStop) {
Expand All @@ -186,13 +225,13 @@ function traverseInner<T>(
const sOut: number[] = [];
export default function traverse<T>( node: Babel.Node,
visitor: Visitor<T>,
scope: Scope | undefined,
scopeId: number | undefined,
state: T,
path?: NodePath<Babel.Node>) {
traverseInner(node, visitor, scope, state, path);
if (!sOut.includes(scopeId)) {
log.debug("Scopes created", scopeId);
sOut.push(scopeId);
traverseInner(node, visitor, scopeId, state, path);
if (!sOut.includes(scopeIdCounter)) {
log.debug("Scopes created", scopeIdCounter, " Scopes removed", removedScopes);
console.log("Scopes created", scopeIdCounter, " Scopes removed", removedScopes, "Paths created", pathsCreated);
sOut.push(scopeIdCounter);
}

}

0 comments on commit 65d14de

Please sign in to comment.