7.9 KiB
7.9 KiB
clj-kondo Quick Reference
Command Line
# Basic linting
clj-kondo --lint <path>
clj-kondo --lint src test
# Output formats
clj-kondo --lint src --config '{:output {:format :json}}'
clj-kondo --lint src --config '{:output {:format :edn}}'
# Cache dependencies
clj-kondo --lint "$(clojure -Spath)" --dependencies --parallel --copy-configs
# Version
clj-kondo --version
Configuration File Location
.clj-kondo/config.edn # Project config
~/.config/clj-kondo/config.edn # Global config
Basic Configuration
{:linters {:unused-binding {:level :warning}
:unused-namespace {:level :warning}
:unresolved-symbol {:level :error}
:invalid-arity {:level :error}
:misplaced-docstring {:level :warning}}
:output {:pattern "{{LEVEL}} {{filename}}:{{row}}:{{col}} {{message}}"
:exclude-files ["generated/" "resources/public/js/"]}}
Linter Levels
:off ; Disable
:info ; Informational
:warning ; Warning (default)
:error ; Error (fails build)
Common Linter Configurations
Disable Specific Linters
{:linters {:unused-binding {:level :off}
:unused-private-var {:level :off}}}
Exclude Namespaces
{:linters {:unused-binding {:exclude-ns [myapp.test-helpers
myapp.dev]}}}
Consistent Aliases
{:linters {:consistent-alias {:level :warning
:aliases {clojure.string str
clojure.set set
clojure.java.io io}}}}
Refer :all
{:linters {:refer-all {:level :warning
:exclude [clojure.test]}}}
Macro as Function
{:lint-as {myapp/my-macro clojure.core/let
myapp/defroute compojure.core/defroutes}}
Inline Suppressions
;; Suppress for entire namespace
(ns myapp.core
{:clj-kondo/config '{:linters {:unused-binding {:level :off}}}})
;; Suppress specific linters for form
#_{:clj-kondo/ignore [:unused-binding :unresolved-symbol]}
(let [x 1] (undefined-fn))
;; Suppress all linters for form
#_{:clj-kondo/ignore true}
(problematic-code)
;; Suppress with underscore prefix
(let [_unused-var 42] ;; No warning
...)
Hook Types
analyze-call Hook
Analyze function/macro calls:
;; config.edn
{:hooks {:analyze-call {mylib/deprecated-fn hooks.my/warn-deprecated}}}
;; hooks/my.clj
(ns hooks.my
(:require [clj-kondo.hooks-api :as api]))
(defn warn-deprecated [{:keys [node]}]
{:findings [{:message "This function is deprecated"
:type :deprecated-api
:row (api/row node)
:col (api/col node)
:level :warning}]})
macroexpand Hook
Transform macro for analysis:
;; config.edn
{:hooks {:macroexpand {mylib/defentity hooks.my/expand-defentity}}}
;; hooks/my.clj
(defn expand-defentity [{:keys [node]}]
(let [[_ name & body] (:children node)]
{:node (api/list-node
[(api/token-node 'def)
name
(api/map-node body)])}))
Hook API - Node Functions
;; Query
(api/tag node) ; :list, :vector, :map, :token, etc.
(api/sexpr node) ; Convert to s-expression
(:children node) ; Get child nodes
(api/string node) ; String representation
;; Position
(api/row node)
(api/col node)
(api/end-row node)
(api/end-col node)
;; Predicates
(api/token-node? node)
(api/keyword-node? node)
(api/string-node? node)
(api/list-node? node)
(api/vector-node? node)
(api/map-node? node)
Hook API - Node Constructors
;; Atoms
(api/token-node 'symbol)
(api/keyword-node :keyword)
(api/string-node "string")
(api/number-node 42)
;; Collections
(api/list-node [node1 node2 node3])
(api/vector-node [node1 node2])
(api/map-node [k1 v1 k2 v2])
(api/set-node [node1 node2])
Hook Return Values
;; Return findings
{:findings [{:message "Error message"
:type :my-custom-type
:row (api/row node)
:col (api/col node)
:level :warning}]}
;; Return transformed node
{:node new-node}
;; Return both
{:node new-node
:findings [{:message "..." ...}]}
Common Hook Patterns
Deprecation Warning
(defn deprecation [{:keys [node]}]
{:findings [{:message "Use new-api instead of old-api"
:type :deprecated
:row (api/row node)
:col (api/col node)
:level :warning}]})
Argument Count Validation
(defn validate-arity [{:keys [node]}]
(let [args (rest (:children node))]
(when (< (count args) 2)
{:findings [{:message "Expected at least 2 arguments"
:type :invalid-arity
:row (api/row node)
:col (api/col node)
:level :error}]})))
Argument Type Validation
(defn validate-type [{:keys [node]}]
(let [first-arg (second (:children node))]
(when-not (api/keyword-node? first-arg)
{:findings [{:message "First argument must be a keyword"
:type :invalid-argument-type
:row (api/row node)
:col (api/col node)
:level :error}]})))
Required Map Keys
(defn validate-keys [{:keys [node]}]
(let [map-node (second (:children node))]
(when (api/map-node? map-node)
(let [keys (->> (:children map-node)
(take-nth 2)
(map api/sexpr)
(set))
required #{:host :port}
missing (clojure.set/difference required keys)]
(when (seq missing)
{:findings [{:message (str "Missing keys: " missing)
:type :missing-keys
:row (api/row node)
:col (api/col node)
:level :error}]})))))
Macro Expansion for DSL
(defn expand-defroute [{:keys [node]}]
(let [[_ method path handler] (:children node)]
{:node (api/list-node
[(api/token-node 'def)
(api/token-node (gensym "route"))
(api/map-node [method path handler])])}))
IDE Integration
VS Code (Calva)
Built-in support, works automatically.
Emacs
(use-package flycheck-clj-kondo :ensure t)
Vim/Neovim (ALE)
let g:ale_linters = {'clojure': ['clj-kondo']}
IntelliJ/Cursive
Native integration via Preferences → Editor → Inspections.
CI/CD Patterns
GitHub Actions
- name: Install clj-kondo
run: |
curl -sLO https://raw.githubusercontent.com/clj-kondo/clj-kondo/master/script/install-clj-kondo
chmod +x install-clj-kondo
./install-clj-kondo
- name: Run clj-kondo
run: clj-kondo --lint src test
GitLab CI
lint:
image: cljkondo/clj-kondo:latest
script:
- clj-kondo --lint src test
Pre-commit Hook
#!/bin/bash
clj-kondo --lint src test
Troubleshooting
False Positive - Macro Generated Symbol
{:lint-as {myapp/my-macro clojure.core/let}}
Exclude Generated Files
{:output {:exclude-files ["generated/" "target/" "node_modules/"]}}
Hook Not Triggering
- Check hook registration in config.edn
- Verify namespace path matches
- Test with minimal example
- Run with
--debugflag
Performance Issues
# Cache dependencies once
clj-kondo --lint "$(clojure -Spath)" --dependencies --parallel
# Exclude large directories
{:output {:exclude-files ["resources/public/"]}}
Tips
- Start with zero config - defaults are good
- Use
--copy-configsto get library-specific rules - Write hooks for domain-specific linting
- Test hooks with minimal examples
- Document why you suppress warnings
- Run in CI to catch issues early
- Cache dependency analysis for speed