Skip to content

Commit f5290d5

Browse files
committed
analyzer: introduce a :compile-like key
1 parent 0077b5c commit f5290d5

File tree

3 files changed

+62
-2
lines changed

3 files changed

+62
-2
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## master (unreleased)
44

5+
* `analyzer`: include a `:compile-like` key which indicates if the error happened at a "compile-like" phase.
6+
* It represents exceptions that happen at runtime (and therefore never include a `:phase`) which however, represent code that cannot possibly work, and therefore are a 'compile-like' exception (i.e. a linter could have caught them).
7+
* The set of conditions which are considered a 'compile-like' exception is private and subject to change.
8+
59
## 0.2.0 (2023-08-20)
610

711
## Changes

src/haystack/analyzer.clj

+12-1
Original file line numberDiff line numberDiff line change
@@ -338,14 +338,25 @@
338338
(flag-duplicates)
339339
(flag-tooling)))))
340340

341+
(defn- compile-like-exception?
342+
[{cause-type :type
343+
^String
344+
cause-message :message}]
345+
(and (= cause-type 'java.lang.IllegalArgumentException)
346+
(or (some-> cause-message (.startsWith "No matching field"))
347+
(some-> cause-message (.startsWith "No matching method")))))
348+
341349
(defn- analyze-cause
342350
"Analyze the `cause-data` of an exception, in `Throwable->map` format."
343351
[cause-data print-fn]
344352
(let [pprint-str #(let [writer (StringWriter.)]
345353
(print-fn % writer)
346354
(str writer))
355+
phase (-> cause-data :data :clojure.error/phase)
347356
m {:class (name (:type cause-data))
348-
:phase (-> cause-data :data :clojure.error/phase)
357+
:phase phase
358+
:compile-like (boolean (and (not phase)
359+
(compile-like-exception? cause-data)))
349360
:message (:message cause-data)
350361
:stacktrace (analyze-stacktrace-data
351362
(cond (seq (:trace cause-data))

test/haystack/analyzer_test.clj

+46-1
Original file line numberDiff line numberDiff line change
@@ -603,7 +603,52 @@
603603
(eval '(let [1]))
604604
(catch Throwable e
605605
(sut/analyze e)))
606-
(map :phase))))))))
606+
(map :phase)))))
607+
(testing "Does not include `:phase` for vanilla runtime exceptions"
608+
(is (= [nil]
609+
(->> (try
610+
(throw (ex-info "" {}))
611+
(catch Throwable e
612+
(sut/analyze e)))
613+
(map :phase)))))))
614+
615+
(testing "`:compile-like`"
616+
(testing "For non-existing fields"
617+
(is (= [true]
618+
(->> (try
619+
(eval '(.-foo ""))
620+
(catch Throwable e
621+
(sut/analyze e)))
622+
(map :compile-like)))))
623+
(testing "For non-existing methods"
624+
(is (= [true]
625+
(->> (try
626+
(eval '(-> "" (.foo 1 2)))
627+
(catch Throwable e
628+
(sut/analyze e)))
629+
(map :compile-like)))))
630+
(testing "For vanilla exceptions"
631+
(is (= [false]
632+
(->> (try
633+
(throw (ex-info "." {}))
634+
(catch Throwable e
635+
(sut/analyze e)))
636+
(map :compile-like)))))
637+
(testing "For vanilla `IllegalArgumentException`s"
638+
(is (= [false]
639+
(->> (try
640+
(throw (IllegalArgumentException. "foo"))
641+
(catch Throwable e
642+
(sut/analyze e)))
643+
(map :compile-like)))))
644+
(testing "For exceptions with a `:phase`"
645+
(is (#{[false false] ;; normal expectation
646+
[false]} ;; clojure 1.8
647+
(->> (try
648+
(eval '(let [1]))
649+
(catch Throwable e
650+
(sut/analyze e)))
651+
(map :compile-like)))))))
607652

608653
(deftest tooling-frame-name?
609654
(are [frame-name expected] (testing frame-name

0 commit comments

Comments
 (0)