You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
While auditing Elixir extraction against a real 829-file Phoenix application, I found four defects that are not in the D1–D8 list of #1239 / docs/elixir-lsp/PLAN.md. Two of them contradict things that plan records as working today, so I am raising them here before they cost @holsee debugging time.
I also have a fork with a broader Elixir grammar-layer rewrite. I am not proposing that upstream — see "Overlap" at the bottom. This issue is about the four defects and about making my other work available if it is useful.
The four defects
1. first_string_arg is never populated for any Elixir call
tree-sitter-elixir attaches no field name to a call's arguments node — its whole field set is key, left, operand, operator, quoted_start, quoted_end, right, target, value. handle_calls() reads ts_node_child_by_field_name(node, "arguments"), which is therefore always null for Elixir.
Consequence: route paths, HTTP/async service URLs and config keys are all dead for Elixir, silently. extract_defs.c has always carried a positional fallback (elixir_call_args()) for exactly this reason; the call side never got one.
2. Phoenix channel extraction is unreachable in both branches
PLAN.md §1.2 lists "Phoenix channels/PubSub (works today) — extract_channels.c:875-970". It does not work — it has never emitted a single Channel node:
the emit path (elixir_process_call) reads the same nonexistent arguments field, so elixir_emit_second_arg() returns early every time
the listener path is dispatched by strcmp(kind, "def") == 0, and there is no def node type in the grammar — Elixir definitions are call nodes. elixir_process_function_def() is dead code, and it also reads name and parameters fields the grammar does not define.
Fixed and verified against Phoenix.PubSub.broadcast/subscribe, bare push/broadcast, and two handle_in clauses including a guarded one.
3. Phoenix routers mint zero Route nodes
PLAN.md §1.2 lists "Phoenix/Oban/Broadway service patterns (work today)". Oban and Broadway do. Phoenix routes do not.
Every entry in route_reg_suffixes is prefixed . or :: (.get, ::get, .MapGet) because it is matched against the tail of a qualified callee. Phoenix registers with bare macros:
get"/wallets",WalletController,:index
so the callee is exactly get and cbm_service_pattern_route_method() returns NULL. The resolved-QN route-library table cannot help either — Phoenix.Router lives in deps/ and never resolves in-tree.
Fix adds an exact-match bare table (get, post, put, patch, delete, head, options, live, resources, forward), gated to Elixir and consulted before resolution. Exact and not suffix on purpose: a suffix rule would classify widget, target and budget as GET routes. extract_handler_arg() also needed to accept Elixir's alias/dot handler node, without which no route gets a HANDLES edge.
4. Elixir functions carry no complexity, fingerprint or line count
Every Elixir Function ships complexity = 0, cognitive = 0, lines = 0 and no MinHash fingerprint, so Elixir is invisible to complexity/bottleneck queries and never participates in SIMILAR_TO — while the query_graph tool description states that "every Function and Method node carries queryable complexity properties".
cbm_compute_complexity() matches on node type, and Elixir has no branching node types: if, unless, case, cond, with, for, try and receive are ordinary call nodes distinguished by the identifier in callee position, and every arm is a stab_clause. That is also why elixir_branch_types = {"call"} in lang_specs.c has never been usable — it would score every function call as a decision.
Measured effect
Fresh index of an 829-file Phoenix application, plus elixir-lang/elixir (6,346 .ex functions) for the complexity numbers:
before
after
Route nodes
0
180
HANDLES edges
0
153
Functions with complexity > 0 — elixir-lang/elixir
4,002 (63%)
4,753 (75%)
Functions with complexity > 0 — Phoenix app
2,023 (49%)
2,500 (61%)
Functions with a fingerprint
7 / 4,504
most
For scale, the TypeScript and Python sources in that same Phoenix repo score 20 of 386 (5%).
My fork also carries a grammar-layer rewrite that overlaps @holsee's D1–D8 almost item for item: guarded def heads (D1), enclosing-function attribution (D2), name/arity identity (D3 — resolved as an arities def field, keeping the QN string format unchanged, which is the option PLAN.md flags as preferable), the full def* family (D4), imports nested in defmodule (D5), nested module QNs (D6), and stronger Elixir test contracts (D8 — 18 extraction tests).
I am deliberately not opening PRs for any of that.@holsee has been staging this since July across 15+ branches and it would be a competing implementation, which helps nobody.
If it is useful as reference material — a second implementation to diff against, or tests to lift — it is at henry-hz/codebase-memory-mcp, branch fix/elixir-routes-channels-complexity-heex. Take anything, or ignore it entirely; no attribution needed. One caveat if you do look: the dotted-module import resolution in pass_pkgmap.c there has no test and touches a candidate-ordering path shared with Lua and R, so treat that part as unproven.
Proposed delivery
Four small PRs, each independent except where noted, all signed off and gated:
Happy to reorder, split further, fold any of them into @holsee's phases instead, or drop them if they conflict with the staged plan. Also happy to hold 2–4 until there is appetite.
Separately: .heex / .eex templates are not indexed at all today, so the LiveView template layer is invisible. That needs a vendored grammar, which CONTRIBUTING says requires prior approval — I have it working locally and will open a separate issue rather than assume.
Summary
While auditing Elixir extraction against a real 829-file Phoenix application, I found four defects that are not in the D1–D8 list of #1239 /
docs/elixir-lsp/PLAN.md. Two of them contradict things that plan records as working today, so I am raising them here before they cost @holsee debugging time.I also have a fork with a broader Elixir grammar-layer rewrite. I am not proposing that upstream — see "Overlap" at the bottom. This issue is about the four defects and about making my other work available if it is useful.
The four defects
1.
first_string_argis never populated for any Elixir calltree-sitter-elixirattaches no field name to a call's arguments node — its whole field set iskey, left, operand, operator, quoted_start, quoted_end, right, target, value.handle_calls()readsts_node_child_by_field_name(node, "arguments"), which is therefore always null for Elixir.Consequence: route paths, HTTP/async service URLs and config keys are all dead for Elixir, silently.
extract_defs.chas always carried a positional fallback (elixir_call_args()) for exactly this reason; the call side never got one.→ PR #1721 (open, 54 lines, additions only).
2. Phoenix channel extraction is unreachable in both branches
PLAN.md§1.2 lists "Phoenix channels/PubSub (works today) —extract_channels.c:875-970". It does not work — it has never emitted a single Channel node:elixir_process_call) reads the same nonexistentargumentsfield, soelixir_emit_second_arg()returns early every timestrcmp(kind, "def") == 0, and there is nodefnode type in the grammar — Elixir definitions arecallnodes.elixir_process_function_def()is dead code, and it also readsnameandparametersfields the grammar does not define.Fixed and verified against
Phoenix.PubSub.broadcast/subscribe, barepush/broadcast, and twohandle_inclauses including a guarded one.3. Phoenix routers mint zero Route nodes
PLAN.md§1.2 lists "Phoenix/Oban/Broadway service patterns (work today)". Oban and Broadway do. Phoenix routes do not.Every entry in
route_reg_suffixesis prefixed.or::(.get,::get,.MapGet) because it is matched against the tail of a qualified callee. Phoenix registers with bare macros:so the callee is exactly
getandcbm_service_pattern_route_method()returns NULL. The resolved-QN route-library table cannot help either —Phoenix.Routerlives indeps/and never resolves in-tree.Fix adds an exact-match bare table (
get,post,put,patch,delete,head,options,live,resources,forward), gated to Elixir and consulted before resolution. Exact and not suffix on purpose: a suffix rule would classifywidget,targetandbudgetas GET routes.extract_handler_arg()also needed to accept Elixir'salias/dothandler node, without which no route gets a HANDLES edge.Depends on #1721 for the path argument.
4. Elixir functions carry no complexity, fingerprint or line count
Every Elixir Function ships
complexity = 0,cognitive = 0,lines = 0and no MinHash fingerprint, so Elixir is invisible to complexity/bottleneck queries and never participates inSIMILAR_TO— while thequery_graphtool description states that "every Function and Method node carries queryable complexity properties".cbm_compute_complexity()matches on node type, and Elixir has no branching node types:if,unless,case,cond,with,for,tryandreceiveare ordinarycallnodes distinguished by the identifier in callee position, and every arm is astab_clause. That is also whyelixir_branch_types = {"call"}inlang_specs.chas never been usable — it would score every function call as a decision.Measured effect
Fresh index of an 829-file Phoenix application, plus
elixir-lang/elixir(6,346.exfunctions) for the complexity numbers:complexity > 0— elixir-lang/elixircomplexity > 0— Phoenix appFor scale, the TypeScript and Python sources in that same Phoenix repo score 20 of 386 (5%).
Overlap with #1239 — and an offer
My fork also carries a grammar-layer rewrite that overlaps @holsee's D1–D8 almost item for item: guarded def heads (D1), enclosing-function attribution (D2), name/arity identity (D3 — resolved as an
aritiesdef field, keeping the QN string format unchanged, which is the option PLAN.md flags as preferable), the fulldef*family (D4), imports nested indefmodule(D5), nested module QNs (D6), and stronger Elixir test contracts (D8 — 18 extraction tests).I am deliberately not opening PRs for any of that. @holsee has been staging this since July across 15+ branches and it would be a competing implementation, which helps nobody.
If it is useful as reference material — a second implementation to diff against, or tests to lift — it is at
henry-hz/codebase-memory-mcp, branchfix/elixir-routes-channels-complexity-heex. Take anything, or ignore it entirely; no attribution needed. One caveat if you do look: the dotted-module import resolution inpass_pkgmap.cthere has no test and touches a candidate-ordering path shared with Lua and R, so treat that part as unproven.Proposed delivery
Four small PRs, each independent except where noted, all signed off and gated:
first_string_arg(open)Happy to reorder, split further, fold any of them into @holsee's phases instead, or drop them if they conflict with the staged plan. Also happy to hold 2–4 until there is appetite.
Separately:
.heex/.eextemplates are not indexed at all today, so the LiveView template layer is invisible. That needs a vendored grammar, which CONTRIBUTING says requires prior approval — I have it working locally and will open a separate issue rather than assume.