Skip to content

Commit 5a8c3f0

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

File tree

3 files changed

+66
-2
lines changed

3 files changed

+66
-2
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,14 +338,29 @@
338338
(flag-duplicates)
339339
(flag-tooling)))))
340340

341+
(defn- compile-like-exception?
342+
"'Compile-like' exceptions are those that happen at runtime
343+
(and therefore never include a `:phase`) which however,
344+
represent code that cannot possibly work,
345+
and therefore are a 'compile-like' exception (i.e. a linter could have caught them)."
346+
[{cause-type :type
347+
^String
348+
cause-message :message}]
349+
(and (= cause-type 'java.lang.IllegalArgumentException)
350+
(or (some-> cause-message (.startsWith "No matching field"))
351+
(some-> cause-message (.startsWith "No matching method")))))
352+
341353
(defn- analyze-cause
342354
"Analyze the `cause-data` of an exception, in `Throwable->map` format."
343355
[cause-data print-fn]
344356
(let [pprint-str #(let [writer (StringWriter.)]
345357
(print-fn % writer)
346358
(str writer))
359+
phase (-> cause-data :data :clojure.error/phase)
347360
m {:class (name (:type cause-data))
348-
:phase (-> cause-data :data :clojure.error/phase)
361+
:phase phase
362+
:compile-like (boolean (and (not phase)
363+
(compile-like-exception? cause-data)))
349364
:message (:message cause-data)
350365
:stacktrace (analyze-stacktrace-data
351366
(cond (seq (:trace cause-data))

test/haystack/analyzer_test.clj

Lines changed: 46 additions & 1 deletion
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)