When I’m writing about or in Ruby 📖 code, sometimes I want to grab the qualified method name. For example, let’s say I have the following Ruby code:
module Hello
module World
def foo
:bar
end
def self.call
:yup
end
end
end
The qualified method name for the method #foo
would be Hello::World#foo
. The qualified method name for the singleton method .call
is Hello::World.call
.
A Ruby documentation convention is that instance methods are prefix with a #
and singleton methods are prefixed with a .
or ::
.
Using treesit-explore-mode
, I was able to quickly refine my recursive queries. Below is treesit’s rendering of the Abstract Syntax Tree (AST 📖) of the above Ruby code:
(program
(module module name: (constant)
(body_statement
(module module name: (constant)
(body_statement
(method def body: (identifier)
(body_statement (simple_symbol))
end)
(singleton_method def object: (self) . body: (identifier)
(body_statement (simple_symbol))
end))
body: end))
body: end))
, in a moment of dreary skies and sleeping dogs, I hacked together the following functions:
jf/treesit/qualified_method_name
- Copy the qualified method name to the paste buffer (e.g. the
kill-ring
). jf/treesit/module_space
- Recurse up from a node to create a list of the module/class ancestors.
(require 'treesit)
(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)))
;; An ugly bit of code to recurse upwards from the node to the "oldest"
;; parent. And collect all module/class nodes along the way. This will
;; return a series of nested lists. It's on the originating caller to
;; flatten that list.
(defun jf/treesit/module_space (node)
(when-let* ((parent (treesit-parent-until
node
(lambda (n) (member (treesit-node-type n)
'("class" "module")))))
(parent_name (treesit-node-text
(car (treesit-filter-child
parent (lambda (n)
(string=
"constant"
(treesit-node-type n))))))))
(list (jf/treesit/module_space parent) parent_name)))
This is most certainly a rainy day kind of project; one that helped me learn just a bit more about the treesit
package.
Postscript
The list returned by jf/treesit/module_space
is '(nil ("Hello" ("World")))
; which is a ugly but workable. Perhaps someone will write to me with a refactor of this code.