Contributing to ClojureScript
If you’ve been considering contributing to ClojureScript, this is a short description of a recent contribution. Hopefully this gives a sense of what the process can be like.
A recent compiler change required that symbols emitted in the JavaScript be further munged for proper operation. A consequence of this change is that function symbols in stack traces show up as cljs$core$seq
instead of cljs.core/seq
.
“Demunging” these symbols is not trivial when you consider that namespaces and function names can include various characters. (As a mathematical aside, it is not clear to me whether munging is even an injective function with a well defined left inverse.) There is no clear solution to this issue, so it sat there for a while.
For some reason, I was recently reading about how source mapping works and noticed in this post that, in addition to line and column source “coordinates,” source mapping files include the original “names.”
I hopped on the #clojurescript
IRC channel and asked David Nolen what his thoughts were on this. He pointed out that a lot of the tooling out there appears to lack support for source mapping names, but that this could be useful for ClojureScript stacktrace mapping, which has its own implementation that consumes source map info. In fact, he immediately pointed me to relevant code, and how easy it would be to get it to read in the original names that had been there all along (but ignored).
A day or so later I dug into this code. Given that it is Clojure, I found that the easiest way to sort out how it behaves was to first produce a unprocessed stacktrace value, by simply using prn
to print one out. With that in hand, I was able to fire up a REPL and pass this raw stacktrace to the machinery to see how it behaves when producing a printed stacktrace. More importantly, I was able to make little tweaks to the code get it to properly make use of the original name info.
One of the main challenges is that the original names in the source map file are the names used at the call sites. By example, if you are in a function foo
, calling bar
, the call to bar
(and its original name) is in the frame associated with foo
. So the solution involved processing the extracted name information to essentially shift the names down by a frame.
Here is an example of an improved stacktrace. You can see that the bottom-most name can’t be “demunged” owing to the shifting described above.
ClojureScript:cljs.user> (ffirst 1)
Error: 1 is not ISeqable
cljs.core/seq (out/cljs/core.cljs:951:20)
cljs.core/first (out/cljs/core.cljs:960:16)
cljs$core$ffirst (out/cljs/core.cljs:1393:11)
I tested this these changes out with all of the REPLs (Node, Nashorn, etc), and all of the browsers. This is made easy with the new Quick Start.
I submitted the patch with an associated ClojureScript JIRA ticket, and within a day it was accepted into master.
Then, as these things go sometimes, I realized that there was an important case I had missed in testing. It is possible to call a function using a local symbol, which defeats the design for those cases. This was addressed with a subsequent patch.
These changes were fun to make, and also very rewarding: It’s nice to know that this contribution might help others who are using the ClojureScript tooling.
Sometimes, contributions like these (which are not at the core of the analyzer and/or compiler logic) are relatively easy to make. I’d encourage making contributions like these if you’d like to help. If you are interested in contributing, and would like to get started, I’d suggest looking for ClojureScript JIRA tickets that are labeled “newbie.” Find one, sign the Contributor Agreement (CA), and join the fun! Many of the tickets are not as hard as you might think!