jonase/eastwood

Standalone executable

NoahTheDuke opened this issue · 3 comments

First off, love Eastwood, has saved my butt many times.

I'd love to use this from the command line. lein eastwood works but lein incurs a heavy start-up cost. Is there any chance of a GraalVM-style standalone executable of Eastwood?

vemv commented

Hi, thanks for the ♡!

Neither Lein startup time or JVM startup time are the bottleneck here - it's by and large tools.analyzer.

These days I'm fostering Eastwood, most of all, as a CI oriented solution, where startup time isn't perceived (people rarely stare at builds) and linting typically runs in parallel, relative to tests.

It tends to be a good idea to run clj-kondo before Eastwood, possibly fail-fasting it. As hopefully you know, both linters don't fully overlap, so running both is worth it.

One might further cut the run time by only running git-touched files, by comparing a PR against master. Polylith (and similar architectures) are good at this, see e.g. polyfy/polylith#179

Finally, it's also possible to run Eastwood from your REPL, integrated with a Reloaded workflow e.g. only refreshed files will be analyzed. https://github.com/nedap/formatting-stack is good at this, I use it daily however I don't consider it ready for prime time - just a few people actually use formatting-stack. We have some refactoring to do.

I hope this give you a good overview.

The only issue with being CI-oriented is that it's the last possible moment to catch certain classes of errors. For example, Eastwood reports that (java.math.BigInteger foo 1) doesn't have a matching ctor, so I have to type hint foo as a string, etc. Such reflection-based errors aren't being caught by clj-kondo, but I won't know that until I've created a set of commits and triggered a CI run that the code is "incorrect".

Being able to use the repl for instant feedback is nice, but it's not always possible sadly (some codebases aren't built for repl-driven development 😭), so I like to do periodic lein eastwood "{:namespace [my-ns.core]}". If you have any hints that aren't "use the repl" to speed up execution (checking a single namespace takes 26 seconds on my fairly beefy machine), I'd love to hear them.

vemv commented

Hi again, sorry for the delay!

If you have any hints that aren't "use the repl" to speed up execution (checking a single namespace takes 26 seconds on my fairly beefy machine), I'd love to hear them.

repl means here more broadly "use your existing JVM to invoke Eastwood".

So if you already have a JVM for your server app, and invoke Eastwood programatically, checking a single ns will take < 0.5s - not 26s.

There are some caveats related to re-evaluation. Perhaps you are familiar with them: evaluating code in an unlucky order can mean opaque issues, particularly related to defprotocol<->defrecord relationships (not if you use metadata-based protocol extension).

By carefully checking out the README you can get a full idea of these tradeoffs.

If going the programmatic route, eastwood.lint/eastwood is the defn to invoke. https://github.com/nedap/formatting-stack/blob/b77370f9ed972b21203b4f15197ad297688e4b29/src/formatting_stack/linters/eastwood.clj is a real-world example.

But honestly, as of 2022 I'm not very interested in fostering these ways. They can work, but they also can be a rabbit hole if hacking with Clojure tooling isn't already your bread and butter.

So as a pragmatic recommendation, I'd say:

  • You can always set *warn-on-reflection* globally, strategically
    • Can be done in your user.clj ns, after requiring many 3rd-party libs, so that you won't get a wall of warnings unrelated to your project
  • You can train yourself to avoid reflective code without depending on a linter so much
    • Definitely it's what I've experienced personally - it's not an instantaneous process but eventually you'll intuitively know which code is "reflect-y", i.e. where are type annotations needed and where are they redundant, etc.

Cheers - V