code.query

Structural code queries and transformations.

code.query provides selectors and traversal helpers over parsed Clojure source. It lets tools ask for forms by structure instead of relying on regular expressions.

1    Motivation

Many maintenance tasks need to identify forms such as namespace requires, facts, defs, macros, or specific nested calls. code.query is the layer that turns parsed std.block trees into searchable and editable source structures.

2    How to use it

Use match predicates for local checks, traversal functions to walk a tree, and compile helpers to turn query data into reusable selectors. code.manage builds on this layer for locate and refactor tasks.

3    Internal usage

code.manage.var/find-usages uses code.query to discover namespace require aliases before searching for candidate symbols. Refactor and formatting tasks use query traversal to modify parsed blocks while preserving source shape.

4    Walkthrough

4.1    Matching a single form

match checks whether a parsed navigator satisfies a pattern. Meta annotations like ^:% mark predicates, ^:%- allow preceding whitespace, and ^:%+ insert matching nodes.

match with predicates and anchors

(q/match (nav/parse-string "(+ 1 1)") '(symbol? _ _))
=> false

(q/match (nav/parse-string "(+ 1 1)") '(^:% symbol? _ _))
=> true

(q/match (nav/parse-string "(+ 1 1)") '(^:%- symbol? _ | _))
=> true

4.2    Selecting and modifying forms

$ is the main query macro. Give it a string or navigator, a selector, and an optional transform function. Selectors can capture values with |, insert nodes with ^:%+, and target nested paths.

select matching forms from source

(q/$ {:string "(defn hello1) (defn hello2)"}
     [(defn _ ^:%+ (keyword "tag"))])
=> '[(defn hello1 :tag) (defn hello2 :tag)]

capture values with the pipe marker

(q/$ {:string "(defn hello1) (defn hello2)"}
     [(defn _ | ^:%+ (keyword "tag"))])
=> '[:tag :tag]

modify matched forms with a function

(nav/string
 (q/modify (nav/parse-root "(defn hello3) (defn hello)")
           ['(defn | _)]
           (fn [zloc]
             (nav/insert-token-to-left zloc :hello))))
=> "(defn :hello hello3) (defn :hello hello)"

4.3    Traversing a tree

traverse walks to the first match and returns a navigator, while select collects every match. Both work with nested patterns like [defn if try].

traverse to a nested form

(nav/value
 (q/traverse (nav/parse-string "(defn hello [] (if (try)))")
             '[defn if try]))
=> '(defn hello [] (if (try)))

select all matching nested forms

(map nav/value
     (q/select (nav/parse-root "(defn a [] (if (try))) (defn b [] (if (try)))")
               '[defn if try]))
=> '((defn a  [] (if (try)))
     (defn b [] (if (try))))

4.4    End-to-end: add a tag to every defn

Combining $, a capture selector, and a replace function makes a tiny refactoring: tag every top-level defn with metadata.

tag every defn in a snippet

(->> (q/$ {:string "(defn a []) (defn b [])"}
           [(defn _ _)]
           (fn [zloc]
             (nav/insert-token-to-right zloc :tagged)))
     (map nav/root-string)
     vec)
=> ["(defn a :tagged [])" "(defn b :tagged [])"]

5    API



^

[context path & args]
Added 3.0

select and manipulation of clojure source code

v 3.0
(defmacro $
  ([context path & args]
   `($* ~context (quote ~path) ~@args)))
link
($ {:string "(defn hello1) (defn hello2)"} [(defn _ ^:%+ (keyword "oeuoeuoe"))]) => '[(defn hello1 :oeuoeuoe) (defn hello2 :oeuoeuoe)] ($ {:string "(defn hello1) (defn hello2)"} [(defn _ | ^:%+ (keyword "oeuoeuoe"))]) => '[:oeuoeuoe :oeuoeuoe] (->> ($ {:string "(defn hello1) (defn hello2)"} [(defn _ | ^:%+ (keyword "oeuoeuoe"))] {:return :string})) => [":oeuoeuoe" ":oeuoeuoe"] (first ($ (nav/parse-root "a b c") [{:is a}])) => 'a

$* ^

[context path & [func? opts?]]
Added 3.0

provides a lower-level interface for selecting and manipulating Clojure source code, used as a helper for `$`

v 3.0
(defn $*
  ([context path & [func? opts?]]
   (let [zloc (context-zloc context)
         [func opts] (cond (nil? func?) [nil opts?]
                           (map? func?) [nil func?]
                           :else [func? opts?])
         results     (cond func
                           (modify zloc path func opts)
                           
                           :else
                           (select zloc path opts))
         opts         (merge {:return (if func :zipper :value)} opts)]
     ((-> (fn [res opts] res)
          wrap-return
          wrap-vec) results opts))))
link

context-zloc ^

[context]
Added 3.0

gets the context for loading forms

v 3.0
(defn context-zloc
  ([context]
   (cond (nav/navigator? context)
         context

         (string? context)
         (nav/parse-root (slurp context))

         (vector? context) context

         (map? context)
         (-> (cond (:source context)
                   (:source context)

                   (:file context)
                   (nav/parse-root (slurp (:file context)))

                   (:string context)
                   (nav/parse-root (:string context))

                   :else (throw (ex-info "keys can only be either :file or :string" context))))
         :else (throw (ex-info "context can only be a string or map" {:value context})))))
link
(context-zloc (nav/parse-string "1")) => (comp nav/navigator?)

match ^

[zloc selector]
Added 3.0

matches the source code

v 3.0
(defn match
  ([zloc selector]
   (let [match-fn (-> selector
                      (compile/expand-all-metas)
                      (common/prepare-deletion)
                      (match/compile-matcher))]
     (try (match-fn zloc)
          (catch Throwable t false)))))
link
(match (nav/parse-string "(+ 1 1)") '(symbol? _ _)) => false (match (nav/parse-string "(+ 1 1)") '(^:% symbol? _ _)) => true (match (nav/parse-string "(+ 1 1)") '(^:%- symbol? _ | _)) => true (match (nav/parse-string "(+ 1 1)") '(^:%+ symbol? _ _)) => false

modify ^

[zloc selectors func] [zloc selectors func opts]
Added 3.0

modifies location given a function

v 3.0
(defn modify
  ([zloc selectors func] (modify zloc selectors func nil))
  ([zloc selectors func opts]
   (let [[match-map [cidx ctype cform]] (compile/prepare selectors)
         match-fn (match/compile-matcher match-map)
         walk-fn (case (:walk opts)
                   :top walk/levelwalk
                   walk/matchwalk)]
     (walk-fn zloc
              [match-fn]
              (fn [zloc]
                (if (= :form ctype)
                  (let [{:keys [level source]} (traverse/traverse zloc cform)
                        nsource (func source)]

                    (if (or (nil? level) (= level 0))
                      nsource
                      (nth (iterate nav/up nsource) level)))
                  (func zloc)))
              opts))))
link
(nav/string (modify (nav/parse-root "^:a (defn hello3) (defn hello)") ['(defn | _)] (fn [zloc] (nav/insert-token-to-left zloc :hello)))) => "^:a (defn :hello hello3) (defn :hello hello)"

select ^

[zloc selectors] [zloc selectors opts]
Added 3.0

selects all patterns from a starting point

v 3.0
(defn select
  ([zloc selectors] (select zloc selectors nil))
  ([zloc selectors opts]
   (let [[match-map [cidx ctype cform]] (compile/prepare selectors)
         match-fn (match/compile-matcher match-map)
         walk-fn (case (:walk opts)
                   :top walk/levelwalk
                   walk/matchwalk)]
     (let [atm  (atom [])]
       (walk-fn zloc
                [match-fn]
                (fn [zloc]
                  (swap! atm conj
                         (if (= :form ctype)
                           (:source (traverse/traverse zloc cform))
                           zloc))
                  zloc)
                opts)
       (if (:first opts)
         (first @atm)
         @atm)))))
link
(map nav/value (select (nav/parse-root "(defn hello [] (if (try))) (defn hello2 [] (if (try)))") '[defn if try])) => '((defn hello [] (if (try))) (defn hello2 [] (if (try))))

traverse ^

[zloc pattern] [zloc pattern func]
Added 3.0

uses a pattern to traverse as well as to edit the form

v 3.0
(defn traverse
  ([zloc pattern]
   (let [pattern (compile/expand-all-metas pattern)]
     (:source (traverse/traverse zloc pattern))))
  ([zloc pattern func]
   (let [pattern (compile/expand-all-metas pattern)
         {:keys [level source]} (traverse/traverse zloc pattern)
         nsource (func source)]
     (if (or (nil? level) (= level 0))
       nsource
       (nth (iterate nav/up nsource) level)))))
link
(nav/value (traverse (nav/parse-string "^:a (+ () 2 3)") '(+ () 2 3))) => '(+ () 2 3) (nav/value (traverse (nav/parse-string "()") '(^:&+ hello))) => '(hello) (nav/value (traverse (nav/parse-string "()") '(+ 1 2 3))) => (throws) ($ {:string "(ns hello) ()"} [(ns _ ^:%+ (keyword "oeuoeuoe"))]) => '[(ns hello :oeuoeuoe)] (nav/value (traverse (nav/parse-string "(defn hello "world" {:a 1} [])") '(defn ^:% symbol? ^:?%- string? ^:?%- map? ^:% vector? & _))) => '(defn hello [])

wrap-return ^

[f]
Added 3.0

decides whether to return a string, zipper or sexp representation`

v 3.0
(defn wrap-return
  ([f]
   (fn [res {:keys [return] :as opts}]
     (case return
       :string (nav/string (f res opts))
       :zipper res
       :value  (nav/value (f res opts))))))
link
((wrap-return (fn [res opts] res)) (nav/parse-string "1") {:return :string}) => "1"

wrap-vec ^

[f]
Added 3.0

ensures an item is wrapped in a vector, or handles it as a vector if it already is one

v 3.0
(defn wrap-vec
  ([f]
   (fn [res opts]
     (if (vector? res)
       (mapv #(f % opts) res)
       (f res opts)))))
link
((wrap-vec (fn [x opts] x)) [1] {}) => [1] ((wrap-vec (fn [x opts] x)) 1 {}) => 1


compile-matcher ^

[template]
Added 3.0

creates a matcher given a datastructure declaring the actual template

v 3.0
(defn compile-matcher
  ([template]
   (cond (-> template meta :-) (p-is template)
         (-> template meta :%) (compile-matcher {:pattern template})
         (symbol? template)    (compile-matcher {:form template})
         (fn? template)        (compile-matcher {:is template})
         (list? template)      (compile-matcher {:pattern template})
         (f/regexp? template)     (compile-matcher {:code template})
         (vector? template)    (apply p-and (map compile-matcher template))
         (set? template)       (apply p-or (map compile-matcher template))
         (collection/hash-map? template)
         (apply p-and
                (map (fn [[k v]]
                       (case k
                         :fn           (p-fn v)
                         :not          (p-not (compile-matcher v))
                         :is           (p-is v)
                         :or           (apply p-or (map compile-matcher v))
                         :and          (apply p-and (map compile-matcher v))
                         :equal        (p-equal v)
                         :type         (p-type v)
                         :meta         (p-meta v)
                         :form         (p-form v)
                         :pattern      (p-pattern v)
                         :code         (p-code v)
                         :parent       (p-parent v)
                         :child        (p-child v)
                         :first        (p-first v)
                         :last         (p-last v)
                         :nth          (p-nth v)
                         :nth-left     (p-nth-left v)
                         :nth-right    (p-nth-right v)
                         :nth-ancestor (p-nth-ancestor v)
                         :nth-contains (p-nth-contains v)
                         :ancestor     (p-ancestor v)
                         :contains     (p-contains v)
                         :sibling      (p-sibling v)
                         :left         (p-left v)
                         :right        (p-right v)
                         :left-of      (p-left-of v)
                         :right-of     (p-right-of v)
                         :left-most    (p-left-most v)
                         :right-most   (p-right-most v)))
                     template))
         :else (compile-matcher {:is template}))))
link
((compile-matcher {:is 'hello}) (nav/parse-string "hello")) => true

matcher ^

[f]
Added 3.0

creates a predicate function that can be used to match against values

v 3.0
(defn matcher
  ([f]
   (Matcher. f)))
link
((matcher string?) "hello") => true

matcher? ^

[x]
Added 3.0

checks if element is a matcher

v 3.0
(defn matcher?
  ([x]
   (instance? Matcher x)))
link
(matcher? (matcher string?)) => true

p-ancestor ^

[template]
Added 3.0

checks that any parent container matches

v 3.0
(defn p-ancestor
  ([template]
   (let [template (if (symbol? template) {:form template} template)
         m-fn (compile-matcher template)]
     (Matcher. (fn [nav]
                 (-> nav (nav/up) (tree-search m-fn nav/up (fn [_]))))))))
link
((p-ancestor {:form 'if}) (-> (nav/parse-string "(if (= x y))") nav/down nav/next nav/next)) => true ((p-ancestor 'if) (-> (nav/parse-string "(if (= x y))") nav/down nav/next nav/next)) => true

p-and ^

[& matchers]
Added 3.0

takes multiple predicates and ensures that all are correct

v 3.0
(defn p-and
  ([& matchers]
   (Matcher. (fn [nav]
               (->> (map (fn [m] (m nav)) matchers)
                    (every? true?))))))
link
((p-and (p-code #"defn") (p-type :token)) (nav/parse-string "(defn ^{:a 1} x [])")) => false ((p-and (p-code #"defn") (p-type :list)) (nav/parse-string "(defn ^{:a 1} x [])")) => true

p-child ^

[template]
Added 3.0

checks that there is a child of a container that has a certain characteristic

v 3.0
(defn p-child
  ([template]
   (let [template (if (symbol? template) {:is template} template)
         m-fn (compile-matcher template)]
     (Matcher. (fn [nav]
                 (if-let [child (nav/down nav)]
                   (->> child
                        (iterate nav/right)
                        (take-while identity)
                        (map m-fn)
                        (some identity)
                        nil? not)
                   false))))))
link
((p-child {:form '=}) (nav/parse-string "(if (= x y))")) => true ((p-child '=) (nav/parse-string "(if (= x y))")) => false

p-code ^

[template]
Added 3.0

checks if the form matches a string in the form of a regex expression

v 3.0
(defn p-code
  ([template]
   (Matcher. (fn [nav]
               (cond (f/regexp? template)
                     (if (->> nav nav/string (re-find template))
                       true false))))))
link
((p-code #"defn") (nav/parse-string "(defn ^{:a 1} x [])")) => true

p-contains ^

[template]
Added 3.0

checks that any element (deeply nested also) of the container matches

v 3.0
(defn p-contains
  ([template]
   (let [template (if (symbol? template) {:is template} template)
         m-fn (compile-matcher template)]
     (Matcher. (fn [nav]
                 (-> nav (nav/down) (tree-search m-fn nav/right nav/down)))))))
link
((p-contains '=) (nav/parse-string "(if (= x y))")) => true ((p-contains 'x) (nav/parse-string "(if (= x y))")) => true

p-equal ^

[template]
Added 3.0

checks if the node is equivalent, takes meta into account

v 3.0
(defn p-equal
  ([template]
   (Matcher. (fn [nav]
               (let [expr (nav/value nav)]
                 (p-equal-loop expr template))))))
link
((p-equal '^{:a 1} defn) (nav/parse-string "defn")) => false ((p-equal '^{:a 1} defn) (nav/parse-string "^{:a 1} defn")) => true ((p-equal '^{:a 1} defn) (nav/parse-string "^{:a 2} defn")) => false

p-equal-loop ^

[expr template]
Added 3.0

helper function for `p-equal`

v 3.0
(defn p-equal-loop
  ([expr template]
   (and (or (= (meta expr) (meta template))
            (and (empty? (meta expr))
                 (empty? (dissoc (meta template) :line :column))))
        (cond (or (list? expr) (vector? expr))
              (and (= (count expr) (count template))
                   (every? true? (map p-equal-loop expr template)))

              (set? expr)
              (and (= (count expr) (count template))
                   (every? true? (map p-equal-loop
                                      (sort expr)
                                      (sort template))))

              (map? expr)
              (and (= (count expr) (count template))

                   (every? true? (map p-equal-loop
                                      (sort (keys expr))
                                      (sort (keys template))))
                   (every? true? (map p-equal-loop
                                      (map #(get expr %) (keys expr))
                                      (map #(get template %) (keys expr)))))

              :else (= expr template)))))
link
((p-equal [1 2 3]) (nav/parse-string "[1 2 3]")) => true ((p-equal (list 'defn)) (nav/parse-string "(defn)")) => true ((p-equal '(defn)) (nav/parse-string "(defn)")) => true

p-first ^

[template]
Added 3.0

checks that the first element of the container has a certain characteristic

v 3.0
(defn p-first
  ([template]
   (let [template (if (symbol? template) {:is template} template)
         m-fn (compile-matcher template)]
     (Matcher. (fn [nav]
                 (if-let [child (nav/down nav)]
                   (m-fn child)))))))
link
((p-first 'defn) (-> (nav/parse-string "(defn x [])"))) => true ((p-first 'x) (-> (nav/parse-string "[x y z]"))) => true ((p-first 'x) (-> (nav/parse-string "[y z]"))) => false

p-fn ^

[template]
Added 3.0

takes a predicate function to check the state of the zipper

v 3.0
(defn p-fn
  ([template]
   (Matcher. (fn [nav]
               (template nav)))))
link
((p-fn (fn [nav] (-> nav (nav/tag) (= :symbol)))) (nav/parse-string "defn")) => true

p-form ^

[template]
Added 3.0

checks if it is a form with the symbol as the first element

v 3.0
(defn p-form
  ([template]
   (Matcher. (fn [nav]
               (and (-> nav nav/tag (= :list))
                    (-> nav nav/down nav/value (= template)))))))
link
((p-form 'defn) (nav/parse-string "(defn x [])")) => true ((p-form 'let) (nav/parse-string "(let [])")) => true

p-is ^

[template]
Added 3.0

checks if node is equivalent, does not meta into account

v 3.0
(defn p-is
  ([template]
   (Matcher. (fn [nav]
               (and nav
                    (if (fn? template)
                      (template (nav/value nav))
                      (= template (nav/value nav))))))))
link
((p-is 'defn) (nav/parse-string "defn")) => true ((p-is '^{:a 1} defn) (nav/parse-string "defn")) => true ((p-is 'defn) (nav/parse-string "is")) => false ((p-is '(defn & _)) (nav/parse-string "(defn x [])")) => false

p-last ^

[template]
Added 3.0

checks that the last element of the container has a certain characteristic

v 3.0
(defn p-last
  ([template]
   (let [template (if (symbol? template) {:is template} template)
         m-fn (compile-matcher template)]
     (Matcher. (fn [nav]
                 (if-let [child (-> nav nav/down nav/right-most)]
                   (m-fn child)))))))
link
((p-last 1) (-> (nav/parse-string "(defn [] 1)"))) => true ((p-last 'z) (-> (nav/parse-string "[x y z]"))) => true ((p-last 'x) (-> (nav/parse-string "[y z]"))) => false

p-left ^

[template]
Added 3.0

checks that the element on the left has a certain characteristic

v 3.0
(defn p-left
  ([template]
   (let [template (if (symbol? template) {:is template} template)
         m-fn (compile-matcher template)]
     (Matcher. (fn [nav]
                 (if-let [left (-> nav nav/left)]
                   (m-fn left)))))))
link
((p-left '=) (-> (nav/parse-string "(if (= x y))") nav/down nav/next nav/next nav/next)) => true ((p-left 'if) (-> (nav/parse-string "(if (= x y))") nav/down nav/next)) => true

p-left-most ^

[bool]
Added 3.0

checks if the navigator is at the left-most expression within its current level

v 3.0
(defn p-left-most
  ([bool]
   (Matcher. (fn [nav] (= (-> nav nav/left nil?) bool)))))
link
((p-left-most true) (-> (nav/parse-string "(= x y)") nav/down)) => true ((p-left-most true) (-> (nav/parse-string "(= x y)") nav/down nav/next)) => false

p-left-of ^

[template]
Added 3.0

checks that any element on the left has a certain characteristic

v 3.0
(defn p-left-of
  ([template]
   (let [template (if (symbol? template) {:is template} template)
         m-fn (compile-matcher template)]
     (Matcher. (fn [nav]
                 (or (-> nav nav/left (tree-search m-fn nav/left (fn [_])))
                     false))))))
link
((p-left-of '=) (-> (nav/parse-string "(= x y)") nav/down nav/next)) => true ((p-left-of '=) (-> (nav/parse-string "(= x y)") nav/down nav/next nav/next)) => true

p-meta ^

[template]
Added 3.0

checks if meta is the same

v 3.0
(defn p-meta
  ([template]
   (Matcher. (fn [nav]
               (let [mloc (nav/up nav)]
                 (and (= :meta (nav/tag mloc))
                      (= (nav/value (nav/down mloc)) template)))))))
link
((p-meta {:a 1}) (nav/down (nav/parse-string "^{:a 1} defn"))) => true ((p-meta {:a 1}) (nav/down (nav/parse-string "^{:a 2} defn"))) => false

p-not ^

[matcher]
Added 3.0

checks if node is the inverse of the given matcher

v 3.0
(defn p-not
  ([matcher]
   (Matcher. (fn [nav]
               (not (matcher nav))))))
link
((p-not (p-is 'if)) (nav/parse-string "defn")) => true ((p-not (p-is 'if)) (nav/parse-string "if")) => false

p-nth ^

[[num template]]
Added 3.0

checks that the element at a specific Nth index within the container has a certain characteristic

v 3.0
(defn p-nth
  ([[num template]]
   (let [template (if (symbol? template) {:is template} template)
         m-fn (compile-matcher template)]
     (Matcher. (fn [nav]
                 (if-let [child (nav/down nav)]
                   (let [child (if (zero? num)
                                 child
                                 (-> (iterate nav/next child)
                                     (nth num)))]
                     (m-fn child))))))))
link
((p-nth [0 'defn]) (-> (nav/parse-string "(defn [] 1)"))) => true ((p-nth [2 'z]) (-> (nav/parse-string "[x y z]"))) => true ((p-nth [2 'x]) (-> (nav/parse-string "[y z]"))) => false

p-nth-ancestor ^

[[num template]]
Added 3.0

search for match n-levels up

v 3.0
(defn p-nth-ancestor
  ([[num template]]
   (p-nth-move num template nav/up)))
link
((p-nth-ancestor [2 {:contains 3}]) (-> (nav/parse-string "(* (- (+ 1 2) 3) 4)") nav/down nav/right nav/down nav/right nav/down)) => true

p-nth-contains ^

[[num template]]
Added 3.0

search for match n-levels down

v 3.0
(defn p-nth-contains
  ([[num template]]
   (let [template (if (symbol? template) {:is template} template)
         m-fn (compile-matcher template)]
     (Matcher. (fn [nav]
                 (let [[dir num] (if (zero? num)
                                   [nav num]
                                   [(nav/down nav) (dec num)])]
                   (tree-depth-search dir m-fn num nav/down nav/right)))))))
link
((p-nth-contains [2 {:contains 1}]) (nav/parse-string "(* (- (+ 1 2) 3) 4)")) => true

p-nth-left ^

[[num template]]
Added 3.0

checks that the element at a specific Nth index to the left of the current position has a certain characteristic

v 3.0
(defn p-nth-left
  ([[num template]]
   (p-nth-move num template nav/left)))
link
((p-nth-left [0 'defn]) (-> (nav/parse-string "(defn [] 1)") nav/down)) => true ((p-nth-left [1 ^:& vector?]) (-> (nav/parse-string "(defn [] 1)") nav/down nav/right-most)) => true

p-nth-move ^

[num template directon]

NONE
(defn- p-nth-move
  [num template directon]
  (let [template (if (symbol? template) {:is template} template)
        m-fn (compile-matcher template)]
    (Matcher. (fn [nav]
                (let [dir (if (zero? num)
                            nav
                            (-> (iterate directon nav) (nth num)))]
                  (m-fn dir))))))
link

p-nth-right ^

[[num template]]
Added 3.0

checks that the element at a specific Nth index to the right of the current position has a certain characteristic

v 3.0
(defn p-nth-right
  ([[num template]]
   (p-nth-move num template nav/right)))
link
((p-nth-right [0 'defn]) (-> (nav/parse-string "(defn [] 1)") nav/down)) => true ((p-nth-right [1 vector?]) (-> (nav/parse-string "(defn [] 1)") nav/down)) => true

p-or ^

[& matchers]
Added 3.0

takes multiple predicates and ensures that at least one is correct

v 3.0
(defn p-or
  ([& matchers]
   (Matcher. (fn [nav]
               (->> (map (fn [m] (m nav)) matchers)
                    (some true?))))))
link
((p-or (p-code #"defn") (p-type :token)) (nav/parse-string "(defn ^{:a 1} x [])")) => true ((p-or (p-code #"defn") (p-type :list)) (nav/parse-string "(defn ^{:a 1} x [])")) => true

p-parent ^

[template]
Added 3.0

checks that the parent of the element contains a certain characteristic

v 3.0
(defn p-parent
  ([template]
   (let [template (if (symbol? template) {:form template} template)
         m-fn (compile-matcher template)]
     (Matcher. (fn [nav]
                 (if-let [parent (nav/up nav)]
                   (and (not= (nav/value nav)
                              (nav/value parent))
                        (m-fn parent))))))))
link
((p-parent 'defn) (-> (nav/parse-string "(defn x [])") nav/next nav/next)) => true ((p-parent {:parent 'if}) (-> (nav/parse-string "(if (= x y))") nav/down nav/next nav/next)) => true ((p-parent {:parent 'if}) (-> (nav/parse-string "(if (= x y))") nav/down)) => false

p-pattern ^

[template]
Added 3.0

checks if the form matches a particular pattern

v 3.0
(defn p-pattern
  ([template]
   (Matcher. (fn [nav]
               (let [mf (pattern/pattern-fn template)]
                 (-> nav nav/value mf))))))
link
((p-pattern '(defn ^:% symbol? & _)) (nav/parse-string "(defn ^{:a 1} x [])")) => true ((p-pattern '(defn ^:% symbol? ^{:% true :? true} string? [])) (nav/parse-string "(defn ^{:a 1} x [])")) => true

p-right ^

[template]
Added 3.0

checks that the element on the right has a certain characteristic

v 3.0
(defn p-right
  ([template]
   (let [template (if (symbol? template) {:is template} template)
         m-fn (compile-matcher template)]
     (Matcher. (fn [nav]
                 (if-let [right (-> nav nav/right)]
                   (m-fn right)))))))
link
((p-right 'x) (-> (nav/parse-string "(if (= x y))") nav/down nav/next nav/next)) => true ((p-right {:form '=}) (-> (nav/parse-string "(if (= x y))") nav/down)) => true

p-right-most ^

[bool]
Added 3.0

checks if the navigator is at the right-most expression within its current level

v 3.0
(defn p-right-most
  ([bool]
   (Matcher. (fn [nav] (= (-> nav nav/right nil?) bool)))))
link
((p-right-most true) (-> (nav/parse-string "(= x y)") nav/down nav/next)) => false ((p-right-most true) (-> (nav/parse-string "(= x y)") nav/down nav/next nav/next)) => true

p-right-of ^

[template]
Added 3.0

checks that any element on the right has a certain characteristic

v 3.0
(defn p-right-of
  ([template]
   (let [template (if (symbol? template) {:is template} template)
         m-fn (compile-matcher template)]
     (Matcher. (fn [nav]
                 (or (-> nav nav/right (tree-search m-fn nav/right (fn [_])))
                     false))))))
link
((p-right-of 'x) (-> (nav/parse-string "(= x y)") nav/down)) => true ((p-right-of 'y) (-> (nav/parse-string "(= x y)") nav/down)) => true ((p-right-of 'z) (-> (nav/parse-string "(= x y)") nav/down)) => false

p-sibling ^

[template]
Added 3.0

checks that any element on the same level has a certain characteristic

v 3.0
(defn p-sibling
  ([template]
   (let [template (if (symbol? template) {:is template} template)
         m-fn (compile-matcher template)]
     (Matcher. (fn [nav]
                 (or (-> nav nav/right (tree-search m-fn nav/right (fn [_])))
                     (-> nav nav/left  (tree-search m-fn nav/left (fn [_])))
                     false))))))
link
((p-sibling '=) (-> (nav/parse-string "(if (= x y))") nav/down nav/next nav/next)) => false ((p-sibling 'x) (-> (nav/parse-string "(if (= x y))") nav/down nav/next nav/next)) => true

p-type ^

[template]
Added 3.0

check on the type of element

v 3.0
(defn p-type
  ([template]
   (Matcher. (fn [nav]
               (-> nav nav/tag (= template))))))
link
((p-type :symbol) (nav/parse-string "defn")) => true ((p-type :symbol) (-> (nav/parse-string "^{:a 1} defn") nav/down nav/right)) => true

tree-depth-search ^

[nav m-fn level dir1 dir2]
Added 3.0

performs a depth-first search for a match N levels down in a tree structure, used as a helper for `p-nth-contains`

v 3.0
(defn tree-depth-search
  ([nav m-fn level dir1 dir2]
   (if nav
     (cond
       (< level 0) nil
       (and (= level 0) (m-fn nav)) true
       :else
       (or (tree-depth-search (dir1 nav) m-fn (dec level) dir1 dir2)
           (tree-depth-search (dir2 nav) m-fn level dir1 dir2))))))
link
(let [m-fn (fn [nav] (= 'x (nav/value nav)))] (tree-depth-search (nav/parse-string "((a b) (c (d x)))") m-fn 3 nav/down nav/right)) => true

tree-search ^

[nav m-fn dir1 dir2]
Added 3.0

recursively searches a tree structure for elements matching a predicate, used as a helper for `p-contains`

v 3.0
(defn tree-search
  ([nav m-fn dir1 dir2]
   (binding [zip/*handler* (assoc zip/*handler*
                                  :step
                                  zip/+nil-handler+)]
     (if nav
       (cond (nil? nav) nil
             (m-fn nav) true

             :else
             (or (tree-search (dir1 nav) m-fn dir1 dir2)
                 (tree-search (dir2 nav) m-fn dir1 dir2)))))))
link
(let [m-fn (fn [nav] (= 'x (nav/value nav)))] (tree-search (nav/parse-string "((a b) (c (d x)))") m-fn nav/down nav/right)) => true


count-elements ^

[pattern]
Added 3.0

counts the number of elements in a given code structure

v 3.0
(defn count-elements
  ([pattern]
   (let [sum (atom 0)]
     (walk/postwalk (fn [x] (swap! sum inc))
                 pattern)
     @sum)))
link
(count-elements [1 2 3]) => 4

pattern-zip ^

[root]
Added 3.0

creates a clojure.zip pattern

v 3.0
(defn pattern-zip
  ([root]
   (zip/zipper root
               (merge {:create-container  list
                       :create-element    identity
                       :is-container?     #(or (seq? %) (vector? %))
                       :is-empty-container? empty?
                       :is-element?       (complement nil?)
                       :list-elements     seq
                       :update-elements   (fn [container children] (with-meta children (meta container)))
                       :add-element       conj}
                      zip/+base+))))
link
(pattern-zip [1 2 3]) => (satisfies std.lib.zip.Zipper)

prep-insert-pattern ^

[pattern]
Added 3.0

prepares a pattern for insertion operations during code traversal

v 3.0
(defn prep-insert-pattern
  ([pattern]
   (let [pnode (zip/get pattern)
         {evaluate? :%} (meta pnode)]
     (if evaluate? (eval pnode) (with-meta pnode nil)))))
link

source ^

[pos]
Added 3.0

retrives the source of a traverse

v 3.0
(defn source
  ([pos]
   (-> pos :source nav/value)))
link
(source (traverse (nav/parse-string "()") '(^:+ hello))) => '(hello)

traverse ^

[source pattern]
Added 3.0

basic traverse functions

v 3.0
(defn traverse
  ([source pattern]
   (let [pseq    (optional/pattern-seq pattern)
         lookup   (collection/map-juxt [common/prepare-deletion identity] pseq)
         p-dels   (->> (nav/value source)
                       ((pattern/pattern-matches (common/prepare-deletion pattern))))
         p-del   (case  (count p-dels)
                   0 (throw (ex-info "Needs to have a match."
                                     {:matches p-dels
                                      :source (nav/value source)
                                      :pattern pattern}))
                   1 (first p-dels)
                   (->> p-dels
                        (sort-by count-elements)
                        (last)))
         p-match (get lookup p-del)
         p-ins   (common/prepare-insertion p-match)
         op-del  {:delete-form  (wrap-meta traverse-delete-form)
                  :delete-level (wrap-delete-next traverse-delete-level)
                  :delete-node  traverse-delete-node}
         del-pos (-> (map->Position {:source  source
                                     :pattern (pattern-zip p-del)
                                     :op op-del})
                     ((:delete-form op-del)))
         op-ins  {:insert-form  (wrap-meta traverse-insert-form)
                  :insert-level (wrap-insert-next traverse-insert-level)
                  :insert-node  traverse-insert-node}
         ins-pos (-> del-pos
                     (assoc :pattern (pattern-zip p-ins)
                            :op op-ins)
                     ((:insert-form op-ins)))
         p-cursor (common/remove-items common/deletion? p-match)]
     (if (= p-cursor p-ins)
       ins-pos
       (let [op-cursor {:cursor-form  (wrap-meta traverse-cursor-form)
                        :cursor-level (wrap-cursor-next traverse-cursor-level)}
             cursor-pos (-> ins-pos
                            (assoc :pattern (pattern-zip p-cursor)
                                   :op op-cursor
                                   :level 0)
                            ((:cursor-form op-cursor)))]
         cursor-pos)))))
link
(source (traverse (nav/parse-string "^:a (+ () 2 3)") '(+ () 2 3))) => '(+ () 2 3) (source (traverse (nav/parse-string "^:a (hello)") '(hello))) => '(hello) (source (traverse (nav/parse-string "^:a (hello)") '(^:- hello))) => () (source (traverse (nav/parse-string "(hello)") '(^:- hello))) => () (source (traverse (nav/parse-string "((hello))") '((^:- hello)))) => '(()) ;; Insertions (source (traverse (nav/parse-string "()") '(^:+ hello))) => '(hello) (source (traverse (nav/parse-string "(())") '((^:+ hello)))) => '((hello))

traverse-cursor-form ^

[{:keys [source pattern op], :as pos}]
Added 3.0

traversing cursor form

v 3.0
(defn traverse-cursor-form
  ([{:keys [source pattern op] :as pos}]
   (let [sexpr (nav/value source)
         pnode (zip/get pattern)
         pos   (update-in pos [:level] inc)]
     (cond (empty? pnode)
           pos

           :else
           ((:cursor-level op) (assoc pos
                                      :source (nav/down source)
                                      :pattern (zip/step-inside pattern)))))))
link

traverse-cursor-level ^

[{:keys [source pattern op], :as pos}]
Added 3.0

traversing cursor level

v 3.0
(defn traverse-cursor-level
  ([{:keys [source pattern op] :as pos}]
   (let [sexpr (nav/value source)
         pnode (zip/get pattern)]
     (cond (= '| pnode)
           ((:cursor-level op) (assoc pos :end true))

           (= '& pnode)
           ((:cursor-level op) (assoc pos
                                      :source  (nav/right-most source)
                                      :pattern (zip/step-right-most pattern)
                                      :next true))

           (and (or (list? pnode) (vector? pnode))
                (not (empty? pnode)))
           (-> pos
               ((:cursor-form op))
               (assoc :next true)
               ((:cursor-level op)))

           :else
           ((:cursor-level op) (assoc pos :next true))))))
link

traverse-delete-form ^

[{:keys [source pattern op], :as pos}]
Added 3.0

traversing deletion form

v 3.0
(defn traverse-delete-form
  ([{:keys [source pattern op] :as pos}]
   (let [sexpr (nav/value source)
         pnode (zip/get pattern)]
     ((:delete-level op) (assoc pos
                                :source  (nav/down source)
                                :pattern (zip/step-inside pattern))))))
link

traverse-delete-level ^

[{:keys [source pattern op], :as pos}]
Added 3.0

traversing deletion level

v 3.0
(defn traverse-delete-level
  ([{:keys [source pattern op] :as pos}]
   (let [pnode (zip/get pattern)
         sexpr (nav/value source)
         delete? (-> pnode meta :-)]
     (cond (= '& pnode)
           ((:delete-level op) (assoc pos
                                      :source  (nav/right-most source)
                                      :pattern (zip/step-right-most pattern)
                                      :next true))

           delete?
           ((:delete-node op) pos)
           #_(-> ((:delete-node op) pos)
                 (assoc :next true)
                 ((:delete-level op)))

           (or (list? pnode) (vector? pnode))
           (-> pos
               ((:delete-form op))
               (assoc :next true)
               ((:delete-level op)))

           :else
           ((:delete-level op) (assoc pos :next true))))))
link

traverse-delete-node ^

[{:keys [source pattern op], :as pos}]
Added 3.0

traversing deletion node

v 3.0
(defn traverse-delete-node
  ([{:keys [source pattern op] :as pos}]
   (cond (and (nav/left-most? source)
              (nav/right-most? source))
         (assoc pos
                :source (nav/up (nav/delete source))
                :pattern (zip/step-outside pattern))

         :else
         ((:delete-level op)
          (assoc pos
                 :source (-> (nav/delete source)
                             (nav/left))
                 :pattern pattern
                 :next true)))))
link

traverse-insert-form ^

[{:keys [source pattern op], :as pos}]
Added 3.0

traversing insertion form

v 3.0
(defn traverse-insert-form
  ([{:keys [source pattern op] :as pos}]
   (let [sexpr (nav/value source)
         pnode (zip/get pattern)]
     ((:insert-level op) (assoc pos
                                :source (nav/down source)
                                :pattern (zip/step-inside pattern))))))
link

traverse-insert-level ^

[{:keys [source pattern op], :as pos}]
Added 3.0

traversing insertion level

v 3.0
(defn traverse-insert-level
  ([{:keys [source pattern op] :as pos}]
   (let [pnode (zip/get pattern)
         sexpr (nav/value source)
         insert? (-> pnode meta :+)]
     (cond (= '& pnode)
           ((:insert-level op) (assoc pos
                                      :source  (nav/right-most source)
                                      :pattern (zip/step-right-most pattern)
                                      :next true))

           insert?
           ((:insert-node op) pos)

           (or (list? pnode) (vector? pnode))
           (-> pos
               ((:insert-form op))
               (assoc :next true)
               ((:insert-level op)))

           :else
           ((:insert-level op) (assoc pos :next true))))))
link

traverse-insert-node ^

[{:keys [source pattern op], :as pos}]
Added 3.0

traversing insertion node

v 3.0
(defn traverse-insert-node
  ([{:keys [source pattern op] :as pos}]
   ((:insert-level op)
    (let [val (prep-insert-pattern pattern)]
      (assoc pos
             :source (nav/insert-token-to-left source val)
             :next true)))))
link

wrap-cursor-next ^

[f]
Added 3.0

provides a wrapper function to locate the cursor at the next element during code traversal

v 3.0
(defn wrap-cursor-next
  ([f]
   (fn [{:keys [source pattern next end] :as pos}]
     (cond end pos

           next
           (let [nsource (nav/right source)
                 npattern (zip/step-right pattern)]
             (cond (and nsource npattern)
                   (f (-> pos
                          (assoc :source nsource :pattern npattern)
                          (dissoc :next)))

                   npattern
                   (f (-> pos
                          (assoc :source source :pattern npattern)
                          (dissoc :next)))

                   (nil? nsource)
                   (-> pos
                       (assoc :source (nav/up source) :pattern (zip/step-outside pattern))
                       (update-in [:level] dec)
                       (dissoc :next))))
           :else (f pos)))))
link

wrap-delete-next ^

[f]
Added 3.0

provides a wrapper function to delete the element immediately following the current position in a zipper

v 3.0
(defn wrap-delete-next
  ([f]
   (fn [{:keys [source pattern next] :as pos}]
     (if next
       (if-let [nsource (nav/right source)]
         (f (-> pos
                (assoc :source nsource
                       :pattern (zip/step-right pattern))
                (dissoc :next)))
         (-> pos
             (assoc :source (nav/up source)
                    :pattern (zip/step-outside pattern))
             (dissoc :next)))
       (f pos)))))
link

wrap-insert-next ^

[f]
Added 3.0

provides a wrapper function to insert an element immediately following the current position in a zipper

v 3.0
(defn wrap-insert-next
  ([f]
   (fn [{:keys [source pattern next] :as pos}]
     (if-not next
       (f pos)
       (let [nsource  (nav/right source)
             npattern (zip/step-right pattern)]
         (cond (and nsource npattern)
               (f (-> pos
                      (assoc :source nsource
                             :pattern npattern)
                      (dissoc :next)))

               (and npattern (not= '& (zip/get npattern)))
               (let [inserts (->> (iterate zip/step-right npattern)
                                  (take-while zip/get)
                                  (map prep-insert-pattern))
                     nsource (reduce nav/insert-token-to-right source (reverse inserts))]
                 (-> pos
                     (assoc :source  (nav/up nsource)
                            :pattern (zip/step-outside pattern))
                     (dissoc :next)))

               :else
               (-> pos
                   (assoc :source (nav/up source) :pattern (zip/step-outside pattern))
                   (dissoc :next))))))))
link

wrap-meta ^

[f]
Added 3.0

provides a wrapper for traversing and manipulating metadata tags within code structures

v 3.0
(defn wrap-meta
  ([f]
   (fn [{:keys [source level] :as pos}]
     (if (not= :meta (nav/tag source))
       (f pos)
       (let [ppos   (if level (update-in pos [:level] inc) pos)
             npos   (f (assoc ppos :source (-> source nav/down nav/right)))]
         (if (:end npos)
           npos
           (assoc npos
                  :source (-> (:source npos) nav/up)
                  :level level)))))))
link


levelwalk ^

[nav [matcher] f] [nav [matcher] f {:keys [debug], :as opts}]
Added 3.0

only match the form at the top level

v 3.0
(defn levelwalk
  ([nav [matcher] f]
   (levelwalk nav [matcher] f {}))
  ([nav [matcher] f {:keys [debug] :as opts}]
   (let [levelwalk-fn (cond-> zip/levelwalk
                        (not debug) wrap-suppress
                        :then wrap-meta)]
     (levelwalk-fn nav [matcher] f levelwalk-fn (assoc opts
                                                       :move-right nav/right
                                                       :can-move-right? nav/right)))))
link
(-> (levelwalk (nav/parse-string "(+ (+ (+ 8 9)))") [(match/compile-matcher '+) (match/compile-matcher '+)] (fn [nav] (-> nav nav/down (nav/replace '-) nav/up))) nav/value) => '(- (+ (+ 8 9)))

matchwalk ^

[nav matchers f] [nav matchers f {:keys [suppress], :as opts}]
Added 3.0

match every entry within a form

v 3.0
(defn matchwalk
  ([nav matchers f]
   (matchwalk nav matchers f {}))
  ([nav matchers f {:keys [suppress] :as opts}]
   (let [matchwalk-fn (cond-> zip/matchwalk
                        suppress wrap-suppress
                        :then wrap-meta)]
     (matchwalk-fn nav matchers f matchwalk-fn (assoc opts
                                                      :move-right nav/right
                                                      :can-move-right? nav/right)))))
link
(-> (matchwalk (nav/parse-string "(+ (+ (+ 8 9)))") [(match/compile-matcher '+)] (fn [nav] (-> nav nav/down (nav/replace '-) nav/up))) nav/value) => '(- (- (- 8 9)))

wrap-meta ^

[walk-fn]
Added 3.0

allows matchwalk to handle meta tags

v 3.0
(defn wrap-meta
  ([walk-fn]
   (fn [nav matchers f recur-fn opts]
     (if (= :meta (nav/tag nav))
       (let [nloc (nav/up (walk-fn (-> nav nav/down nav/right) matchers f recur-fn opts))]
         (if (nav/right nloc)
           (walk-fn (nav/right nloc) matchers f recur-fn opts)
           nloc))
       (walk-fn nav matchers f recur-fn opts)))))
link
(let [f (wrap-meta (fn [nav _ _ _ _] nav))] (f (nav/parse-string "^:x ()") nil nil nil nil) => (satisfies nav/navigator?))

wrap-suppress ^

[walk-fn]
Added 3.0

allows matchwalk to handle exceptions

v 3.0
(defn wrap-suppress
  ([walk-fn]
   (fn [nav matchers f recur-fn opts]
     (try (walk-fn nav matchers f recur-fn opts)
          (catch Exception t
            nav)))))
link
(let [f (wrap-suppress (fn [_ _ _ _ _] (throw (Exception.))))] (f :nav nil nil nil nil) => :nav)


compile-section ^

[direction prev {:keys [type step], optional? :?, :as section}]
Added 3.0

compiles a query section based on direction, context, and element details

v 3.0
(defn compile-section
  ([direction prev {:keys [type step] optional? :? :as section}]
   (let [base (-> section
                  compile-section-base)
         dkey (get-in moves [type direction])
         current (merge base prev)
         current (if optional?
                   {:or #{current (merge {:is common/any} prev)}}
                   current)]
     (if (= type :nth)
       {dkey [step current]}
       {dkey current}))))
link
(compile-section :up nil '{:element if, :type :nth, :step 3}) => '{:nth-ancestor [3 {:form if}]} (compile-section :down nil '{:element if, :type :multi}) => '{:contains {:form if}}

compile-section-base ^

[section]
Added 3.0

compiles an element section

v 3.0
(defn compile-section-base
  ([section]
   (let [{:keys [element] evaluate? :%} section]
     (cond evaluate?
           (compile-section-base (-> section
                                     (assoc :element (eval element))
                                     (dissoc :%)))

           (= '_ element)    {:is common/any}
           (fn? element)     {:is element}
           (map? element)    (walk/postwalk
                              (fn [ele]
                                (cond (:% (meta ele))
                                      (eval (with-meta ele
                                              (-> (meta ele)
                                                  (dissoc :%))))
                                      :else ele))
                              element)
           (list? element)   {:pattern element}
           (symbol? element) {:form element}
           :else {:is element}))))
link
(compile-section-base '{:element defn}) => '{:form defn} (compile-section-base '{:element (if & _)}) => '{:pattern (if & _)} (compile-section-base '{:element _}) => {:is code.query.common/any}

compile-submap ^

[direction sections]
Added 3.0

compiles a nested sub-query map based on traversal direction and a processed path

v 3.0
(defn compile-submap
  ([direction sections]
   (reduce (fn [i section]
             (compile-section direction i section))
           nil sections)))
link
(compile-submap :down (process-path '[if try])) => '{:child {:child {:form if}, :form try}} (compile-submap :up (process-path '[defn if])) => '{:parent {:parent {:form defn}, :form if}}

cursor-info ^

[selectors]
Added 3.0

finds the information related to the cursor

v 3.0
(defn cursor-info
  ([selectors]
   (let [candidates
         (->> selectors
              (keep-indexed
               (fn [i ele]
                 (cond (= ele '|) [i :cursor]
                       (and (list? ele)
                            (not= (common/prepare-query ele)
                                  ele)) [i :form ele]))))]
     (case (count candidates)
       0 (if (list? (last selectors))
           [(dec (count selectors)) :form (last selectors)]
           [nil :cursor])
       1 (let [max      (dec (count selectors))
               [i type :as candidate] (first candidates)
               _ (case type
                   :form   (if (not= i max)
                             (throw (Exception. "Form should be in the last position of the selectors")))
                   :cursor (if (= i max)
                             (throw (Exception. "Cursor cannot be in the last position of the selectors"))))]
           candidate)
       (throw (ex-info (format "There should only be one of %s in the path.")
                       {:candidates candidates}))))))
link
(cursor-info '[(defn ^:?& _ | & _)]) => '[0 :form (defn _ | & _)] (cursor-info (expand-all-metas '[(defn ^:?& _ | & _)])) => '[0 :form (defn _ | & _)] (cursor-info '[defn if]) => [nil :cursor] (cursor-info '[defn | if]) => [1 :cursor]

expand-all-metas ^

[selectors]
Added 3.0

converts the shorthand meta into a map-based meta

v 3.0
(defn expand-all-metas
  ([selectors]
   (common/prewalk (fn [ele] (if (instance? clojure.lang.IObj ele)
                               (common/expand-meta ele)
                               ele))
                   selectors)))
link
(meta (expand-all-metas '^:%? sym?)) => {:? true, :% true} (-> (expand-all-metas '(^:%+ + 1 2)) first meta) => {:+ true, :% true}

moves ^

NONE
(def moves)
link

prepare ^

[selectors]
Added 3.0

prepares a query vector by compiling its sections and identifying cursor positions

v 3.0
(defn prepare
  ([selectors]
   (let [selectors  (expand-all-metas selectors)
         [cidx ctype cform :as cursor]     (cursor-info selectors)
         qselectors (mapv (fn [ele]
                            (if (list? ele)
                              (common/prepare-deletion ele) ele))
                          selectors)
         {:keys [up down]}   (split-path qselectors cursor)
         up (process-path up)
         [curr & down] (process-path down)
         match-map (merge (compile-section-base curr)
                          (compile-submap :up (reverse up))
                          (compile-submap :down (reverse down)))]
     [match-map cursor])))
link
(prepare '[defn if]) => '[{:child {:form if}, :form defn} [nil :cursor]] (prepare '[defn | if]) => '[{:parent {:form defn}, :form if} [1 :cursor]]

process-path ^

[path] [[x y & xs :as more] out]
Added 3.0

converts a path into more information

v 3.0
(defn process-path
  ([path] (process-path path []))
  ([[x y & xs :as more] out]
   (if-not (empty? more)
     (let [xmap  (process-special x)
           xmeta (meta x)
           ymap  (process-special y)
           ymeta (meta y)]
       (cond (and (nil? xmap) (= 1 (count more)))
             (conj out (merge {:type :step :element x} xmeta))

             (nil? xmap)
             (recur (cons y xs)
                    (conj out (merge {:type :step :element x} xmeta)))

             (and xmap ymap)
             (recur (cons y xs)
                    (conj out (assoc xmap :element '_)))

             (and xmap (= 1 (count more)))
             (conj out (assoc xmap :element '_))

             :else
             (recur xs
                    (conj out (merge (assoc xmap :element y) ymeta)))))
     out)))
link
(process-path '[defn if try]) => '[{:type :step, :element defn} {:type :step, :element if} {:type :step, :element try}] (process-path '[defn :* try :3 if]) => '[{:type :step, :element defn} {:element try, :type :multi} {:element if, :type :nth, :step 3}]

process-special ^

[ele]
Added 3.0

converts a keyword into a map

v 3.0
(defn process-special
  ([ele]
   (if (keyword? ele)
     (or (if (= :* ele) {:type :multi})
         (if-let [step (try (Integer/parseInt (name ele))
                            (catch java.lang.NumberFormatException e))]
           (if (> step 0)
             {:type :nth :step step}))
         (throw (ex-info "Not a valid keyword (either :* or :)" {:value ele}))))))
link
(process-special :*) => {:type :multi} (process-special :1) => {:type :nth, :step 1} (process-special :5) => {:type :nth, :step 5}

split-path ^

[selectors [idx ctype]]
Added 3.0

splits the path into up and down

v 3.0
(defn split-path
  ([selectors [idx ctype]]
   (let [[up down] (cond (nil? idx)
                         [[] selectors]

                         (= :cursor ctype)
                         [(reverse (subvec selectors 0 idx))
                          (subvec selectors (inc idx) (count selectors))]

                         (= :form ctype)
                         [(reverse (subvec selectors 0 idx))
                          (subvec selectors idx (count selectors))]

                         :else (throw (Exception. "Should not be here")))]
     {:up up :down down})))
link
(split-path '[defn | if try] [1 :cursor]) => '{:up (defn), :down [if try]} (split-path '[defn if try] [nil :cursor]) => '{:up [], :down [defn if try]}

6    code.query Guide

code.query enables advanced searching and modification of Clojure source code using a pattern-matching DSL over zippers. It allows you to find code structures that match a specific shape and transform them.

6.1    Core Concepts

  • ZLoc: A zipper location representing a node in the syntax tree.n- Selector: A vector or list pattern describing the structure to match.n- Directives: Special symbols (e.g., ^:%, |, ^:?) in patterns that control matching behavior (capturing, optionality, cursor placement).

6.2    Usage

6.2.1    The $ Macro

The $ macro is the main interface. It takes a context (string, file, zipper), a selector path, and optional arguments (transformation function, options).

(require '[code.query :as query])

6.2.2    Scenarios

6.2.2.1    1. Finding Specific Forms

Scenario: Find all defn names in a string.

We match (defn name ...) and capture the name.

(def code \"(defn foo [x] x) (defn bar [y] y)\")\n\n(query/$ code\n         ;; Pattern: List starting with defn, capture the second element (symbol)\n         '[(defn ^:% symbol? & _)])\n;; => (foo bar)

Scenario: Find all maps containing a specific key.

(def code \"{:a 1 :b 2} {:a 3}\")\n\n(query/$ code\n         ;; Pattern: Map containing key :a\n         '[{:a _}])\n;; => ({:a 1 :b 2} {:a 3})

6.2.2.2    2. Structural Editing

Scenario: Add a docstring to a function if it's missing.

We need to match defn forms that don't have a string as the third element.

(def code \"(defn my-fn [x] x)\")\n\n(query/$ code\n         ;; Match defn where 3rd element is a vector (args), not a string\n         '[(defn symbol? ^:% vector? & _)]\n\n         ;; Transformation function: insert docstring before args\n         (fn [zloc]\n           (std.block.navigate/insert-left zloc \"Added docstring\")))\n;; => \"(defn my-fn \\\"Added docstring\\\" [x] x)\"

6.2.2.3    3. Context-Aware Modification

Scenario: Rename a variable only within a specific binding scope.

This often requires a multi-step approach or using | to position the cursor exactly where the modification should happen.

(def code \"(let [x 1] (+ x 1))\")\n\n(query/$ code\n         ;; Match `let` block, then find `x` inside the vector, position cursor (|) on it\n         '[(let [| x _] & _)]\n         (fn [zloc]\n           (std.block.navigate/set-value zloc 'y)))\n;; => \"(let [y 1] (+ x 1))\"\n;; Note: This only changed the binding name. A full refactor would need to traverse the body too.

6.2.2.4    4. Advanced Pattern Matching Directives

  • ^:% Capture: Returns the matched element.n- ^:? Optional: The element may or may not exist.n- ^:+ One or more: Repeat match.n- ^:* Zero or more: Repeat match.n- | Cursor: Sets the "focus" of the match. The transformation function applies to this node, not the whole pattern root.n- & _ Rest: Matches the rest of the collection (like & args in functions).

Scenario: Extracting dependencies from ns form.

(def ns-form \"(ns my.ns (:require [a.b :as ab] [c.d :refer [x]]))\")\n\n(query/$ ns-form\n         ;; Find :require, then inside it, match vectors\n         '[(ns _ (:require ^:%+ vector?))])\n;; => ([a.b :as ab] [c.d :refer [x]])

7    code.query.block Tutorial

Module: code.query.blocknSource File: src/code/query/block.cljnTest File: test/code/query/block_test.clj

The code.query.block module provides a powerful navigation and manipulation API for std.block ASTs, built on top of std.lib.zip (zippers). It allows for cursor-based traversal, inspection, and modification of Clojure code represented as blocks, making it ideal for structural editing, refactoring tools, and code analysis.

7.1    Core Concepts

  • Navigator: The central data structure, which is a std.lib.zip/Zipper specifically configured for std.blocks. It maintains a current position within the AST and provides functions to move around and modify the tree.n Cursor (#|): A special block (construct/cursor) used to denote the current position within the code string representation of a navigator.n Position Tracking: The navigator tracks the [line column] position of the cursor within the code.n* Expression vs. Element: Functions often distinguish between "expressions" (blocks with a Clojure value) and "elements" (any block, including whitespace and comments).

7.2    Functions

7.2.1    nav-template

^{:refer code.query.block/nav-template :added "3.0"}

A helper macro for generating navigation function definitions. It takes a symbol and a block tag function, and creates a function that can be used to query properties of the current block in a navigator.

nav-template example

(nav-template '-tag- #'std.block.base/block-tag)
=> '(clojure.core/defn -tag-
         ([zip] (-tag- zip :right))
         ([zip step]
          (clojure.core/if-let [elem (std.lib.zip/get zip)]
            (std.block.base/block-tag elem))))

7.2.2    left-anchor

^{:refer code.query.block/left-anchor :added "3.0" :class [:nav/primitive]}

Calculates the length from the start of the current line to the current cursor position, considering newlines.

left-anchor example

(left-anchor (-> (navigator nil)
                   (zip/step-right)))
=> 3

7.2.3    update-step-left

^{:refer code.query.block/update-step-left :added "3.0" :class [:nav/primitive]}

Updates the navigator's position when moving left, adjusting line and column numbers based on the block's dimensions.

update-step-left example

(-> {:position [0 7]}
      (update-step-left (construct/block [1 2 3])))
=> {:position [0 0]}

7.2.4    update-step-right

^{:refer code.query.block/update-step-right :added "3.0" :class [:nav/primitive]}

Updates the navigator's position when moving right, adjusting line and column numbers.

update-step-right example

(-> {:position [0 0]}
      (update-step-right (construct/block [1 2 3])))
=> {:position [0 7]}

7.2.5    update-step-inside

^{:refer code.query.block/update-step-inside :added "3.0" :class [:nav/primitive]}

Updates the navigator's position when stepping inside a container block, placing the cursor after the opening delimiter.

update-step-inside example

(-> {:position [0 0]}
      (update-step-inside (construct/block #{})))
=> {:position [0 2]}

7.2.6    update-step-inside-left

^{:refer code.query.block/update-step-inside-left :added "3.0" :class [:nav/primitive]}

Updates the navigator's position when stepping inside a container block from the right, placing the cursor before the closing delimiter.

update-step-inside-left example

(-> {:position [0 3]}
      (update-step-inside-left (construct/block #{})))
=> {:position [0 2]}

7.2.7    update-step-outside

^{:refer code.query.block/update-step-outside :added "3.0" :class [:nav/primitive]}

Updates the navigator's position when stepping outside a container block.

update-step-outside example

(let [left-elems [(construct/block [1 2 3]) (construct/newline)]]
    (-> {:position [1 0]
         :left left-elems}
        (update-step-outside left-elems)
        :position))
=> [0 7]

7.2.8    display-navigator

^{:refer code.query.block/display-navigator :added "3.0" :class [:nav/primitive]}

Returns a string representation of the navigator, including its position and the current block.

display-navigator example

(-> (navigator [1 2 3 4])
      (display-navigator))
=> "<0,0> |[1 2 3 4]"

7.2.9    navigator

^{:refer code.query.block/navigator :added "3.0" :class [:nav/general]}

Creates a new std.block navigator from a block or Clojure data. This is the primary way to start navigating an AST.

navigator example

(str (navigator [1 2 3 4]))
=> "<0,0> |[1 2 3 4]"

7.2.10    navigator?

^{:refer code.query.block/navigator? :added "3.0" :class [:nav/general]}

Checks if an object is a std.block navigator.

navigator? example

(navigator? (navigator [1 2 3 4]))
=> true

7.2.11    from-status

^{:refer code.query.block/from-status :added "3.0" :class [:nav/general]}

Constructs a navigator from a given status (a block with a cursor).

from-status example

(str (from-status (construct/block [1 2 3 (construct/cursor) 4])))
=> "<0,7> [1 2 3 |4]"

7.2.12    parse-string

^{:refer code.query.block/parse-string :added "3.0" :class [:nav/general]}

Parses a string into a navigator, automatically placing the cursor at the beginning or at a #| marker if present.

parse-string example

(str (parse-string "(2   #|   3  )"))
=> "<0,5> (2   |   3  )"

7.2.13    parse-root

^{:refer code.query.block/parse-root :added "3.0" :class [:nav/general]}

Parses a root string into a navigator.

parse-root example

(str (parse-root "a b c"))
=> "<0,0> |a b c"

7.2.14    parse-root-status

^{:refer code.query.block/parse-root-status :added "3.0" :class [:nav/general]}

Parses a string and creates a navigator from its status, similar to from-status but for root strings.

parse-root-status example

(str (parse-root-status "a b #|c"))
=> "<0,6> a b |c"

7.2.15    root-string

^{:refer code.query.block/root-string :added "3.0" :class [:nav/general]}

Returns the string representation of the entire root block of the navigator.

root-string example

(root-string (navigator [1 2 3 4]))
=> "[1 2 3 4]"

7.2.16    left-expression

^{:refer code.query.block/left-expression :added "3.0" :class [:nav/general]}

Returns the first expression block to the left of the current cursor position.

left-expression example

(-> {:left [(construct/newline)
            (construct/block [1 2 3])]}
      (left-expression)
      (base/block-value))
=> [1 2 3]

7.2.17    left-expressions

^{:refer code.query.block/left-expressions :added "3.0" :class [:nav/general]}

Returns all expression blocks to the left of the current cursor position.

left-expressions example

(->> {:left [(construct/newline)
             (construct/block :b)
             (construct/space)
             (construct/space)
             (construct/block :a)]}
       (left-expressions)
       (mapv base/block-value))
=> [:a :b]

7.2.18    right-expression

^{:refer code.query.block/right-expression :added "3.0" :class [:nav/general]}

Returns the first expression block to the right of the current cursor position.

right-expression example

(-> {:right [(construct/newline)
              (construct/block [1 2 3])]}
      (right-expression)
      (base/block-value))
=> [1 2 3]

7.2.19    right-expressions

^{:refer code.query.block/right-expressions :added "3.0" :class [:nav/general]}

Returns all expression blocks to the right of the current cursor position.

right-expressions example

(->> {:right [(construct/newline)
               (construct/block :b)
               (construct/space)
               (construct/space)
               (construct/block :a)]}
        (right-expressions)
        (mapv base/block-value))
=> [:b :a]

7.2.20    left

^{:refer code.query.block/left :added "3.0" :class [:nav/move]}

Moves the navigator's cursor to the next expression block on the left.

left example

(-> (parse-string "(1  [1 2 3]    #|)")
      (left)
      str)
=> "<0,4> (1  |[1 2 3]    )"

7.2.21    left-most

^{:refer code.query.block/left-most :added "3.0" :class [:nav/move]}

Moves the navigator's cursor to the leftmost expression block in the current level.

left-most example

(-> (parse-string "(1  [1 2 3]  3 4   #|)")
      (left-most)
      str)
=> "<0,1> (|1  [1 2 3]  3 4   )"

7.2.22    left-most?

^{:refer code.query.block/left-most? :added "3.0" :class [:nav/move]}

Checks if the navigator's cursor is at the leftmost expression block.

left-most? example

(-> (from-status [1 [(construct/cursor) 2 3]])
      (left-most?))
=> true

7.2.23    right

^{:refer code.query.block/right :added "3.0" :class [:nav/move]}

Moves the navigator's cursor to the next expression block on the right.

right example

(-> (parse-string "(#|[1 2 3]  3 4  ) ")
      (right)
      str)
=> "<0,10> ([1 2 3]  |3 4  )"

7.2.24    right-most

^{:refer code.query.block/right-most :added "3.0" :class [:nav/move]}

Moves the navigator's cursor to the rightmost expression block in the current level.

right-most example

(-> (parse-string "(#|[1 2 3]  3 4  ) ")
      (right-most)
      str)
=> "<0,12> ([1 2 3]  3 |4  )"

7.2.25    right-most?

^{:refer code.query.block/right-most? :added "3.0" :class [:nav/move]}

Checks if the navigator's cursor is at the rightmost expression block.

right-most? example

(-> (from-status [1 [2 3 (construct/cursor)]])
      (right-most?))
=> true

7.2.26    up

^{:refer code.query.block/up :added "3.0" :class [:nav/move]}

Moves the navigator's cursor up to the parent container.

up example

(str (up (from-status [1 [2 (construct/cursor) 3]])))
=> "<0,3> [1 |[2 3]]"

7.2.27    down

^{:refer code.query.block/down :added "3.0" :class [:nav/move]}

Moves the navigator's cursor down into the first child expression of the current container.

down example

(str (down (from-status [1 (construct/cursor) [2 3]])))
=> "<0,4> [1 [|2 3]]"

7.2.28    right

^{:refer code.query.block/right* :added "3.0" :class [:nav/move]}

Moves the navigator's cursor to the next element (including whitespace) on the right.

right example

(str (right* (from-status [(construct/cursor) 1 2])))
=> "<0,2> [1| 2]"

7.2.29    left

^{:refer code.query.block/left* :added "3.0" :class [:nav/move]}

Moves the navigator's cursor to the next element (including whitespace) on the left.

left example

(str (left* (from-status [1 (construct/cursor) 2])))
=> "<0,2> [1| 2]"

7.2.30    block

^{:refer code.query.block/block :added "3.0" :class [:nav/general]}

Returns the std.block AST node at the current cursor position.

block example

(block (from-status [1 [2 (construct/cursor) 3]]))
=> (construct/block 3)

7.2.31    prev

^{:refer code.query.block/prev :added "3.0" :class [:nav/move]}

Moves the navigator's cursor to the previous expression block in a depth-first traversal.

prev example

(-> (parse-string "([1 2 [3]] #|)")
      (prev)
      str)
=> "<0,7> ([1 2 [|3]] )"

7.2.32    next

^{:refer code.query.block/next :added "3.0" :class [:nav/move]}

Moves the navigator's cursor to the next expression block in a depth-first traversal.

next example

(-> (parse-string "(#|  [[3]]  )")
      (next)
      (next)
      (next)
      str)
=> "<0,5> (  [[|3]]  )"

7.2.33    find-next-token

^{:refer code.query.block/find-next-token :added "3.0" :class [:nav/move]}

Moves the navigator's cursor to the next token block whose value matches the given data.

find-next-token example

(-> (parse-string "(#|  [[3 2]]  )")
      (find-next-token 2)
      str)
=> "<0,7> (  [[3 |2]]  )"

7.2.34    prev-anchor

^{:refer code.query.block/prev-anchor :added "3.0" :class [:nav/move]}

Moves the navigator's cursor to the previous newline or the beginning of the current line.

prev-anchor example

(-> (parse-string "( \n \n [[3 \n]] #|  )")
      (prev-anchor)
      (:position))
=> [3 0]

(-> (parse-string "( #| )")
      (prev-anchor)
      (:position))
=> [0 0]

7.2.35    next-anchor

^{:refer code.query.block/next-anchor :added "3.0" :class [:nav/move]}

Moves the navigator's cursor to the next newline.

next-anchor example

(-> (parse-string "( \n \n#| [[3 \n]]  )")
      (next-anchor)
      (:position))
=> [3 0]

7.2.36    left-token

^{:refer code.query.block/left-token :added "3.0" :class [:nav/move]}

Moves the navigator's cursor to the next token block on the left.

left-token example

(-> (parse-string "(1  {}  #|2 3 4)")
      (left-token)
      str)
=> "<0,1> (|1  {}  2 3 4)"

7.2.37    left-most-token

^{:refer code.query.block/left-most-token :added "3.0" :class [:nav/move]}

Moves the navigator's cursor to the leftmost token block in the current level.

left-most-token example

(-> (parse-string "(1  {}  2 3 #|4)")
      (left-most-token)
      str)
=> "<0,10> (1  {}  2 |3 4)"

7.2.38    right-token

^{:refer code.query.block/right-token :added "3.0" :class [:nav/move]}

Moves the navigator's cursor to the next token block on the right.

right-token example

(-> (parse-string "(#|1  {}  2 3 4)")
      (right-token)
      str)
=> "<0,8> (1  {}  |2 3 4)"

7.2.39    right-most-token

^{:refer code.query.block/right-most-token :added "3.0" :class [:nav/move]}

Moves the navigator's cursor to the rightmost token block in the current level.

right-most-token example

(-> (parse-string "(#|1  {}  2 3 [4])")
      (right-most-token)
      str)
=> "<0,10> (1  {}  2 |3 [4])"

7.2.40    prev-token

^{:refer code.query.block/prev-token :added "3.0" :class [:nav/move]}

Moves the navigator's cursor to the previous token block in a depth-first traversal.

prev-token example

(-> (parse-string "(1 (2 3 [4])#|)")
      (prev-token)
      str)
=> "<0,9> (1 (2 3 [|4]))"

7.2.41    next-token

^{:refer code.query.block/next-token :added "3.0" :class [:nav/move]}

Moves the navigator's cursor to the next token block in a depth-first traversal.

next-token example

(-> (parse-string "(#|[[1 2 3 4]])")
      (next-token)
      str)
=> "<0,3> ([[|1 2 3 4]])"

7.2.42    position-left

^{:refer code.query.block/position-left :added "3.0" :class [:nav/move]}

Moves the cursor to the left expression, skipping whitespace.

position-left example

(-> (parse-string "( 2   #|   3  )")
      (position-left)
      str)
=> "<0,2> ( |2      3  )"

(-> (parse-string "(   #|   3  )")
      (position-left)
      str)
=> "<0,1> (|      3  )"

7.2.43    position-right

^{:refer code.query.block/position-right :added "3.0" :class [:nav/move]}

Moves the cursor to the right expression, skipping whitespace.

position-right example

(-> (parse-string "(2   #|    3  )")
      (position-right)
      str)
=> "<0,9> (2       |3  )"

(-> (parse-string "(2   #|     )")
      (position-right)
      str)
=> "<0,10> (2        |)"

7.2.44    tighten-left

^{:refer code.query.block/tighten-left :added "3.0" :class [:nav/edit]}

Removes extra spaces on the left of the current expression.

tighten-left example

(-> (parse-string "(1 2 3   #|4)")
      (tighten-left)
      str)
=> "<0,7> (1 2 3 |4)"

(-> (parse-string "(1 2 3   #|    4)")
      (tighten-left)
      str)
=> "<0,7> (1 2 3 |4)"

(-> (parse-string "(    #|     )")
      (tighten-left)
      str)
=> "<0,1> (|)"

7.2.45    tighten-right

^{:refer code.query.block/tighten-right :added "3.0" :class [:nav/edit]}

Removes extra spaces on the right of the current expression.

tighten-right example

(-> (parse-string "(1 2 #|3       4)")
      (tighten-right)
      str)
=> "<0,5> (1 2 |3 4)"

(-> (parse-string "(1 2 3   #|    4)")
      (tighten-right)
      str)
=> "<0,5> (1 2 |3 4)"

(-> (parse-string "(    #|     )")
      (tighten-right)
      str)
=> "<0,1> (|)"

7.2.46    tighten

^{:refer code.query.block/tighten :added "3.0" :class [:nav/edit]}

Removes extra spaces on both the left and right of the current expression.

tighten example

(-> (parse-string "(1 2      #|3       4)")
      (tighten)
      str)
=> "<0,5> (1 2 |3 4)"

7.2.47    level-empty?

^{:refer code.query.block/level-empty? :added "3.0" :class [:nav/edit]}

Checks if the current container has no expression children.

level-empty? example

(-> (parse-string "( #| )")
      (level-empty?))
=> true

7.2.48    insert-empty

^{:refer code.query.block/insert-empty :added "3.0" :class [:nav/edit]}

Inserts an element into an empty container.

insert-empty example

(-> (parse-string "( #| )")
      (insert-empty 1)
      str)
=> "<0,1> (|1  )"

7.2.49    insert-right

^{:refer code.query.block/insert-right :added "3.0" :class [:nav/edit]}

Inserts an element to the right of the current cursor position.

insert-right example

(-> (parse-string "(#|0)")
      (insert-right 1)
      str)
=> "<0,1> (|0 1)"

(-> (parse-string "(#|)")
      (insert-right 1)
      str)
=> "<0,1> (|1)"

(-> (parse-string "( #| )")
      (insert-right 1)
      str)
=> "<0,1> (|1  )"

7.2.50    insert-token-to-left

^{:refer code.query.block/insert-token-to-left :added "3.0" :class [:nav/edit]}

Inserts an element to the left of the current cursor position.

insert-token-to-left example

(-> (parse-string "(#|0)")
      (insert-token-to-left 1)
      str)
=> "<0,3> (1 |0)"

(-> (parse-string "(#|)")
      (insert-token-to-left 1)
      str)
=> "<0,1> (|1)"

(-> (parse-string "( #| )")
      (insert-token-to-left 1)
      str)
=> "<0,1> (|1  )"

7.2.51    insert

^{:refer code.query.block/insert :added "3.0" :class [:nav/edit]}

Inserts an element at the current cursor position and moves the cursor past the inserted element.

insert example

(-> (parse-string "(#|0)")
      (insert 1)
      str)
=> "<0,3> (0 |1)"

(-> (parse-string "(#|)")
      (insert-right 1)
      str)
=> "<0,1> (|1)"

(-> (parse-string "( #| )")
      (insert-right 1)
      str)

=> "<0,1> (|1  )"

7.2.52    insert-all

^{:refer code.query.block/insert-all :added "3.0"}

Inserts all expressions from a collection into the block at the current cursor position.

insert-all example

;; No direct test example, but it would involve:
(-> (parse-string "(#|)")
    (insert-all [1 2 3])
    str)
=> "<0,7> (1 2 3|)"

7.2.53    insert-newline

^{:refer code.query.block/insert-newline :added "3.0"}

Inserts one or more newline blocks at the current cursor position.

insert-newline example

;; No direct test example, but it would involve:
(-> (parse-string "(#|)")
    (insert-newline)
    str)
=> "<0,1> (|\n)"

7.2.54    insert-space

^{:refer code.query.block/insert-space :added "3.0"}

Inserts one or more space blocks at the current cursor position.

insert-space example

;; No direct test example, but it would involve:
(-> (parse-string "(#|)")
    (insert-space)
    str)
=> "<0,1> (| \t)"

7.2.55    delete-left

^{:refer code.query.block/delete-left :added "3.0" :class [:nav/edit]}

Deletes the element to the left of the current cursor position.

delete-left example

(-> (parse-string "(1 2   #|3)")
      (delete-left)
      str)
=> "<0,3> (1 |3)"

(-> (parse-string "(  #|1 2 3)")
      (delete-left)
      str)
=> "<0,1> (|1 2 3)"

(-> (parse-string "( #| )")
      (delete-left)
      str)
=> "<0,1> (|)"

7.2.56    delete-right

^{:refer code.query.block/delete-right :added "3.0" :class [:nav/edit]}

Deletes the element to the right of the current cursor position.

delete-right example

(-> (parse-string "(  #|1 2 3)")
      (delete-right)
      str)
=> "<0,3> (  |1 3)"

(-> (parse-string "(1 2   #|3)")
      (delete-right)
      str)
=> "<0,7> (1 2   |3)"

(-> (parse-string "( #| )")
      (delete-right)
      str)
=> "<0,1> (|)"

7.2.57    delete

^{:refer code.query.block/delete :added "3.0" :class [:nav/edit]}

Deletes the element at the current cursor position.

delete example

(-> (parse-string "(  #|1   2 3)")
      (delete)
      str)
=> "<0,3> (  |2 3)"

(-> (parse-string "(1 2   #|3)")
      (delete)
      str)
=> "<0,7> (1 2   |)"

(-> (parse-string "(  #|    )")
      (delete)
      str)
=> "<0,1> (|)"

7.2.58    backspace

^{:refer code.query.block/backspace :added "3.0" :class [:nav/edit]}

Performs a "backspace" operation, deleting the element to the left of the cursor and moving the cursor.

backspace example

(-> (parse-string "(0  #|1   2 3)")
      (backspace)
      str)
=> "<0,1> (|0 2 3)"

(-> (parse-string "(  #|1   2 3)")
      (backspace)
      str)
=> "<0,1> (|2 3)"

7.2.59    replace

^{:refer code.query.block/replace :added "3.0" :class [:nav/edit]}

Replaces the element at the current cursor position with new data.

replace example

(-> (parse-string "(0  #|1   2 3)")
      (position-right)
      (replace :a)
      str)
=> "<0,4> (0  |:a   2 3)"

7.2.60    swap

^{:refer code.query.block/swap :added "3.0" :class [:nav/edit]}

Applies a function to the element at the current cursor position, replacing it with the result.

swap example

(-> (parse-string "(0  #|1   2 3)")
      (position-right)
      (swap inc)
      str)
=> "<0,4> (0  |2   2 3)"

7.2.61    update-children

^{:refer code.query.block/update-children :added "3.0" :class [:nav/edit]}

Replaces all children of the current container block with a new sequence of children.

update-children example

(-> (update-children (parse-string "[1 2 3]")
                     [(construct/block 4)
                      (construct/space)
                      (construct/block 5)])
    str)
=> "<0,0> |[4 5]"

7.2.62    line-info

^{:refer code.query.block/line-info :added "3.0" :class [:nav/general]}

Returns a map containing line and column information for the current block.

line-info example

(line-info (parse-string "[1 \n  2 3]"))
=> {:row 1, :col 1, :end-row 2, :end-col 7}

8    code.query.common Tutorial

Module: code.query.commonnSource File: src/code/query/common.cljnTest File: test/code/query/common_test.clj

The code.query.common module provides a set of utility functions for working with Clojure forms, particularly in the context of code querying, transformation, and diffing. It includes predicates for special symbols (like cursor markers), functions for manipulating metadata-driven flags (insertion, deletion), and tools for walking and cleaning up forms.

8.1    Core Concepts

  • Metadata Flags: Uses metadata (^:+, ^:-, ^:?) to mark forms for insertion, deletion, or optional presence during code transformation.n Cursor Marker: The | symbol is used as a placeholder for a cursor position within a form.n Form Walking: Leverages std.lib.walk for recursive traversal and transformation of Clojure forms.

8.2    Functions

8.2.1    any

^{:refer code.query.common/any :added "3.0"}

A predicate that always returns true for any input. Useful as a wildcard or default matcher.

any example

(any nil)
=> true

(any '_) ; Note: The test uses '_ as a symbol, which is fine.
=> true

8.2.2    none

^{:refer code.query.common/none :added "3.0"}

A predicate that always returns false for any input.

none example

(none nil)
=> false

(none '_) ; Note: The test uses '_ as a symbol, which is fine.
=> false

8.2.3    expand-meta

^{:refer code.query.common/expand-meta :added "3.0"}

Takes a form and expands its metadata keywords (e.g., :?, :+, :%) into a map of boolean flags.

expand-meta example

(meta (expand-meta ^:? ()))
=> {:? true}

(meta (expand-meta ^:+%? ()))
=> {:? true, :% true, :+ true}

8.2.4    cursor?

^{:refer code.query.common/cursor? :added "3.0"}

Checks if an element is the cursor marker (|).

cursor? example

(cursor? '|)
=> true

(cursor? '_) ; Note: The test uses '_ as a symbol, which is fine.
=> false

8.2.5    insertion?

^{:refer code.query.common/insertion? :added "3.0"}

Checks if a form has the ^:+ metadata flag, indicating it's marked for insertion.

insertion? example

(insertion? '^:+ a)
=> true

(insertion? 'a)
=> false

8.2.6    deletion?

^{:refer code.query.common/deletion? :added "3.0"}

Checks if a form has the ^:- metadata flag, indicating it's marked for deletion.

deletion? example

(deletion? '^:- a)
=> true

(deletion? 'a)
=> false

8.2.7    prewalk

^{:refer code.query.common/prewalk :added "3.0"}

Applies a function to elements in a depth-first, pre-order traversal, eagerly modifying them. It preserves metadata.

prewalk example

Example from test code, but no direct assertion provided.
(prewalk inc '(1 (2 3)))
=> '(2 (3 4))

8.2.8    remove-items

^{:refer code.query.common/remove-items :added "3.0"}

Recursively removes items from a form that match a given predicate.

remove-items example

(remove-items #{1} '(1 2 3 4))
=> '(2 3 4)

(remove-items #{1} '(1 (1 (1 (1)))))
=> '(((())))

8.2.9    prepare-deletion

^{:refer code.query.common/prepare-deletion :added "3.0"}

Prepares a form for a deletion walk by removing cursor markers and insertion-flagged elements.

prepare-deletion example

(prepare-deletion '(+ a 2))
=> '(+ a 2)

(prepare-deletion '(+ ^:+ a | 2))
=> '(+ 2)

8.2.10    prepare-insertion

^{:refer code.query.common/prepare-insertion :added "3.0"}

Prepares a form for an insertion operation by removing cursor markers and deletion-flagged elements.

prepare-insertion example

(prepare-insertion '(+ a 2))
=> '(+ a 2)

(prepare-insertion '(+ ^:+ a | ^:- b 2))
=> '(+ a 2)

8.2.11    prepare-query

^{:refer code.query.common/prepare-query :added "3.0"}

Prepares a form for a query walk by removing cursor markers, deletion-flagged, and insertion-flagged elements.

prepare-query example

(prepare-query '(+ ^:+ a | ^:- b 2))
=> '(+ 2)

8.2.12    find-index

^{:refer code.query.common/find-index :added "3.0"}

Returns the index of the first occurrence of an element matching a predicate in a sequence.

find-index example

(find-index #{2} '(1 2 3 4))
=> 1

8.2.13    finto

^{:refer code.query.common/finto :added "3.0"}

A version of into that correctly handles lists by reversing the from collection before intoing.

finto example

(finto () '(1 2 3))
=> '(1 2 3)

9    code.query.compile Tutorial

Module: code.query.compilenSource File: src/code/query/compile.cljnTest File: test/code/query/compile_test.clj

The code.query.compile module is responsible for transforming a symbolic query path (a vector of Clojure forms and special keywords) into a structured map that can be used by the code.query.match module to find matching code structures. It handles cursor positions, metadata-driven flags, and special query operators like :* (multi-match) and :n (nth element).

9.1    Core Concepts

  • Query Path: A vector of Clojure forms and special symbols that describe a desired code structure.n Cursor (|): Marks the position of interest within the query path.n Metadata Flags: ^:+, ^:-, ^:? on forms in the query path indicate insertion, deletion, or optional elements.n Special Keywords:n _: Matches any single element.n :*: Matches multiple elements (like * in regex).n :n (e.g., :1, :5): Matches the nth element.n* Compiled Query Map: The output of this module, a nested map describing the query for code.query.match.

9.2    Functions

9.2.1    cursor-info

^{:refer code.query.compile/cursor-info :added "3.0"}

Finds the information related to the cursor (|) within a sequence of selectors (query path). It returns a vector [index type form] where index is the position, type is :cursor or :form, and form is the element if type is :form.

cursor-info example

(cursor-info '[(defn ^:?& _ | & _)])
=> '[0 :form (defn _ | & _)]

(cursor-info (expand-all-metas '[(defn ^:?& _ | & _)]))
=> '[0 :form (defn _ | & _)]

(cursor-info '[defn if])
=> [nil :cursor]

(cursor-info '[defn | if])
=> [1 :cursor]

9.2.2    expand-all-metas

^{:refer code.query.compile/expand-all-metas :added "3.0"}

Converts shorthand metadata (e.g., ^:%?) on forms within the query path into a map-based metadata (e.g., {:? true, :% true}).

expand-all-metas example

(meta (expand-all-metas '^:%? sym?))
=> {:? true, :% true}

(-> (expand-all-metas '(^:%+ + 1 2))
    first meta)
=> {:+ true, :% true}

9.2.3    split-path

^{:refer code.query.compile/split-path :added "3.0"}

Splits the query path into two parts: up (elements before the cursor/form) and down (elements from the cursor/form onwards).

split-path example

(split-path '[defn | if try] [1 :cursor])
=> '{:up (defn), :down [if try]}

(split-path '[defn if try] [nil :cursor])
=> '{:up [], :down [defn if try]}

9.2.4    process-special

^{:refer code.query.compile/process-special :added "3.0"}

Converts special keywords (:*, :n) in the query path into a map representation (e.g., {:type :multi}, {:type :nth, :step 1}).

process-special example

(process-special :*)
=> {:type :multi}

(process-special :1)
=> {:type :nth, :step 1}

(process-special :5)
=> {:type :nth, :step 5}

9.2.5    process-path

^{:refer code.query.compile/process-path :added "3.0"}

Converts a raw query path (vector of forms and special keywords) into a more structured sequence of maps, where each map describes an element or a special operation.

process-path example

(process-path '[defn if try])
=> '[{:type :step, :element defn}
        {:type :step, :element if}
        {:type :step, :element try}]

(process-path '[defn :* try :3 if])
=> '[{:type :step, :element defn}
        {:element try, :type :multi}
        {:element if, :type :nth, :step 3}]

9.2.6    compile-section-base

^{:refer code.query.compile/compile-section-base :added "3.0"}

Compiles a single element section of the query path into its base matching criteria (e.g., :form, :pattern, :is).

compile-section-base example

(compile-section-base '{:element defn})
=> '{:form defn}

(compile-section-base '{:element (if & _)})
=> '{:pattern (if & _)}

(compile-section-base '{:element _})
=> {:is code.query.common/any}

9.2.7    compile-section

^{:refer code.query.compile/compile-section :added "3.0"}

Compiles a query section based on the traversal direction (:up or :down), previous context, and element details. It translates special types (:multi, :nth) into corresponding matching strategies (:contains, :nth-ancestor).

compile-section example

(compile-section :up nil '{:element if, :type :nth, :step 3})
=> '{:nth-ancestor [3 {:form if}]}

(compile-section :down nil '{:element if, :type :multi})
=> '{:contains {:form if}}

9.2.8    compile-submap

^{:refer code.query.compile/compile-submap :added "3.0"}

Compiles a nested sub-query map based on the traversal direction and a processed path. It builds up the :child or :parent relationships in the query map.

compile-submap example

(compile-submap :down (process-path '[if try]))
=> '{:child {:child {:form if}, :form try}}

(compile-submap :up (process-path '[defn if]))
=> '{:parent {:parent {:form defn}, :form if}}

9.2.9    prepare

^{:refer code.query.compile/prepare :added "3.0"}

The main function for compiling a query. It takes a raw query path, expands metadata, splits the path, processes special elements, and returns a compiled query map along with cursor information.

prepare example

(prepare '[defn if])
=> '[{:child {:form if}, :form defn} [nil :cursor]]

(prepare '[defn | if])
=> '[{:parent {:form defn}, :form if} [1 :cursor]]

10    code.query.match Tutorial

Module: code.query.matchnSource File: src/code/query/match.cljnTest File: test/code/query/match_test.clj

The code.query.match module provides a powerful and flexible system for pattern matching against Clojure code represented as std.block navigators. It allows you to define complex matching rules using a declarative syntax, enabling tasks like code analysis, refactoring, and linting. The module is built around the concept of "matchers" – functions that take a navigator and return true if the current position matches a given pattern.

10.1    Core Concepts

  • Matcher: A function (or an instance of code.query.match/Matcher record) that takes a code.query.block navigator as input and returns a boolean indicating whether the current block matches a defined pattern.n Predicate Functions (p-*): A rich set of functions (prefixed with p-) that create matchers for various conditions, such as checking the value, type, metadata, or structural relationships (parent, child, sibling) of a block.n Query Language: Matchers can be composed using logical operators (p-and, p-or, p-not) and can be built from a declarative data structure using compile-matcher.n* Navigator (nav/): Functions from code.query.block (aliased as nav) are used extensively to create and manipulate the AST context for matching.

10.2    Functions

10.2.1    matcher

^{:refer code.query.match/matcher :added "3.0"}

Creates a Matcher record from a predicate function. This allows any function that takes a navigator and returns a boolean to be used as a matcher.

matcher example

((matcher string?) "hello")
=> true

10.2.2    matcher?

^{:refer code.query.match/matcher? :added "3.0"}

Checks if an object is a Matcher instance.

matcher? example

(matcher? (matcher string?))
=> true

10.2.3    p-fn

^{:refer code.query.match/p-fn :added "3.0"}

Creates a matcher that applies a given predicate function directly to the navigator.

p-fn example

((p-fn (fn [nav]
           (-> nav (nav/tag) (= :symbol))))
 (nav/parse-string "defn"))
=> true

10.2.4    p-not

^{:refer code.query.match/p-not :added "3.0"}

Creates a matcher that negates the result of another matcher.

p-not example

((p-not (p-is 'if)) (nav/parse-string "defn"))
=> true

((p-not (p-is 'if)) (nav/parse-string "if"))
=> false

10.2.5    p-is

^{:refer code.query.match/p-is :added "3.0"}

Creates a matcher that checks if the current block's value is equivalent to a given template, ignoring metadata.

p-is example

((p-is 'defn) (nav/parse-string "defn"))
=> true

((p-is '^{:a 1} defn) (nav/parse-string "defn"))
=> true

((p-is 'defn) (nav/parse-string "is"))
=> false

((p-is '(defn & _)) (nav/parse-string "(defn x [])"))
=> false

10.2.6    p-equal-loop

^{:refer code.query.match/p-equal-loop :added "3.0"}

A helper function for p-equal that recursively compares two Clojure forms for deep equality, including collection contents.

p-equal-loop example

((p-equal [1 2 3]) (nav/parse-string "[1 2 3]"))
=> true

((p-equal (list 'defn)) (nav/parse-string "(defn)"))
=> true

((p-equal '(defn)) (nav/parse-string "(defn)"))
=> true

10.2.7    p-equal

^{:refer code.query.match/p-equal :added "3.0"}

Creates a matcher that checks for deep equality between the current block's value and a template, including metadata.

p-equal example

((p-equal '^{:a 1} defn) (nav/parse-string "defn"))
=> false

((p-equal '^{:a 1} defn) (nav/parse-string "^{:a 1} defn"))
=> true

((p-equal '^{:a 1} defn) (nav/parse-string "^{:a 2} defn"))
=> false

10.2.8    p-meta

^{:refer code.query.match/p-meta :added "3.0"}

Creates a matcher that checks if the metadata of the current block's parent (if it's a meta form) matches a given template.

p-meta example

((p-meta {:a 1}) (nav/down (nav/parse-string "^{:a 1} defn")))
=> true

((p-meta {:a 1}) (nav/down (nav/parse-string "^{:a 2} defn")))
=> false

10.2.9    p-type

^{:refer code.query.match/p-type :added "3.0"}

Creates a matcher that checks if the block-tag of the current block matches a given type keyword (e.g., :symbol, :list).

p-type example

((p-type :symbol) (nav/parse-string "defn"))
=> true

((p-type :symbol) (-> (nav/parse-string "^{:a 1} defn") nav/down nav/right))
=> true

10.2.10    p-form

^{:refer code.query.match/p-form :added "3.0"}

Creates a matcher that checks if the current block is a list form whose first element (the function/macro name) matches a given symbol.

p-form example

((p-form 'defn) (nav/parse-string "(defn x [])"))
=> true
((p-form 'let) (nav/parse-string "(let [])"))
=> true

10.2.11    p-pattern

^{:refer code.query.match/p-pattern :added "3.0"}

Creates a matcher that checks if the current block's value matches a complex pattern defined using Clojure forms and special query symbols (like _, &). This leverages code.query.match.pattern.

p-pattern example

((p-pattern '(defn ^:% symbol? & _)) (nav/parse-string "(defn ^{:a 1} x [])"))
=> true

((p-pattern '(defn ^:% symbol? ^{:% true :? true} string? []))
 (nav/parse-string "(defn ^{:a 1} x [])"))
=> true

10.2.12    p-code

^{:refer code.query.match/p-code :added "3.0"}

Creates a matcher that checks if the string representation of the current block matches a given regular expression.

p-code example

((p-code #"defn") (nav/parse-string "(defn ^{:a 1} x [])"))
=> true

10.2.13    p-and

^{:refer code.query.match/p-and :added "3.0"}

Combines multiple matchers, returning true only if all of them match.

p-and example

((p-and (p-code #"defn")
          (p-type :token)) (nav/parse-string "(defn ^{:a 1} x [])"))
=> false

((p-and (p-code #"defn")
          (p-type :list)) (nav/parse-string "(defn ^{:a 1} x [])"))
=> true

10.2.14    p-or

^{:refer code.query.match/p-or :added "3.0"}

Combines multiple matchers, returning true if at least one of them matches.

p-or example

((p-or (p-code #"defn")
          (p-type :token)) (nav/parse-string "(defn ^{:a 1} x [])"))
=> true

((p-or (p-code #"defn")
          (p-type :list)) (nav/parse-string "(defn ^{:a 1} x [])"))
=> true

10.2.15    compile-matcher

^{:refer code.query.match/compile-matcher :added "3.0"}

The main entry point for creating complex matchers from a declarative data structure (a map, vector, symbol, or function). It recursively compiles the structure into a composite matcher.

compile-matcher example

((compile-matcher {:is 'hello}) (nav/parse-string "hello"))
=> true

10.2.16    p-parent

^{:refer code.query.match/p-parent :added "3.0"}

Creates a matcher that checks if the parent of the current block matches a given template.

p-parent example

((p-parent 'defn) (-> (nav/parse-string "(defn x [])") nav/next nav/next))
=> true

((p-parent {:parent 'if}) (-> (nav/parse-string "(if (= x y))") nav/down nav/next nav/next))
=> true

((p-parent {:parent 'if}) (-> (nav/parse-string "(if (= x y))") nav/down))
=> false

10.2.17    p-child

^{:refer code.query.match/p-child :added "3.0"}

Creates a matcher that checks if any child of the current container block matches a given template.

p-child example

((p-child {:form '=}) (nav/parse-string "(if (= x y))"))
=> true

((p-child '=) (nav/parse-string "(if (= x y))"))
=> false

10.2.18    p-first

^{:refer code.query.match/p-first :added "3.0"}

Creates a matcher that checks if the first element of the current container block matches a given template.

p-first example

((p-first 'defn) (-> (nav/parse-string "(defn x [])")))
=> true

((p-first 'x) (-> (nav/parse-string "[x y z]")))
=> true

((p-first 'x) (-> (nav/parse-string "[y z]")))
=> false

10.2.19    p-last

^{:refer code.query.match/p-last :added "3.0"}

Creates a matcher that checks if the last element of the current container block matches a given template.

p-last example

((p-last 1) (-> (nav/parse-string "(defn [] 1)")))
=> true

((p-last 'z) (-> (nav/parse-string "[x y z]")))
=> true

((p-last 'x) (-> (nav/parse-string "[y z]")))
=> false

10.2.20    p-nth

^{:refer code.query.match/p-nth :added "3.0"}

Creates a matcher that checks if the element at a specific Nth index within the current container block matches a given template.

p-nth example

((p-nth [0 'defn]) (-> (nav/parse-string "(defn [] 1)")))
=> true

((p-nth [2 'z]) (-> (nav/parse-string "[x y z]")))
=> true

((p-nth [2 'x]) (-> (nav/parse-string "[y z]")))
=> false

10.2.21    p-nth-left

^{:refer code.query.match/p-nth-left :added "3.0"}

Creates a matcher that checks if the element at a specific Nth index to the left of the current position has a certain characteristic.

p-nth-left example

((p-nth-left [0 'defn]) (-> (nav/parse-string "(defn [] 1)") nav/down))
=> true

((p-nth-left [1 ^:& vector?]) (-> (nav/parse-string "(defn [] 1)") nav/down nav/right-most))
=> true

10.2.22    p-nth-right

^{:refer code.query.match/p-nth-right :added "3.0"}

Creates a matcher that checks if the element at a specific Nth index to the right of the current position has a certain characteristic.

p-nth-right example

((p-nth-right [0 'defn]) (-> (nav/parse-string "(defn [] 1)") nav/down))
=> true

((p-nth-right [1 vector?]) (-> (nav/parse-string "(defn [] 1)") nav/down))
=> true

10.2.23    p-nth-ancestor

^{:refer code.query.match/p-nth-ancestor :added "3.0"}

Creates a matcher that searches for a match n levels up in the ancestor chain.

p-nth-ancestor example

((p-nth-ancestor [2 {:contains 3}])
 (-> (nav/parse-string "(* (- (+ 1 2) 3) 4)")
     nav/down nav/right nav/down nav/right nav/down))
=> true

10.2.24    tree-search

^{:refer code.query.match/tree-search :added "3.0"}

A helper function for p-contains that recursively searches a tree structure for elements matching a predicate.

No direct test example, but used internally by p-contains.

10.2.25    p-contains

^{:refer code.query.match/p-contains :added "3.0"}

Creates a matcher that checks if any element (deeply nested) within the current container matches a given template.

p-contains example

((p-contains '=) (nav/parse-string "(if (= x y))"))
=> true

((p-contains 'x) (nav/parse-string "(if (= x y))"))
=> true

10.2.26    tree-depth-search

^{:refer code.query.match/tree-depth-search :added "3.0"}

A helper function for p-nth-contains that performs a depth-first search for a match n levels down in a tree structure.

No direct test example, but used internally by p-nth-contains.

10.2.27    p-nth-contains

^{:refer code.query.match/p-nth-contains :added "3.0"}

Creates a matcher that searches for a match n levels down in the tree.

p-nth-contains example

((p-nth-contains [2 {:contains 1}])
 (nav/parse-string "(* (- (+ 1 2) 3) 4)"))
=> true

10.2.28    p-ancestor

^{:refer code.query.match/p-ancestor :added "3.0"}

Creates a matcher that checks if any ancestor of the current block matches a given template.

p-ancestor example

((p-ancestor {:form 'if}) (-> (nav/parse-string "(if (= x y))") nav/down nav/next nav/next))
=> true

((p-ancestor 'if) (-> (nav/parse-string "(if (= x y))") nav/down nav/next nav/next))
=> true

10.2.29    p-sibling

^{:refer code.query.match/p-sibling :added "3.0"}

Creates a matcher that checks if any element on the same level (sibling) as the current block matches a given template.

p-sibling example

((p-sibling '=) (-> (nav/parse-string "(if (= x y))") nav/down nav/next nav/next))
=> false

((p-sibling 'x) (-> (nav/parse-string "(if (= x y))") nav/down nav/next nav/next))
=> true

10.2.30    p-left

^{:refer code.query.match/p-left :added "3.0"}

Creates a matcher that checks if the immediate left sibling of the current block matches a given template.

p-left example

((p-left '=) (-> (nav/parse-string "(if (= x y))") nav/down nav/next nav/next nav/next))
=> true

((p-left 'if) (-> (nav/parse-string "(if (= x y))") nav/down nav/next))
=> true

10.2.31    p-right

^{:refer code.query.match/p-right :added "3.0"}

Creates a matcher that checks if the immediate right sibling of the current block matches a given template.

p-right example

((p-right 'x) (-> (nav/parse-string "(if (= x y))") nav/down nav/next nav/next))
=> true

((p-right {:form '=}) (-> (nav/parse-string "(if (= x y))") nav/down))
=> true

10.2.32    p-left-of

^{:refer code.query.match/p-left-of :added "3.0"}

Creates a matcher that checks if any element to the left of the current block (on the same level) matches a given template.

p-left-of example

((p-left-of '=) (-> (nav/parse-string "(= x y)") nav/down nav/next))
=> true

((p-left-of '=) (-> (nav/parse-string "(= x y)") nav/down nav/next nav/next))
=> true

10.2.33    p-right-of

^{:refer code.query.match/p-right-of :added "3.0"}

Creates a matcher that checks if any element to the right of the current block (on the same level) matches a given template.

p-right-of example

((p-right-of 'x) (-> (nav/parse-string "(= x y)") nav/down))
=> true

((p-right-of 'y) (-> (nav/parse-string "(= x y)") nav/down))
=> true

((p-right-of 'z) (-> (nav/parse-string "(= x y)") nav/down))
=> false

10.2.34    p-left-most

^{:refer code.query.match/p-left-most :added "3.0"}

Creates a matcher that checks if the current block is the leftmost expression within its current level.

p-left-most example

((p-left-most true) (-> (nav/parse-string "(= x y)") nav/down))
=> true

((p-left-most true) (-> (nav/parse-string "(= x y)") nav/down nav/next))
=> false

10.2.35    p-right-most

^{:refer code.query.match/p-right-most :added "3.0"}

Creates a matcher that checks if the current block is the rightmost expression within its current level.

p-right-most example

((p-right-most true) (-> (nav/parse-string "(= x y)") nav/down nav/next))
=> false

((p-right-most true) (-> (nav/parse-string "(= x y)") nav/down nav/next nav/next))
=> true

11    code.query Summary

code.query is a library for querying and manipulating code structures represented as std.block trees. It provides a powerful way to navigate, match, and transform code, making it a key component of the foundation-base transpiler and code analysis tools.

Core Concepts:

  • Navigator: A zipper-like data structure (code.query.block/navigator) that allows for efficient traversal and manipulation of the std.block tree.n Matchers: Predicate functions (code.query.match) that can be used to find specific nodes or patterns in the code tree.n Traversal: Functions for walking the code tree and applying transformations (code.query.walk and code.query.traverse).n* Compilation: The code.query.compile namespace provides functions for compiling a query pattern into an efficient matcher.

Key Areas of Functionality (with Examples):

  • Navigation (code.query.block):n navigator: Creates a navigator from a std.block tree.n up, down, left, right: Functions for moving the navigator around the tree.n node: Returns the current block at the navigator's focus.n root-string: Returns the string representation of the entire code tree.n ```clojuren (require '[code.query.block :as nav])nn (def nav (nav/parse-string "(+ 1 2)"))n (-> nav nav/down nav/right nav/node base/block-value)n ;; => 1n ```nn Matching (code.query.match):n p-is: Matches a specific value.n p-form: Matches a form with a specific symbol as its first element.n p-pattern: Matches a code pattern with wildcards and predicates.n p-and, p-or, p-not: For combining matchers.n p-parent, p-child, p-ancestor, p-contains: For matching based on relationships between nodes.n ```clojuren (require '[code.query.match :as match])nn (def nav (nav/parse-string "(defn my-fn [x] (+ x 1))"))n ((match/p-form 'defn) nav)n ;; => truen ((match/p-pattern '(defn [] _)) nav)n ;; => truen ```nn Walking and Traversal (code.query.walk, code.query.traverse):n matchwalk: Traverses the code tree and applies a function to all nodes that match a given pattern.n levelwalk: Similar to matchwalk, but only applies the function to nodes at the top level of the tree.n traverse: A more powerful traversal function that can be used to perform complex transformations, including insertions and deletions.n ```clojuren (require '[code.query.walk :as walk])n (require '[code.query.match :as match])nn (-> (walk/matchwalk (nav/parse-string "(+ 1 (+ 2 3))")n [(match/p-is '+)]n (fn [nav] (nav/replace nav '-)))n nav/root-string)n ;; => "(- 1 (- 2 3))"n ```nn Compilation (code.query.compile):n prepare: Compiles a query pattern into a more efficient internal representation that can be used by the matching and traversal functions. This is mostly used internally by the library.

12    code.query.traverse Tutorial

Module: code.query.traversenSource File: src/code/query/traverse.cljnTest File: test/code/query/traverse_test.clj

The code.query.traverse module provides a powerful mechanism for traversing and transforming Clojure code ASTs (represented as std.block navigators) based on declarative patterns. It enables complex operations like inserting, deleting, and modifying code elements while maintaining structural integrity. This module is fundamental for building advanced code manipulation tools.

12.1    Core Concepts

  • Position Record: A record that encapsulates the current state of a traversal, including the source navigator, the pattern navigator, and the op (operation) map.n Pattern-driven Traversal: The traversal is guided by a pattern (a Clojure form with special metadata and symbols) that dictates how to navigate and what actions to perform.n Operations (op map): A map of functions (:delete-form, :insert-level, :cursor-node, etc.) that define how to handle different parts of the pattern during traversal.n Metadata Flags: ^:+ (insert), ^:- (delete), ^:? (optional) on forms within the pattern control the transformation behavior.n Cursor (|): Marks a specific point of interest in the pattern, allowing for precise targeting of operations.

12.2    Functions

12.2.1    pattern-zip

^{:refer code.query.traverse/pattern-zip :added "3.0"}

Creates a clojure.zip zipper specifically configured for traversing Clojure forms (lists and vectors) as patterns.

No direct test example, but it's used internally to create pattern navigators.

12.2.2    wrap-meta

^{:refer code.query.traverse/wrap-meta :added "3.0"}

A higher-order function that wraps a traversal function to handle metadata tags. It ensures that metadata forms are correctly processed during traversal.

No direct test example, but used internally by traversal functions.

12.2.3    wrap-delete-next

^{:refer code.query.traverse/wrap-delete-next :added "3.0"}

A wrapper function for deleting the element immediately following the current position in a zipper. Used internally by deletion traversal.

No direct test example, but used internally by deletion traversal functions.

12.2.4    traverse-delete-form

^{:refer code.query.traverse/traverse-delete-form :added "3.0"}

Traverses a form marked for deletion, recursively applying deletion logic to its children.

No direct test example, but used internally by deletion traversal functions.

12.2.5    traverse-delete-node

^{:refer code.query.traverse/traverse-delete-node :added "3.0"}

Handles the deletion of a single node during traversal.

No direct test example, but used internally by deletion traversal functions.

12.2.6    traverse-delete-level

^{:refer code.query.traverse/traverse-delete-level :added "3.0"}

Traverses a level within the AST, applying deletion logic based on the pattern.

No direct test example, but used internally by deletion traversal functions.

12.2.7    prep-insert-pattern

^{:refer code.query.traverse/prep-insert-pattern :added "3.0"}

Prepares a pattern element for insertion by removing its metadata and evaluating it if marked with :%.

No direct test example, but used internally by insertion traversal functions.

12.2.8    wrap-insert-next

^{:refer code.query.traverse/wrap-insert-next :added "3.0"}

A wrapper function for inserting an element immediately following the current position in a zipper. Used internally by insertion traversal.

No direct test example, but used internally by insertion traversal functions.

12.2.9    traverse-insert-form

^{:refer code.query.traverse/traverse-insert-form :added "3.0"}

Traverses a form marked for insertion, recursively applying insertion logic to its children.

No direct test example, but used internally by insertion traversal functions.

12.2.10    traverse-insert-node

^{:refer code.query.traverse/traverse-insert-node :added "3.0"}

Handles the insertion of a single node during traversal.

No direct test example, but used internally by insertion traversal functions.

12.2.11    traverse-insert-level

^{:refer code.query.traverse/traverse-insert-level :added "3.0"}

Traverses a level within the AST, applying insertion logic based on the pattern.

No direct test example, but used internally by insertion traversal functions.

12.2.12    wrap-cursor-next

^{:refer code.query.traverse/wrap-cursor-next :added "3.0"}

A wrapper function for locating the cursor at the next element during code traversal.

No direct test example, but used internally by cursor traversal functions.

12.2.13    traverse-cursor-form

^{:refer code.query.traverse/traverse-cursor-form :added "3.0"}

Traverses a form to locate the cursor within it.

No direct test example, but used internally by cursor traversal functions.

12.2.14    traverse-cursor-level

^{:refer code.query.traverse/traverse-cursor-level :added "3.0"}

Traverses a level within the AST to locate the cursor.

No direct test example, but used internally by cursor traversal functions.

12.2.15    count-elements

^{:refer code.query.traverse/count-elements :added "3.0"}

Counts the number of elements in a given code structure. Used internally for pattern matching.

No direct test example, but used internally.

12.2.16    traverse

^{:refer code.query.traverse/traverse :added "3.0"}

The main traversal function. It takes a source navigator and a pattern, and applies the transformations (insertions, deletions) defined by the pattern to the source.

traverse example

(source
 (traverse (nav/parse-string "^:a (+ () 2 3)")
           '(+ () 2 3)))
=> '(+ () 2 3)

(source
 (traverse (nav/parse-string "^:a (hello)")
           '(hello)))
=> '(hello)

;; Deletions
(source
 (traverse (nav/parse-string "^:a (hello)")
           '(^:- hello)))
=> ()

(source
 (traverse (nav/parse-string "(hello)")
           '(^:- hello)))
=> ()

(source
 (traverse (nav/parse-string "((hello))")
           '((^:- hello))))
=> '(())

;; Insertions
(source
 (traverse (nav/parse-string "()")
           '(^:+ hello)))
=> '(hello)

(source
 (traverse (nav/parse-string "(())")
           '((^:+ hello))))
=> '((hello))

;; More advanced transformations
(source
 (traverse (nav/parse-string "(defn hello)")
           '(defn ^{:? true :% true} symbol? ^:+ [])))
=> '(defn hello [])

(source
 (traverse (nav/parse-string "(defn hello)")
           '(defn ^{:? true :% true :- true} symbol? ^:+ [])))
=> '(defn [])

(source
 (traverse (nav/parse-string "(defn hello)")
           '(defn ^{:? true :% true :- true} symbol? | ^:+ [])))
=> []

(source
 (traverse (nav/parse-string "(defn hello "world" {:a 1} [])")
           '(defn ^:% symbol?
              ^{:? true :% true :- true} string?
              ^{:? true :% true :- true} map?
              ^:% vector? & _)))
=> '(defn hello [])

(source
 (traverse (nav/parse-string "(defn hello [] (+ 1 1))")
           '(defn _ _ (+ | 1 & _))))
=> 1

(source
 (traverse (nav/parse-string "(defn hello [] (+ 1 1))")
           '(#{defn} | & _)))
=> 'hello

(source
 (traverse (nav/parse-string "(fact "hello world")")
           '(fact | & _)))
=> "hello world"

12.2.17    source

^{:refer code.query.traverse/source :added "3.0"}

Retrieves the final source code (Clojure form) from a traversed Position record.

source example

(source
 (traverse (nav/parse-string "()")
           '(^:+ hello)))
=> '(hello)

13    code.query.walk Tutorial

Module: code.query.walknSource File: src/code/query/walk.cljnTest File: test/code/query/walk_test.clj

The code.query.walk module provides functions for traversing and transforming std.block navigators (ASTs) based on patterns. It offers two primary walking strategies: matchwalk for deep, recursive traversal and levelwalk for top-level matching. These functions are essential for implementing automated code transformations and refactorings.

13.1    Core Concepts

  • Navigator (nav/): Functions from code.query.block (aliased as nav) are used to create and manipulate the AST context.n Matchers: Predicates (created using code.query.match functions) that determine whether a block matches a specific pattern.n Transformation Function (f): A function that takes a matching navigator and returns a transformed navigator.n* Wrappers: Functions like wrap-meta and wrap-suppress enhance the walking behavior by handling metadata and exceptions.

13.2    Functions

13.2.1    wrap-meta

^{:refer code.query.walk/wrap-meta :added "3.0"}

A higher-order function that wraps a walk function to correctly handle metadata tags. It ensures that metadata forms are properly traversed and processed.

No direct test example, but used internally by matchwalk and levelwalk.

13.2.2    wrap-suppress

^{:refer code.query.walk/wrap-suppress :added "3.0"}

A higher-order function that wraps a walk function to suppress exceptions during traversal, returning the original navigator in case of an error.

No direct test example, but used internally by matchwalk and levelwalk.

13.2.3    matchwalk

^{:refer code.query.walk/matchwalk :added "3.0"}

Performs a deep, recursive traversal of the AST. It applies a transformation function f to every block that matches any of the provided matchers.

matchwalk example

(-> (matchwalk (nav/parse-string "(+ (+ (+ 8 9)))")
               [(match/compile-matcher '+)]
               (fn [nav]
                 (-> nav nav/down (nav/replace '-) nav/up)))
    nav/value)
=> '(- (- (- 8 9)))

13.2.4    levelwalk

^{:refer code.query.walk/levelwalk :added "3.0"}

Performs a top-level traversal of the AST. It applies a transformation function f only to blocks at the current level that match the provided matcher.

levelwalk example

(-> (levelwalk (nav/parse-string "(+ (+ (+ 8 9)))")
               [(match/compile-matcher '+)]
               (fn [nav]
                 (-> nav nav/down (nav/replace '-) nav/up)))
    nav/value)
=> '(- (+ (+ 8 9)))