@@ -150,7 +150,7 @@ module Noir::GoCalleeExtractor
150150 row = Noir ::TreeSitter .node_start_row(node)
151151 next unless route_rows.includes?(row)
152152
153- handler_arg = find_handler_arg(node, source)
153+ handler_arg = find_handler_arg(node, source).try { | arg | unwrap_handler_arg(arg, source) }
154154 next unless handler_arg
155155
156156 callees = [] of Tuple (String , String , Int32 )
@@ -182,21 +182,62 @@ module Noir::GoCalleeExtractor
182182 # First non-string positional argument after a string-literal arg in a
183183 # verb-route call. Mirrors the convention used by
184184 # `TreeSitterGoRouteExtractor#decode_verb_call`.
185+ #
186+ # Mux builder chains are the exception: `.Path("/x").HandlerFunc(h)`
187+ # carries the path and handler in different calls. When the routed call
188+ # itself is `Handler` / `HandlerFunc`, the first non-string arg is the
189+ # handler.
185190 private def find_handler_arg (call_node : LibTreeSitter ::TSNode , source : String ) : LibTreeSitter ::TSNode ?
186191 args = Noir ::TreeSitter .field(call_node, " arguments" )
187192 return unless args
193+ handler_only = handler_only_call?(call_node, source)
188194 seen_path = false
189195 Noir ::TreeSitter .each_named_child(args) do |arg |
190196 ty = Noir ::TreeSitter .node_type(arg)
191197 if ty == " interpreted_string_literal" || ty == " raw_string_literal"
192198 seen_path = true
193199 next
194200 end
195- return arg if seen_path
201+ return arg if seen_path || handler_only
196202 end
197203 nil
198204 end
199205
206+ private def handler_only_call? (call_node : LibTreeSitter ::TSNode , source : String ) : Bool
207+ function = Noir ::TreeSitter .field(call_node, " function" )
208+ return false unless function
209+ return false unless Noir ::TreeSitter .node_type(function) == " selector_expression"
210+ field = Noir ::TreeSitter .field(function, " field" )
211+ return false unless field
212+
213+ case Noir ::TreeSitter .node_text(field, source)
214+ when " Handler" , " HandlerFunc"
215+ true
216+ else
217+ false
218+ end
219+ end
220+
221+ private def unwrap_handler_arg (arg : LibTreeSitter ::TSNode , source : String ) : LibTreeSitter ::TSNode
222+ return arg unless Noir ::TreeSitter .node_type(arg) == " call_expression"
223+
224+ function = Noir ::TreeSitter .field(arg, " function" )
225+ return arg unless function
226+ return arg unless Noir ::TreeSitter .node_type(function) == " selector_expression"
227+
228+ field = Noir ::TreeSitter .field(function, " field" )
229+ return arg unless field
230+ return arg unless Noir ::TreeSitter .node_text(field, source) == " HandlerFunc"
231+
232+ args = Noir ::TreeSitter .field(arg, " arguments" )
233+ return arg unless args
234+ Noir ::TreeSitter .each_named_child(args) do |child |
235+ return child unless Noir ::TreeSitter .node_type(child) == " interpreted_string_literal" ||
236+ Noir ::TreeSitter .node_type(child) == " raw_string_literal"
237+ end
238+ arg
239+ end
240+
200241 # Walk `body_node` for call expressions and append `(name, file_path,
201242 # file_line)` tuples. `line_offset` lets callers translate
202243 # tree-sitter rows (already absolute when the parse covers the whole
0 commit comments