Skip to content

feat(elixir): index Elixir with Phoenix, Plug and Ecto flow coverage - #1648

Open
ferrine wants to merge 1 commit into
colbymchenry:mainfrom
ferrine:feat/elixir-support
Open

feat(elixir): index Elixir with Phoenix, Plug and Ecto flow coverage#1648
ferrine wants to merge 1 commit into
colbymchenry:mainfrom
ferrine:feat/elixir-support

Conversation

@ferrine

@ferrine ferrine commented Aug 30, 2026

Copy link
Copy Markdown

Elixir's grammar has no declaration node types — defmodule, def, alias, an Ecto schema and a Phoenix route are all the same call node, told apart only by the text of the target identifier. So the generic node-type ladder has nothing to match and every construct is dispatched through the visitNode hook, which also owns call extraction (visitFunctionBody does not invoke the hook, and Elixir call sites live inside bodies).

Three things beyond plain symbol extraction:

  • Alias expansion — alias Foo.{A, B}, as:, __MODULE__ and the implicit nested-module alias — so a call written Repo.insert(...) carries MyApp.Repo::insert and resolves by exact qualified name instead of by bare name.
  • Clause merging by (module, name, arity). The GenServer idiom spells a multi-clause function as repeated defs, which otherwise index as one identical node per clause and scatter every caller edge.
  • Macro-argument dispatch, closed end to end rather than half-bridged: Phoenix routes become route nodes linked to the controller action they dispatch to (nested scope path + alias composition, resources expansion, forward, live); plug :atom / plug Module link to the function or call/2 that runs; Ecto schema fields and association target modules are extracted.

Kernel special forms (case, if, quote, raise, …) are suppressed as call refs — they are syntax, and on plug they were ~1,900 refs that could only ever resolve wrongly.

Validated on plug (S), phoenix (M) and firezone (L, 2,111 files): extraction PASS on all three, 137/144 route→action edges resolved at 100% precision on firezone, node/edge counts stable across re-index and incremental sync. Agent A/B (sonnet/high, 2 runs/arm) takes Read to ~0 and cuts tool calls 4-8x on every repo; wall-clock only improves on the large repo, which is recorded honestly in the coverage playbook.

Grammar is the tree-sitter-wasms tree-sitter-elixir build (ABI 14), health-checked — no vendored wasm needed.

Claude-Session: https://claude.ai/code/session_013Bsi9EH64kMisnik4E1gf7

Elixir's grammar has no declaration node types — `defmodule`, `def`,
`alias`, an Ecto `schema` and a Phoenix route are all the same `call`
node, told apart only by the text of the target identifier. So the
generic node-type ladder has nothing to match and every construct is
dispatched through the visitNode hook, which also owns call extraction
(visitFunctionBody does not invoke the hook, and Elixir call sites live
inside bodies).

Three things beyond plain symbol extraction:

- Alias expansion — `alias Foo.{A, B}`, `as:`, `__MODULE__` and the
  implicit nested-module alias — so a call written `Repo.insert(...)`
  carries `MyApp.Repo::insert` and resolves by exact qualified name
  instead of by bare name.
- Clause merging by (module, name, arity). The GenServer idiom spells a
  multi-clause function as repeated `def`s, which otherwise index as one
  identical node per clause and scatter every caller edge.
- Macro-argument dispatch, closed end to end rather than half-bridged:
  Phoenix routes become `route` nodes linked to the controller action
  they dispatch to (nested `scope` path + alias composition, `resources`
  expansion, `forward`, `live`); `plug :atom` / `plug Module` link to the
  function or `call/2` that runs; Ecto schema fields and association
  target modules are extracted.

Kernel special forms (`case`, `if`, `quote`, `raise`, …) are suppressed
as call refs — they are syntax, and on plug they were ~1,900 refs that
could only ever resolve wrongly.

Validated on plug (S), phoenix (M) and firezone (L, 2,111 files):
extraction PASS on all three, 137/144 route→action edges resolved at
100% precision on firezone, node/edge counts stable across re-index and
incremental sync. Agent A/B (sonnet/high, 2 runs/arm) takes Read to ~0
and cuts tool calls 4-8x on every repo; wall-clock only improves on the
large repo, which is recorded honestly in the coverage playbook.

Grammar is the tree-sitter-wasms tree-sitter-elixir build (ABI 14),
health-checked — no vendored wasm needed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Bsi9EH64kMisnik4E1gf7
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