Skip to content

Commit 76f0c59

Browse files
[stacktrace] Add ex-str-formatted message to analyzed causes
1 parent 92a9049 commit 76f0c59

File tree

4 files changed

+51
-20
lines changed

4 files changed

+51
-20
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## master (unreleased)
44

5+
* [#325](https://github.com/clojure-emacs/orchard/pull/325): Add `ex-str`-formatted message to analyzed causes.
6+
57
## 0.31.0 (2025-03-14)
68

79
* [#317](https://github.com/clojure-emacs/orchard/pull/317): **BREAKING:** Remove deprecated functions:

src/orchard/misc.clj

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,11 @@
101101
(last xs)))]
102102
(apply f (filter identity xs))))
103103

104+
(defn assoc-some
105+
"Assoc key-value to the map `m` if `v` is non-nil."
106+
[m k v]
107+
(if (nil? v) m (assoc m k v)))
108+
104109
(defn parse-java-version
105110
"Parse a Java version string according to JEP 223 and return the appropriate
106111
version."

src/orchard/stacktrace.clj

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
:author "Jeff Valk, Oleksandr Yakushev"}
66
(:require
77
[clojure.java.io :as io]
8+
[clojure.main]
89
[clojure.pprint :as pp]
910
[clojure.repl :as repl]
1011
[clojure.spec.alpha :as s]
1112
[clojure.string :as str]
1213
[orchard.info :as info]
13-
[orchard.java.resource :as resource])
14+
[orchard.java.resource :as resource]
15+
[orchard.misc :refer [assoc-some]])
1416
(:import
1517
(java.io StringWriter)
1618
(java.net URL)
@@ -166,14 +168,15 @@
166168
(if (< i 0)
167169
frames
168170
(let [frame-name (:name (get frames i))
169-
tooling? (or (tooling-frame-name? frame-name)
170-
;; Everything runs from a Thread, so this frame, if at
171-
;; the end, is irrelevant. However one can invoke this
172-
;; method 'by hand', which is why we only skip
173-
;; consecutive frames that match this.
174-
(and all-tooling-so-far?
175-
(re-find #"^java\.lang\.Thread/run|^java\.util\.concurrent"
176-
frame-name)))]
171+
tooling? (and frame-name
172+
(or (tooling-frame-name? frame-name)
173+
;; Everything runs from a Thread, so this frame,
174+
;; if at the end, is irrelevant. However one can
175+
;; invoke this method 'by hand', which is why we
176+
;; only skip consecutive frames that match this.
177+
(and all-tooling-so-far?
178+
(re-find #"^java\.lang\.Thread/run|^java\.util\.concurrent"
179+
frame-name))))]
177180
(recur (cond-> frames
178181
tooling? (update i flag-frame :tooling))
179182
(dec i) (and all-tooling-so-far? tooling?))))))
@@ -260,12 +263,13 @@
260263
(print-fn % writer)
261264
(str writer))
262265
phase (-> cause-data :data :clojure.error/phase)
263-
m {:class (name (:type cause-data))
264-
:phase phase
265-
:message (:message cause-data)
266-
:stacktrace (analyze-stacktrace-data
267-
(cond (seq (:trace cause-data)) (:trace cause-data)
268-
(:at cause-data) [(:at cause-data)]))}]
266+
m (-> {:class (name (:type cause-data))
267+
:phase phase
268+
:message (:message cause-data)
269+
:stacktrace (analyze-stacktrace-data
270+
(cond (seq (:trace cause-data)) (:trace cause-data)
271+
(:at cause-data) [(:at cause-data)]))}
272+
(assoc-some :triage (:triage cause-data)))]
269273
(if-let [data (filter-ex-data (:data cause-data))]
270274
(if (::s/failure data)
271275
(assoc m
@@ -280,14 +284,28 @@
280284
:clojure.error/symbol])))
281285
m)))
282286

287+
(defn- maybe-triage-message
288+
"If the exception is a compiler error which carries Spec-based explanation data,
289+
transform it into human readable error message string."
290+
[exception-data]
291+
(try
292+
;; ex-triage may throw an exception if :phase is incorrect
293+
(when-let [explanation-data (:clojure.error/spec
294+
(clojure.main/ex-triage exception-data))]
295+
(with-out-str (s/explain-out explanation-data)))
296+
(catch Exception _)))
297+
283298
(defn- analyze-causes
284299
"Analyze the cause chain of the `exception-data` in `Throwable->map` format."
285300
[exception-data print-fn]
286-
(let [causes (vec (:via exception-data))
287-
;; If the first cause lacks :trace, add :trace of the exception there.
288-
causes (if (:trace (first causes))
289-
causes
290-
(assoc-in causes [0 :trace] (:trace exception-data)))]
301+
(let [triage-message (maybe-triage-message exception-data)
302+
causes (update (vec (:via exception-data)) 0
303+
#(cond-> %
304+
;; If the first cause lacks :trace, add :trace of the
305+
;; exception there.
306+
(nil? (:trace %)) (assoc :trace (:trace exception-data))
307+
;; If non-nil, assoc triage-message to first cause.
308+
triage-message (assoc :triage triage-message)))]
291309
(mapv #(extract-location (analyze-cause % print-fn)) causes)))
292310

293311
(defn analyze

test/orchard/stacktrace_test.clj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
(ns orchard.stacktrace-test
22
(:require
33
[clojure.spec.alpha :as s]
4+
[clojure.string :as str]
45
[clojure.test :refer [are deftest is testing]]
56
[matcher-combinators.matchers :as matchers]
67
[orchard.stacktrace :as sut]))
@@ -180,6 +181,11 @@
180181
:clojure.error/symbol 'clojure.core/let}
181182
(:location cause))))))
182183

184+
(deftest ex-triage-test
185+
(testing "compilation errors that can be triaged contain :triage message"
186+
(is (= "[a] - failed: even-number-of-forms? in: [0] at: [:bindings] spec: :clojure.core.specs.alpha/bindings"
187+
(str/trim (:triage (first (catch-and-analyze (eval '(let [a]))))))))))
188+
183189
(deftest test-analyze-throwable
184190
(testing "shape of analyzed throwable"
185191
(is (match?

0 commit comments

Comments
 (0)