Files
graphify/tests/test_js_walk_order.py
T
Pini Vaknin 74d784f943 perf(resolution): walk each JS/TS tree once when collecting symbol facts
_collect_js_symbol_resolution_facts walked every parsed tree in full four
times, and each pass kept only a few node types.

The first pass now walks once and keeps the nodes of the six types the
whole-tree passes filter on, in walk order. Later passes iterate that
list. Each fact list is filled by a single pass, so fact order does not
change.

_walk_js_tree now uses a TreeCursor instead of reading .children at every
node. A cursor is bounded by its start node, so the walks that start
inside a tree (function bodies, and the import and export nodes passed to
_js_named_specifiers) stay inside that subtree.

Overlaps #3353, which also indexes JS nodes once and frees each tree
after use.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 243deb39e7)
2026-10-10 22:11:03 +01:00

60 lines
1.8 KiB
Python

"""_walk_js_tree: pre-order over the whole tree, bounded by its start node.
The JS/TS symbol-fact passes rely on walk order for fact order, and the
call-use pass walks function bodies as subtrees. Both must hold for the
TreeCursor walk exactly as they did for the ``.children`` stack walk.
"""
from graphify.extractors.resolution import _parse_js_tree, _walk_js_tree
SOURCE = """\
import { a, b as c } from "./dep.js";
export const x = a, y = c;
export class K extends Base {
m() { return helper(a, () => b()); }
}
function outer() {
const inner = () => { call1(); if (x) { call2(); } };
return inner;
}
export { outer as default };
"""
def _span(node):
return (node.type, node.start_byte, node.end_byte)
def _reference_preorder(node):
out = [node]
for child in node.children:
out.extend(_reference_preorder(child))
return out
def test_walk_is_children_preorder(tmp_path):
path = tmp_path / "m.ts"
path.write_text(SOURCE, encoding="utf-8")
parsed = _parse_js_tree(path)
assert parsed is not None
_source, root = parsed
assert [_span(n) for n in _walk_js_tree(root)] == [
_span(n) for n in _reference_preorder(root)
]
def test_walk_from_any_node_stays_inside_its_subtree(tmp_path):
"""Start a walk at every node, including ones with later siblings, as
_js_named_specifiers does with an import_statement or export_clause."""
path = tmp_path / "m.ts"
path.write_text(SOURCE, encoding="utf-8")
parsed = _parse_js_tree(path)
assert parsed is not None
_source, root = parsed
starts = _reference_preorder(root)
assert any(n.next_sibling is not None and n.child_count for n in starts)
for start in starts:
assert [_span(n) for n in _walk_js_tree(start)] == [
_span(n) for n in _reference_preorder(start)
]