Where parallels cross

Interesting bits of life

Make Emacs help in creating an example of Scala classes

I recently found myself in need of creating test examples for Scala case classes. These tend to have quite a lot of nested fields, here an example of what I am dealing with:

case class A(a: Option[Int])
case class B(a: A, b: Boolean)
case class C(b: B, d: Int, e: Double)

C // edit manually, nooo!!

Now for a test I would need to derive an example of this by hand 1.

I did that and it is boring. So Emacs to support me! A little Elisp hack saves the day:

(defun my/s-replace-regexp-all (replacements s)
    "REPLACEMENTS is a list of cons-cells. Each `car` is replaced with `cdr` in S."
    (declare (pure t) (side-effect-free t))
    (if (equal nil replacements)
        s
      (my/s-replace-regexp-all
       (cdr replacements)
       (s-replace-regexp (car (car replacements)) (cdr (car replacements)) s 'fixed-case))))

(defun my/stub-scala-class ()
  (interactive)
  (let ((replacements ;; supported replacements
         '(("Option\\[[A-Za-z]*\\]" . "None") ;; TODO can I generalize these higher order datatypes (e.g., Either?
                        ("List\\[[A-Za-z]*\\]" . "Nil")
                        ("Set\\[[A-Za-z]*\\]" . "Set()")
                        ("String" . "\"someString\"")
                        ("Int" . "123")
                        ("Long" . "123")
                        ("Double" . "123.123")
                        ("Boolean" . "false")
                        (":" . " =") ;; not types anymore, but values
                        ))
        (class
         (save-excursion
           (save-window-excursion
             (let ((s (thing-at-point 'symbol 'no-props)))
               (or
                ;; try to work around xref-find-definitions
                (when-let ((p (save-excursion
                                (goto-char (point-min))
                                (search-forward (concat "class " s) nil t)
                                (- (point) 1))))
                  (and (goto-char p) t))
                ;; use xref (lsp-metals provides this)
                (xref-find-definitions s)))
             (if (equal major-mode 'scala-mode)
                 (progn (search-forward "(")
                        (goto-char (- (point) 1))
                        (thing-at-point 'sexp 'no-props))
               (error "No class to stub at point"))
             ))))
    (goto-char (cdr (bounds-of-thing-at-point 'symbol)))
    (insert (my/s-replace-regexp-all replacements class))
    ))

If you run this with your cursor on the C above, you will get:

C(b = B, d = 123, e = 123.123)

You can see that it didn't stub B, but we can do it manually:

C(b = B(a = A, b = false), d = 123, e = 123.123)

And one more on the A:

C(b = B(a = A(a = None), b = false), d = 123, e = 123.123)

This is just a hack to speed up my work, so I didn't look into adding recursion (I must say that it satisfies me to fill manually, maybe because it also let me familiarize with the data structure for now?) and only few data types are supported (already Either requires some manual edits).

All in all, it was a useful thought exercise to make Emacs take some error-prone work away from me :)

Happy hacking!

Footnotes:

1

Sure I could generated the data with ScalaCheck and do some property based testing. But here I want to focus on creating a single example.