Expanding on using Tree Sitter in Emacs for Getting Qualified Ruby Method Name

Minor Refinements of a Recently Created Function

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.