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
33 changes: 33 additions & 0 deletions graphify/extractors/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -2559,6 +2559,39 @@ def _php_emit_base(base_name: str, rel: str, at_line: int) -> None:
add_edge(class_nid, target, "references", line,
context="generic_arg")

# Kotlin-specific: primary_constructor class_parameters carry
# constructor-injected fields (`private val x: Foo` - the idiomatic
# Hilt/DI pattern). Mirrors the Scala class_parameters handling
# below; without this, every constructor-injected dependency was
# invisible to the graph (only body-level property_declaration
# vals were captured, and only delegation_specifiers for supertypes).
if config.ts_module == "tree_sitter_kotlin":
for child in node.children:
if child.type != "primary_constructor":
continue
for pc in child.children:
if pc.type != "class_parameters":
continue
for cp in pc.children:
if cp.type != "class_parameter":
continue
type_node = next(
(t for t in cp.children
if t.type in ("user_type", "nullable_type", "type_reference")),
None,
)
if type_node is None:
continue
cp_line = cp.start_point[0] + 1
refs: list[tuple[str, str]] = []
_kotlin_collect_type_refs(type_node, source, False, refs)
for ref_name, role in refs:
ctx = "generic_arg" if role == "generic_arg" else "field"
target_nid = ensure_named_node(ref_name, cp_line)
if target_nid != class_nid:
add_edge(class_nid, target_nid, "references",
cp_line, context=ctx)

# Ruby: `class Dog < Animal` puts the base class in the `superclass`
# field (a `<` token followed by a constant or scope_resolution).
# There was no Ruby branch, so every Ruby inherits edge was dropped.
Expand Down
14 changes: 14 additions & 0 deletions tests/test_languages.py
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,20 @@ def test_kotlin_user_types_still_emit_references():
assert ("run", "DataProcessor") in _edge_labels(r, "references", "parameter_type")


def test_kotlin_primary_constructor_val_emits_references():
# `class HttpClient(private val config: Config)` - a constructor-injected
# field declared directly in the primary constructor's class_parameters,
# as opposed to a body-level `val`/`var` (property_declaration, already
# covered by test_kotlin_user_types_still_emit_references above). This is
# the idiomatic Kotlin/Hilt DI pattern (`@Inject constructor(private val
# x: Foo)`) and was previously dropped entirely: the Kotlin branch only
# walked delegation_specifiers (supertypes) and property_declaration
# (body vals), never primary_constructor/class_parameters, so every
# constructor-injected dependency was invisible to the graph.
r = extract_kotlin(FIXTURES / "sample.kt")
assert ("HttpClient", "Config") in _edge_labels(r, "references", "field")


# ── Scala ─────────────────────────────────────────────────────────────────────

def test_scala_no_error():
Expand Down