Fix clj-refactor Libspec Stealing

(This post is regarding a very particular issue at the intersection of Emacs, Clojure, and CIDER, so most readers can probably skip it, but I figured the solution deserved to be somewhere on the internet.)

CIDER has this awesome feature via clj-refactor where it can automatically add missing libspecs to your ns form as soon as you type an aliased var name using an :as alias you have previously used for that namespace:

Screen recording showing how CIDER and clj-refactor can add missing libspecs.

Screen recording showing how CIDER and clj-refactor can add missing libspecs.

You can define a preferred set of (:as) aliases via cljr-magic-require-namespaces in your Emacs config, and, whether you’ve done that or not, CIDER tries hard to be smart, offering you choices if you’ve previously used the same alias for different namespaces in different places in your project (e.g. s/ for clojure.spec some places and the same for clojure.string in other places).

Moreover, CIDER evaluates the namespace form, bringing in the new requires, allowing you to just keep typing and coding, without breaking your flow.

This is all fantastic, but I ran across one particular scenario where this kept biting me: my user namespace.

In my dev/user.clj file, I had a number of commands for managing the state of my program during development (viz. reloaded.repl and friends) that I used constantly, including while testing out various functions I’d just written in the current namespace (e.g. (new-db-query (user/db))). But I also happen to have another (rarely-used) namespace in my app which was, somewhere, aliased as user. Something like app.ui.views.user.

Since references to the (top-level) user namespace never occur in the (production-shared) broader codebase, every time I typed user/, CIDER immediately added a require for the namespace I didn’t want (the only usage of the alias in the codebase) and loaded it. Meaning I not only had to remove it, but also undefine the alias (C-c C-u)—and hopefully not accidentally trigger the same thing yet again though muscle memory….

I tried everything I could think of to fix this, including just specifying via cljr-magic-require-namespaces that user should expand to user, but nothing worked, so I suspect a proper solution is going to require an upstream patch.

In the meantime, a quick and dirty solution is just to use advice in Emacs:

(use-package clj-refactor
  :defer t
  :config

  ;; Don't auto add `require' form for `user' namespace.
  (defun cljr--unresolved-alias-ref--unless-user (alias-ref)
    (unless (string= "user" alias-ref)
      alias-ref))

  (advice-add 'cljr--unresolved-alias-ref :before-while
              #'cljr--unresolved-alias-ref--unless-user))

If the advice function here (cljr--unresolved-alias-ref--unless-user) returns nil, it essentially tells clj-refactor that an alias is already handled and it doesn’t need to worry about adding an import for it.