Created
June 20, 2022 18:13
-
-
Save riotrah/be60b7ccefe99436b28c9bb11bbe08d9 to your computer and use it in GitHub Desktop.
clj-kondo hook for clj-commons.secretary.core/defroute
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns clj-kondo.clj-commons.secretary | |
(:require | |
[clj-kondo.hooks-api :as api] | |
[clojure.string :as str])) | |
#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]} | |
(defn defroute | |
"Macro analysis for `secretary.core/defroute`. | |
Does some extra work to make `defroute` more user-friendly, for those whose IDEs make use of clj-kondo. | |
0. For unnamed routes, it does nothing, and returns the original `defroute` form. | |
1. For named routes (`(defroute some-route \"/some/route\" ...)`): | |
It turns: | |
``` | |
(defroute | |
some-route \"/some/:route/:param1\" | |
[route] | |
...) | |
``` | |
into: | |
``` | |
(defn some-route | |
\"Route handler for url: `/some/:route/:param1`. | |
Route props: `:route`, `:param1`\" | |
[route param1] | |
...) | |
``` | |
- It makes said named routes have better go-to-definition etc by transforming them into defn calls. | |
- It makes the implicitly destructed path params (`(defroute some-route \")) into explicit destructed params, | |
instead of an inner `let` binding as done in the actual macroexpansion for IDE arg hover purposes. | |
- It adds a docstring to the \"generated\" `defn`, which contains the route's path string and the route's props. | |
- It makes clj-kondo ensure that routes with no path-params will lint-error if any args are passed to them. | |
- Conversely, routes with path-params will lint-error if the arg (a map, though it doesn't check if it is a map) | |
are _not_ passed to them." | |
[{:keys [node]}] | |
(let [[_defroute-sym & body] (-> node :children) | |
[named-route-fn-sym body] (if (api/token-node? (first body)) | |
[(first body) (next body)] | |
[nil body]) | |
is-named-route? (some? named-route-fn-sym) | |
[route-string body] (if (api/string-node? (first body)) | |
[(first body) (next body)] | |
[nil body]) | |
[let-binding-vector body] (if (api/vector-node? (first body)) | |
[(-> body first) (next body)] | |
[nil body]) | |
has-url-params? (boolean (seq (:children let-binding-vector))) | |
expanded (if is-named-route? | |
(api/list-node | |
(list* | |
;; Transform the `defroute` sym into a `defn` one. | |
(api/token-node 'defn) | |
;; The original route name. | |
named-route-fn-sym | |
;; The following automatically generates (in-IDE hover) docstring like so: | |
;; | |
;; "Route handler for url: `/databases/:database-id/draft/table_versions/:table-name/:version-id`. | |
;; <newline> | |
;; Route props: `:database-id` `:table-name` `:version-id`." | |
(api/string-node | |
(list* "Route handler for url: `", | |
(api/sexpr route-string), | |
"`.", | |
(when has-url-params? | |
(list | |
"\nRoute props: " | |
(str/join ", " | |
(map #(str "`:" % "`") | |
(:children let-binding-vector))) | |
".")))) | |
;; The path param arg vector. | |
;; If there are none, it's an empty vector, raising an error if any args are | |
;; passed when called. Otherwise, it's a vector of the destructed single map arg.. | |
(api/vector-node | |
(if has-url-params? | |
;; The destructed map arg. | |
[(api/map-node [(api/keyword-node :keys) | |
let-binding-vector])] | |
[])) | |
body)) | |
node)] | |
(comment (when expanded | |
(-> node api/sexpr prn) | |
(-> expanded prn) | |
(prn))) | |
{:node expanded})) | |
(comment | |
;; Example 1: usage without named route - the hook does nothing | |
#_(sec/defroute | |
"route/:string1" | |
[string1] | |
(prn string1)) | |
;; Example 2: usage with named route - this is the _output_ of the hook, after it's been tranformed into a `defn` etc. | |
(defn named-route-fn-sym | |
"route/:string1" ; maybe use as optional docstring | |
[url-params] | |
(let | |
[{:keys [string1]} url-params] | |
(prn string1)))) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment