363 lines
7.9 KiB
Markdown
363 lines
7.9 KiB
Markdown
# clj-kondo Quick Reference
|
|
|
|
## Command Line
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```clojure
|
|
{: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
|
|
|
|
```clojure
|
|
:off ; Disable
|
|
:info ; Informational
|
|
:warning ; Warning (default)
|
|
:error ; Error (fails build)
|
|
```
|
|
|
|
## Common Linter Configurations
|
|
|
|
### Disable Specific Linters
|
|
|
|
```clojure
|
|
{:linters {:unused-binding {:level :off}
|
|
:unused-private-var {:level :off}}}
|
|
```
|
|
|
|
### Exclude Namespaces
|
|
|
|
```clojure
|
|
{:linters {:unused-binding {:exclude-ns [myapp.test-helpers
|
|
myapp.dev]}}}
|
|
```
|
|
|
|
### Consistent Aliases
|
|
|
|
```clojure
|
|
{:linters {:consistent-alias {:level :warning
|
|
:aliases {clojure.string str
|
|
clojure.set set
|
|
clojure.java.io io}}}}
|
|
```
|
|
|
|
### Refer :all
|
|
|
|
```clojure
|
|
{:linters {:refer-all {:level :warning
|
|
:exclude [clojure.test]}}}
|
|
```
|
|
|
|
### Macro as Function
|
|
|
|
```clojure
|
|
{:lint-as {myapp/my-macro clojure.core/let
|
|
myapp/defroute compojure.core/defroutes}}
|
|
```
|
|
|
|
## Inline Suppressions
|
|
|
|
```clojure
|
|
;; 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:
|
|
|
|
```clojure
|
|
;; 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:
|
|
|
|
```clojure
|
|
;; 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
|
|
|
|
```clojure
|
|
;; 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
|
|
|
|
```clojure
|
|
;; 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
|
|
|
|
```clojure
|
|
;; 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
|
|
|
|
```clojure
|
|
(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
|
|
|
|
```clojure
|
|
(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
|
|
|
|
```clojure
|
|
(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
|
|
|
|
```clojure
|
|
(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
|
|
|
|
```clojure
|
|
(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
|
|
```elisp
|
|
(use-package flycheck-clj-kondo :ensure t)
|
|
```
|
|
|
|
### Vim/Neovim (ALE)
|
|
```vim
|
|
let g:ale_linters = {'clojure': ['clj-kondo']}
|
|
```
|
|
|
|
### IntelliJ/Cursive
|
|
Native integration via Preferences → Editor → Inspections.
|
|
|
|
## CI/CD Patterns
|
|
|
|
### GitHub Actions
|
|
|
|
```yaml
|
|
- 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
|
|
|
|
```yaml
|
|
lint:
|
|
image: cljkondo/clj-kondo:latest
|
|
script:
|
|
- clj-kondo --lint src test
|
|
```
|
|
|
|
### Pre-commit Hook
|
|
|
|
```bash
|
|
#!/bin/bash
|
|
clj-kondo --lint src test
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### False Positive - Macro Generated Symbol
|
|
|
|
```clojure
|
|
{:lint-as {myapp/my-macro clojure.core/let}}
|
|
```
|
|
|
|
### Exclude Generated Files
|
|
|
|
```clojure
|
|
{:output {:exclude-files ["generated/" "target/" "node_modules/"]}}
|
|
```
|
|
|
|
### Hook Not Triggering
|
|
|
|
1. Check hook registration in config.edn
|
|
2. Verify namespace path matches
|
|
3. Test with minimal example
|
|
4. Run with `--debug` flag
|
|
|
|
### Performance Issues
|
|
|
|
```bash
|
|
# 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-configs` to 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
|