Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions code_review_graph/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -3108,6 +3108,38 @@ def _do_resolve_module(
# ``dart:core`` / ``dart:async`` etc. are SDK libraries we do
# not track; fall through to return None.

elif language == "java":
# ``import com.example.pkg.ClassName;`` — convert dot-notation
# to a relative path and walk up from the caller's directory to
# find the source root. Wildcards (``import pkg.*``) and static
# member imports (``import static pkg.Class.member``) that don't
# resolve as-is are retried after dropping the last segment
# (the member name).
if module.endswith(".*"):
return None # wildcard import — can't resolve to one file
rel_path = module.replace(".", "/") + ".java"
current = caller_dir
while True:
target = current / rel_path
if target.is_file():
return str(target.resolve())
if current == current.parent:
break
current = current.parent
# Static import: ``pkg.Class.member`` — strip member, try again
dot = module.rfind(".")
if dot > 0:
class_module = module[:dot]
rel_path2 = class_module.replace(".", "/") + ".java"
current = caller_dir
while True:
target = current / rel_path2
if target.is_file():
return str(target.resolve())
if current == current.parent:
break
current = current.parent

return None

def _find_dart_pubspec_root(
Expand Down
75 changes: 75 additions & 0 deletions tests/test_multilang.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,81 @@ def test_finds_calls(self):
assert len(calls) >= 3


class TestJavaImportResolution:
"""Test that Java imports are resolved to absolute file paths."""

def test_resolves_project_import(self, tmp_path):
"""Import of a project class resolves to its .java file."""
# Create a mini Java project with two packages
auth = tmp_path / "src/main/java/com/example/auth"
auth.mkdir(parents=True)
(auth / "User.java").write_text(
"package com.example.auth;\npublic class User {}\n"
)
svc = tmp_path / "src/main/java/com/example/service"
svc.mkdir(parents=True)
(svc / "App.java").write_text(
"package com.example.service;\n"
"import com.example.auth.User;\n"
"public class App {}\n"
)

parser = CodeParser()
_, edges = parser.parse_file(svc / "App.java")
imports = [e for e in edges if e.kind == "IMPORTS_FROM"]
assert len(imports) == 1
assert imports[0].target == str((auth / "User.java").resolve())

def test_jdk_import_stays_unresolved(self):
"""JDK imports have no local file and remain as raw strings."""
parser = CodeParser()
_, edges = parser.parse_file(FIXTURES / "SampleJava.java")
imports = [e for e in edges if e.kind == "IMPORTS_FROM"]
# All imports in SampleJava.java are java.util.* (JDK)
for e in imports:
assert not e.target.endswith(".java"), (
f"JDK import should not resolve to a file: {e.target!r}"
)

def test_static_import_resolves_to_class(self, tmp_path):
"""Static import of a member resolves to the enclosing class file."""
pkg = tmp_path / "src/main/java/com/example/util"
pkg.mkdir(parents=True)
(pkg / "Helper.java").write_text(
"package com.example.util;\n"
"public class Helper { public static int MAX = 1; }\n"
)
app_dir = tmp_path / "src/main/java/com/example/app"
app_dir.mkdir(parents=True)
(app_dir / "App.java").write_text(
"package com.example.app;\n"
"import static com.example.util.Helper.MAX;\n"
"public class App {}\n"
)

parser = CodeParser()
_, edges = parser.parse_file(app_dir / "App.java")
imports = [e for e in edges if e.kind == "IMPORTS_FROM"]
assert len(imports) == 1
assert imports[0].target == str((pkg / "Helper.java").resolve())

def test_wildcard_import_stays_unresolved(self, tmp_path):
"""Wildcard imports cannot resolve to a single file."""
app_dir = tmp_path / "src/main/java/com/example"
app_dir.mkdir(parents=True)
(app_dir / "App.java").write_text(
"package com.example;\n"
"import java.util.*;\n"
"public class App {}\n"
)

parser = CodeParser()
_, edges = parser.parse_file(app_dir / "App.java")
imports = [e for e in edges if e.kind == "IMPORTS_FROM"]
assert len(imports) == 1
assert imports[0].target == "java.util.*"


class TestCParsing:
def setup_method(self):
self.parser = CodeParser()
Expand Down