mirror of
https://github.com/safishamsi/graphify.git
synced 2026-10-21 11:21:27 +00:00
fix(ts): don't guess between members the normalized key can't tell apart
Review on #4479: after the exact-name lookup, the `_key` fallback in
_resolve_typescript_member_calls still matched loosely. With #4475 splitting
close/#close, updateHash/_updateHash and set/$set into separate nodes, one
normalized key can now map to two members, and method_index kept whichever
was registered last. So c._close() bound to #close, c.UpdateHash() to
_updateHash, and c.a() to $a, none of which is the called name. Track keys
that more than one member normalizes to and skip the fallback for them (no
edge rather than a guess). Exact hits, and loose hits with a single
candidate, are unchanged.
Co-Authored-By: Grok Bot
(cherry picked from commit a1ce057757)
This commit is contained in:
1 parent
70a600196f
commit
c4589a0c1b
2 files changed
+35
-2
No files matched your search
+7
-2
@@ -5048,6 +5048,8 @@ def _resolve_typescript_member_calls(
|
||||
# `updateHash` / `_updateHash` are two nodes of one class, so the exact
|
||||
# name is tried first and `_key` stays the fallback.
|
||||
method_exact: dict[tuple[str, str], str] = {}
|
||||
# Keys more than one member normalizes to: the fallback can't tell them apart.
|
||||
method_key_ambiguous: set[tuple[str, str]] = set()
|
||||
class_of_method: dict[str, str] = {}
|
||||
for e in all_edges:
|
||||
if e.get("relation") != "method":
|
||||
@@ -5055,7 +5057,10 @@ def _resolve_typescript_member_calls(
|
||||
src, tgt = e.get("source"), e.get("target")
|
||||
tnode = node_by_id.get(tgt)
|
||||
if tnode is not None:
|
||||
method_index[(src, _key(tnode.get("label", "")))] = tgt
|
||||
key = (src, _key(tnode.get("label", "")))
|
||||
if method_index.get(key, tgt) != tgt:
|
||||
method_key_ambiguous.add(key)
|
||||
method_index[key] = tgt
|
||||
method_exact[(src, str(tnode.get("label", "")).removesuffix("()").removeprefix("."))] = tgt
|
||||
if isinstance(src, str) and isinstance(tgt, str):
|
||||
class_of_method[tgt] = src
|
||||
@@ -5188,7 +5193,7 @@ def _resolve_typescript_member_calls(
|
||||
type_nid = visible[0]
|
||||
method_nid = method_exact.get((type_nid, str(callee)))
|
||||
exact = method_nid is not None
|
||||
if not exact:
|
||||
if not exact and (type_nid, _key(callee)) not in method_key_ambiguous:
|
||||
method_nid = method_index.get((type_nid, _key(callee)))
|
||||
if not method_nid:
|
||||
# Receiver typed, but the type has no such method. The old fallback
|
||||
|
||||
@@ -99,6 +99,34 @@ def test_cross_file_ts_typed_receiver_hits_each_member(tmp_path):
|
||||
assert ("b()", ".$set()") in calls and ("b()", ".set()") not in calls
|
||||
|
||||
|
||||
|
||||
@pytest.mark.parametrize("members,call", [
|
||||
(" close(): void {}\n #close(): void {}\n", "c._close()"),
|
||||
(" updateHash(): void {}\n _updateHash(): void {}\n", "c.$updateHash()"),
|
||||
(" updateHash(): void {}\n _updateHash(): void {}\n", "c.UpdateHash()"),
|
||||
(" set(v: number): void {}\n $set(v: number): void {}\n", "c._set(1)"),
|
||||
(" _a(): void {}\n $a(): void {}\n", "c.a()"),
|
||||
])
|
||||
def test_cross_file_call_matching_no_member_exactly_is_not_guessed(tmp_path, members, call):
|
||||
# Review on #4479: neither member has the called name, and the loose key fits
|
||||
# both, so picking one would be a guess (it was whichever registered last).
|
||||
r = _extract(tmp_path, {
|
||||
"ctx.ts": "export class Ctx {\n" + members + "}\n",
|
||||
"use.ts": ('import { Ctx } from "./ctx";\n'
|
||||
"export function go(c: Ctx) { " + call + "; }\n"),
|
||||
})
|
||||
assert not [t for s, t in _calls(r) if s == "go()"], _calls(r)
|
||||
|
||||
|
||||
def test_cross_file_loose_match_with_a_single_candidate_is_unchanged(tmp_path):
|
||||
# One member per key still resolves loosely, as on v8 (not touched here).
|
||||
r = _extract(tmp_path, {
|
||||
"ctx.ts": "export class Ctx {\n _close(): void {}\n}\n",
|
||||
"use.ts": ('import { Ctx } from "./ctx";\n'
|
||||
"export function go(c: Ctx) { c.close(); }\n"),
|
||||
})
|
||||
assert ("go()", "._close()") in _calls(r)
|
||||
|
||||
def test_dedup_build_and_merge_keep_both(tmp_path):
|
||||
from graphify.build import build, build_merge
|
||||
from graphify.cluster import cluster
|
||||
|
||||
Reference in new issue
Block a user