forked from safishamsi/graphify
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_java_type_resolution.py
More file actions
171 lines (145 loc) · 6.71 KB
/
Copy pathtest_java_type_resolution.py
File metadata and controls
171 lines (145 loc) · 6.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
from __future__ import annotations
from pathlib import Path
from graphify.build import build_from_json
from graphify.extract import extract
def _write(path: Path, text: str) -> Path:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(text, encoding="utf-8")
return path
def _node_by_id(result: dict, nid: str) -> dict | None:
return next((n for n in result["nodes"] if n.get("id") == nid), None)
def test_java_cross_file_implements_resolves_to_real_def(tmp_path: Path):
# #1318: a cross-file `implements` must land on the real interface def, not a
# bare no-source shadow stub.
iface = _write(
tmp_path / "src/com/x/handler/AIResponseHandler.java",
"package com.x.handler;\npublic interface AIResponseHandler {}\n",
)
impl = _write(
tmp_path / "src/com/x/service/DifyAiServiceImpl.java",
"package com.x.service;\n"
"import com.x.handler.AIResponseHandler;\n"
"public class DifyAiServiceImpl implements AIResponseHandler {}\n",
)
result = extract([iface, impl], cache_root=tmp_path)
implements = [e for e in result["edges"] if e["relation"] == "implements"]
assert implements, "expected an implements edge"
for e in implements:
tgt = _node_by_id(result, e["target"])
assert tgt is not None, f"implements target {e['target']} is not a node"
# The target must be the real definition (has a source_file), not a shadow stub.
assert tgt.get("source_file"), f"implements landed on shadow stub {e['target']}"
assert "handler" in tgt["source_file"]
def test_java_ambiguous_implements_disambiguated_by_import(tmp_path: Path):
# #1318 core case: two interfaces with the SAME simple name in different
# packages. The importing file's `import` must pick the right one, and no
# orphan shadow node may remain.
a = _write(
tmp_path / "src/com/a/handler/AIResponseHandler.java",
"package com.a.handler;\npublic interface AIResponseHandler {}\n",
)
b = _write(
tmp_path / "src/com/b/handler/AIResponseHandler.java",
"package com.b.handler;\npublic interface AIResponseHandler {}\n",
)
impl = _write(
tmp_path / "src/com/x/service/Impl.java",
"package com.x.service;\n"
"import com.a.handler.AIResponseHandler;\n"
"public class Impl implements AIResponseHandler {}\n",
)
result = extract([a, b, impl], cache_root=tmp_path)
# No bare shadow stub for the interface should survive.
shadow = [
n for n in result["nodes"]
if n.get("label") == "AIResponseHandler" and not n.get("source_file")
]
assert not shadow, f"orphan shadow node(s) remain: {[n['id'] for n in shadow]}"
implements = [e for e in result["edges"] if e["relation"] == "implements"]
assert len(implements) == 1
tgt = _node_by_id(result, implements[0]["target"])
assert tgt is not None and tgt.get("source_file")
# Must resolve to the imported package (com/a), not com/b.
assert "com/a/handler" in tgt["source_file"]
assert "com/b/handler" not in tgt["source_file"]
def test_java_implements_edge_survives_build(tmp_path: Path):
# #1318: the re-pointed edge must connect real nodes after graph assembly,
# so the interface is not classified as an isolated community.
iface = _write(
tmp_path / "src/com/x/handler/Handler.java",
"package com.x.handler;\npublic interface Handler {}\n",
)
impl = _write(
tmp_path / "src/com/x/service/Svc.java",
"package com.x.service;\n"
"import com.x.handler.Handler;\n"
"public class Svc implements Handler {}\n",
)
result = extract([iface, impl], cache_root=tmp_path)
G = build_from_json(result, directed=True)
impl_edges = [
(u, v) for u, v, d in G.edges(data=True) if d.get("relation") == "implements"
]
assert impl_edges
# The interface node has an incoming implements edge (not isolated).
assert any(G.in_degree(v) >= 1 for _, v in impl_edges)
def _label_edges(result: dict, relations):
by_id = {n["id"]: n.get("label", "") for n in result["nodes"]}
return {
(by_id.get(e["source"], ""), e["relation"], by_id.get(e["target"], ""))
for e in result["edges"]
if e.get("relation") in relations
}
def test_java_record_becomes_type_node(tmp_path: Path):
# #1373: a Java `record` must produce a first-class type node (with a
# `contains` edge from its file), not be left as an isolated file node.
rec = _write(
tmp_path / "Foo.java",
"package com.app;\npublic record Foo(int x, String y) {}\n",
)
result = extract([rec], cache_root=tmp_path)
foo = [n for n in result["nodes"]
if n.get("label") == "Foo" and n.get("source_file")]
assert foo, "record Foo should be a type node, not just the file node"
contains = _label_edges(result, {"contains"})
assert ("Foo.java", "contains", "Foo") in contains
def test_java_record_implements_interface(tmp_path: Path):
# Records reuse class interface handling: `record Foo implements I` emits it.
iface = _write(tmp_path / "I.java", "package com.app;\npublic interface I {}\n")
rec = _write(
tmp_path / "Foo.java",
"package com.app;\npublic record Foo(int x) implements I {}\n",
)
result = extract([iface, rec], cache_root=tmp_path)
implements = [e for e in result["edges"] if e["relation"] == "implements"]
assert implements, "record implementing an interface should emit an implements edge"
def test_java_cross_file_constructor_call_resolves(tmp_path: Path):
# #1373: `new Foo(...)` in a method body must produce a cross-file edge to the
# Foo definition. Foo is NOT used as a return type here, so the edge can only
# come from the constructor call (object_creation_expression), not return-type
# handling.
foo = _write(
tmp_path / "Foo.java",
"package com.app;\npublic record Foo(int x, String y) {}\n",
)
caller = _write(
tmp_path / "Helper.java",
"package com.app;\n"
"public class Helper {\n"
" public void build() {\n"
" Object o = new Foo(1, \"a\");\n"
" System.out.println(o);\n"
" }\n"
"}\n",
)
result = extract([foo, caller], cache_root=tmp_path)
foo_id = next(n["id"] for n in result["nodes"]
if n.get("label") == "Foo" and n.get("source_file"))
call_targets = {
e["target"] for e in result["edges"]
if e.get("relation") in ("calls", "references")
}
assert foo_id in call_targets, "new Foo(...) should produce a calls/references edge to Foo"
# Survives graph construction (target is a real node).
g = build_from_json(result)
assert foo_id in set(g.nodes())