Initial commit
This commit is contained in:
905
skills/clj-kondo/SKILL.md
Normal file
905
skills/clj-kondo/SKILL.md
Normal file
@@ -0,0 +1,905 @@
|
||||
---
|
||||
name: clj-kondo
|
||||
description: A guide to using clj-kondo for Clojure code linting, including configuration, built-in linters, and writing custom hooks.
|
||||
---
|
||||
|
||||
# clj-kondo Skill Guide
|
||||
|
||||
A comprehensive guide to using clj-kondo for Clojure code linting, including configuration, built-in linters, and writing custom hooks.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Introduction](#introduction)
|
||||
2. [Installation](#installation)
|
||||
3. [Getting Started](#getting-started)
|
||||
4. [Configuration](#configuration)
|
||||
5. [Built-in Linters](#built-in-linters)
|
||||
6. [Custom Hooks](#custom-hooks)
|
||||
7. [IDE Integration](#ide-integration)
|
||||
8. [CI/CD Integration](#cicd-integration)
|
||||
9. [Best Practices](#best-practices)
|
||||
10. [Troubleshooting](#troubleshooting)
|
||||
|
||||
## Introduction
|
||||
|
||||
### What is clj-kondo?
|
||||
|
||||
clj-kondo is a fast, static analyzer and linter for Clojure code. It:
|
||||
|
||||
- Catches syntax errors and common mistakes
|
||||
- Enforces code style and best practices
|
||||
- Provides immediate feedback during development
|
||||
- Supports custom linting rules via hooks
|
||||
- Integrates with all major editors and CI systems
|
||||
- Requires no project dependencies or runtime
|
||||
|
||||
### Why Use clj-kondo?
|
||||
|
||||
- **Fast**: Native binary with instant startup
|
||||
- **Accurate**: Deep understanding of Clojure semantics
|
||||
- **Extensible**: Custom hooks for domain-specific rules
|
||||
- **Zero configuration**: Works out of the box
|
||||
- **Cross-platform**: Native binaries for Linux, macOS, Windows
|
||||
- **IDE integration**: Works with Emacs, VS Code, IntelliJ, Vim, etc.
|
||||
|
||||
## Installation
|
||||
|
||||
### macOS/Linux (Homebrew)
|
||||
|
||||
```bash
|
||||
brew install clj-kondo/brew/clj-kondo
|
||||
```
|
||||
|
||||
### Manual Binary Installation
|
||||
|
||||
Download from [GitHub Releases](https://github.com/clj-kondo/clj-kondo/releases):
|
||||
|
||||
```bash
|
||||
# Linux
|
||||
curl -sLO https://raw.githubusercontent.com/clj-kondo/clj-kondo/master/script/install-clj-kondo
|
||||
chmod +x install-clj-kondo
|
||||
./install-clj-kondo
|
||||
|
||||
# Place in PATH
|
||||
sudo mv clj-kondo /usr/local/bin/
|
||||
```
|
||||
|
||||
### Via Clojure CLI
|
||||
|
||||
```bash
|
||||
clojure -Ttools install-latest :lib io.github.clj-kondo/clj-kondo :as clj-kondo
|
||||
clojure -Tclj-kondo run :lint '"src"'
|
||||
```
|
||||
|
||||
### Verify Installation
|
||||
|
||||
```bash
|
||||
clj-kondo --version
|
||||
# clj-kondo v2024.11.14
|
||||
```
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Basic Usage
|
||||
|
||||
Lint a single file:
|
||||
|
||||
```bash
|
||||
clj-kondo --lint src/myapp/core.clj
|
||||
```
|
||||
|
||||
Lint a directory:
|
||||
|
||||
```bash
|
||||
clj-kondo --lint src
|
||||
```
|
||||
|
||||
Lint multiple paths:
|
||||
|
||||
```bash
|
||||
clj-kondo --lint src test
|
||||
```
|
||||
|
||||
### Understanding Output
|
||||
|
||||
```
|
||||
src/myapp/core.clj:12:3: warning: unused binding x
|
||||
src/myapp/core.clj:25:1: error: duplicate key :name
|
||||
linting took 23ms, errors: 1, warnings: 1
|
||||
```
|
||||
|
||||
Format: `file:line:column: level: message`
|
||||
|
||||
### Output Formats
|
||||
|
||||
**Human-readable (default):**
|
||||
```bash
|
||||
clj-kondo --lint src
|
||||
```
|
||||
|
||||
**JSON (for tooling):**
|
||||
```bash
|
||||
clj-kondo --lint src --config '{:output {:format :json}}'
|
||||
```
|
||||
|
||||
**EDN:**
|
||||
```bash
|
||||
clj-kondo --lint src --config '{:output {:format :edn}}'
|
||||
```
|
||||
|
||||
### Creating Cache
|
||||
|
||||
For better performance on subsequent runs:
|
||||
|
||||
```bash
|
||||
clj-kondo --lint "$(clojure -Spath)" --dependencies --parallel --copy-configs
|
||||
```
|
||||
|
||||
This caches analysis of dependencies and copies their configurations.
|
||||
|
||||
## Configuration
|
||||
|
||||
### Configuration File Location
|
||||
|
||||
clj-kondo looks for `.clj-kondo/config.edn` in:
|
||||
1. Current directory
|
||||
2. Parent directories (walking up)
|
||||
3. Home directory (`~/.config/clj-kondo/config.edn`)
|
||||
|
||||
### Basic Configuration
|
||||
|
||||
`.clj-kondo/config.edn`:
|
||||
|
||||
```clojure
|
||||
{:linters {:unused-binding {:level :warning}
|
||||
:unused-namespace {:level :warning}
|
||||
:unresolved-symbol {:level :error}
|
||||
:invalid-arity {:level :error}}
|
||||
:output {:pattern "{{LEVEL}} {{filename}}:{{row}}:{{col}} {{message}}"}}
|
||||
```
|
||||
|
||||
### Linter Levels
|
||||
|
||||
- `:off` - Disable the linter
|
||||
- `:info` - Informational message
|
||||
- `:warning` - Warning (default for most)
|
||||
- `:error` - Error (fails build)
|
||||
|
||||
### Global Configuration
|
||||
|
||||
Disable specific linters:
|
||||
|
||||
```clojure
|
||||
{:linters {:unused-binding {:level :off}}}
|
||||
```
|
||||
|
||||
Configure linter options:
|
||||
|
||||
```clojure
|
||||
{:linters {:consistent-alias {:aliases {clojure.string str
|
||||
clojure.set set}}}}
|
||||
```
|
||||
|
||||
### Local Configuration
|
||||
|
||||
Suppress warnings in specific namespaces:
|
||||
|
||||
```clojure
|
||||
{:linters {:unused-binding {:level :off
|
||||
:exclude-ns [myapp.test-helpers]}}}
|
||||
```
|
||||
|
||||
### Inline Configuration
|
||||
|
||||
In source files:
|
||||
|
||||
```clojure
|
||||
;; Disable for entire namespace
|
||||
(ns myapp.core
|
||||
{:clj-kondo/config '{:linters {:unused-binding {:level :off}}}})
|
||||
|
||||
;; Disable for specific form
|
||||
#_{:clj-kondo/ignore [:unused-binding]}
|
||||
(let [x 1] 2)
|
||||
|
||||
;; Disable all linters for form
|
||||
#_{:clj-kondo/ignore true}
|
||||
(some-legacy-code)
|
||||
```
|
||||
|
||||
### Configuration Merging
|
||||
|
||||
Configurations merge in this order:
|
||||
1. Built-in defaults
|
||||
2. Home directory config
|
||||
3. Project config (`.clj-kondo/config.edn`)
|
||||
4. Inline metadata
|
||||
|
||||
## Built-in Linters
|
||||
|
||||
### Namespace and Require Linters
|
||||
|
||||
**`:unused-namespace`** - Warns about unused required namespaces
|
||||
|
||||
```clojure
|
||||
(ns myapp.core
|
||||
(:require [clojure.string :as str])) ;; Warning if 'str' never used
|
||||
|
||||
;; Fix: Remove unused require
|
||||
```
|
||||
|
||||
**`:unsorted-required-namespaces`** - Enforces sorted requires
|
||||
|
||||
```clojure
|
||||
{:linters {:unsorted-required-namespaces {:level :warning}}}
|
||||
```
|
||||
|
||||
**`:namespace-name-mismatch`** - Ensures namespace matches file path
|
||||
|
||||
```clojure
|
||||
;; In src/myapp/utils.clj
|
||||
(ns myapp.helpers) ;; Error: should be myapp.utils
|
||||
```
|
||||
|
||||
### Binding and Symbol Linters
|
||||
|
||||
**`:unused-binding`** - Warns about unused let bindings
|
||||
|
||||
```clojure
|
||||
(let [x 1
|
||||
y 2] ;; Warning: y is unused
|
||||
x)
|
||||
|
||||
;; Fix: Remove or prefix with underscore
|
||||
(let [x 1
|
||||
_y 2]
|
||||
x)
|
||||
```
|
||||
|
||||
**`:unresolved-symbol`** - Catches typos and undefined symbols
|
||||
|
||||
```clojure
|
||||
(defn foo []
|
||||
(bar)) ;; Error: unresolved symbol bar
|
||||
|
||||
;; Fix: Define bar or require it
|
||||
```
|
||||
|
||||
**`:unused-private-var`** - Warns about unused private definitions
|
||||
|
||||
```clojure
|
||||
(defn- helper []) ;; Warning if never called
|
||||
|
||||
;; Fix: Remove or use it
|
||||
```
|
||||
|
||||
### Function and Arity Linters
|
||||
|
||||
**`:invalid-arity`** - Catches incorrect function call arities
|
||||
|
||||
```clojure
|
||||
(defn add [a b] (+ a b))
|
||||
(add 1) ;; Error: wrong arity, expected 2 args
|
||||
|
||||
;; Fix: Provide correct number of arguments
|
||||
```
|
||||
|
||||
**`:missing-body-in-when`** - Warns about empty when blocks
|
||||
|
||||
```clojure
|
||||
(when condition) ;; Warning: missing body
|
||||
|
||||
;; Fix: Add body or use when-not/if
|
||||
```
|
||||
|
||||
### Collection and Syntax Linters
|
||||
|
||||
**`:duplicate-map-key`** - Catches duplicate keys in maps
|
||||
|
||||
```clojure
|
||||
{:name "Alice"
|
||||
:age 30
|
||||
:name "Bob"} ;; Error: duplicate key :name
|
||||
```
|
||||
|
||||
**`:duplicate-set-key`** - Catches duplicate values in sets
|
||||
|
||||
```clojure
|
||||
#{1 2 1} ;; Error: duplicate set element
|
||||
```
|
||||
|
||||
**`:misplaced-docstring`** - Warns about incorrectly placed docstrings
|
||||
|
||||
```clojure
|
||||
(defn foo
|
||||
[x]
|
||||
"This is wrong" ;; Warning: docstring after params
|
||||
x)
|
||||
|
||||
;; Fix: Place before params
|
||||
(defn foo
|
||||
"This is correct"
|
||||
[x]
|
||||
x)
|
||||
```
|
||||
|
||||
### Type and Spec Linters
|
||||
|
||||
**`:type-mismatch`** - Basic type checking
|
||||
|
||||
```clojure
|
||||
(inc "string") ;; Warning: expected number
|
||||
```
|
||||
|
||||
**`:invalid-arities`** - Checks arities for core functions
|
||||
|
||||
```clojure
|
||||
(map) ;; Error: map requires at least 2 arguments
|
||||
```
|
||||
|
||||
## Custom Hooks
|
||||
|
||||
### What Are Hooks?
|
||||
|
||||
Hooks are custom linting rules written in Clojure that analyze your code using clj-kondo's analysis data. They enable:
|
||||
|
||||
- Domain-specific linting rules
|
||||
- API usage validation
|
||||
- Deprecation warnings
|
||||
- Team convention enforcement
|
||||
- Advanced static analysis
|
||||
|
||||
### When to Use Hooks
|
||||
|
||||
Use hooks when:
|
||||
- Built-in linters don't cover your needs
|
||||
- You have library-specific patterns to enforce
|
||||
- You want to warn about deprecated APIs
|
||||
- You need to validate domain-specific logic
|
||||
- You want to enforce team coding standards
|
||||
|
||||
### Hook Architecture
|
||||
|
||||
Hooks receive:
|
||||
1. **Analysis context** - Information about the code being analyzed
|
||||
2. **Node** - The AST node being examined
|
||||
|
||||
Hooks return:
|
||||
1. **Findings** - Lint warnings/errors to report
|
||||
2. **Updated analysis** - Modified context for downstream analysis
|
||||
|
||||
### Creating Your First Hook
|
||||
|
||||
#### 1. Hook Directory Structure
|
||||
|
||||
```
|
||||
.clj-kondo/
|
||||
config.edn
|
||||
hooks/
|
||||
my_hooks.clj
|
||||
```
|
||||
|
||||
#### 2. Basic Hook Template
|
||||
|
||||
`.clj-kondo/hooks/my_hooks.clj`:
|
||||
|
||||
```clojure
|
||||
(ns hooks.my-hooks
|
||||
(:require [clj-kondo.hooks-api :as api]))
|
||||
|
||||
(defn my-hook
|
||||
"Description of what this hook does"
|
||||
[{:keys [node]}]
|
||||
(let [sexpr (api/sexpr node)]
|
||||
(when (some-condition? sexpr)
|
||||
{:findings [{:message "Custom warning message"
|
||||
:type :my-custom-warning
|
||||
:row (api/row node)
|
||||
:col (api/col node)}]})))
|
||||
```
|
||||
|
||||
#### 3. Register Hook
|
||||
|
||||
`.clj-kondo/config.edn`:
|
||||
|
||||
```clojure
|
||||
{:hooks {:analyze-call {my.ns/my-macro hooks.my-hooks/my-hook}}}
|
||||
```
|
||||
|
||||
### Hook Types
|
||||
|
||||
#### `:analyze-call` Hooks
|
||||
|
||||
Triggered when analyzing function/macro calls:
|
||||
|
||||
```clojure
|
||||
;; Hook for analyzing (deprecated-fn ...) calls
|
||||
{:hooks {:analyze-call {my.api/deprecated-fn hooks.deprecation/check}}}
|
||||
```
|
||||
|
||||
Hook implementation:
|
||||
|
||||
```clojure
|
||||
(defn check [{:keys [node]}]
|
||||
{:findings [{:message "my.api/deprecated-fn is deprecated, use new-fn instead"
|
||||
:type :deprecated-api
|
||||
:row (api/row node)
|
||||
:col (api/col node)
|
||||
:level :warning}]})
|
||||
```
|
||||
|
||||
#### `:macroexpand` Hooks
|
||||
|
||||
Transform macro calls for better analysis:
|
||||
|
||||
```clojure
|
||||
;; For macros that expand to def forms
|
||||
{:hooks {:macroexpand {my.dsl/defentity hooks.dsl/expand-defentity}}}
|
||||
```
|
||||
|
||||
Hook implementation:
|
||||
|
||||
```clojure
|
||||
(defn expand-defentity [{:keys [node]}]
|
||||
(let [[_ name-node & body] (rest (:children node))
|
||||
new-node (api/list-node
|
||||
[(api/token-node 'def)
|
||||
name-node
|
||||
(api/map-node body)])]
|
||||
{:node new-node}))
|
||||
```
|
||||
|
||||
### Hook API Reference
|
||||
|
||||
#### Node Functions
|
||||
|
||||
```clojure
|
||||
;; Get node type
|
||||
(api/tag node) ;; => :list, :vector, :map, :token, etc.
|
||||
|
||||
;; Get children nodes
|
||||
(api/children node)
|
||||
|
||||
;; Convert node to s-expression
|
||||
(api/sexpr node)
|
||||
|
||||
;; Get position
|
||||
(api/row node)
|
||||
(api/col node)
|
||||
(api/end-row node)
|
||||
(api/end-col node)
|
||||
|
||||
;; String representation
|
||||
(api/string node)
|
||||
```
|
||||
|
||||
#### Node Constructors
|
||||
|
||||
```clojure
|
||||
;; Create nodes
|
||||
(api/token-node 'symbol)
|
||||
(api/keyword-node :keyword)
|
||||
(api/string-node "string")
|
||||
(api/number-node 42)
|
||||
|
||||
(api/list-node [node1 node2 node3])
|
||||
(api/vector-node [node1 node2])
|
||||
(api/map-node [key-node val-node key-node val-node])
|
||||
(api/set-node [node1 node2])
|
||||
```
|
||||
|
||||
#### Node Predicates
|
||||
|
||||
```clojure
|
||||
(api/token-node? node)
|
||||
(api/keyword-node? node)
|
||||
(api/string-node? node)
|
||||
(api/list-node? node)
|
||||
(api/vector-node? node)
|
||||
(api/map-node? node)
|
||||
```
|
||||
|
||||
### Practical Hook Examples
|
||||
|
||||
#### Example 1: Deprecation Warning
|
||||
|
||||
Warn about deprecated function usage:
|
||||
|
||||
```clojure
|
||||
(ns hooks.deprecation
|
||||
(:require [clj-kondo.hooks-api :as api]))
|
||||
|
||||
(defn warn-deprecated-fn [{:keys [node]}]
|
||||
{:findings [{:message "old-api is deprecated. Use new-api instead."
|
||||
:type :deprecated-function
|
||||
:row (api/row node)
|
||||
:col (api/col node)
|
||||
:level :warning}]})
|
||||
```
|
||||
|
||||
Config:
|
||||
|
||||
```clojure
|
||||
{:hooks {:analyze-call {mylib/old-api hooks.deprecation/warn-deprecated-fn}}}
|
||||
```
|
||||
|
||||
#### Example 2: Argument Validation
|
||||
|
||||
Ensure specific argument types:
|
||||
|
||||
```clojure
|
||||
(ns hooks.validation
|
||||
(:require [clj-kondo.hooks-api :as api]))
|
||||
|
||||
(defn validate-query-args [{:keys [node]}]
|
||||
(let [args (rest (:children node))
|
||||
first-arg (first args)]
|
||||
(when-not (and first-arg (api/keyword-node? first-arg))
|
||||
{:findings [{:message "First argument to query must be a keyword"
|
||||
:type :invalid-argument
|
||||
:row (api/row node)
|
||||
:col (api/col node)
|
||||
:level :error}]})))
|
||||
```
|
||||
|
||||
Config:
|
||||
|
||||
```clojure
|
||||
{:hooks {:analyze-call {mylib/query hooks.validation/validate-query-args}}}
|
||||
```
|
||||
|
||||
#### Example 3: DSL Expansion
|
||||
|
||||
Expand custom DSL for better analysis:
|
||||
|
||||
```clojure
|
||||
(ns hooks.dsl
|
||||
(:require [clj-kondo.hooks-api :as api]))
|
||||
|
||||
(defn expand-defrequest
|
||||
"Expand (defrequest name & body) to (def name (request & body))"
|
||||
[{:keys [node]}]
|
||||
(let [[_ name-node & body-nodes] (:children node)
|
||||
request-call (api/list-node
|
||||
(list* (api/token-node 'request)
|
||||
body-nodes))
|
||||
expanded (api/list-node
|
||||
[(api/token-node 'def)
|
||||
name-node
|
||||
request-call])]
|
||||
{:node expanded}))
|
||||
```
|
||||
|
||||
Config:
|
||||
|
||||
```clojure
|
||||
{:hooks {:macroexpand {myapp.http/defrequest hooks.dsl/expand-defrequest}}}
|
||||
```
|
||||
|
||||
#### Example 4: Thread-Safety Check
|
||||
|
||||
Warn about unsafe concurrent usage:
|
||||
|
||||
```clojure
|
||||
(ns hooks.concurrency
|
||||
(:require [clj-kondo.hooks-api :as api]))
|
||||
|
||||
(defn check-atom-swap [{:keys [node]}]
|
||||
(let [args (rest (:children node))
|
||||
fn-arg (second args)]
|
||||
(when (and fn-arg
|
||||
(api/list-node? fn-arg)
|
||||
(= 'fn (api/sexpr (first (:children fn-arg)))))
|
||||
{:findings [{:message "Consider using swap-vals! for atomicity"
|
||||
:type :concurrency-hint
|
||||
:row (api/row node)
|
||||
:col (api/col node)
|
||||
:level :info}]})))
|
||||
```
|
||||
|
||||
#### Example 5: Required Keys Validation
|
||||
|
||||
Ensure maps have required keys:
|
||||
|
||||
```clojure
|
||||
(ns hooks.maps
|
||||
(:require [clj-kondo.hooks-api :as api]))
|
||||
|
||||
(defn validate-config-keys [{:keys [node]}]
|
||||
(let [args (rest (:children node))
|
||||
config-map (first args)]
|
||||
(when (api/map-node? config-map)
|
||||
(let [keys (->> (:children config-map)
|
||||
(take-nth 2)
|
||||
(map api/sexpr)
|
||||
(set))
|
||||
required #{:host :port :timeout}
|
||||
missing (clojure.set/difference required keys)]
|
||||
(when (seq missing)
|
||||
{:findings [{:message (str "Missing required keys: " missing)
|
||||
:type :missing-config-keys
|
||||
:row (api/row node)
|
||||
:col (api/col node)
|
||||
:level :error}]})))))
|
||||
```
|
||||
|
||||
### Testing Hooks
|
||||
|
||||
#### Manual Testing
|
||||
|
||||
1. Create test file:
|
||||
|
||||
```clojure
|
||||
;; test-hook.clj
|
||||
(ns test-hook
|
||||
(:require [mylib :as lib]))
|
||||
|
||||
(lib/deprecated-fn) ;; Should trigger warning
|
||||
```
|
||||
|
||||
2. Run clj-kondo:
|
||||
|
||||
```bash
|
||||
clj-kondo --lint test-hook.clj
|
||||
```
|
||||
|
||||
#### Unit Testing Hooks
|
||||
|
||||
Use `clj-kondo.core` for testing:
|
||||
|
||||
```clojure
|
||||
(ns hooks.my-hooks-test
|
||||
(:require [clojure.test :refer [deftest is testing]]
|
||||
[clj-kondo.core :as clj-kondo]))
|
||||
|
||||
(deftest test-my-hook
|
||||
(testing "detects deprecated function usage"
|
||||
(let [result (with-in-str "(ns test) (mylib/old-api)"
|
||||
(clj-kondo/run!
|
||||
{:lint ["-"]
|
||||
:config {:hooks {:analyze-call
|
||||
{mylib/old-api
|
||||
hooks.deprecation/warn-deprecated-fn}}}}))]
|
||||
(is (= 1 (count (:findings result))))
|
||||
(is (= :deprecated-function
|
||||
(-> result :findings first :type))))))
|
||||
```
|
||||
|
||||
### Distributing Hooks
|
||||
|
||||
#### As Library Config
|
||||
|
||||
Include hooks with your library:
|
||||
|
||||
```
|
||||
my-library/
|
||||
.clj-kondo/
|
||||
config.edn # Hook registration
|
||||
hooks/
|
||||
my_library.clj # Hook implementations
|
||||
src/
|
||||
my_library/
|
||||
core.clj
|
||||
```
|
||||
|
||||
Users get hooks automatically via `--copy-configs`.
|
||||
|
||||
#### Standalone Hook Library
|
||||
|
||||
Create a dedicated hook library:
|
||||
|
||||
```clojure
|
||||
;; deps.edn
|
||||
{:paths ["."]
|
||||
:deps {clj-kondo/clj-kondo {:mvn/version "2024.11.14"}}}
|
||||
```
|
||||
|
||||
Users install via:
|
||||
|
||||
```bash
|
||||
clj-kondo --lint "$(clojure -Spath -Sdeps '{:deps {my/hooks {:git/url \"...\"}}}')" --dependencies --copy-configs
|
||||
```
|
||||
|
||||
### Hook Best Practices
|
||||
|
||||
1. **Performance**: Keep hooks fast - they run on every lint
|
||||
2. **Specificity**: Target specific forms, not every function call
|
||||
3. **Clear messages**: Provide actionable error messages
|
||||
4. **Documentation**: Document what hooks check and why
|
||||
5. **Testing**: Test hooks with various inputs
|
||||
6. **Versioning**: Version hooks with your library
|
||||
7. **Graceful degradation**: Handle malformed code gracefully
|
||||
|
||||
## IDE Integration
|
||||
|
||||
### VS Code
|
||||
|
||||
Install [Calva](https://marketplace.visualstudio.com/items?itemName=betterthantomorrow.calva):
|
||||
|
||||
- clj-kondo linting enabled by default
|
||||
- Real-time feedback as you type
|
||||
- Automatic `.clj-kondo` directory recognition
|
||||
|
||||
### Emacs
|
||||
|
||||
With `flycheck-clj-kondo`:
|
||||
|
||||
```elisp
|
||||
(use-package flycheck-clj-kondo
|
||||
:ensure t)
|
||||
```
|
||||
|
||||
### IntelliJ IDEA / Cursive
|
||||
|
||||
- Native clj-kondo integration
|
||||
- Configure via Preferences → Editor → Inspections
|
||||
|
||||
### Vim/Neovim
|
||||
|
||||
With ALE:
|
||||
|
||||
```vim
|
||||
let g:ale_linters = {'clojure': ['clj-kondo']}
|
||||
```
|
||||
|
||||
## CI/CD Integration
|
||||
|
||||
### GitHub Actions
|
||||
|
||||
```yaml
|
||||
name: Lint
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
clj-kondo:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- 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
|
||||
|
||||
`.git/hooks/pre-commit`:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
clj-kondo --lint src test
|
||||
exit $?
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Start with Defaults
|
||||
|
||||
Begin with zero configuration - clj-kondo's defaults catch most issues.
|
||||
|
||||
### 2. Gradual Adoption
|
||||
|
||||
For existing projects:
|
||||
|
||||
```bash
|
||||
# Generate baseline
|
||||
clj-kondo --lint src --config '{:output {:exclude-warnings true}}'
|
||||
|
||||
# Fix incrementally
|
||||
```
|
||||
|
||||
### 3. Team Configuration
|
||||
|
||||
Standardize via `.clj-kondo/config.edn`:
|
||||
|
||||
```clojure
|
||||
{:linters {:consistent-alias {:level :warning
|
||||
:aliases {clojure.string str
|
||||
clojure.set set}}}
|
||||
:output {:exclude-files ["generated/"]}}
|
||||
```
|
||||
|
||||
### 4. Leverage Hooks for Domain Logic
|
||||
|
||||
Write hooks for:
|
||||
- API deprecations
|
||||
- Team conventions
|
||||
- Domain-specific validations
|
||||
|
||||
### 5. Cache Dependencies
|
||||
|
||||
```bash
|
||||
# Run once after dep changes
|
||||
clj-kondo --lint "$(clojure -Spath)" --dependencies --parallel --copy-configs
|
||||
```
|
||||
|
||||
### 6. Ignore Thoughtfully
|
||||
|
||||
Prefer fixing over ignoring. When ignoring:
|
||||
|
||||
```clojure
|
||||
;; Document why
|
||||
#_{:clj-kondo/ignore [:unresolved-symbol]
|
||||
:reason "Macro generates this symbol"}
|
||||
(some-macro)
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### False Positives
|
||||
|
||||
**Unresolved symbol in macro:**
|
||||
|
||||
```clojure
|
||||
;; Add to config
|
||||
{:lint-as {myapp/my-macro clojure.core/let}}
|
||||
```
|
||||
|
||||
**Incorrect arity for variadic macro:**
|
||||
|
||||
Write a macroexpand hook (see Custom Hooks section).
|
||||
|
||||
### Performance Issues
|
||||
|
||||
**Slow linting:**
|
||||
|
||||
```bash
|
||||
# Cache dependencies
|
||||
clj-kondo --lint "$(clojure -Spath)" --dependencies --parallel
|
||||
|
||||
# Exclude large dirs
|
||||
{:output {:exclude-files ["node_modules/" "target/"]}}
|
||||
```
|
||||
|
||||
### Hook Debugging
|
||||
|
||||
**Hook not triggering:**
|
||||
|
||||
1. Check hook registration in config.edn
|
||||
2. Verify namespace matches
|
||||
3. Test with minimal example
|
||||
4. Check for typos in qualified symbols
|
||||
|
||||
**Hook errors:**
|
||||
|
||||
```bash
|
||||
# Run with debug output
|
||||
clj-kondo --lint src --debug
|
||||
```
|
||||
|
||||
### Configuration Not Loading
|
||||
|
||||
Check:
|
||||
1. File is named `.clj-kondo/config.edn` (note the dot)
|
||||
2. EDN syntax is valid
|
||||
3. File is in project root or parent directory
|
||||
|
||||
## Resources
|
||||
|
||||
- [Official Documentation](https://github.com/clj-kondo/clj-kondo/tree/master/doc)
|
||||
- [Hook Examples](https://github.com/clj-kondo/clj-kondo/tree/master/examples)
|
||||
- [Configuration Reference](https://github.com/clj-kondo/clj-kondo/blob/master/doc/config.md)
|
||||
- [Hooks API Reference](https://github.com/clj-kondo/clj-kondo/blob/master/doc/hooks.md)
|
||||
- [Linters Reference](https://github.com/clj-kondo/clj-kondo/blob/master/doc/linters.md)
|
||||
|
||||
## Summary
|
||||
|
||||
clj-kondo is an essential tool for Clojure development offering:
|
||||
- Immediate feedback on code quality
|
||||
- Extensive built-in linting rules
|
||||
- Powerful custom hooks for domain-specific rules
|
||||
- Seamless IDE and CI/CD integration
|
||||
- Zero-configuration operation with extensive customization options
|
||||
|
||||
Start with the defaults, customize as needed, and leverage hooks for your specific requirements.
|
||||
Reference in New Issue
Block a user