Skip to content

Commit 5999738

Browse files
committed
CLJS-2945: Print spec failure details
1 parent fc7abd0 commit 5999738

File tree

2 files changed

+179
-2
lines changed

2 files changed

+179
-2
lines changed

src/main/cljs/cljs/repl.cljs

Lines changed: 178 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88

99
(ns cljs.repl
1010
(:require-macros cljs.repl)
11-
(:require [cljs.spec.alpha :as spec]))
11+
(:require [cljs.spec.alpha :as spec]
12+
[goog.string :as gstring]
13+
[goog.string.format]))
1214

1315
(defn print-doc [{n :ns nm :name :as m}]
1416
(println "-------------------------")
@@ -56,3 +58,178 @@
5658
(doseq [role [:args :ret :fn]]
5759
(when-let [spec (get fnspec role)]
5860
(print (str "\n " (name role) ":") (spec/describe spec)))))))))
61+
62+
(defn Error->map
63+
"Constructs a data representation for a Error with keys:
64+
:cause - root cause message
65+
:phase - error phase
66+
:via - cause chain, with cause keys:
67+
:type - exception class symbol
68+
:message - exception message
69+
:data - ex-data
70+
:at - top stack element
71+
:trace - root cause stack elements"
72+
[o]
73+
(let [base (fn [t]
74+
(merge {:type (cond
75+
(instance? ExceptionInfo t) 'ExceptionInfo
76+
(instance? js/EvalError t) 'js/EvalError
77+
(instance? js/RangeError t) 'js/RangeError
78+
(instance? js/ReferenceError t) 'js/ReferenceError
79+
(instance? js/SyntaxError t) 'js/SyntaxError
80+
(instance? js/URIError t) 'js/URIError
81+
(instance? js/Error t) 'js/Error
82+
:else nil)}
83+
(when-let [msg (ex-message t)]
84+
{:message msg})
85+
(when-let [ed (ex-data t)]
86+
{:data ed})
87+
#_(let [st (extract-canonical-stacktrace t)]
88+
(when (pos? (count st))
89+
{:at st}))))
90+
via (loop [via [], t o]
91+
(if t
92+
(recur (conj via t) (ex-cause t))
93+
via))
94+
root (peek via)]
95+
(merge {:via (vec (map base via))
96+
:trace nil #_(extract-canonical-stacktrace (or root o))}
97+
(when-let [root-msg (ex-message root)]
98+
{:cause root-msg})
99+
(when-let [data (ex-data root)]
100+
{:data data})
101+
(when-let [phase (-> o ex-data :clojure.error/phase)]
102+
{:phase phase}))))
103+
104+
(defn ex-triage
105+
"Returns an analysis of the phase, error, cause, and location of an error that occurred
106+
based on Throwable data, as returned by Throwable->map. All attributes other than phase
107+
are optional:
108+
:clojure.error/phase - keyword phase indicator, one of:
109+
:read-source :compile-syntax-check :compilation :macro-syntax-check :macroexpansion
110+
:execution :read-eval-result :print-eval-result
111+
:clojure.error/source - file name (no path)
112+
:clojure.error/line - integer line number
113+
:clojure.error/column - integer column number
114+
:clojure.error/symbol - symbol being expanded/compiled/invoked
115+
:clojure.error/class - cause exception class symbol
116+
:clojure.error/cause - cause exception message
117+
:clojure.error/spec - explain-data for spec error"
118+
[datafied-throwable]
119+
(let [{:keys [via trace phase] :or {phase :execution}} datafied-throwable
120+
{:keys [type message data]} (last via)
121+
{::spec/keys [:problems :fn :cljs.spec.test.alpha/caller]} data
122+
{:keys [:clojure.error/source] :as top-data} (:data (first via))]
123+
(assoc
124+
(case phase
125+
:read-source
126+
(let [{:keys [:clojure.error/line :clojure.error/column]} data]
127+
(cond-> (merge (-> via second :data) top-data)
128+
source (assoc :clojure.error/source source)
129+
(#{"NO_SOURCE_FILE" "NO_SOURCE_PATH"} source) (dissoc :clojure.error/source)
130+
message (assoc :clojure.error/cause message)))
131+
132+
(:compile-syntax-check :compilation :macro-syntax-check :macroexpansion)
133+
(cond-> top-data
134+
source (assoc :clojure.error/source source)
135+
(#{"NO_SOURCE_FILE" "NO_SOURCE_PATH"} source) (dissoc :clojure.error/source)
136+
type (assoc :clojure.error/class type)
137+
message (assoc :clojure.error/cause message)
138+
problems (assoc :clojure.error/spec data))
139+
140+
(:read-eval-result :print-eval-result)
141+
(let [[source method file line] (-> trace first)]
142+
(cond-> top-data
143+
line (assoc :clojure.error/line line)
144+
file (assoc :clojure.error/source file)
145+
(and source method) (assoc :clojure.error/symbol (vector #_java-loc->source source method))
146+
type (assoc :clojure.error/class type)
147+
message (assoc :clojure.error/cause message)))
148+
149+
:execution
150+
(let [[source method file line] (->> trace #_(drop-while #(core-class? (name (first %)))) first)
151+
file (first (remove #(or (nil? %) (#{"NO_SOURCE_FILE" "NO_SOURCE_PATH"} %)) [(:file caller) file]))
152+
err-line (or (:line caller) line)]
153+
(cond-> {:clojure.error/class type}
154+
err-line (assoc :clojure.error/line err-line)
155+
message (assoc :clojure.error/cause message)
156+
(or fn (and source method)) (assoc :clojure.error/symbol (or fn (vector #_java-loc->source source method)))
157+
file (assoc :clojure.error/source file)
158+
problems (assoc :clojure.error/spec data))))
159+
:clojure.error/phase phase)))
160+
161+
(defn ex-str
162+
"Returns a string from exception data, as produced by ex-triage.
163+
The first line summarizes the exception phase and location.
164+
The subsequent lines describe the cause."
165+
[{:clojure.error/keys [phase source line column symbol class cause spec] :as triage-data}]
166+
(let [loc (str (or source "<cljs repl>") ":" (or line 1) (if column (str ":" column) ""))
167+
class-name (name (or class ""))
168+
simple-class class-name
169+
cause-type (if (contains? #{"Exception" "RuntimeException"} simple-class)
170+
"" ;; omit, not useful
171+
(str " (" simple-class ")"))
172+
format gstring/format]
173+
(case phase
174+
:read-source
175+
(format "Syntax error reading source at (%s).\n%s\n" loc cause)
176+
177+
:macro-syntax-check
178+
(format "Syntax error macroexpanding %sat (%s).\n%s"
179+
(if symbol (str symbol " ") "")
180+
loc
181+
(if spec
182+
(with-out-str
183+
(spec/explain-out
184+
(if true #_(= s/*explain-out* s/explain-printer)
185+
(update spec ::spec/problems
186+
(fn [probs] (map #(dissoc % :in) probs)))
187+
spec)))
188+
(format "%s\n" cause)))
189+
190+
:macroexpansion
191+
(format "Unexpected error%s macroexpanding %sat (%s).\n%s\n"
192+
cause-type
193+
(if symbol (str symbol " ") "")
194+
loc
195+
cause)
196+
197+
:compile-syntax-check
198+
(format "Syntax error%s compiling %sat (%s).\n%s\n"
199+
cause-type
200+
(if symbol (str symbol " ") "")
201+
loc
202+
cause)
203+
204+
:compilation
205+
(format "Unexpected error%s compiling %sat (%s).\n%s\n"
206+
cause-type
207+
(if symbol (str symbol " ") "")
208+
loc
209+
cause)
210+
211+
:read-eval-result
212+
(format "Error reading eval result%s at %s (%s).\n%s\n" cause-type symbol loc cause)
213+
214+
:print-eval-result
215+
(format "Error printing return value%s at %s (%s).\n%s\n" cause-type symbol loc cause)
216+
217+
:execution
218+
(if spec
219+
(format "Execution error - invalid arguments to %s at (%s).\n%s"
220+
symbol
221+
loc
222+
(with-out-str
223+
(spec/explain-out
224+
(if true #_(= s/*explain-out* s/explain-printer)
225+
(update spec ::spec/problems
226+
(fn [probs] (map #(dissoc % :in) probs)))
227+
spec))))
228+
(format "Execution error%s at %s(%s).\n%s\n"
229+
cause-type
230+
(if symbol (str symbol " ") "")
231+
loc
232+
cause)))))
233+
234+
(defn error->str [error]
235+
(ex-str (ex-triage (Error->map error))))

src/main/clojure/cljs/repl/node_repl.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ var server = net.createServer(function (socket) {
8686
type: "result",
8787
repl: repl,
8888
status: "exception",
89-
value: err.stack
89+
value: cljs.repl.error__GT_str(err)
9090
}));
9191
} else if(ret !== undefined && ret !== null) {
9292
socket.write(JSON.stringify({

0 commit comments

Comments
 (0)