rewrite-clj

https://github.com/clj-commons/rewrite-clj
reefersleep 2021-03-18T18:32:52.001900Z

I’m trying to use rewrite-clj to manipulate datastructures that include ::’ed keys, and I’m seeing a lot of :?_current-ns_?. I thought I could use

(n/sexpr v
         {:auto-resolve (fn [alias]
                          (prn "calling autoresolve")
                          (get {:current *ns*}
                               alias
                               (symbol (str alias "-unresolved"))))})
to do replace it with something else, but it seems like the :auto-resolve fn is never called.

reefersleep 2021-03-18T18:38:48.002500Z

Actually, I could just do

(n/sexpr v
         {:auto-resolve (partial identity *ns*)})
, I guess.

borkdude 2021-03-18T18:45:22.002800Z

you are never seeing the prns?

reefersleep 2021-03-18T18:46:29.003Z

never

reefersleep 2021-03-18T18:49:45.003800Z

I know that *ns* is not supported anymore ( 😞 ), but I don’t understand why the fn isn’t called.

reefersleep 2021-03-18T18:50:38.004700Z

(and for resolving the ns dynamically, I guess I’ll have to travel the ns form and find the right symbol myself… and carry that down to the place where I need it.)

borkdude 2021-03-18T18:57:06.004900Z

@reefersleep

user=> (require '[rewrite-clj.node :as n])
nil
user=> (require '[rewrite-clj.parser :as p])
nil
user=> (p/parse-string "::foo")
<token: ::foo>
user=> (n/sexpr (p/parse-string "::foo"))
:?_current-ns_?/foo
user=> (n/sexpr (p/parse-string "::foo") {:auto-resolve (fn [m] (prn m))})
:current
:foo

borkdude 2021-03-18T18:57:59.005100Z

user=> (n/sexpr (p/parse-string "{:a ::foo}") {:auto-resolve (fn [m] (prn m))})
:current
{:a :foo}

lread 2021-03-18T19:10:19.006400Z

@reefersleep, carrying on from @borkdude’s example but also using *ns*:

> clj
Clojure 1.10.3
user=> (require '[rewrite-clj.node :as n])
nil
user=> (require '[rewrite-clj.parser :as p])
nil
user=> (def my-node (p/parse-string "::foo"))
#'user/my-node
user=> (n/sexpr my-node {:auto-resolve (fn [x] (ns-name *ns*))})
:user/foo

lread 2021-03-18T19:10:58.007100Z

But maybe binding *ns* to what you really want it to be will be the challenge? Dunno.

lread 2021-03-18T19:12:23.008400Z

Also can’t remember if you are using zip API. If so you can provide :auto-resolve at zipper creation time and it will be used automatically for any zipper operations that auto-resolve.

lread 2021-03-18T19:15:27.010600Z

Depending on your use case, you’ll also want to look at auto-resolve fn argument. As @borkdude illustrated, it will be :current for current namespace auto-resolves ::foo, and the ns alias for namespace alias auto-resolves ::my-ns-alias/foo

reefersleep 2021-03-18T19:31:48.011500Z

@lee your example works as expected. Don’t know why my code does not!

reefersleep 2021-03-18T19:36:47.012300Z

I’m a dumb. I was referring to the wrong value, I think. Let me investigate.

reefersleep 2021-03-18T19:36:53.012700Z

If so, I’m really sorry for wasting your time!

lread 2021-03-18T19:37:12.013Z

Not a problem at all, we’ve all been there!

lread 2021-03-18T19:37:47.013500Z

Actually I sometimes live there! :simple_smile:

lread 2021-03-18T19:39:10.014500Z

When ya do figure it out, let us know what it was. It might help us to improve the docs or the library.

reefersleep 2021-03-18T19:46:29.015700Z

It was nothing to do with rewrite-clj, I should have simplified my code example and/or more thoroughly inspected the values I was manipulating. The code had gotten a bit unwieldy, I guess 🙂 (And maybe I’m suffering from baby-related sleep deprivation issues 😉 )

lread 2021-03-18T19:47:38.016300Z

Well, whatever the reason, glad you are back on track!

reefersleep 2021-03-18T19:47:43.016500Z

Thanks!

reefersleep 2021-03-18T19:48:10.016900Z

The next challenge, as you said, is to get the correct ns.

reefersleep 2021-03-18T19:48:54.017500Z

For (ns-name *ns*), I’m getting the ns I’m calling from, not the ns I’m navigating with rewrite-clj.

reefersleep 2021-03-18T19:49:35.017800Z

(As stated in the docs, in some way)

lread 2021-03-18T19:51:14.018500Z

Yeah, rewrite-clj doesn’t look at or modify *ns*.

reefersleep 2021-03-18T19:51:55.019Z

Is there a plan to provide the current ns in some other way?

lread 2021-03-18T19:52:08.019500Z

Of the file you are parsing?

reefersleep 2021-03-18T19:52:18.019800Z

Or does rewrite-clj just avoid doing it because it’s not really a compiler? 🙂

reefersleep 2021-03-18T19:52:20.020100Z

yes

reefersleep 2021-03-18T19:52:53.021100Z

I mean, I get that *ns* can be dynamically changed throughout a file, so it’s not straightforward

reefersleep 2021-03-18T19:53:49.023Z

plus it requires the full scope of the read/compiled file, and I guess that’s not really the mission of rewrite-clj.

lread 2021-03-18T19:55:21.023800Z

I was considering parsing out namespace info in earlier designs of rewrite-clj v1. Thankfully @borkdude talked me out of it. I documented the https://github.com/clj-commons/rewrite-clj/blob/main/doc/design/namespaced-elements.adoc#sexpr-rabbit-hole.

lread 2021-03-18T19:57:07.024900Z

Yeah, even if rewrite-clj did parse out this info (which it doesn’t and we don’t have plans for it to do so), it would not change or read *ns*.

reefersleep 2021-03-18T19:59:17.026700Z

Wonderful notes, great investment!

lread 2021-03-18T19:59:45.027200Z

Thanks, I write things down because I am rather forgetful!

lread 2021-03-18T20:00:35.028200Z

It would not be difficult to get parsing of the current ns out of a source file correct most of the time. But most of the time isn’t good enough for rewrite-clj.

reefersleep 2021-03-18T20:00:42.028400Z

I try to do the same, but I tend to forget that I have the notes or where they are 😅

lread 2021-03-18T20:01:10.028700Z

Well chalk it up to sleep deprevation!

lread 2021-03-18T20:03:03.030300Z

That said, I think cljfmt, which uses rewrite-clj does parse out ns info. You might want to look there for inspiration, if you are trying to do something similar.

reefersleep 2021-03-18T20:03:22.030700Z

> most of the time isn’t good enough for rewrite-clj I totally get that. You’d be making promises you couldn’t keep. I guess I’ll try reading the ns form; I think we make very few ns gymnastics in our code base, but I might be surprised.

reefersleep 2021-03-18T20:03:34.030900Z

Thanks, I’ll have a look!

lread 2021-03-18T20:04:54.032100Z

If you are just looking for the current ns and not ns aliases, you’ll likely not have a terrible slog.

reefersleep 2021-03-18T20:07:10.033800Z

That I am, and I’m thinking the same. I’ve read before that the ns form is terribly dynamic, but I don’t think we make particular use of that dynamism. (let [namespaze (-> zloc z/down z/right)] will probably work most of the time.

lread 2021-03-18T20:08:11.034700Z

There’s also https://github.com/clj-kondo/clj-kondo/tree/master/analysis, if you want to look at that route.

reefersleep 2021-03-18T20:13:24.035500Z

I might go for that in the next iteration. For now, cljfmt’s solution looks like what I had imagined doing 🙂

lread 2021-03-18T20:14:41.036300Z

Not to sway you at all, but I just used https://github.com/lread/test-doc-blocks/blob/b2f43e25191ff0ca4609bb78fac60b89c2a5bf07/src/lread/test_doc_blocks/impl/doc_parse.clj#L287. Super duper easy to use. So that option is there too.

reefersleep 2021-03-18T20:17:54.037100Z

Consider me unintentionally swayed 🙂

lread 2021-03-18T20:19:46.037600Z

Ha! Do what works for you, all just ideas.

reefersleep 2021-03-18T20:21:16.037900Z

Looks easy enough!

reefersleep 2021-03-18T20:21:56.038700Z

I’m just reluctant to add more deps. This project has way too many already. But what I’m doing should really be refactored into its own project anyway, eventually.

reefersleep 2021-03-18T20:40:47.039200Z

Yeah, like you did, that seems fine!

(defn get-namespace [^java.io.File file]
  (-> (clj-kondo/run! {:lint   [(.getPath file)]
                       :config {:output {:analysis true}}})
      :analysis
      :namespace-definitions
      first
      :name))

borkdude 2021-03-18T20:41:43.039700Z

@reefersleep you can even compare the locations of var definitions reported by clj-kondo against your rewrite-clj node

borkdude 2021-03-18T20:41:51.039900Z

and their namespaces

reefersleep 2021-03-18T20:43:47.040400Z

That’s pretty cool, so you can get the exact ns for each node? Wow.

borkdude 2021-03-18T21:01:50.040900Z

@reefersleep it seems you were interested in keywords though right?

borkdude 2021-03-18T21:02:03.041200Z

you can get those too in the analysis with an extra option

reefersleep 2021-03-18T21:09:02.041400Z

Which one? @borkdude

borkdude 2021-03-18T21:09:17.041600Z

:keywords true

reefersleep 2021-03-18T21:23:01.042300Z

Beautiful.

reefersleep 2021-03-18T21:23:34.043100Z

Trying to figure out how to match that with the rewrite-clj node, I’m expecting the node API to hold the row and col somewhere, just haven’t found it yet

reefersleep 2021-03-18T21:25:16.043500Z

Ah. :track-position true

lread 2021-03-18T21:26:31.044600Z

If you aren’t needing to keep track of changes to position you can opt out of :track-position and use https://cljdoc.org/d/rewrite-clj/rewrite-clj/1.0.591-alpha/doc/user-guide#nodes

lread 2021-03-18T21:29:11.046Z

@borkdude’s https://github.com/borkdude/carve might be a good clj-kondo/rewrite-clj example for you

reefersleep 2021-03-18T21:29:23.046300Z

ahhh nice

borkdude 2021-03-18T21:38:48.046600Z

@reefersleep yeah, just call meta on the node

borkdude 2021-03-18T21:39:11.047200Z

This is what sold me to rewrite-clj initially: I needed location info of every possible thing in clojure code for clj-kondo

reefersleep 2021-03-18T21:39:28.047400Z

🙂

reefersleep 2021-03-18T21:41:33.047900Z

Damn. Wrote the code blind, and it worked the first time around.

reefersleep 2021-03-18T21:42:13.048700Z

Lovely! Fetched the row and col of each keyword node and composed the namespaced keyword from clj-kondo’s analysis.

reefersleep 2021-03-18T21:58:15.049200Z

@lee and @borkdude, thanks for all your help!

🎉 2
lread 2021-03-18T21:58:47.049600Z

Thanks for your interest and sharing your journey!