Jake McCrary

ClojureScript: Treat warnings as errors

Recently my team deployed a new version of our ClojureScript UI and it had a minor bug. It was trivial to fix the problem, a ClojureScript build warning pointed us to the cause. As a result we started thinking it would be nice to have build warnings count as errors and fail our ClojureScript build.

We use Leiningen (version 2.5.3) and lein-cljsbuild (version 1.1.1). After some searching we found that lein-cljsbuild supports specifying custom warning handlers as the value to the :warning-handlers key. The lein-cljsbuild README even provides an example, which we took and added a (System/exit 1) to the end of it. This resulted in a build configuration that looked similar to below.

1
2
3
4
5
6
7
8
9
10
11
{:id "prod"
 :warning-handlers [(fn [warning-type env extra]
                      (when-let [s (cljs.analyzer/error-message warning-type extra)]
                        (binding [*out* *err*]
                          (println "WARNING:" (cljs.analyzer/message env s)))
                        (System/exit 1)))]
 :source-paths ["src/cljc" "src/cljs"]
 :compiler {:output-to "resources/public/js/compiled/ui.js"
            :externs ["resources/intercom-externs.js"
                      "resources/mixpanel-externs.js"]
            :optimizations :advanced}}

This worked! Well, it sort of worked. Our build failed whenever there was a warning but now we were seeing spurious warnings. We saw “Use of undeclared Var” warnings when functions created in a letfn where calling each other. Definitely not a situation that warrants a warning and definitely not a build failure.

We weren’t seeing this warning before so we opened ClojureScript’s source and found the default warning handler. The default handler checks that warning-type has a truthy value in the map *cljs-warnings*. Inspired by the default handler we added the same check to the start of our warning handler.

1
2
3
4
5
6
:warning-handlers [(fn [warning-type env extra]
                     (when (warning-type cljs.analyzer/*cljs-warnings*)
                       (when-let [s (cljs.analyzer/error-message warning-type extra)]
                         (binding [*out* *err*]
                           (println "WARNING:" (cljs.analyzer/message env s)))
                         (System/exit 1))))]

Success! Now we no longer get incorrect warnings when compiling our letfn form and our build still fails if a warning occurs. Now we can build and deploy with a little more confidence.

Looking forward to the next article? Never miss a post by subscribing using e-mail or RSS. The e-mail newsletter goes out periodically (at most once a month) and includes reviews of books I've been reading and links to stuff I've found interesting.

Comments