eastwood

All things realted to eastwood - the Clojure linter
2018-10-04T00:00:44.000100Z

I just imagined someone putting error/log message text into source code that then goes through a demangling process like rot13 before being printed, in a huge code base with many developers, and shuddered. Someone would have to be pretty wicked to do that.

2018-10-04T00:05:56.000100Z

haha

2018-10-04T17:58:23.000100Z

if I have

(ns foo.bar
  (:require [baz.baz :as b]))

(def x {::b/baz 42})
- is it a known bug that eastwood would call ::b/baz an invalid keyword?

slipset 2018-10-04T17:59:32.000100Z

hang on, I’ll check

slipset 2018-10-04T18:02:33.000100Z

Cannot reproduce locally, given:

slipset 2018-10-04T18:02:47.000100Z

(ns baz.core
  (:require [baz.non-core :as b])
  (:gen-class))

(def b  {::b/id 'a})
(defn -main
  "I don't do a whole lot ... yet."
  [& args]
  (println "Hello, World! " b))

slipset 2018-10-04T18:02:58.000100Z

and

slipset 2018-10-04T18:03:00.000100Z

(ns baz.non-core)

(def lol 'lol)

slipset 2018-10-04T18:03:39.000100Z

Could you create a minimal repro on github for me to see if I can repro as well?

2018-10-04T18:04:08.000100Z

hmm.. trying to make a minimal repro right now - this namespace is super small so I think I'll start with it, and pare it down...

2018-10-04T18:04:30.000100Z

yeah, the smallest example doesn't fail so this must be some corner case I'm hitting

2018-10-04T18:07:20.000100Z

I think Clojure 1.9 calls that an invalid keyword?

2018-10-04T18:08:03.000100Z

and Eastwood uses not Clojure's reader, but a version of the tools.reader library that has been updated to be compatible with Clojure 1.9 features like namespaced keywords in maps.

2018-10-04T18:08:20.000100Z

it is valid, and the simple version does accept it

2018-10-04T18:08:29.000100Z

there's some corner case rejecting it

2018-10-04T18:08:47.000100Z

I have a half-recollection that :b/baz is the correct syntax according to Clojure 1.9, and it rejects ::b/baz. Let me check.

2018-10-04T18:09:06.000100Z

:b/baz is a literal, ::b/baz auto-expands to whatever b is aliased to

slipset 2018-10-04T18:09:19.000200Z

I use ::b/baz is as @noisesmith describes.

2018-10-04T18:10:28.000100Z

OK, my half-recollection is incorrect, then.

2018-10-04T18:13:03.000200Z

OK, the change I was half-remembering is that it used to be Clojure and tools.reader accepted ::anythinghere/kw, but then changed so that anythinghere was required to be an alias.

2018-10-04T18:13:25.000100Z

fascinating

2018-10-04T18:14:19.000100Z

The case you have hit sounds like it could be a bug in tools.reader, or more likely in how Eastwood is using it.

2018-10-04T18:15:31.000100Z

:thumbsup: - I'll let you know if I come up with a repro that makes sense outside this complex project

2018-10-04T18:16:34.000100Z

Is the complex project using Clojure 1.9?

2018-10-04T18:17:21.000100Z

yeah

2018-10-04T18:17:43.000100Z

ugh - it's not a thing I can disable in config since it's tools.reader blowing up

2018-10-04T18:17:46.000100Z

If so, then I would guess that unless it has a bug in its decision of whether the alias is a valid alias or not (seems unlikely), then it is an actual valid alias, hopefully one obviously in the ns form

2018-10-04T18:18:14.000100Z

you can disable linting the entire namespace in Eastwood options.

2018-10-04T18:18:24.000100Z

the alias is definitely in the ns form - I'll do that for now, thanks

slipset 2018-10-04T18:21:51.000100Z

@andy.fingerhut while you’re here.

slipset 2018-10-04T18:22:18.000100Z

Do you have much knowledge of the different callbacks that are set up and passed around in opts?

slipset 2018-10-04T18:22:57.000100Z

Specifically, are they part of the public api of Eastwood, or is it more something that’s used by devs when developing Eastwood?

2018-10-04T18:35:04.000100Z

I am pretty sure I implemented those, a few years back. I don't recall those being documented in the README anywhere. If anyone relies on them, they haven't asked me about them.

2018-10-04T18:36:22.000200Z

The most likely people to have ever looked at and/or taken advantage of such things would be IDE/editor integrations with Eastwood.

2018-10-04T18:37:20.000100Z

The 'print to stdout' and 'return a sequence of maps' APIs are documented in the README, and would be good to preserve their APIs

slipset 2018-10-04T18:38:20.000100Z

:callback - a default message callback function, which simply formats all callback data as strings and prints it to *out*, or the writer specified by the value of the :out key in the options map (e.g. if it is a string, the file named by that string will be written).

2018-10-04T18:38:48.000100Z

By those, I mean the eastwood.lint/eastwood and eastwood.lint/lint functions.

2018-10-04T18:39:23.000200Z

my issue goes away when I eliminate a completely different error (calling defmethod on something that isn't a multimethod), which is deeper in recursive loads of loading the ns that had the alias that erroneously errored

2018-10-04T18:39:26.000100Z

README has said this for a long time: "There is no documentation for this callback function yet. You are welcome to read Eastwood source code to see examples of how to write one, but note that this is alpha-status code that will likely have API changes in future Eastwood versions."

slipset 2018-10-04T18:39:50.000100Z

Nice!

2018-10-04T18:40:15.000100Z

I left us some freedom there 🙂

slipset 2018-10-04T18:40:39.000100Z

So basically, as long as the public api of eastwood.lint/eastwood and eastwood.lint/lint is preserved we’re good?

2018-10-04T18:41:27.000100Z

Well, have you heard of Hyrum's Law? Despite that, I have no reason to believe that very many people would be affected by changing the callback parts of the implementation.

2018-10-04T18:41:43.000100Z

(if any would)

slipset 2018-10-04T18:42:29.000200Z

The reason I’d like to change it is that (at least for me) it’s kind of hard to follow.

2018-10-04T18:42:30.000100Z

Changing those APIs would seem very break-y to me, yes. Keeping them I expect will keep 95% or more of Eastwood users content, if not 100%

slipset 2018-10-04T18:42:57.000100Z

in terms of what functions are actually called (at the call site)

2018-10-04T18:43:19.000200Z

Understood. I do not consider it a shining example of code I would suggest for a future edition of the book "Beautiful Code" 🙂

slipset 2018-10-04T18:43:28.000100Z

lol 🙂

slipset 2018-10-04T18:43:32.000100Z

(defn report-warnings [cb warning-count warnings]
  (swap! warning-count + (count warnings))
  (doseq [warning warnings]
    (cb warnings)))

slipset 2018-10-04T18:43:59.000100Z

“What is cb at this point?” is a hard question to answer 🙂

slipset 2018-10-04T18:49:37.000100Z

But I don’t think I have a better solution of the top of my head 😞

2018-10-04T18:49:40.000100Z

Yes, that can be a general issue with passing functions around in any functional programming language. I saw your comment (maybe an Eastwood issue?) about ripping out and replacing the callback stuff. If that helps you out, I have no objections.

slipset 2018-10-04T18:51:07.000100Z

Since we basically have two usecases here. One is reporting progress as we go along, that would be eastwood.lint/eastwood, the other is summarizing the findings as data which would be eastwood.lint/lint

slipset 2018-10-04T18:52:32.000100Z

I guess one way to solve it would be to have some protocol which defined “warning” “exception” and “note” kind of functions and have two implementations of that which we could pass along.

slipset 2018-10-04T18:54:03.000100Z

Another way would be to include core.async as a lib and have the results pushed onto a channel which could then be read and the data could either be collected or printed.

slipset 2018-10-04T18:54:51.000100Z

Yet another way would be to have a global atom that holds the linter state, and install watchers on that atom that could print progress.

2018-10-04T19:09:41.000100Z

I don't have experience using core.async personally, but it seems like kind of a big dependency to take on for this purpose. A global atom is maybe ugly from the 'global variable' perspective, but a lot less machinery involved.

2018-10-04T19:10:34.000100Z

Eastwood code runs in the same JVM that the user's code is linted, and thus eval'd, so using core.async on projects that use core.async means adding a copy of core.async to the copieddeps directory.

slipset 2018-10-04T19:19:24.000100Z

yeah, I think core.async is out of the question as well.

slipset 2018-10-04T19:23:12.000100Z

I believe passing a thing that “implements” an IEastwoodReporter protocol would be my preferred way.

slipset 2018-10-04T19:25:04.000100Z

Which could be initialized with the values from opts, like :debug and :out

2018-10-04T20:06:50.000100Z

justin.smith@C02RW05WFVH6: ~/foo$ cat src/foo/core.clj 
(ns foo.core
  (:require [clojure.srting]
            [clojure.walk :as w]))

(def bar
  #{::w/foo})

(defn foo
  "I don't do a whole lot."
  [x]
  (println x "Hello, World!" bar))
justin.smith@C02RW05WFVH6: ~/foo$ lein eastwood
== Eastwood 0.3.1 Clojure 1.8.0 JVM 1.8.0_171
Directories scanned for source files:
  src test
== Linting foo.core ==
Linting failed:
ExceptionInfo foo/core.clj [line 6, col 12] Invalid keyword: ::w/foo.

2018-10-04T20:07:07.000100Z

minimal repro of the namespace keyword false-positive

2018-10-04T20:07:18.000100Z

the real error is srting not being string

2018-10-04T20:08:02.000100Z

the thing that made it hard to reproduce is that the problem isn't even in a related ns to the one the keyword aliases to

2018-10-04T20:09:34.000100Z

Does Clojure compile that file without error?

2018-10-04T20:09:48.000100Z

I would think it would throw an exception trying to eval the ns form?

2018-10-04T20:09:53.000100Z

no, it gives the expected error that it can't find clojure.srting

2018-10-04T20:10:15.000100Z

the problem is that eastwood is only erroring on ::w/foo, not the ns form

2018-10-04T20:10:15.000200Z

So Eastwood is continuing on past that exception, rather than stopping as i would hope it would.

2018-10-04T20:10:26.000100Z

right, and also reporting the wrong error

2018-10-04T20:10:33.000100Z

e.g. I would expect Eastwood should raise the same exception when eval'ing the ns form that Clojure would, and stop.

2018-10-04T20:10:40.000100Z

:thumbsup:

2018-10-04T20:11:52.000100Z

Out of curiosity, can you try an older version of Eastwood on that same file, e.g. maybe 0.2.6 or 0.2.7, to see if they raise an exception and stop? There may be changes in the exception handling since then, is my guess.

2018-10-04T20:12:39.000100Z

I'll try some time today, sure

2018-10-04T20:13:00.000100Z

I can try it later, too, so no rush. You want to file an issue, or you'd prefer I do?

2018-10-04T20:13:19.000100Z

btw I'm running into these issues because eastwood is saving me a lot of time and labor doing complex refactors and validating the results, so thanks :D

2018-10-04T20:13:47.000100Z

I'll plan to file an issue and compare to what earlier eastwood versions do later today

2018-10-04T20:14:41.000100Z

Cool. It is always nice to see minimal consistent reproducible problems, rather than "my 200 file project that I can't show you the source code of gives this error".

2018-10-04T20:14:52.000100Z

and I know those can take effort to create.

2018-10-04T20:15:41.000100Z

(both the 200 file project, and the minimal reproductions of the problems :-)

2018-10-04T20:15:51.000100Z

haha, right

2018-10-04T20:48:23.000100Z

reproduced the error on versions back to 0.2.4 so far

2018-10-04T21:00:29.000100Z

@andy.fingerhut I'll make an issue for this, the most recent eastwood version that catches this correctly is 0.1.3 (!)

2018-10-04T21:03:58.000100Z

OK, so said finger I was planning to surreptitiously point at @slipset now correctly belongs at me 🙂

2018-10-04T21:04:11.000100Z

oh me, of little trust.