From b4527cc8a67fe85b5e989ad5e70b2e4004c21450 Mon Sep 17 00:00:00 2001 From: xiongjianxu <6457197+xiongjianxu@users.noreply.github.com> Date: Mon, 7 Sep 2026 17:50:59 +0800 Subject: [PATCH] Activate the TS/JS member-call resolver for .mjs and .cjs `_DISPATCH` routes `.mjs`/`.cjs` to `extract_js` and the cross-file call gate already lists them, but `typescript_member_calls` claimed only six suffixes, so a repo written entirely in `.mjs` was the one shape where the resolver never ran and `const s = new Svc(); s.doThing()` produced no edge. Both the suffix set and the resolver now read one `_JS_TS_SUFFIXES` constant. (cherry picked from commit 84fce94ce82e25715cd451fa2abe70f42b27f31b) --- graphify/extract.py | 8 +++++++- tests/test_ts_receiver_member_calls.py | 11 +++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/graphify/extract.py b/graphify/extract.py index 56d4b0d53..c0bf5466e 100644 --- a/graphify/extract.py +++ b/graphify/extract.py @@ -4976,6 +4976,11 @@ def _resolve_python_member_calls( _emit_call(caller, children[0], rc) +# Every suffix the TS/JS extractors claim. The member-call resolver both activates on +# and parks by this set, so a pure-ESM (`.mjs`) repo is not silently left out. +_JS_TS_SUFFIXES = (".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs") + + def _resolve_typescript_member_calls( per_file: list[dict], all_nodes: list[dict], @@ -7178,7 +7183,8 @@ register_language_resolver( LanguageResolver("ruby_member_calls", frozenset({".rb", ".rake"}), resolve_ruby_member_calls) ) register_language_resolver( - LanguageResolver("typescript_member_calls", frozenset({".ts", ".tsx", ".mts", ".cts", ".js", ".jsx"}), _resolve_typescript_member_calls) + LanguageResolver("typescript_member_calls", frozenset(_JS_TS_SUFFIXES), + _resolve_typescript_member_calls) ) # C++ (#1547) and ObjC (#1556) receiver-typed member-call resolution. `.h` is in # both suffix sets because it routes to extract_cpp or extract_objc by content; the diff --git a/tests/test_ts_receiver_member_calls.py b/tests/test_ts_receiver_member_calls.py index 03771cbb8..5752156ac 100644 --- a/tests/test_ts_receiver_member_calls.py +++ b/tests/test_ts_receiver_member_calls.py @@ -296,3 +296,14 @@ def test_public_property_sharing_a_private_methods_node_keeps_its_edge(tmp_path) "export function go(c: Ctx): number { return c.respond(); }\n"), }) assert any("go" in str(s) and "respond" in str(t) for s, t in calls), calls + + +def test_a_pure_esm_corpus_still_activates_the_resolver(tmp_path): + # `.mjs` and `.cjs` route to extract_js like `.js` does, so a repo that uses + # only those must not be the one shape where the resolver never runs. + calls, _ = _calls(tmp_path, { + "svc.mjs": "export class Svc {\n doThing() { return 1; }\n}\n", + "app.mjs": ('import { Svc } from "./svc.mjs";\nconst s = new Svc();\n' + "export function usesDirect() { return s.doThing(); }\n"), + }) + assert any("usesDirect" in s and "doThing" in t for s, t in calls)