Skip to content

fix(parser): extract Ruby method calls as CALLS edges#681

Open
beagleknight wants to merge 1 commit into
tirth8205:mainfrom
beagleknight:fix/ruby-call-graph-extraction
Open

fix(parser): extract Ruby method calls as CALLS edges#681
beagleknight wants to merge 1 commit into
tirth8205:mainfrom
beagleknight:fix/ruby-call-graph-extraction

Conversation

@beagleknight

Copy link
Copy Markdown

fix(parser): extract Ruby method calls as CALLS edges

Problem

Ruby produces zero CALLS edges — callers_of, callees_of, impact radius,
and caller-based dead-code are all blind on Ruby codebases. Definitions parse fine
(classes/methods/CONTAINS), but the call graph is empty.

Reproduce on any Ruby repo:

# app.rb
class Foo
  def greet
    helper(1)      # expect CALLS Foo.greet -> Foo.helper
  end
  def helper(n) = n
end

query callers_of helper → 0 results.

Root cause

Two issues in parser.py:

  1. Dispatch swallows calls. Ruby's Tree-sitter grammar reuses the call node
    type for both require/require_relative and ordinary method invocation.
    _extract_from_tree checks import node types before call node types and
    continues unconditionally, so every Ruby call is consumed by the import
    branch (which only emits an edge when the text contains require) and never
    reaches call extraction.
  2. No Ruby case in _get_call_name. Even when a call node is reached, there
    was no Ruby handling, so member calls (User.new, @users.size) produced no
    call name and no edge.

Fix

  • _extract_imports now returns bool (whether it emitted an import edge). The
    dispatcher only skips call extraction when an import was actually produced, so
    non-import call nodes fall through to call handling. This is a general
    improvement for any grammar that overloads a node type for imports and calls.
  • _get_call_name handles Ruby call/method_call via the node's method field,
    covering paren calls (save(user)), command calls (puts ...), and member calls
    (User.new, @users.size).

Result

Same-file / same-class calls resolve to the defining method node, so
callers_of/callees_of work within a file:

create_user  ->  sample.rb::UserRepository.save   (resolved)
create_user  ->  new, size                         (bare, cross-type)

Scope / limitations

  • Cross-file, receiver-typed targets (Bar.new.runrun) remain bare, exactly
    like the existing behavior for unresolved JS/TS calls. Precise typed resolution for
    Ruby is best handled by a follow-up Sorbet-backed resolver (analogous to the
    existing jedi/Spring resolvers).
  • Bare implicit-self calls with no parens (a lone helper) parse as identifier
    rather than call, so they are not captured here (also a Sorbet-resolver concern —
    it can disambiguate a send from a local variable).

Tests

  • Adds tests/test_multilang.py::TestRubyParsing::test_finds_calls (fails on main
    with 0 CALLS; passes with this change).
  • Full suite: 1962 passed, 5 skipped, 2 xpassed; ruff check clean.

Ruby produced zero CALLS edges. Its grammar reuses the `call` node type
for both `require`/`require_relative` and ordinary method invocation, and
the dispatcher checked imports before calls and always `continue`d — so
every Ruby `call` was consumed by the import branch and method calls were
silently dropped. Even when reached, `_get_call_name` had no Ruby case, so
member calls yielded no name.

- `_extract_imports` now returns whether it emitted an import edge; the
  dispatcher only skips call extraction when an import was actually
  produced, so non-import `call` nodes fall through to call handling.
- `_get_call_name` handles Ruby `call`/`method_call` via the node's
  `method` field, covering paren calls (`save(user)`), command calls
  (`puts ...`) and member calls (`User.new`, `@users.size`).

Same-file/same-class calls now resolve to the defining method node, so
callers_of/callees_of work within a file. Bare implicit-self calls with no
parens parse as `identifier` (not `call`) and remain out of scope (future
work: a Sorbet-backed resolver for typed cross-file resolution).

Adds TestRubyParsing::test_finds_calls.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant