mirror of
https://github.com/safishamsi/graphify.git
synced 2026-08-27 08:46:43 +00:00
fix(js): retain lazy CommonJS require dependencies (#2700)
A require(...) written inside a function body (the canonical lazy-require idiom for breaking circular deps) produced no edge at all, while the identical require at module scope resolved as EXTRACTED imports_from/imports edges. walk_calls now feeds nested require declarations to the same _require_imports_js routine, attributing the edge to the enclosing callable; dynamic require(var) is still skipped and no bare local node is minted (#1077 scope guard). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
committed by
safishamsi
co-authored by
Claude Opus 4.8
parent
94c2050f93
commit
259bb6acfb
@@ -1861,7 +1861,7 @@ def _find_require_call(value_node):
|
||||
return _find_require_call(obj)
|
||||
return None
|
||||
|
||||
def _require_imports_js(node, source: bytes, file_nid: str, stem: str, edges: list, str_path: str) -> bool:
|
||||
def _require_imports_js(node, source: bytes, importer_nid: str, stem: str, edges: list, str_path: str) -> bool:
|
||||
"""Detect CommonJS require imports inside lexical_declaration / variable_declaration.
|
||||
|
||||
Handles three patterns:
|
||||
@@ -1900,7 +1900,7 @@ def _require_imports_js(node, source: bytes, file_nid: str, stem: str, edges: li
|
||||
tgt_nid, resolved_path = resolved
|
||||
line = node.start_point[0] + 1
|
||||
edge = {
|
||||
"source": file_nid,
|
||||
"source": importer_nid,
|
||||
"target": tgt_nid,
|
||||
"relation": "imports_from",
|
||||
"context": "import",
|
||||
@@ -1937,7 +1937,7 @@ def _require_imports_js(node, source: bytes, file_nid: str, stem: str, edges: li
|
||||
if target_stem is not None:
|
||||
for sym in sym_names:
|
||||
edges.append({
|
||||
"source": file_nid,
|
||||
"source": importer_nid,
|
||||
"target": _make_id(target_stem, sym),
|
||||
"relation": "imports",
|
||||
"context": "import",
|
||||
@@ -4746,6 +4746,14 @@ def _extract_generic(
|
||||
walk_calls(child, caller_nid, receiver_types, closure_locals)
|
||||
return
|
||||
|
||||
# CommonJS imports are valid at any lexical depth. The module-level
|
||||
# pass records top-level require() declarations; this pass owns function
|
||||
# bodies, so detect lazy/cycle-breaking requires here and attribute the
|
||||
# dependency to the enclosing callable rather than silently dropping it.
|
||||
if (config.ts_module in ("tree_sitter_javascript", "tree_sitter_typescript")
|
||||
and node.type in ("lexical_declaration", "variable_declaration")):
|
||||
_require_imports_js(node, source, caller_nid, stem, edges, str_path)
|
||||
|
||||
if node.type in config.call_types:
|
||||
# JS/TS dynamic imports: await import('./foo.js')
|
||||
if config.ts_module in ("tree_sitter_javascript", "tree_sitter_typescript"):
|
||||
|
||||
@@ -776,6 +776,31 @@ def test_extract_js_member_require_emits_property_symbol():
|
||||
assert _make_id(helpers_stem, "helperFn") in sym_targets
|
||||
|
||||
|
||||
def test_extract_js_function_scoped_require_emits_import_edge(tmp_path):
|
||||
"""Lazy CommonJS requires belong to their enclosing function, not nowhere."""
|
||||
target = tmp_path / "target.js"
|
||||
target.write_text("exports.helper = () => 42;\n", encoding="utf-8")
|
||||
caller = tmp_path / "lazy.js"
|
||||
caller.write_text(
|
||||
"function useItLazily() {\n"
|
||||
" const { helper } = require('./target');\n"
|
||||
" return helper();\n"
|
||||
"}\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
result = extract([caller, target], cache_root=tmp_path, root=tmp_path, parallel=False)
|
||||
labels = {node["id"]: node["label"] for node in result["nodes"]}
|
||||
lazy_edges = [
|
||||
edge for edge in result["edges"]
|
||||
if edge["relation"] == "imports_from" and "target" in edge["target"]
|
||||
]
|
||||
|
||||
assert len(lazy_edges) == 1
|
||||
assert labels[lazy_edges[0]["source"]] == "useItLazily()"
|
||||
assert lazy_edges[0]["confidence"] == "EXTRACTED"
|
||||
|
||||
|
||||
def test_extract_js_arrow_function_still_extracted():
|
||||
"""Regression: arrow functions in lexical_declaration must still produce nodes."""
|
||||
from graphify.extract import extract_js
|
||||
|
||||
Reference in New Issue
Block a user