From 65d14de56ace41032f4dbb1864c2760994fbfb3f Mon Sep 17 00:00:00 2001 From: Erlend Oftedal Date: Thu, 15 Feb 2024 13:31:54 +0100 Subject: [PATCH] Speed and memory --- .gitignore | 3 +- src/index.ts | 4 +- src/traverse.ts | 175 +++++++++++++++++++++++++++++------------------- 3 files changed, 111 insertions(+), 71 deletions(-) diff --git a/.gitignore b/.gitignore index 7f719dd..4f8a86d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ lib node_modules **/.DS_Store -tmp \ No newline at end of file +tmp/ +coverage/ diff --git a/src/index.ts b/src/index.ts index 6febfe6..9f7919e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -273,7 +273,7 @@ function resolveBinding(path: NodePath) : NodePath | 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; @@ -427,7 +427,7 @@ function travHandle>(queries: T, root: NodePath< state.matches.pop(); state.functionCalls.pop(); } - }, root.scope, state, root); + }, root.scopeId, state, root); return results; } diff --git a/src/traverse.ts b/src/traverse.ts index af67391..2aca7ee 100644 --- a/src/traverse.ts +++ b/src/traverse.ts @@ -15,33 +15,12 @@ export type Binding = { export type Scope = { bindings: Record; - parentScope?: Scope; + parentScopeId?: number; id: number; + hasEntries: boolean; }; -let scopeId = 0; - -function createScope(parentScope?: Scope) { - const id = scopeId++; - const bindings: Record = {}; - 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 = new Array(100000); const voidFn = () => {}; @@ -52,7 +31,7 @@ export type NodePath = { parentKey?: string; stop: () => void; get(key: string): NodePath[]; - scope: Scope, + scopeId: number; shouldStop: boolean; }; @@ -61,7 +40,59 @@ type Visitor = { exit?: (path: NodePath, state: T) => void; } -function createNodePath(node: Babel.Node, key: string | undefined, parentKey: string | undefined, scope: Scope | undefined, nodePath?: NodePath) : NodePath { + +let scopeIdCounter = 0; +let removedScopes = 0; + +function createScope(parentScopeId?: number): number { + const id = scopeIdCounter++; + /*const bindings: Record = {}; + 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) : NodePath { if (node.extra && node.extra["babel-q-path"]) { const path = node.extra["babel-q-path"] as NodePath; path.key = key; @@ -69,58 +100,63 @@ function createNodePath(node: Babel.Node, key: string | undefined, parentKey: st 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)[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)[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]; @@ -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++; } } @@ -146,21 +185,21 @@ function isNode(candidate: unknown): candidate is Babel.Node { function traverseInner( node: Babel.Node, visitor: Visitor, - scope: Scope | undefined, + scopeId: number | undefined, state: T, path?: NodePath ) { - 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[]; @@ -171,7 +210,7 @@ function traverseInner( 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) { @@ -186,13 +225,13 @@ function traverseInner( const sOut: number[] = []; export default function traverse( node: Babel.Node, visitor: Visitor, - scope: Scope | undefined, + scopeId: number | undefined, state: T, path?: NodePath) { - 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); } - } \ No newline at end of file