Skip to content

Commit ce25dce

Browse files
mfikesdnolen
authored andcommitted
CLJS-1492: Warn when using :optimisations instead of :optimizations
Detect when an unknown compiler or REPL option is passed by comparing against explicitly-maintained set of all known options. When an unknown option is passed, find a known one with minimum Levenshtein distance from the unknown option (within a threshold), and warn and suggest correct option name.
1 parent 6f14ccb commit ce25dce

File tree

5 files changed

+80
-0
lines changed

5 files changed

+80
-0
lines changed

src/main/clojure/cljs/build/api.clj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,9 @@
203203
env/*compiler*
204204
(env/default-compiler-env opts))))
205205
([source opts compiler-env]
206+
(doseq [[unknown-opt suggested-opt] (util/unknown-opts (set (keys opts)) closure/known-opts)]
207+
(println (str "WARNING: Unknown compiler option '" unknown-opt "'."
208+
(when suggested-opt (str " Did you mean '" suggested-opt "'?")))))
206209
(binding [ana/*cljs-warning-handlers* (:warning-handlers opts ana/*cljs-warning-handlers*)]
207210
(closure/build source opts compiler-env))))
208211

src/main/clojure/cljs/closure.clj

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,15 @@
160160
:unknown-defines DiagnosticGroups/UNKNOWN_DEFINES
161161
:visiblity DiagnosticGroups/VISIBILITY})
162162

163+
(def known-opts
164+
"Set of all known compiler options."
165+
#{:anon-fn-naming-policy :asset-path :cache-analysis :closure-defines :closure-extra-annotations
166+
:closure-warnings :compiler-stats :dump-core :elide-asserts :externs :foreign-libs
167+
:hashbang :language-in :language-out :libs :main :modules :source-map-path :optimizations
168+
:optimize-constants :output-dir :output-to :output-wrapper :parallel-build :preamble
169+
:pretty-print :print-input-delimiter :pseudo-names :recompile-dependents :source-map
170+
:source-map-inline :source-map-timestamp :static-fns :target :verbose :warnings})
171+
163172
(defn set-options
164173
"TODO: Add any other options that we would like to support."
165174
[opts ^CompilerOptions compiler-options]

src/main/clojure/cljs/repl.cljc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
(:refer-clojure :exclude [load-file])
1111
(:require [clojure.java.io :as io]
1212
[clojure.string :as string]
13+
[clojure.set :as set]
1314
[clojure.data.json :as json]
1415
[clojure.tools.reader :as reader]
1516
[clojure.tools.reader.reader-types :as readers]
@@ -36,6 +37,11 @@
3637
(def ^:dynamic *cljs-verbose* false)
3738
(def ^:dynamic *repl-opts* nil)
3839

40+
(def known-repl-opts
41+
"Set of all known REPL options."
42+
#{:analyze-path :caught :def-emits-var :flush :need-prompt :print :print-no-newline :prompt :read
43+
:reader :repl-verbose :watch :watch-fn})
44+
3945
(defmacro err-out [& body]
4046
`(binding [*out* *err*]
4147
~@body))
@@ -773,6 +779,9 @@
773779
[cljs.pprint :refer [pprint] :refer-macros [pp]]]
774780
bind-err true}
775781
:as opts}]
782+
(doseq [[unknown-opt suggested-opt] (util/unknown-opts (set (keys opts)) (set/union known-repl-opts cljsc/known-opts))]
783+
(println (str "WARNING: Unknown option '" unknown-opt "'."
784+
(when suggested-opt (str " Did you mean '" suggested-opt "'?")))))
776785
(let [repl-opts (-repl-options repl-env)
777786
repl-requires (into repl-requires (:repl-requires repl-opts))
778787
{:keys [analyze-path repl-verbose warn-on-undeclared special-fns static-fns] :as opts

src/main/clojure/cljs/util.cljc

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,3 +208,42 @@
208208

209209
(defn boolean? [x]
210210
(or (true? x) (false? x)))
211+
212+
(defn levenshtein-distance
213+
"The the minimum number of single-element edits needed to
214+
transform s in to t."
215+
[s t]
216+
(let [f (fn [f s t]
217+
(cond
218+
(empty? s) (count t)
219+
(empty? t) (count s)
220+
:else (let [cost (if (= (first s) (first t))
221+
0
222+
1)]
223+
(min (inc (f f (rest s) t))
224+
(inc (f f s (rest t)))
225+
(+ cost (f f (rest s) (rest t)))))))
226+
g (memoize f)]
227+
(g g s t)))
228+
229+
(defn suggestion
230+
"Provides a best suggestion for an unknown, taken from knowns,
231+
minimizing the Levenshtein distance, returning nil if threshold
232+
cannot be satisfied."
233+
[threshold unknown knowns]
234+
(let [distance (partial levenshtein-distance unknown)
235+
closest (apply min-key distance knowns)
236+
closest-dist (distance closest)]
237+
(when (<= closest-dist threshold)
238+
closest)))
239+
240+
(defn unknown-opts
241+
"Takes a set of passed opt keys and known opt keys and for each
242+
unknown opt key returns a vector of the key and its (potentially
243+
nil) suggestion."
244+
[passed knowns]
245+
{:pre [(set? passed) (set? knowns)]}
246+
(for [unknown (set/difference passed knowns)]
247+
[unknown (some-> (suggestion 3 (str unknown) (map str knowns))
248+
(subs 1)
249+
keyword)]))

src/test/clojure/cljs/util_tests.clj

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
(ns cljs.util-tests
2+
(:require [cljs.util :as util])
3+
(:use clojure.test))
4+
5+
(deftest test-levenshtein-distance
6+
(testing "levenshtein-distance"
7+
(is (= 0 (util/levenshtein-distance "abc" "abc")))
8+
(is (= 1 (util/levenshtein-distance "abc" "abcd")))
9+
(is (= 1 (util/levenshtein-distance "abcd" "abc")))
10+
(is (= 3 (util/levenshtein-distance "kitten" "sitting")))))
11+
12+
(deftest test-suggestion
13+
(testing "suggestion"
14+
(is (= ":optimization" (util/suggestion 3 ":optimization" [":optimization" ":static-fns"])))))
15+
16+
(deftest test-unknown-opts
17+
(testing "unknown-opts"
18+
(is (= [[:bogus nil]
19+
[:optimisations :optimizations]]
20+
(sort (util/unknown-opts #{:optimisations :bogus} #{:optimizations :static-fns}))))))

0 commit comments

Comments
 (0)