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 #call
.
The other edge case was as follows:
module A
C = Struct.new do
def call
end
end
end
The original code would return A#call
.
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.