mirror of
https://github.com/safishamsi/graphify.git
synced 2026-10-13 23:49:14 +00:00
fix(ts): keep a public call's edge when foo and #foo share one node
The extractor gives a class's `foo()` and `#foo()` the same node id, labeled after whichever is declared first. Skipping a `c.foo()` match whose label starts with `#` therefore dropped a real edge when the private member came first (hono's `c.newResponse()`, whose public arrow property has no node of its own). A call written `c.foo()` can only mean a public member, so keep the edge to that node. Only the first commit of #4233 shipped in 0.9.81; this is its second one. Covered for both declaration orders. vs 0.9.84: hono +3 edges, nothing removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B8gwEpjajiWHVW3c9smuKf
This commit is contained in:
1 parent
afd4fa55c6
commit
ad88f5bc70
2 files changed
+15
-18
No files matched your search
@@ -4669,12 +4669,6 @@ def _resolve_typescript_member_calls(
|
||||
# (a `references` edge to the type node) was another fabrication
|
||||
# vector; skip instead, matching the C# resolver.
|
||||
continue
|
||||
# _key() drops the `#`, so `c.newResponse()` also keys onto a private
|
||||
# `#newResponse()`. A `#name` is only reachable as `#name`; never bind
|
||||
# across that difference.
|
||||
method_name = str(node_by_id.get(method_nid, {}).get("label", "")).lstrip(".")
|
||||
if method_name.startswith("#") != str(callee).startswith("#"):
|
||||
continue
|
||||
if method_nid == caller or (caller, method_nid) in existing_pairs:
|
||||
continue
|
||||
existing_pairs.add((caller, method_nid))
|
||||
|
||||
@@ -255,15 +255,18 @@ def test_same_named_type_the_import_cannot_decide_emits_no_edge(tmp_path):
|
||||
assert not _hits(calls, "neither")
|
||||
|
||||
|
||||
def test_a_public_call_never_binds_to_a_private_hash_method(tmp_path):
|
||||
# `_key()` folds `#newResponse` and `newResponse` together; a `#name` is only
|
||||
# reachable as `#name`, so `c.newResponse()` must not land on it.
|
||||
calls, _ = _calls(tmp_path, {
|
||||
"ctx.ts": ("export class Ctx {\n"
|
||||
" #newResponse(): number { return 1; }\n"
|
||||
" build(): number { return this.#newResponse(); }\n"
|
||||
"}\n"),
|
||||
"use.ts": ('import { Ctx } from "./ctx";\n'
|
||||
"export function make(c: Ctx): number { return c.newResponse(); }\n"),
|
||||
})
|
||||
assert not _hits(calls, "make", "newResponse")
|
||||
def test_public_call_reaches_the_member_node_in_either_declaration_order(tmp_path):
|
||||
# `foo()` and `#foo()` share one node (ids drop `#`), labeled after whichever is
|
||||
# declared first. `c.foo()` can only mean the public one, so it must keep its
|
||||
# edge to that node in both orders.
|
||||
orders = (
|
||||
" foo(): number { return 1; }\n #foo(): number { return 2; }\n",
|
||||
" #foo(): number { return 2; }\n foo(): number { return 1; }\n",
|
||||
)
|
||||
for i, members in enumerate(orders):
|
||||
calls, _ = _calls(tmp_path / f"order{i}", {
|
||||
"ctx.ts": "export class Ctx {\n" + members + "}\n",
|
||||
"use.ts": ('import { Ctx } from "./ctx";\n'
|
||||
"export function make(c: Ctx): number { return c.foo(); }\n"),
|
||||
})
|
||||
assert any("make" in str(s) and "foo" in str(t) for s, t in calls), (i, calls)
|
||||
Reference in new issue
Block a user