mirror of
https://github.com/Graphify-Labs/graphify.git
synced 2026-09-14 19:34:09 +08:00
fix(resolution): require owned sources for type-use edges
(cherry picked from commit dcd9a7ee2d)
This commit is contained in:
@@ -1094,6 +1094,11 @@ def _apply_symbol_resolution_facts(
|
||||
if target_id is None:
|
||||
continue
|
||||
source_id = use_fact.source_id
|
||||
# Structural walking can omit named-function-local declarations while
|
||||
# materializing callback-local ones. Only actual node ownership decides
|
||||
# whether a type relationship has a represented source declaration.
|
||||
if use_fact.relation in ("inherits", "implements", "references") and source_id not in owned:
|
||||
continue
|
||||
if use_fact.relation == "calls" and source_id not in owned:
|
||||
source_id = source_file_id.get(file_path)
|
||||
if source_id is None:
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
from pathlib import Path
|
||||
from graphify.extract import extract
|
||||
|
||||
|
||||
def run(tmp_path, sources):
|
||||
paths = []
|
||||
for name, text in sources.items():
|
||||
p = tmp_path / name
|
||||
p.parent.mkdir(parents=True, exist_ok=True)
|
||||
p.write_text(text)
|
||||
paths.append(p)
|
||||
return extract(paths, root=tmp_path, cache_root=tmp_path, parallel=False)
|
||||
|
||||
|
||||
def triples(g):
|
||||
return {(e["source"], e["target"], e["relation"]) for e in g["edges"]}
|
||||
|
||||
|
||||
def test_callback_local_method_preserved_named_function_local_omitted(tmp_path):
|
||||
g = run(
|
||||
tmp_path,
|
||||
{
|
||||
"base.ts": "export class Base {}\nexport interface Result {}\n",
|
||||
"local.ts": 'import {Base,Result} from "./base.js";\nexport function make() { class Hidden extends Base { method(): Result { return {}; } } return Hidden; }\nit("x", () => { class Visible extends Base { method(): Result { return {}; } } });\n',
|
||||
},
|
||||
)
|
||||
ids = {n["id"] for n in g["nodes"]}
|
||||
edges = triples(g)
|
||||
assert ("local_visible_method", "base_result", "references") in edges
|
||||
assert ("local_visible", "base_base", "inherits") in edges
|
||||
assert not any(e["source"] not in ids for e in g["edges"])
|
||||
assert not any("hidden" in e["source"] for e in g["edges"])
|
||||
|
||||
|
||||
def test_same_basename_sources_do_not_suppress_other_callers(tmp_path):
|
||||
g = run(
|
||||
tmp_path,
|
||||
{
|
||||
"base.ts": "export class Base {}\nexport interface Result {}\n",
|
||||
"a/local.ts": 'import {Base,Result} from "../base.js";\nexport function make() { class Shared extends Base { method(): Result { return {}; } } return Shared; }\n',
|
||||
"b/local.ts": 'import {Base,Result} from "../base.js";\nit("x", () => { class Shared extends Base { method(): Result { return {}; } } });\n',
|
||||
},
|
||||
)
|
||||
ids = {n["id"] for n in g["nodes"]}
|
||||
edges = triples(g)
|
||||
assert ("b_local_shared_method", "base_result", "references") in edges
|
||||
assert ("b_local_shared", "base_base", "inherits") in edges
|
||||
assert not any(e["source"] not in ids for e in g["edges"])
|
||||
|
||||
|
||||
def test_abstract_method_omissions_do_not_drop_concrete_method(tmp_path):
|
||||
g = run(
|
||||
tmp_path,
|
||||
{
|
||||
"base.ts": "export interface Result {}\n",
|
||||
"local.ts": 'import {Result} from "./base.js";\nexport abstract class Abstract { abstract method(): Result; concrete(): Result { return {}; } }\n',
|
||||
},
|
||||
)
|
||||
ids = {n["id"] for n in g["nodes"]}
|
||||
assert ("local_abstract_concrete", "base_result", "references") in triples(g)
|
||||
assert not any(e["source"] not in ids for e in g["edges"])
|
||||
Reference in New Issue
Block a user