pedant
A fast, opinionated Rust linter for keeping AI-generated code clean.
The Problem
LLMs write code that compiles but violates best practices. Common patterns:
- Nested conditionals —
ifinsideif,matchinsidematch - Panic-prone code —
.unwrap(),.expect(),todo!() - Silenced warnings —
#[allow(dead_code)],#[allow(clippy::*)] - Lazy cloning —
.clone()to satisfy the borrow checker - Debug artifacts —
dbg!(),println!()left in production code - Mixed concerns — unrelated types dumped into a single file
These patterns pass cargo check and often slip through code review. pedant catches them.
Why pedant?
Built for LLM workflows. Designed to validate AI-generated code before it enters your codebase:
- Stdin support — pipe generated code directly:
echo "$code" | pedant --stdin - JSON output — structured violations for automated pipelines
- Exit codes —
0clean,1violations,2error - Fast — single-pass AST analysis, no type inference
Catches what Clippy misses. Clippy focuses on correctness. pedant enforces taste:
- Configurable nesting depth limits
- Branch-in-branch detection (all combinations of
if/match) - Else-chain detection for long
if/else ifsequences - Mixed concerns via type-graph connectivity analysis
- Per-path overrides for tests and generated code
Installation
Usage
# Check files
# Validate LLM output
|
# CI integration
if ! ; then
fi
LLM Integration
Pre-commit validation
#!/bin/bash
In automated pipelines
# Validate before accepting generated code
if [; then
# feed violations back to LLM for correction
fi
Checks
| Check | Flag to disable | Description |
|---|---|---|
max-depth |
-d N |
Nesting exceeds limit (default: 3) |
nested-if |
--no-nested-if |
if inside if |
if-in-match |
--no-if-in-match |
if inside match arm |
nested-match |
--no-nested-match |
match inside match |
match-in-if |
--no-match-in-if |
match inside if |
else-chain |
--no-else-chain |
Long if/else if chains (3+) |
Pattern checks (config file only)
| Check | Config key | Description |
|---|---|---|
forbidden-attribute |
forbid_attributes |
Banned attributes (e.g., allow(dead_code)) |
forbidden-type |
forbid_types |
Banned type patterns (e.g., Arc<String>) |
forbidden-call |
forbid_calls |
Banned method calls (e.g., .unwrap()) |
forbidden-macro |
forbid_macros |
Banned macros (e.g., panic!, dbg!) |
forbidden-else |
forbid_else |
Use of else keyword |
forbidden-unsafe |
forbid_unsafe |
Use of unsafe blocks |
Performance and dispatch checks (off by default)
| Check | Config key | Description |
|---|---|---|
dyn-return |
check_dyn_return |
Box<dyn T> / Arc<dyn T> in return types |
dyn-param |
check_dyn_param |
&dyn T / Box<dyn T> in function parameters |
vec-box-dyn |
check_vec_box_dyn |
Vec<Box<dyn T>> anywhere |
dyn-field |
check_dyn_field |
Box<dyn T> / Arc<dyn T> in struct fields |
clone-in-loop |
check_clone_in_loop |
.clone() inside loop bodies |
default-hasher |
check_default_hasher |
HashMap/HashSet with default SipHash |
Structure checks (off by default)
| Check | Config key | Description |
|---|---|---|
mixed-concerns |
check_mixed_concerns |
Disconnected type groups in a single file |
Use pedant --explain <CHECK> for detailed rationale and fix guidance.
Configuration
Optional .pedant.toml in project root:
= 3
= true
= true
= true
= true
= true
= 3
# Pattern checks — disabled by default, enable with patterns
[]
= true
= ["allow(dead_code)", "allow(clippy::*)"]
[]
= true
= ["Arc<String>", "Arc<Vec<*>>", "Box<dyn*Error*>"]
[]
= true
= [".unwrap()", ".expect(*)", ".clone()"]
[]
= true
= ["panic!", "todo!", "dbg!", "println!"]
= false
= true
# Performance and dispatch checks — off by default
= false
= false
= false
= false
= false
= false
# Structure checks — off by default
= false
[]
= 4
[]
= false
Exit Codes
0- No violations1- Violations found2- Error (file not found, parse error)