expound

Quentin Le Guennec 2021-07-20T13:42:15.000900Z

I can't get expound to work on clojurescript running on chrome. Can anyone help?

bbrinck 2021-07-20T13:48:00.001800Z

@quentin.leguennec1 Can you expand a bit on this? What have you tried? What are you expecting to see? And what do you see now?

Quentin Le Guennec 2021-07-20T14:08:29.002800Z

@bbrinck Yes. I'm using https://github.com/nedap/speced.def and I'm expecting to see the expound formatted message in the chrome console when there is a spec mismatch. I have this preloaded:

(ns analis-desktop.preload
  (:require
   [js-deps-bundle]
   [expound.alpha :as expound]
   [cljs.spec.alpha :as s]
   [cljs.repl :as repl]))

(println "requiring js bundle before re-frame-10x...")

(set! s/*explain-out* expound/printer)

(def devtools-error-formatter
  "Uses cljs.repl utilities to format ExceptionInfo objects in Chrome devtools console."
  #js{:header
      (fn [object _config]
        (when (instance? ExceptionInfo object)
          (let [message (some->> (repl/error->str object)
                                 (re-find #"[^\n]+"))]
            #js["span" message])))
      :hasBody (constantly true)
      :body    (fn [object _config]
                 #js["div" (repl/error->str object)])})

(defonce _
  (some-> js/window.devtoolsFormatters
          (.unshift devtools-error-formatter)))

Quentin Le Guennec 2021-07-20T14:08:55.003300Z

(s/explain int? :hi) correctly displays with the expound formatting

Quentin Le Guennec 2021-07-20T14:09:37.003900Z

spec mismatchs display this:

REPL eval error 
Execution error (ExceptionInfo) at (<cljs repl>:1).
Execution error (ExceptionInfo) at (<cljs repl>:1).
Validation failed

Quentin Le Guennec 2021-07-20T14:12:33.004300Z

(I'm using emacs, lein, figwheel, chrome)

bbrinck 2021-07-20T14:14:55.005400Z

By “spec mismatchs”, do you mean the error that occurs when you call a specced function with invalid arguments?

bbrinck 2021-07-20T14:15:53.006700Z

Two things to try: 1. Can you double check the error handler is working? Perhaps by just raising some other ExpectionInfo object in your code and making sure your custom formatter is invoked?

bbrinck 2021-07-20T14:16:36.007200Z

2. Can you remove speced.def from the repro and see what happens when you use s/fdef?

Quentin Le Guennec 2021-07-20T14:16:47.007300Z

Yes

Quentin Le Guennec 2021-07-20T14:17:58.007700Z

(ExceptionInfo. "hello") doesn't do anything, neither to the repl on the chrome console

Quentin Le Guennec 2021-07-20T14:18:38.007900Z

I'm testing 2. rn

Quentin Le Guennec 2021-07-20T14:20:59.008800Z

oh nvm (throw (ExceptionInfo. "hello")) correctly throws an error

bbrinck 2021-07-20T14:21:42.009400Z

Does the devtools-error-formatter above print it as you expected in the console?

Quentin Le Guennec 2021-07-20T14:22:03.009800Z

no, it shows:

react_devtools_backend.js:2574 REPL eval error 
Execution error (ExceptionInfo) at (<cljs repl>:1).
Execution error (ExceptionInfo) at (<cljs repl>:1).
hello

bbrinck 2021-07-20T14:23:12.010900Z

I see. So although there may be an issue with the Expound config, I think the first thing is to debug why (throw (ExceptionInfo. "hello")) isn’t being formatted by the devtools-error-formatter

Quentin Le Guennec 2021-07-20T14:23:37.011200Z

I see, thanks

Quentin Le Guennec 2021-07-20T14:24:16.012200Z

devtools-error-formatter, isn't called at all, console.log calls don't do anything in the :header function

bbrinck 2021-07-20T14:25:22.013200Z

Interesting. I wonder if the defonce is being called correctly. Or if you can inspect the value of js/window.devtoolsFormatters once the page is loaded and see what is in there.

Quentin Le Guennec 2021-07-20T14:25:40.013400Z

It looks correct

Quentin Le Guennec 2021-07-20T14:25:55.013600Z

#js[#js{:header #object[header], :hasBody #object[G__11224], :body #object[body]} #js{:header #object[header], :hasBody #object[G__11224], :body #...

bbrinck 2021-07-20T14:27:15.014600Z

Can you confirm those values are not other formatters that are pre-installed?

bbrinck 2021-07-20T14:27:55.015200Z

It’s hard to say just from looking at what you showed above, since the values are opaque objects

Quentin Le Guennec 2021-07-20T14:28:51.015400Z

Oh yes I see

Quentin Le Guennec 2021-07-20T14:30:09.015900Z

Yep, I named the header function custom-header-function and : #js[#js{:header #object[analis_desktop$preload$custom_header_function], :hasBody #object[G__11224], :body

Quentin Le Guennec 2021-07-20T14:30:24.016400Z

Why unshift though? Can't I just make a new array?

Quentin Le Guennec 2021-07-20T14:31:10.017500Z

Oh nvm, it's displaying my console.log calls now

bbrinck 2021-07-20T14:31:37.018Z

Since s/window.devtoolsFormatters is a mutable JS array, we need to mutate it with unshift. We could make a new array, but we’d need to assign it somewhere

Quentin Le Guennec 2021-07-20T14:31:46.018300Z

Oh I see

bbrinck 2021-07-20T14:32:49.019300Z

Oh OK, that’s good to know that the console logs are printing. Do you see the custom message somewhere? It might be useful to add some weird text to it to make sure it’s easy to see e.g. "--- debugging----"

Quentin Le Guennec 2021-07-20T14:35:49.019600Z

Yeah it seems like the defonce _ call is not working

Quentin Le Guennec 2021-07-20T14:35:58.019900Z

It works when I remove it

Quentin Le Guennec 2021-07-20T14:36:48.020200Z

I got this now

Quentin Le Guennec 2021-07-20T14:38:05.021600Z

I don't see the result of the :body call though

Quentin Le Guennec 2021-07-20T14:38:14.021900Z

#js{:header
      (fn custom-header-function [object _config]
        (.log js/console "---debugging---" (instance? ExceptionInfo object))
        (when (instance? ExceptionInfo object)
          (let [message (some->> (repl/error->str object)
                                 (re-find #"[^\n]+"))]
            #js["span" message])))
      :hasBody (constantly true)
      :body    (fn [object _config]
                 (.log js/console "hello")
                 #js["div" (repl/error->str object)])}

bbrinck 2021-07-20T14:39:58.023400Z

OK, maybe you can simplify by just printing out random values to make sure header and body are being shown. Instead of #js["span" message], do #js["span" "test header"] and instead of #js["div" (repl/error->str object)] do #js["div" "test body"]

Quentin Le Guennec 2021-07-20T14:42:20.024100Z

Yep that works

Quentin Le Guennec 2021-07-20T14:42:21.024200Z

Quentin Le Guennec 2021-07-20T14:44:02.025Z

The console.log call only runs when I expand the body but I guess it's intended

Quentin Le Guennec 2021-07-20T14:44:58.025300Z

So I guess there's no issue in that part

bbrinck 2021-07-20T14:45:03.025600Z

Excellent!

bbrinck 2021-07-20T14:46:38.026900Z

So, if you put the old code back, then do something like (throw (ex-info "some error" {:a 1 :b 2})), then expand the body, you should see details about the error. Does that all work?

Quentin Le Guennec 2021-07-20T14:47:30.027100Z

Yep that works

Quentin Le Guennec 2021-07-20T14:47:40.027400Z

REPL eval error #error {:message "some error", :data {:a 1, 😛 2}}

bbrinck 2021-07-20T14:47:45.027600Z

Awesome!

bbrinck 2021-07-20T14:49:43.028700Z

OK, then next, can you do something like this (untested, I don’t have a Clojure repl handy)

(s/fdef my-inc
  :args (s/cat :x number?))
(defn my-inc [x] (+ x 1))

bbrinck 2021-07-20T14:49:56.029Z

Then call (my-inc "foo")

bbrinck 2021-07-20T14:50:15.029300Z

then expand the body, see what happens

Quentin Le Guennec 2021-07-20T14:51:02.029800Z

Evaluating this in the repl doesn't throw anything:

(do
  (s/fdef my-inc
    :args (s/cat :x number?))

  (defn my-inc [x] (+ x 1))

  (my-inc "foo"))

bbrinck 2021-07-20T14:53:14.030600Z

oh, shoot, I forgot

(stest/instrument `my-inc)

bbrinck 2021-07-20T14:53:58.031200Z

then (my-inc "foo")

Quentin Le Guennec 2021-07-20T14:55:13.031600Z

I got this in the chrome console:

#error {:message 
"Call to #'analis-des … not conform to spec."
, :data 
{:cljs.spec.alpha/problems [
{…}

Quentin Le Guennec 2021-07-20T14:56:26.032300Z

A standard ex-info message from what I understand, not formatted by expound

bbrinck 2021-07-20T14:57:50.032800Z

Is that in the body when you expand it?

bbrinck 2021-07-20T15:01:19.033300Z

The devtools-error-formatter code only sets the header and body, it won’t change what is displayed in the console itself.

Quentin Le Guennec 2021-07-20T15:03:41.033600Z

Well there are several things going on, it seems

Quentin Le Guennec 2021-07-20T15:03:44.033700Z

bbrinck 2021-07-20T15:42:30.035Z

Got it. OK, so it looks to me that there are a few things going on here. My guess is that if you removed that defonce, it means that each time you eval this code, it’s adding an additional formatter, so you’re seeing the results of old and new formatters

bbrinck 2021-07-20T15:42:40.035400Z

I don’t know if you can hard-reload the page or something to just see one

bbrinck 2021-07-20T15:43:25.035700Z

I could be mistaken and you’re seeing two outputs for other reasons.

bbrinck 2021-07-20T15:45:47.037200Z

It looks like for some reason, the following code isn’t taking effect (set! s/*explain-out* expound/printer). Can you double check that is being called? You might even copy/paste it into the body function to make sure it’s being called before you call repl/error->str

bbrinck 2021-07-20T15:47:16.038500Z

Or you could try

clojure
:body    (fn [object _config]
  #js["div" (binding [s/*explain-out* expound/printer]
  (repl/error->str object))
 ])

Quentin Le Guennec 2021-07-20T15:52:15.038700Z

Ok, I'll try this

Quentin Le Guennec 2021-07-20T16:03:58.040200Z

@bbrinck I partially got it. I actually had to move the code away from the preload and it works even with the defonce. I still only have "Validation error" for speced, though, but I guess I should directly contact the author.

👍 1
Quentin Le Guennec 2021-07-20T16:04:02.040400Z

Thanks a lot!

bbrinck 2021-07-20T16:46:45.041500Z

It’s possible the issue is that specced may need expound to be set up during macro-expansion time, which confusingly happens in Clojure, not Clojurescript.

bbrinck 2021-07-20T16:47:02.041700Z

More details here https://github.com/bhb/expound#clojurescript-considerations

bbrinck 2021-07-20T16:49:09.042900Z

i.e. you might have to set up a .cljc file and then do something like

(require '[expound.alpha :as expound]) #?(:clj (set! clojure.spec.alpha/*explain-out* expound.alpha/printer))

bbrinck 2021-07-20T16:49:35.043300Z

And have that happen before you start using any specced macros. That’s a total guess though.

Quentin Le Guennec 2021-07-20T18:10:49.043600Z

Hmm I did that and the issue remains

Quentin Le Guennec 2021-07-20T18:12:00.044900Z

It seems like it's rather chrome not displaying the complete message for speced specs though, because this gets printed in the REPL, and that looks like a expound string: #error {:message "Validation failed", :data {:explanation "-- Spec failed --------------------\n\n :a\n\nshould satisfy\n\n string?\n\n-------------------------\nDetected 1 error\n", :spec-object #object[cljs$core$string_QMARK_], :spec cljs.core/string?, :quoted-spec cljs.core/string?, :faulty-value-object :a, :quoted-faulty-value a, :faulty-value a}}

bbrinck 2021-07-20T18:47:04.045500Z

Yep, that is an expound message in the :explanation

bbrinck 2021-07-20T18:55:42.045900Z

But is that what is thrown from specced?

bbrinck 2021-07-20T18:55:58.046300Z

If so, it doesn’t appear to be compatible with error->str https://github.com/clojure/clojurescript/blob/a4673b880756531ac5690f7b4721ad76c0810327/src/main/cljs/cljs/repl.cljs#L229-L230

bbrinck 2021-07-20T18:56:39.047300Z

error->str (or rather ex-str, which is called by it) expects a normal spec error. If specced is already changing the error, you’d need to adjust your handler to specifically deal with specced data

bbrinck 2021-07-20T18:58:23.048200Z

I haven’t tested this, but something like

clojure
:body    (fn [object _config]
                 (.log js/console "hello")
                 #js["div" (get-in (repl/Error->map object) [:data :explanation])])

bbrinck 2021-07-20T22:37:42.048600Z

@quentin.leguennec1 -^