Files
gh-hugoduncan-library-skill…/skills/clj-kondo/QUICK_REFERENCE.md
2025-11-29 18:47:15 +08:00

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