mirror of
https://github.com/safishamsi/graphify.git
synced 2026-10-16 08:59:26 +00:00
fix(ts): index only the JS/TS interop family for receiver typing
A corpus-wide type index let a same-named class in another language stand in for
the receiver's type, which hid from the parking branch that nothing in the JS/TS
family declares it. SFC script blocks are in the family, so a class exported from
a .vue file still answers — here and at merge time.
(cherry picked from commit 078c106e62)
This commit is contained in:
1 parent
60c2f16ea3
commit
bf13b63ca4
3 files changed
+43
-7
No files matched your search
@@ -46,8 +46,10 @@ _LANG_SUFFIXES: dict[str, frozenset[str]] = {
|
||||
"swift": frozenset({".swift"}),
|
||||
"php": frozenset({".php", ".phtml", ".php3", ".php4", ".php5", ".php7", ".phps"}),
|
||||
# TS and JS share one key: a TS class legitimately answers a JS call site, so
|
||||
# splitting them would drop every TS<->JS cross-repo call.
|
||||
"typescript": frozenset({".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"}),
|
||||
# splitting them would drop every TS<->JS cross-repo call. An SFC script block is
|
||||
# TS/JS too, so a class exported from `.vue` answers as well.
|
||||
"typescript": frozenset({".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs",
|
||||
".vue", ".svelte", ".astro"}),
|
||||
}
|
||||
|
||||
# A declaration owns its members through a `method` edge, except in C++, where an
|
||||
|
||||
+9
-5
@@ -4976,8 +4976,8 @@ 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.
|
||||
# Every suffix the TS/JS extractors claim, so a pure-ESM (`.mjs`) repo still activates
|
||||
# the member-call resolver below.
|
||||
_JS_TS_SUFFIXES = (".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs")
|
||||
|
||||
|
||||
@@ -5022,11 +5022,15 @@ def _resolve_typescript_member_calls(
|
||||
|
||||
contained = {e.get("target") for e in all_edges if e.get("relation") == "contains"}
|
||||
|
||||
# Only the JS/TS interop family declares a type this receiver can hold (SFC script
|
||||
# blocks included). A same-named class in another language must neither answer nor
|
||||
# hide from the parking branch that nothing local declares the name.
|
||||
type_def_nids: dict[str, list[str]] = {}
|
||||
node_by_id: dict[str, dict] = {}
|
||||
for n in all_nodes:
|
||||
node_by_id[n.get("id")] = n
|
||||
if n.get("source_file") and n.get("id") in contained and _is_type_like_definition(n):
|
||||
if (_lang_family(n.get("source_file")) == "jsts"
|
||||
and n.get("id") in contained and _is_type_like_definition(n)):
|
||||
type_def_nids.setdefault(_key(n.get("label", "")), []).append(n["id"])
|
||||
|
||||
method_index: dict[tuple[str, str], str] = {}
|
||||
@@ -5093,8 +5097,8 @@ def _resolve_typescript_member_calls(
|
||||
if not type_defs:
|
||||
# Table-typed only: an uppercase receiver also matches namespace aliases and
|
||||
# default imports, and node_modules is unscanned, so those are npm names.
|
||||
# The suffix stands in for the `lang` tag TS raw_calls do not carry.
|
||||
if not type_qualified and str(rc.get("source_file", "")).endswith(_JS_TS_SUFFIXES):
|
||||
# The caller's family stands in for the `lang` tag TS raw_calls do not carry.
|
||||
if not type_qualified and _lang_family(rc.get("source_file")) == "jsts":
|
||||
_park_unresolved_member_call(
|
||||
node_by_id.get(caller), callee, type_name, "typescript", rc,
|
||||
)
|
||||
|
||||
@@ -350,3 +350,33 @@ def test_a_type_the_origin_gate_rejected_is_not_parked(tmp_path):
|
||||
"}\n"),
|
||||
})
|
||||
assert _parked(r) == []
|
||||
|
||||
|
||||
def test_a_class_from_another_language_neither_answers_nor_blocks_parking(tmp_path):
|
||||
# The declaration index is corpus-wide, so a same-named PHP class used to make the
|
||||
# receiver look locally declared and the call was dropped instead of parked.
|
||||
_, r = _calls(tmp_path, {
|
||||
"app.ts": ("import { Greeter } from 'greeter-pkg';\n"
|
||||
"export class App {\n"
|
||||
" constructor(private greeter: Greeter) {}\n"
|
||||
" run(): void { this.greeter.greet(); }\n"
|
||||
"}\n"),
|
||||
"Greeter.php": "<?php\nclass Greeter {\n public function greet(): void {}\n}\n",
|
||||
})
|
||||
assert _parked(r) == [{"callee": "greet", "receiver_type": "Greeter",
|
||||
"lang": "typescript", "line": "L4"}]
|
||||
|
||||
|
||||
def test_a_class_declared_in_an_sfc_script_block_still_answers(tmp_path):
|
||||
# An SFC script block is TS, so its declarations belong to the same interop family
|
||||
# as the caller's and must keep resolving.
|
||||
calls, _ = _calls(tmp_path, {
|
||||
"app.ts": ("import { Greeter } from './Greeter.vue';\n"
|
||||
"export class App {\n"
|
||||
" constructor(private greeter: Greeter) {}\n"
|
||||
" run(): void { this.greeter.greet(); }\n"
|
||||
"}\n"),
|
||||
"Greeter.vue": ("<script lang=\"ts\">\nexport class Greeter {\n"
|
||||
" greet(): void {}\n}\n</script>\n"),
|
||||
})
|
||||
assert any("run" in str(s) and "greet" in str(t) for s, t in calls), calls
|
||||
Reference in new issue
Block a user