I wrote about
Using Built-in Emacs 29 Tree Sitter Package to Get Qualified Ruby Function Name. at work I used my
jf/treesit/qualified_method_name function about 15 times. That function grabs the method name and it’s class/module scope.
During time, I encountered two edge cases that didn’t work with the implementation I originally wrote. These were self-inflicted edge-cases that related to some idiomatic Ruby 📖. The first edge case was as follows:
module A::B def call end end
My original code returned
The other edge case was as follows:
module A C = Struct.new do def call end end end
The original code would return
I spent a bit of time —five minutes or so—resolving the following test case:
module A::B C::D = Struct.new do def call end end end
The expected result is
A::B::C::D#call. Let’s look at the Abstract Syntax Tree (AST 📖):
(program (module module name: (scope_resolution scope: (constant) :: name: (constant)) (body_statement (module module name: (constant) ; end) (assignment left: (scope_resolution scope: (constant) :: name: (constant)) = right: (call method: (constant) block: . (identifier) (do_block do (body_statement (method def body: (identifier) end)) body: end)))) body: end))
I use the following two functions:
(cl-defun jf/treesit/qualified_method_name (&key (type "method")) "Get the fully qualified name of method at point." (interactive) (if-let ((func (treesit-defun-at-point))) ;; Instance method or class method? (let* ((method_type (if (string= type (treesit-node-type func)) "#" ".")) (method_name (treesit-node-text (car (treesit-filter-child func (lambda (node) (string= "identifier" (treesit-node-type node))))))) (module_space (s-join "::" (-flatten (jf/treesit/module_space func)))) (qualified_name (concat module_space method_type method_name))) (message qualified_name) (kill-new (substring-no-properties qualified_name))) (user-error "No %s at point." type))) (defun jf/treesit/module_space (node) (when-let* ((parent (treesit-parent-until node (lambda (n) (member (treesit-node-type n) '("class" "module" "assignment"))))) (parent_name (treesit-node-text (car (treesit-filter-child parent (lambda (n) (member (treesit-node-type n) '("constant" "scope_resolution")))))))) (list (jf/treesit/module_space parent) parent_name)))
The key was adding
assignment to the list of parents and
scope_resolution to the list of parent’s child nodes to check.
You can see my updated code here.