Skip to content
Closed
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
92 changes: 91 additions & 1 deletion code_review_graph/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,9 @@ class EdgeInfo:
"zig": ["container_declaration"],
"powershell": ["class_statement"],
"julia": ["struct_definition", "abstract_definition"],
# GDScript: inner classes use ``class Name:`` (class_definition); the
# file-level ``class_name Name`` gives the script itself an identity.
"gdscript": ["class_definition", "class_name_statement"],
}

_FUNCTION_TYPES: dict[str, list[str]] = {
Expand Down Expand Up @@ -222,6 +225,8 @@ class EdgeInfo:
"function_definition",
"short_function_definition",
],
# GDScript: ``func name(args) -> ReturnType:`` — includes ``static func``.
"gdscript": ["function_definition"],
}

_IMPORT_TYPES: dict[str, list[str]] = {
Expand Down Expand Up @@ -262,6 +267,11 @@ class EdgeInfo:
"powershell": [],
# Julia: import/using are import_statement nodes.
"julia": ["import_statement", "using_statement"],
# GDScript has no ``import`` keyword. The closest analogue is
# ``extends OtherClass`` / ``extends "res://path.gd"``, which establishes
# a hard dependency on the parent script. preload()/load() calls remain
# as ordinary CALLS edges.
"gdscript": ["extends_statement"],
}

_CALL_TYPES: dict[str, list[str]] = {
Expand Down Expand Up @@ -300,6 +310,9 @@ class EdgeInfo:
"zig": ["call_expression", "builtin_call_expr"],
"powershell": ["command_expression"],
"julia": ["call_expression"],
# GDScript: bare calls produce ``call``; ``obj.method()`` is an
# ``attribute`` node whose right-hand side is an ``attribute_call``.
"gdscript": ["call", "attribute_call"],
}

# Patterns that indicate a test function
Expand Down Expand Up @@ -3753,6 +3766,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 Expand Up @@ -4002,6 +4047,16 @@ def _get_name(self, node, language: str, kind: str) -> Optional[str]:
for child in node.children:
if child.type == "field_identifier":
return child.text.decode("utf-8", errors="replace")
# Java methods: tree-sitter-java puts type_identifier or generic_type
# (return type) before identifier (method name). Must run before
# the generic loop, which would match the return type's
# type_identifier (e.g. "String", "ConfigBean").
# Constructors are fine — they have no return type node.
# Kotlin is unaffected: its syntax places the name before the type.
if language == "java" and node.type == "method_declaration":
for child in node.children:
if child.type == "identifier":
return child.text.decode("utf-8", errors="replace")
# Swift extensions: name is inside user_type > type_identifier
# (e.g. `extension MyClass: Protocol { ... }`)
if language == "swift" and node.type == "class_declaration":
Expand Down Expand Up @@ -4095,7 +4150,23 @@ def _get_bases(self, node, language: str, source: bytes) -> list[str]:
for arg in child.children:
if arg.type in ("identifier", "attribute"):
bases.append(arg.text.decode("utf-8", errors="replace"))
elif language in ("java", "csharp", "kotlin"):
elif language == "java":
# Java: superclass and super_interfaces wrap the keyword
# (extends/implements) around type_identifier children.
# Taking .text would include the keyword (e.g. "implements Foo").
# Drill into the children to extract bare type names.
for child in node.children:
if child.type == "superclass":
for sub in child.children:
if sub.type in ("type_identifier", "generic_type"):
bases.append(sub.text.decode("utf-8", errors="replace"))
elif child.type == "super_interfaces":
for sub in child.children:
if sub.type == "type_list":
for ident in sub.children:
if ident.type in ("type_identifier", "generic_type"):
bases.append(ident.text.decode("utf-8", errors="replace"))
elif language in ("csharp", "kotlin"):
# Look for superclass/interfaces in extends/implements clauses
for child in node.children:
if child.type in (
Expand Down Expand Up @@ -4295,6 +4366,25 @@ def _find_string_literal(n) -> Optional[str]:
val = _find_string_literal(node)
if val:
imports.append(val)
elif language == "gdscript":
# ``extends Node`` → type > identifier("Node")
# ``extends "res://path.gd"`` → string literal
# ``extends SomeClass.Nested`` → type node (keep full text)
for child in node.children:
if child.type == "type":
txt = child.text.decode("utf-8", errors="replace").strip()
if txt:
imports.append(txt)
elif child.type == "string":
val = child.text.decode("utf-8", errors="replace").strip("'\"")
if val:
imports.append(val)
elif child.type == "identifier":
# Fallback: some grammar variants expose the parent type as
# a bare identifier next to the ``extends`` keyword.
txt = child.text.decode("utf-8", errors="replace")
if txt and txt != "extends":
imports.append(txt)
else:
# Fallback: just record the text
imports.append(text)
Expand Down
2 changes: 1 addition & 1 deletion tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def fake_run(**kwargs):

monkeypatch.setattr(crg_main.mcp, "run", fake_run)
crg_main.main(repo_root=None)
assert calls == [{"transport": "stdio"}]
assert calls == [{"transport": "stdio", "show_banner": False}]

def test_http_calls_mcp_run_with_host_port(self, monkeypatch):
calls: list[dict] = []
Expand Down