nedap/speced.def

How can I get the stack or line number for error?

DogLooksGood opened this issue · 10 comments

When spec check failed, an error is raised, and I can see it in the browser console, but there's no line number or stack information.

I don't know if it is limited by the Clojure's pre/post?

vemv commented

Hi!

Thanks for reaching out. Since there are various ways of setting up clojurescript, I cannot know the exact conditions you have.

Could you try a couple experiments?

  • Create a plain defn (not speced/), with a :pre that fails
  • Create a plain defn that performs (throw (ex-info "example" {})) in the middle of its body

Do those correctly report their line number and stack info?

I'm using shadow-cljs with the :browser target.

Just have some experiments,

  • Plain defn with :pre that fails will have a proper stack.
  • speced/def with (throw (ex-info "" {})) don't have a meaningful stack.
  • Plain defn with (throw (ex-info "" {})) don't have a meaningful stack.
  • Plain defn with (throw (js/Error. "")) have a proper stack.

So I think it could be related to how shadow-cljs works or how the stacktrace of ex-info is generated.
I will turn to shadow-cljs for more information.

vemv commented

I wasn't familiar with those intricacies. Great to know thanks!

Feel free to share your findings here. I might also play with shadow-cljs out of curiosity.

@vemv what environment you are using, is it behave correctly in your case?

vemv commented

For developing libraries such as this one, I use the cljsbuild :node target. That's a pretty barebones setup, and I'm used to always have meaningless stacktraces there (for all kind of code: speced or not, :pre, js/Error or ex-info). Because they have no source mapping.

I do have a shadow-cljs app around though, I just don't happen to work with it too often. Will let you know if I find anything interesting.

If the error was not raised while loading the given script, and not caused by the code from repl, a meaningful stacktrace will be provided.

It's probably because of the :script loader-mode provided by shadow-cljs. shadow-cljs has two ways for loading compiled js: :script and :eval.

The experiments I did will all work correctly if code is loaded via tag. But the :eval way provides better performance and better experience with more use case, such as React Native.

Still don't know the difference between ex-info and js/Error.

vemv commented

Thanks! I also checked the discussion in Clojurians #shadow-cljs.

Accordingly I edited a bit your post above so I can remember these conclusions later too (I understand EN is not your first language).

I'll make sure to play with shadow-cljs soon. If changing ex-info -> js/Error is proven to be better, I'll change that.

Btw as you may have noticed, there are 3 pull requests open in this project, 2 of which are done. All of them are relevant for cljs projects, particularly the one that solves #70

Very likely sometime in January all those goodies will be released as speced.def 2.0!

vemv commented

You're right!

with js/Error

meaningful stacktrace + source mapping:

image

with ex-info

no niceties:

image

I'll fix this over the week.

If you're impatient, you can apply this patch https://gist.github.com/vemv/e9a7534a5bc7f277395add3e7c23a291 to https://github.com/nedap/utils.spec/tree/ca6097b013488ed6da4463a734c3132bb0e68b8e and make your project use that utils.spec patched version (speced.def depends on utils.spec)

I didn't notice this before is because for most case the error is not raised during loading. But as far as I use speced.def, this become common.

vemv commented

I have given the topic good thought.

Because clojure.spec is based on invoking arbitrary functions including those outside of my control, there are many sources that can throw an ex-info during load time.

So, even if I committed the patch I attached above, you'd still run into occasional ex-infos. Generally I try not to author or encourage solutions that don't fully solve a problem.

In this case, there are two possible solutions which appear to be complete:

  • use :loader-mode :script
  • use :loader-mode :eval but initialize the app asynchronously
    • e.g. (defn main [] (js/setTimeout myapp 0))
    • suggested by the shadow-cljs author in Clojurians
    • seems good to me, although maybe it can bring some new problems on its own.

The :loader-mode differences don't appear to be documented right now, so I requested that. He says he will think about it: https://clojurians.slack.com/archives/C6N245JGG/p1578250009291400?thread_ts=1578218029.277500&cid=C6N245JGG