Fork me on GitHub
#clj-kondo
<
2021-11-11
>
joost-diepenmaat14:11:34

hi everybody! is there a way to write custom per-project linters with clj-kondo? Would be a good way to try out new linters and to enforce specific styles

joost-diepenmaat14:11:18

any pointers how to go about writing new linters in the clj-kondo codebase would be helpful too

borkdude14:11:00

@joost-diepenmaat yes, this is possible using hooks (or specific config for your project-specific functions). https://github.com/clj-kondo/clj-kondo/blob/master/doc/hooks.md

borkdude14:11:24

@joost-diepenmaat You can see several examples of this of libraries that export their custom hooks: https://github.com/clj-kondo/clj-kondo/issues/1383

joost-diepenmaat14:11:15

thanks @borkdude. do hooks allow me to add additional linters to def* forms? I would like to do additional checks on docstrings, similar to emacs’ checkdoc function for elisp.

joost-diepenmaat14:11:46

so I won’t be dealing with new macro’s, but actually adding checks to existing ones

borkdude14:11:12

yes, just write the hook for the core macro and then don't return a new node, just emit warnings using reg-finding!

joost-diepenmaat14:11:28

I’ll give it a shot. thanks!

mynomoto17:11:50

Can I ignore a specific expression on "Redundant expression"? My usecase is I'm using https://github.com/hyperfiddle/rcf and random := have meaning on the tests expression.

mynomoto17:11:01

The default config only has a level config for that linter.

borkdude17:11:09

@mynomoto You can ignore any warning by prepending the form with #_:clj-kondo/ignore

mynomoto18:11:26

Yeah, the problem is that this would make the minimal syntax not so minimal or I would have no linting on the whole form. I think I will disable this linter on the namespace configuration.

borkdude19:11:20

@mynomoto or you can write a hook for this macro

mynomoto19:11:16

Yeah, I will try to do that on the project. Is there a how to write hooks guide?

borkdude19:11:03

I think it can be a :analyze-call hook which rewrites := to (= x y)

mynomoto19:11:27

Ok, thanks!

borkdude19:11:28

what's the reason for this syntax instead of (is (= 1 2 3))?

mynomoto19:11:36

I like my syntax really regular but after spending some time with this library I got to like this choice of syntax. And it wins hands down against comment blocks that what this is replacing in the codebase.

borkdude19:11:13

what's wrong with just (is (= 1 2 3))?

borkdude19:11:01

I changed the phrasing of my earlier comment

mynomoto20:11:02

You mean inside the tests or inside a deftest?

mynomoto20:11:18

Inside tests would not report an error, inside a deftest it would not be elided on prod.

mynomoto20:11:29

I like the tradeoff, but I'm always open to suggestions or finding new options.

borkdude20:11:51

yes, there are always trade-offs

borkdude17:11:14

interesting syntax choice...

mynomoto18:11:28

If someone does another version of this lib using a more regular syntax I would go for it. 😉

otfrom17:11:10

I'm trying to write a hook for tech.v3.datatype.export-symbols I've got a function that is being called by the hook and it is getting the right data (I think). I think I'm struggling to make the appropriate tokens for clj-kondo to understand. (more in thread)

otfrom17:11:39

my hook looks like this:

(ns hooks.export-symbols
  (:require [clj-kondo.hooks-api :as api]))

;; export-symbols takes a name space and then a bunch of funcitons in
;; that namespace to export
(defn export-symbols [{:keys [node]}]
  (let [[es ns & fns] (:children node)]
    (when-not (and es ns (seq fns))
      (throw (ex-info "Missing namespace or functions." {})))
    (into []
          (map
           (fn [f]
             (prn "List Node: "
                  (api/list-node
                   (list* (api/token-node 'def)
                          f
                          (concat ns "/" f))))))
          fns)
    {:node
     (into []
           (map
            (fn [f]
              (api/list-node
               (list* (api/token-node 'def)
                      f
                      (concat ns "/" f)))))
           fns)}))

otfrom17:11:06

the code I'm checking looks like this:

(ns tech.v3.datatype.foo
  (:require [tech.v3.datatype.datetime.base :as datetime-base]
            [tech.v3.datatype.export-symbols :refer [export-symbols]]))

(export-symbols tech.v3.datatype.datetime.base
                system-zone-id)

borkdude17:11:29

I'll be back after dinner

otfrom17:11:02

the macro-expand is doing this:

(do
  (require 'tech.v3.datatype.datetime.base)
  (let*
    [varval__5802__auto__
     (requiring-resolve
       (symbol "tech.v3.datatype.datetime.base" "system-zone-id"))
     var-meta__5803__auto__
     (meta varval__5802__auto__)]
    (if varval__5802__auto__
      nil
      (do
        (throw
          (ex-info
            (format
              "Failed to find symbol '%s' in namespace '%s'"
              "system-zone-id"
              "tech.v3.datatype.datetime.base")
            {:symbol 'system-zone-id,
             :src-ns 'tech.v3.datatype.datetime.base}))))
    (if (:macro var-meta__5803__auto__)
      (do
        (throw
          (ex-info
            (format
              "Cannot export macros as this breaks aot: %s"
              'system-zone-id)
            {:symbol 'system-zone-id}))))
    (def system-zone-id (deref varval__5802__auto__))
    (alter-meta!
      #'system-zone-id
      merge
      (select-keys
        var-meta__5803__auto__
        [:file :line :column :doc :column :tag :arglists]))))

borkdude17:11:16

@U0525KG62 you can try the :macroexpand hook as well, then you can re-use parts of the original macro

otfrom17:11:17

which is basically expanding

(export-symbols tech.v3.datatype.datetime.base
                system-zone-id)
to
(def system-zone-id tech.v3.datatype.datetime.base/system-zone-id)

borkdude17:11:42

see the hooks.md docs, search for :macroexpand. I'll be back in an hour

otfrom17:11:51

thx. I'll have a look again

otfrom18:11:32

:macroexpand looks good. I was hoping I'd be able to get something that pointed at the right lines in the source file

otfrom18:11:45

right, :macroexpand seems to "just work" ™️

otfrom18:11:36

I've got a config.edn with the following:

{:hooks {:macroexpand {tech.v3.datatype.export-symbols/export-symbols
                       hooks.export-symbols/export-symbols}}}

otfrom18:11:36

and I just copy-pasta'd the export-symbols macro from tech.v3.datatype.export-symbols and things are good now

otfrom18:11:10

IIUC, putting this config in the .clj-kondo of the project where the macro is defined will mean that it works for all projects that use that macro (so I only need to define it once)

borkdude18:11:42

> IIUC, putting this config in the .clj-kondo of the project where the macro is defined will mean that it works for all projects that use that macro

borkdude18:11:01

Nope this isn't true. It works only for the current project. If you want to share it with other projects, put it in clj-kondo.exports

borkdude18:11:16

Same documentation page, but a little bit towards the bottom

otfrom18:11:28

me goes to look at clj-kondo.exports

borkdude18:11:16

The recommendation is to put it in resources/clj-kondo.exports/cnuernber/dtype-next/config.edn

borkdude18:11:42

and also namespace the hooks code:

resources/clj-kondo.exports/cnuernber/dtype-next/tech/v3/datatype/export_symbols.clj

borkdude18:11:51

and then configure accordingly

otfrom19:11:17

does this mean that clojure-lsp will automagically work as well?

borkdude19:11:52

almost automagically. after linting dependencies, there will be a .clj-kondo/cnuerber/dtype-next directory with your exported code/config

borkdude19:11:14

and you have to add this as :config-paths ["cnuerber/dtype-next"] in your config.edn

borkdude19:11:47

the reason it's not automatically added is to prevent opting into config that you don't want or contains invalid code

otfrom19:11:28

is that in the ~/.clj-kondo/config.edn or for the project?

borkdude19:11:03

but you can add this to your home dir as well. but projects have specific versions of deps and configuration can change over versions

borkdude19:11:26

if you want to add it to your home dir you should do it manually

borkdude19:11:49

You can see a list of other library exported config here: https://github.com/clj-kondo/clj-kondo/issues/1383

borkdude19:11:10

I was considering doing a workshop on clj-kondo hooks at re:clojure but I'm a bit late to the party I think

otfrom19:11:31

would be good as a london-clojurians talk regardless of when 😄

otfrom19:11:39

(still organising after all these years)

otfrom19:11:33

thx for the help @borkdude and for :expandmacro

borkdude19:11:07

My pleasure. Are you going to do a follow up PR with exports?

otfrom22:11:44

I am, but I ran out of brain and time tonight. I'll read up tomorrow and over the weekend and and do a write up so people in scicloj can use it.

borkdude22:11:12

ok, let me know if you're stuck :-)

otfrom22:11:17

thx. Will do. I'd still like to find a better way to do the actual linting than the :expandmacro. It looks like I need to understand a lot more about clj-rewrite works though as it seems like that's the data structure I need to make.

otfrom22:11:56

but I'm going to go with what I have for now as that makes things better now.

otfrom22:11:11

I don't want the perfect to be the enemy of the good. 🙂

borkdude22:11:01

you mean, emit warnings? you can do this using :macroexpand too