This article is part of the “Meta Advent 2019” series. I’ve committed to writing a new blog post here every day until Christmas.

One common problem with third-party nREPL middleware is that often they aren’t (properly) documented and you have to dig into the middleware code to figure out how to use it. Here I’m not referring to Clojure-level API documentation, but to nREPL operation (a.k.a. “op”) API documentation. Think things like eval, interrupt, etc - the different type of commands that a client would send to an nREPL server to make it do its bidding.1

The funny thing is that nREPL had always had special provisions to ensure that op APIs were properly documented. Each middleware has to have a descriptor (think middleware metadata) which lists things like handled ops, their parameters, return values, etc. Here’s an example of a middleware descriptor:

(set-descriptor! #'wrap-load-file
                 {:requires #{#'caught/wrap-caught #'print/wrap-print}
                  :expects #{"eval"}
                  :handles {"load-file"
                            {:doc "Loads a body of code, using supplied path and filename info to set source file and line number metadata. Delegates to underlying \"eval\" middleware/handler."
                             :requires {"file" "Full contents of a file of code."}
                             :optional (merge caught/wrap-caught-optional-arguments
                                              print/wrap-print-optional-arguments
                                              {"file-path" "Source-path-relative path of the source file, e.g. clojure/java/io.clj"
                                               "file-name" "Name of source file, e.g. io.clj"})
                             :returns (-> (meta #'eval/interruptible-eval)
                                          ::middleware/descriptor
                                          :handles
                                          (get "eval")
                                          :returns)}}})

You can read more about middleware descriptors here.

nREPL leverages those descriptors in its own documentation and the result is pretty nice. This op API documentation is generated by a simple script that can output both AsciiDoc and Markdown.

Is there a reason why third-party middleware libraries can’t apply the same approach? Not really. After a lot of procrastination I finally emulated all of this for cider-nrepl and its nREPL op API is finally well-documented. I had to tweak a bit the original script from nREPL, but the modifications were tiny2 and the result was pretty good. I was thinking it’d be cool if the documentation script was made more generic and reusable across projects (e.g. in the form of a Leiningen plugin), but I didn’t have time to pursue this idea.

Actually, when it comes to cider-nrepl’s documentation, I did much more than documenting the nREPL op API. During the course of CIDER’s recent funding round by “Clojurists Together”, I actually created a documentation site for the project. It’s still pretty basic, but I hope to extend and polish it in the months to come. Feedback and help would be most welcome!

Given the fact that middleware like cider-nrepl and refactor-nrepl are widely used today by many editors I think it’s really important to invest in documentation. That way client authors can focus on building great features for their users, instead of wasting their time figuring out how are they supposed to use some API. I doubt I’ll have time to tackle refactor-nrepl’s documentation any time soon, but anyone with enough desire to help out can easily replicate what I’ve done for cider-nrepl. Believe it or not - documenting APIs can be a lot of fun!

That’s all I had for you today. See you tomorrow!

  1. You can read more those here

  2. Most notably - I had to exclude the built-in middleware prior to generating the output.