Fork me on GitHub
#rewrite-clj
<
2024-02-29
>
lukasz15:02:11

Can rewrite-clj read metadata? I'm particularly interested in ^:deprecated and ^:added - I wrote a tiny documentation generator using Babashka, and that' the only bit that I'm missing and I couldn't figure out how to do. I did notice that the ^:deprecated string appears when I use rewrite-clj.node but I don't see an obvious way of reading that programmatically without using regexes (uh)

borkdude15:02:09

btw you could maybe also use the clj-kondo analysis data, this spits out data about vars as well by reading rewrite-clj nodes

borkdude15:02:26

This is used in #C03EC4DD26S for documentation generation

lukasz15:02:37

I noticed that your doc generator uses kondo pod, I was thinking of taking that route eventually but just for my own education I'd love to learn to how to do this just with rewrite-clj :-)

lukasz15:02:40

(I also looked into how Kondo does this, but couldn't dive that deep because of time constraints)

borkdude15:02:02

user=> (p/parse-string "^:deprecated x")
<meta: ^:deprecated x>
This returns a meta node

borkdude15:02:10

What is the part, from there, that you don't understand?

lukasz15:02:04

that part, I get but it's not clear how to read the meta part, here's the relevant part of my code: https://gist.github.com/lukaszkorecki/bddc0c296e044b9e6d0ddfc5623adfd7

lukasz15:02:22

when I use (z/sexpr) - metadata is not included

borkdude15:02:11

user=> (meta (n/sexpr (p/parse-string "^:deprecated x")))
{:deprecated true}

lukasz15:02:50

hmm, I remember trying that out - let me double check

borkdude15:02:48

user=> (meta (z/sexpr (z/of-string "^:deprecated x")))
{:deprecated true}

lukasz16:02:28

I see, the above works, but why this doesn't?

# bb
Babashka v1.3.188 REPL.
Use :repl/quit or :repl/exit to quit the REPL.
Clojure rocks, Bash reaches.

user=> (require '[rewrite-clj.zip :as z] '[rewrite-clj.parser :as p])
user=> (meta (z/sexpr (z/of-string "(defn ^:deprecated create \"some doc\" [] :x)")))
nil
user=>

borkdude16:02:04

because the metadata is not on defn but on the create symbol

lukasz16:02:01

I assumed I can do something like (into [] (s/expr (z/of-string ...fn source...))) and read the meta that way

lukasz16:02:07

ok, that's helpful - I'll keep digging, thank you!

lukasz16:02:15

thank you @U04V15CAJ, that was super useful, final impl looks like this:

(defn get-fns-info [parsed]
  (let [fn-info (atom [])]
    (-> parsed
        (z/prewalk (fn [thing]
                     (when (and
                            (z/list? thing)
                            (= "defn" (str (first (z/sexpr thing)))))
                       (let [sexpr (z/sexpr thing)
                             [_f name docstring & _rest] sexpr
                             node (z/node thing)
                             metadata (when-let [meta? (first (filter #(= :meta (:tag %)) (remove n/whitespace? (n/children node))))]
                                        (meta (n/sexpr meta?)))
                             source (str node)
                             position (zipmap [:line :column] (z/position thing))]

                         (swap! fn-info conj
                                {:name name
                                 :metadata metadata
                                 :position position
                                 :source source
                                 :docstring (when (string? docstring)
                                              docstring)})))

                     thing)))
    (sort-by #(-> % :position :line) (deref fn-info))))