feat(code-graph): add Elixir language support to the v2 code indexer

What does this MR do and why?

Adds Elixir to the v2 code indexer so .ex and .exs files are indexed.

As the issue warns, tree-sitter-elixir parses virtually every construct as a call node with the keyword as the target identifier, so scope rules can't be flat kind matches. This MR uses when(...) predicates on the call target text (the issue's suggested alternative to scope_fn):

  • defmodule -> Module definition. Dotted names (Foo.Bar) are a single alias token, so they land as one FQN-aligned scope segment.
  • def/defp -> Function definition. Head extraction covers parenthesized, zero-arity, keyword do:, and when-guard forms. Arity overloads collapse to name-only FQNs per the issue.
  • alias/import/require/use -> CanonicalImport entries via an on_import hook, including as: renames and multi-alias expansion (Foo.{Bar, Baz}). Imports are wildcards keyed on the full module path so bare calls to imported functions resolve.
  • Remote (Baz.hello(name)), aliased, imported, and local calls resolve to Calls edges cross-file.

Since definitions, control flow (if/case/with), and bare field access (user.name) all parse as calls too, the reference rules carry exclusions for def heads, Kernel.SpecialForms, lowercase-receiver field access, and module attribute contents; each exclusion has a negative unit test.

Closes #766 (closed)

Acceptance criteria

  • tree-sitter-elixir = { version = "0.3.5", optional = true } + feature flag
  • SupportLang::Elixir + LanguageExt arm
  • Elixir row in define_languages!: support_lang: Elixir, extensions: ["ex", "exs"], exclude: [], separator: ".", names: ["elixir"]
  • ElixirDsl + ElixirRules in langs/generic/elixir.rs
  • Module registration + registry entry
  • Fixtures: module + function defs, alias/import/use imports, cross-module calls + yaml_test! registrations

Out of scope (per issue) / follow-ups

Arity in FQNs, macro expansion, protocols, behaviour resolution, and the pin operator are out of scope per the issue. Also left for follow-ups: import only:/except: restriction fidelity (restricted imports currently over-approximate to the whole module), defdelegate definitions, bare zero-arity local calls (parse as plain identifiers), and the user-facing docs language tables. In addition, some documentation (the README and docs/source language lists) could be improved to add that Elixer is now supported.

Testing

cargo nextest run -p code-graph -E 'test(elixir)'                   # 8 unit tests
cargo nextest run -p integration-tests-codegraph -E 'test(elixir)'  # 3 fixture suites
cargo nextest run -p integration-tests-codegraph                    # full suite

Performance Analysis

No performance regression expected: non-Elixir indexing paths are untouched (extension/kind-keyed dispatch), and Elixir files were previously skipped entirely, so their indexing cost is new coverage. Per-node predicate cost in Elixir is bounded and follows the Ruby precedent for grammars where rules fire on common node kinds.

  • This merge request does not introduce any performance regression. If a performance regression is expected, explain why.

Agent context — long-form analysis, file-by-file walkthroughs, profiler output, alternatives considered

  • feat(code-graph): add Elixir language support to the v2 code indexer

Index .ex and .exs files with the generic tree-sitter DSL pipeline.In tree-sitter-elixir virtually every construct parses as a call node with the keyword as the target identifier, so scope and reference rules dispatch on the call target text via when() predicates:

  • defmodule -> Module definitions; dotted names (Foo.Bar) are a single alias token, so they arrive as one FQN-aligned segment
  • def/defp -> Function definitions, covering parenthesized, zero-arity, keyword do:, and when-guard heads; arity overloads collapse to name-only FQNs
  • alias/import/require/use -> CanonicalImport entries via an on_import hook, including as: renames and multi-alias expansion; imports are wildcards keyed on the full module path so bare calls to imported functions resolve
  • remote (Baz.hello), aliased, imported, and local calls resolve to Calls edges

References exclude constructs that parse as calls but are not: def heads, Kernel.SpecialForms (if/case/with/...), bare field access with lowercase receivers (user.name), and module attribute contents.Out of scope for v1 (per issue): arity in FQNs, macro expansion, protocols, behaviours, pin operator.

Merge request reports

Loading