fix(extract): a require binding does not shadow the required class

A \const X = require('./m')\ binding is an import, not a module-scope data
binding. The node it produced in the importing file shadowed the class the
required file exports, so \
ew X()\ bound to the local node and the
required class got no incoming edge.
This commit is contained in:
lab 1207 committed 2026-10-21 18:41:48 +05:30
1 parent b90b9df794
commit 66bb69114a
2 files changed
+63 -6

No files matched your search

+15 -6
View File
@@ -4408,12 +4408,21 @@ def _js_extra_walk(node, source: bytes, file_nid: str, stem: str, str_path: str,
function_bodies=function_bodies,
)
arrow_found = True
elif value and (
is_exported_scalar_binding
or value.type in (
"object", "array", "as_expression",
"satisfies_expression", "call_expression",
"new_expression",
elif (
value
# A `const X = require('./m')` binding is an import, not
# a module-scope data binding. A node for it in this file
# shadowed the class the required file exports, so
# `new X()` bound to the local node instead of the
# required one (#4621). `var` never produced one either.
and _find_require_call(value) is None
and (
is_exported_scalar_binding
or value.type in (
"object", "array", "as_expression",
"satisfies_expression", "call_expression",
"new_expression",
)
)
):
# Simple exported identifiers are part of the module API
+48
View File
@@ -0,0 +1,48 @@
"""A CommonJS `const X = require('./m')` binding must not create a local node.
The binding is an import, not a module-scope data binding. A node for it in
the importing file shadowed the class the required file exports, so `new X()`
bound to the local node and the required class got no incoming edge (#4621).
"""
from __future__ import annotations
from pathlib import Path
from graphify.extract import _make_id, extract
def _write(path: Path, text: str) -> Path:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(text, encoding="utf-8")
return path
def test_require_binding_does_not_shadow_the_required_class(tmp_path: Path):
logger = _write(
tmp_path / "src/logger.js",
"function Logger(p) { this.p = p }\n"
"Logger.prototype.warn = function warn() {}\n"
"module.exports = Logger\n",
)
use = _write(
tmp_path / "src/use.js",
"const Logger = require('./logger')\n"
"function Watcher() { return new Logger('w') }\n"
"module.exports = Watcher\n",
)
result = extract([logger, use], cache_root=tmp_path)
node_ids = {node["id"] for node in result["nodes"]}
assert _make_id("src/use", "Logger") not in node_ids, (
"the require binding must not create a node in the importing file"
)
calls = {
(edge["source"], edge["target"])
for edge in result["edges"]
if edge["relation"] == "calls"
}
assert (_make_id("src/use", "Watcher"), _make_id("src/logger", "Logger")) in calls, (
"the call must land on the class the required file exports"
)