Introduction
Welcome to mdbook-lint, a fast and comprehensive markdown linter designed specifically for mdBook projects.
What is mdbook-lint
mdbook-lint is a command-line tool and mdBook preprocessor that helps you maintain high-quality markdown documentation by detecting common issues, enforcing consistent style, and providing mdBook-specific linting rules.
Key Features
- Fast Performance: Built in Rust for speed and efficiency
- Comprehensive Rule Set: 55 standard markdown rules, 18 mdBook-specific rules, and 10 content rules (83 total)
- Flexible Integration: Works as a standalone CLI tool or as an mdBook preprocessor
- Configurable: Customize rules and behavior through configuration files
- Zero Dependencies: Self-contained binary with no external dependencies
Why Use mdbook-lint
Documentation quality matters. Consistent, well-formatted markdown makes your documentation:
- More readable for contributors and users
- Easier to maintain across large documentation projects
- More professional in appearance and structure
- Less prone to rendering issues in mdBook
Getting Started
Ready to improve your documentation quality? Head over to the Installation guide to get started, or jump straight to Getting Started for a quick walkthrough.
Community and Support
mdbook-lint is open source and welcomes contributions. Visit our GitHub repository to:
- Report issues
- Request features
- Contribute code
- Browse the source
For development information, see our Contributing guide.
Acknowledgments
mdbook-lint builds on the excellent work of:
- markdownlint - The original Node.js markdown linter that defined the standard rule set (MD001-MD059)
- rumdl - A fast Rust markdown linter that inspired our implementation approach
We aim to be compatible with markdownlint's rule definitions while adding mdBook-specific functionality.
Installation
mdbook-lint can be installed through several methods depending on your needs.
Homebrew (macOS/Linux)
If you use Homebrew, you can install mdbook-lint from the tap:
brew tap joshrotenberg/brew
brew install mdbook-lint
From Crates.io
Install via Cargo from crates.io:
cargo install mdbook-lint
By default, this includes all rule sets:
- standard - 55 markdown syntax rules (MD001-MD060)
- mdbook - 18 mdBook-specific rules (MDBOOK001-MDBOOK025)
- content - 10 content quality rules (CONTENT001-CONTENT011)
To install without specific rule sets:
# Without content rules
cargo install mdbook-lint --no-default-features --features standard,mdbook,lsp
# Only standard markdown rules
cargo install mdbook-lint --no-default-features --features standard,lsp
From Source
To install the latest development version or contribute to the project:
git clone https://github.com/joshrotenberg/mdbook-lint.git
cd mdbook-lint
cargo install --path .
Pre-built Binaries
Pre-built binaries for common platforms are available on the GitHub releases page.
Download the appropriate binary for your platform and add it to your PATH.
Requirements
- Rust 1.88 or later (if building from source)
- No runtime dependencies required
Verification
After installation, verify that mdbook-lint is working correctly:
mdbook-lint --version
You should see output similar to:
mdbook-lint 0.1.0
Next Steps
Once installed, head to the Getting Started guide to learn how to use mdbook-lint with your projects.
Getting Started
This guide will walk you through using mdbook-lint for the first time.
Quick Start
The fastest way to get started is to run mdbook-lint on some markdown files:
# Lint a single file
mdbook-lint lint README.md
# Lint multiple files
mdbook-lint lint src/*.md docs/*.md
# Lint all markdown files in a directory
mdbook-lint lint .
# Auto-fix violations where possible
mdbook-lint lint --fix src/*.md
Understanding the Output
When mdbook-lint finds issues, it will display them like this:
README.md:15:1: MD013: Line length (line too long, 85 > 80)
src/intro.md:3:1: MD001: Heading levels should only increment by one level at a time
Each line shows:
- File and location:
filename:line:column - Rule ID: The specific rule that was violated (e.g., MD013)
- Description: What the issue is and how to fix it
Your First Configuration
Create a .mdbook-lint.toml file in your project root:
# Fail the build on warnings
fail-on-warnings = true
# Disable rules that don't fit your project
disabled-rules = ["MD013"] # Allow long lines
# Configure specific rules
[MD007]
indent = 4 # Use 4-space indentation for lists
Using with mdBook
To integrate mdbook-lint with your mdBook project:
-
Add to book.toml:
[preprocessor.mdbook-lint] -
Build your book:
mdbook build
mdbook-lint will now check your markdown files every time you build your book.
Choosing Your Integration: You can run mdbook-lint either as an mdBook preprocessor (shown above) OR as a standalone tool in CI. See CI vs Preprocessor to understand when to use each approach.
Automatic Fixing
mdbook-lint can automatically fix some common violations:
# Fix violations automatically
mdbook-lint lint --fix docs/
# Preview what would be fixed without applying changes
mdbook-lint lint --fix --dry-run docs/
# Apply all fixes, including potentially risky ones
mdbook-lint lint --fix-unsafe docs/
Currently supported fixes include:
- MD009: Trailing spaces
- MD010: Hard tabs → spaces
- MD012: Multiple blank lines
- MD018/MD019: Heading spacing issues
- MD022: Blank lines around headings
- MD023: Indented headings
- MD027: Blockquote spacing
- MD030: List marker spacing
- MD034: Bare URLs
- MD047: Missing trailing newline
- And more!
Common Workflow
Here's a typical workflow for using mdbook-lint:
- Initial setup: Add configuration file and run first lint
- Auto-fix simple issues: Use
--fixto handle common problems - Fix remaining issues: Address structural problems manually
- Customize rules: Disable rules that don't fit your style
- Integrate with build: Add to mdBook or CI pipeline
- Maintain quality: Regular linting keeps documentation clean
Exploring Rules
To see all available rules:
# List all rules
mdbook-lint rules
# Show detailed rule descriptions
mdbook-lint rules --detailed
# Show only enabled rules
mdbook-lint rules --enabled
Next Steps
- Learn about Configuration options
- Explore CLI Usage in detail
- Set up mdBook Integration
- Browse the Rules Reference
Configuration
mdbook-lint supports multiple configuration formats and provides flexible options for customizing linting behavior.
Configuration File Formats
mdbook-lint automatically detects and supports multiple configuration formats:
- TOML:
.mdbook-lint.tomlormdbook-lint.toml(recommended) - YAML:
.mdbook-lint.yamlor.mdbook-lint.yml - JSON:
.mdbook-lint.json - markdownlint:
.markdownlint.json(for compatibility)
Configuration Discovery
mdbook-lint searches for configuration files in the following order:
- Current directory
- Parent directories (recursively up to root)
- Custom path via
MDBOOK_LINT_CONFIGenvironment variable
The first configuration file found is used.
Basic Configuration
TOML Format (Recommended)
# Global settings
fail-on-warnings = false
fail-on-errors = true
disabled-rules = ["MD013", "MD033"]
enabled-rules = ["MD001", "MD002"]
# Rule-specific configuration
[MD007]
indent = 2
[MD013]
line-length = 120
code-blocks = false
YAML Format
fail-on-warnings: false
fail-on-errors: true
disabled-rules:
- MD013
- MD033
enabled-rules:
- MD001
- MD002
MD007:
indent: 2
MD013:
line-length: 120
code-blocks: false
JSON Format
{
"fail-on-warnings": false,
"fail-on-errors": true,
"disabled-rules": ["MD013", "MD033"],
"enabled-rules": ["MD001", "MD002"],
"MD007": {
"indent": 2
},
"MD013": {
"line-length": 120,
"code-blocks": false
}
}
Advanced Rule Control
The [rules] Section
The [rules] section provides fine-grained control over which rules run:
[rules]
# Disable all rules by default
default = false
# Only enable specific rules
[rules.enabled]
MD001 = true
MD002 = true
MD013 = true
# Rule-specific configuration still works
[MD013]
line-length = 120
This is particularly useful for:
- Gradual adoption of linting rules
- Testing specific rules
- Creating minimal rule sets
Category-Based Rule Management
Control entire categories of rules at once:
# Enable/disable rule categories
enabled-categories = ["headings", "lists"]
disabled-categories = ["whitespace"]
# Individual rules override categories
enabled-rules = ["MD009"] # Enable even though whitespace is disabled
disabled-rules = ["MD001"] # Disable even though headings is enabled
Available categories:
headings- Heading-related ruleslists- List formatting ruleswhitespace- Whitespace and blank line rulescode- Code block and inline code rulesstyle- General style ruleslinks- Link and reference rulesmdbook- mdBook-specific rules
markdownlint Compatibility
mdbook-lint can read .markdownlint.json files for compatibility:
{
"default": false,
"MD001": true,
"MD013": {
"line_length": 120,
"code_blocks": false
}
}
Enable full markdownlint compatibility mode:
markdownlint-compatible = true
This disables rules that are disabled by default in markdownlint.
Global Configuration Options
| Setting | Type | Default | Description |
|---|---|---|---|
fail-on-warnings | boolean | false | Exit with error code on warnings |
fail-on-errors | boolean | true | Exit with error code on errors |
disabled-rules | array | [] | List of rule IDs to disable |
enabled-rules | array | [] | List of rule IDs to explicitly enable |
enabled-categories | array | [] | List of categories to enable |
disabled-categories | array | [] | List of categories to disable |
markdownlint-compatible | boolean | false | Enable markdownlint compatibility |
deprecated-warning | string | "warn" | How to handle deprecated rules ("warn", "info", "silent") |
malformed-markdown | string | "warn" | How to handle malformed markdown ("error", "warn", "skip") |
Configuration Precedence
Configuration is resolved in the following order (later overrides earlier):
- Built-in defaults
- Configuration file (
.mdbook-lint.toml, etc.) - mdBook preprocessor config (in
book.toml) - Environment variables (
MDBOOK_LINT_*) - Command-line arguments
Environment Variables
MDBOOK_LINT_CONFIG- Path to custom configuration fileMDBOOK_LINT_LOG- Log level (error,warn,info,debug,trace)
mdBook Integration
When used as an mdBook preprocessor, configuration can be specified in book.toml:
[preprocessor.mdbook-lint]
fail-on-warnings = true
disabled-rules = ["MD025"]
[preprocessor.mdbook-lint.MD013]
line-length = 100
Example Configurations
Minimal - Only Critical Rules
[rules]
default = false
[rules.enabled]
MD001 = true # Heading levels should increment
MD003 = true # Heading style consistency
MD009 = true # No trailing spaces
MD047 = true # File should end with newline
Strict - All Rules with Custom Settings
fail-on-warnings = true
fail-on-errors = true
[MD007]
indent = 2
[MD013]
line-length = 80
code-blocks = true
tables = true
headings = true
[MD024]
siblings-only = true
[MD029]
style = "ordered"
mdBook Projects
# Disable rules that conflict with mdBook conventions
disabled-rules = [
"MD025", # Multiple H1s are OK in books
"MD041", # First line doesn't need to be H1
]
# mdBook-specific rules
enabled-categories = ["mdbook"]
[MD013]
line-length = 100 # Longer lines for documentation
Progressive Adoption
Start with a few rules and gradually enable more:
# Phase 1: Start with formatting rules
[rules]
default = false
[rules.enabled]
MD009 = true # Trailing spaces
MD010 = true # Hard tabs
MD012 = true # Multiple blank lines
# Phase 2: Add heading rules (uncomment when ready)
# MD001 = true # Heading increment
# MD003 = true # Heading style
# Phase 3: Add more rules...
Migration from markdownlint
If migrating from markdownlint, start with compatibility mode:
markdownlint-compatible = true
# Then gradually customize...
[MD013]
line-length = 100
Generating Configuration Files
Use the init command to generate a configuration file:
# Generate minimal configuration
mdbook-lint init
# Generate comprehensive configuration with all 83 rules documented
mdbook-lint init --include-all
# Generate in a different format
mdbook-lint init --format yaml
mdbook-lint init --format json
Configuration Examples
For real-world configuration examples:
- example-mdbook-lint.toml - Comprehensive reference with all rules documented and commented
- docs/.mdbook-lint.toml - Real-world example used by this project's own documentation
Configuration Validation
To validate your configuration:
# Check if configuration file is valid
mdbook-lint check .mdbook-lint.toml
# Show which rules are enabled with current config
mdbook-lint rules --enabled
Next Steps
- Configuration Reference - Complete list of options
- Rules Reference - All rules and their configurations
- CLI Usage - Command-line options
CLI Usage
This page documents the command-line interface for mdbook-lint.
Basic Commands
lint
Lint markdown files and directories.
mdbook-lint lint [OPTIONS] [PATHS]...
rules
List available linting rules.
mdbook-lint rules [OPTIONS]
help
Show help information.
mdbook-lint help [COMMAND]
Options
Global Options
-h, --help: Print help information-V, --version: Print version information-v, --verbose: Enable verbose output-q, --quiet: Suppress non-error output
Lint Options
--config <FILE>: Use specific configuration file--fail-on-warnings: Exit with error code on warnings--disable <RULES>: Disable specific rules (comma-separated)--enable <RULES>: Enable only specific rules (comma-separated)--fix: Automatically fix violations where possible--fix-unsafe: Apply all fixes, including potentially unsafe ones--dry-run: Show what would be fixed without applying changes (requires --fix or --fix-unsafe)--no-backup: Skip creating backup files when applying fixes--output <FORMAT>: Output format (default, json, github)--color <WHEN>: Control colored output (auto, always, never)
Rules Options
--detailed: Show detailed rule descriptions--enabled: Show only enabled rules--format <FORMAT>: Output format (text, json)
Output Format
By default, mdbook-lint displays violations in a cargo/rustc-style format with colors:
error[MD001]: Expected heading level 2 but got level 3
--> src/chapter.md:15:1
|
15 | ### Skipped heading level
| ^^^ heading-increment
warning[MD009]: Trailing spaces detected
--> src/intro.md:8:42
|
8 | This line has trailing spaces
| ^ no-trailing-spaces
Found: 1 error(s), 1 warning(s)
Output Formats
- default: Colored, human-readable format (shown above)
- json: Machine-readable JSON output
- github: GitHub Actions annotation format
Controlling Colors
Use --color to control colored output:
# Auto-detect (default) - colors when terminal supports it
mdbook-lint lint docs/
# Always use colors (useful for CI with color support)
mdbook-lint lint --color always docs/
# Never use colors (useful for piping to files)
mdbook-lint lint --color never docs/ > report.txt
Examples
# Lint current directory
mdbook-lint lint .
# Lint specific files
mdbook-lint lint README.md src/chapter1.md
# Lint with custom config
mdbook-lint lint --config custom-lint.toml src/
# Auto-fix violations where possible
mdbook-lint lint --fix docs/
# Preview fixes without applying them
mdbook-lint lint --fix --dry-run docs/
# Apply all fixes including potentially unsafe ones
mdbook-lint lint --fix-unsafe docs/
# Fix without creating backup files
mdbook-lint lint --fix --no-backup docs/
# Show all rules with descriptions
mdbook-lint rules --detailed
# Lint and fail on warnings
mdbook-lint lint --fail-on-warnings docs/
Exit Codes
0: Success (no errors)1: Linting errors found2: Invalid arguments or configuration
Next Steps
- Learn about mdBook Integration
- See Configuration Reference for all options
mdBook Integration
mdbook-lint integrates seamlessly with mdBook as a preprocessor, automatically checking your markdown files during the build process. This guide provides comprehensive documentation for configuring and using mdbook-lint with mdBook projects.
Table of Contents
- Installation
- Basic Setup
- Configuration Options
- Advanced Configuration
- GitHub Actions Integration
- Troubleshooting
- Common Scenarios
Note: If you're unsure whether to use mdbook-lint as a preprocessor or standalone in CI, see our CI vs Preprocessor guide.
Installation
Using Cargo (Recommended)
cargo install mdbook-lint
Using Pre-built Binaries
Download the latest release from GitHub Releases:
# Linux/macOS
curl -L https://github.com/joshrotenberg/mdbook-lint/releases/latest/download/mdbook-lint-$(uname -s)-$(uname -m).tar.gz | tar xz
sudo mv mdbook-lint /usr/local/bin/
# Windows (PowerShell)
Invoke-WebRequest -Uri https://github.com/joshrotenberg/mdbook-lint/releases/latest/download/mdbook-lint-Windows-x86_64.zip -OutFile mdbook-lint.zip
Expand-Archive mdbook-lint.zip -DestinationPath .
Using GitHub Action
- name: Install mdbook-lint
uses: joshrotenberg/mdbook-lint-action@v1
Basic Setup
Minimal Configuration
Add mdbook-lint to your book.toml:
[preprocessor.mdbook-lint]
This enables mdbook-lint with default settings. It will:
- Run all standard markdown rules (MD001-MD059)
- Run all mdBook-specific rules (MDBOOK001-MDBOOK025)
- Report violations as warnings (won't fail the build)
Running mdBook with Linting
# Build with linting
mdbook build
# Serve with live reload and linting
mdbook serve
# Test to verify everything works
mdbook test
Configuration Options
Complete Configuration Example
[preprocessor.mdbook-lint]
# Control build behavior
fail-on-warnings = false # Set to true to fail builds on any violation
# Rule configuration
disabled-rules = ["MD013", "MD033", "MD041"] # Disable specific rules
enabled-rules = [] # If set, ONLY these rules will run
# Rule categories (groups of rules)
disabled-categories = ["whitespace", "html"] # Disable entire categories
enabled-categories = [] # If set, ONLY these categories will run
# File filtering
include = ["src/**/*.md"] # Only lint these files (glob patterns)
exclude = ["src/drafts/**", "src/archive/**"] # Exclude these files
# Output configuration
output = "concise" # Options: "concise", "detailed", "json"
# Rule-specific configuration
[preprocessor.mdbook-lint.rules]
# Configure individual rules
MD013 = { line_length = 100 }
MD024 = { siblings_only = true }
MD026 = { punctuation = ".,;:!" }
Configuration Through External File
You can also use a separate .mdbook-lint.toml file in your project root:
# .mdbook-lint.toml
[core]
fail_on_warnings = true
[rules]
# Default state for all rules
default = true
[rules.disabled]
MD013 = true # Line length
MD033 = true # Inline HTML
MD041 = true # First line heading
[rules.config]
MD013 = { line_length = 100 }
MD024 = { siblings_only = true }
Advanced Configuration
Rule Categories
Rules are organized into categories for easier management:
[preprocessor.mdbook-lint]
# Disable all whitespace-related rules
disabled-categories = ["whitespace"]
# Or enable only specific categories
enabled-categories = ["headings", "links", "code"]
Available categories:
headings- Heading structure and formattinglists- List formatting and consistencywhitespace- Spacing, indentation, line breakscode- Code blocks and inline codelinks- Link validation and formattinghtml- HTML usage in markdownmdbook- mdBook-specific rules
Custom Rule Sets
Define different rule sets for different environments:
# Development: Lenient settings
[preprocessor.mdbook-lint]
fail-on-warnings = false
disabled-rules = ["MD013", "MD033", "MD041"]
# For CI, use environment variables to override:
# MDBOOK_PREPROCESSOR__MDBOOK_LINT__FAIL_ON_WARNINGS=true mdbook build
Per-File Rule Overrides
Use HTML comments in your markdown files to disable rules:
<!-- mdbook-lint-disable MD013 MD033 -->
This content won't be checked for line length or HTML usage.
<!-- mdbook-lint-enable MD013 MD033 -->
<!-- mdbook-lint-disable-next-line MD001 -->
### This heading can skip levels
GitHub Actions Integration
Basic Workflow
name: Documentation
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
lint-and-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache cargo dependencies
uses: Swatinem/rust-cache@v2
- name: Install mdbook and mdbook-lint
run: |
cargo install mdbook
cargo install mdbook-lint
- name: Build documentation
run: mdbook build
env:
# Override settings for CI
MDBOOK_PREPROCESSOR__MDBOOK_LINT__FAIL_ON_WARNINGS: true
- name: Deploy to GitHub Pages
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./book
Using mdbook-lint-action
name: Lint Documentation
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Lint markdown files
uses: joshrotenberg/mdbook-lint-action@v1
with:
format: sarif
output-file: results.sarif
config: .mdbook-lint.toml
- name: Upload SARIF results
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: results.sarif
Matrix Testing with Different Configurations
name: Test Documentation
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
lint-config:
- strict
- standard
- lenient
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: |
cargo install mdbook
cargo install mdbook-lint
- name: Test with ${{ matrix.lint-config }} configuration
run: mdbook build
env:
MDBOOK_PREPROCESSOR__MDBOOK_LINT__CONFIG: .mdbook-lint.${{ matrix.lint-config }}.toml
Troubleshooting
Common Issues and Solutions
Preprocessor Not Running
Problem: mdbook-lint doesn't seem to run during builds.
Solutions:
-
Verify installation:
mdbook-lint --version which mdbook-lint -
Check
book.tomlhas the preprocessor section:[preprocessor.mdbook-lint] -
Run with verbose output:
mdbook build -v 2>&1 | grep mdbook-lint -
Ensure mdbook-lint is in PATH:
export PATH="$HOME/.cargo/bin:$PATH"
Configuration Not Applied
Problem: Settings in book.toml aren't being used.
Configuration Precedence (highest to lowest):
-
Environment variables (e.g.,
MDBOOK_PREPROCESSOR**MDBOOK_LINT**FAIL_ON_WARNINGS) -
book.tomlpreprocessor settings -
.mdbook-lint.tomlfile in project root -
Built-in defaults
Debug Configuration:
# Check what configuration is being used
mdbook-lint lint --debug-config src/chapter1.md
# Test with explicit config
mdbook-lint lint --config .mdbook-lint.toml src/
Build Fails Unexpectedly
Problem: Build fails even with fail-on-warnings = false.
Check for:
- Syntax errors in configuration files
- Invalid rule IDs in enabled/disabled lists
- Conflicting configuration sources
Debug Steps:
# Run linter standalone to see all issues
mdbook-lint lint src/
# Check preprocessor output
mdbook build 2>&1 | grep -A 5 "mdbook-lint"
Performance Issues
Problem: Builds are slow with mdbook-lint enabled.
Solutions:
-
Exclude unnecessary files:
[preprocessor.mdbook-lint] exclude = ["src/generated/**", "src/vendor/**"] -
Disable expensive rules:
disabled-rules = ["MD013", "MD053"] # Line length checks can be slow -
Use caching in CI:
- uses: actions/cache@v3 with: path: ~/.cargo key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
Common Scenarios
Scenario 1: Strict CI, Lenient Local Development
Local book.toml:
[preprocessor.mdbook-lint]
fail-on-warnings = false
disabled-rules = ["MD013"] # Allow long lines locally
CI Override:
- name: Build with strict linting
run: mdbook build
env:
MDBOOK_PREPROCESSOR__MDBOOK_LINT__FAIL_ON_WARNINGS: true
MDBOOK_PREPROCESSOR__MDBOOK_LINT__DISABLED_RULES: ""
Scenario 2: Different Rules for Different Chapters
book.toml:
# Strict rules for main content
[preprocessor.mdbook-lint]
include = ["src/chapters/**"]
fail-on-warnings = true
# Separate preprocessor for examples (lenient)
[preprocessor.mdbook-lint-examples]
command = "mdbook-lint"
renderer = ["html"]
include = ["src/examples/**"]
disabled-rules = ["MD013", "MD033", "MD041"]
Scenario 3: Progressive Rule Adoption
Start lenient and gradually enable more rules:
# Phase 1: Critical rules only
[preprocessor.mdbook-lint]
enabled-rules = ["MDBOOK001", "MDBOOK002", "MDBOOK003", "MD040", "MD041"]
# Phase 2: Add formatting rules
# enabled-rules = ["MDBOOK001", "MDBOOK002", "MDBOOK003", "MD040", "MD041", "MD001", "MD003", "MD009"]
# Phase 3: Full rule set
# Comment out enabled-rules to run all rules
Scenario 4: Integration with Code Quality Tools
name: Documentation Quality
on: [push, pull_request]
jobs:
quality-checks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Markdown linting
- name: Lint markdown
uses: joshrotenberg/mdbook-lint-action@v1
with:
format: sarif
output-file: mdbook-lint.sarif
# Spell checking
- name: Spell check
uses: streetsidesoftware/cspell-action@v2
# Link checking
- name: Check links
uses: lycheeverse/lychee-action@v1
with:
args: --verbose --no-progress './book/**/*.html'
# Upload all results
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: mdbook-lint.sarif
Configuration Discovery
mdbook-lint automatically searches for configuration files in the following order:
- Explicit
--configflag (CLI mode only) - Environment variable:
MDBOOK_LINT_CONFIG book.tomlpreprocessor configuration- Configuration file discovery (searches up the directory tree):
-
.mdbook-lint.toml -
mdbook-lint.toml -
.mdbook-lint.yaml -
.mdbook-lint.yml -
.mdbook-lint.json
The first configuration found is used. Settings from multiple sources are not merged.
Environment Variables
All preprocessor settings can be overridden using environment variables:
# Format: MDBOOK_PREPROCESSOR__MDBOOK_LINT__<SETTING>
export MDBOOK_PREPROCESSOR__MDBOOK_LINT__FAIL_ON_WARNINGS=true
export MDBOOK_PREPROCESSOR__MDBOOK_LINT__DISABLED_RULES="MD013,MD033"
export MDBOOK_PREPROCESSOR__MDBOOK_LINT__OUTPUT=json
mdbook build
Output Formats
Concise (Default)
src/chapter1.md:10:1: MD041 First line in file should be a top-level heading
src/chapter2.md:25:81: MD013 Line length exceeds 80 characters
Detailed
File: src/chapter1.md
Line 10, Column 1: MD041 - First line in file should be a top-level heading
The first line of the file should be a top-level (h1) heading.
Consider adding '# Title' at the beginning of the file.
JSON
{
"files": [
{
"path": "src/chapter1.md",
"violations": [
{
"rule": "MD041",
"line": 10,
"column": 1,
"severity": "warning",
"message": "First line in file should be a top-level heading"
}
]
}
]
}
Next Steps
- Review the Rules Reference for all available rules
- Learn about Creating Custom Rules
- See Configuration Reference for all options
- Check out Example Configurations for real-world setups
CI vs Preprocessor: Choosing Your Integration Strategy
This guide helps you choose between running mdbook-lint as an mdBook preprocessor or as a standalone CLI tool in CI, and explains why you typically want one approach but not both.
TL;DR Recommendation
For CI/CD pipelines: Use the standalone CLI. For local development: Use the preprocessor (optional).
The standalone CLI gives you more control, better error handling, and avoids configuration discovery issues that can occur in preprocessor mode.
Quick Decision Guide
| Use Case | Recommended Approach | Why |
|---|---|---|
| CI/CD pipelines | Standalone CLI | More control, fail fast, better error output |
| Local development with mdBook | Preprocessor | Automatic feedback during mdbook serve |
| Pure markdown documentation (no mdBook) | Standalone CLI | No mdBook dependency needed |
| Need SARIF/GitHub integration | Standalone CLI | Better tool integration options |
| Complex CI pipeline with multiple checks | Standalone CLI | More control over when/how linting runs |
Integration Approaches
Approach 1: mdBook Preprocessor (Best for Local Development)
When to use:
- You want automatic linting during
mdbook serve - You prefer configuration in
book.toml - You want immediate feedback while writing
When NOT to use:
- In CI/CD pipelines (use standalone CLI instead)
- When you need precise control over exit codes
- When you need detailed error output for debugging
Setup in book.toml:
[preprocessor.mdbook-lint]
fail-on-warnings = false # Set to true for strict mode
disabled-rules = ["MD013", "MD033"]
[MD007]
indent = 4
In CI (GitHub Actions):
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install mdBook and mdbook-lint
run: |
cargo install mdbook
cargo install mdbook-lint
- name: Build book (linting happens automatically)
run: mdbook build
env:
# Optional: Override settings for CI
MDBOOK_PREPROCESSOR__MDBOOK_LINT__FAIL_ON_WARNINGS: true
Advantages:
- Linting happens automatically during
mdbook serve - Immediate feedback while writing documentation
- Works seamlessly with local mdBook workflow
Disadvantages:
- Configuration discovery can be tricky in CI environments
- Limited control over error handling and exit codes
- No SARIF output for GitHub Security tab
- Errors appear inline with mdBook build output
- Can't fail fast in CI (must start book build first)
Approach 2: Standalone CLI (Recommended for CI/CD)
When to use:
- CI/CD pipelines (recommended for all CI use cases)
- You want to fail fast before other expensive operations
- You need clear, actionable error output
- You need SARIF output for GitHub Security integration
- You don't use mdBook (just markdown files)
Setup with .mdbook-lint.toml:
fail-on-warnings = true
disabled-rules = ["MD013", "MD033"]
[MD007]
indent = 4
In CI (GitHub Actions):
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Option A: Using GitHub Action
- name: Lint Markdown
uses: joshrotenberg/mdbook-lint-action@v1
with:
files: 'docs/**/*.md'
format: sarif
output-file: results.sarif
# Option B: Direct installation
- name: Install and run mdbook-lint
run: |
cargo install mdbook-lint
mdbook-lint lint docs/ --fail-on-warnings
# Optional: Upload SARIF results
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: results.sarif
Advantages:
- Fails fast in CI pipeline before expensive build steps
- Clear, standalone error output for debugging
- SARIF output for GitHub Security tab
- Full control over when and how linting runs
- Can run in parallel with other checks
- Works with or without mdBook
- Supports smart CLI detection (e.g.,
mdbook-lint docs/)
Disadvantages:
- No automatic linting during local
mdbook serve - Requires explicit invocation in CI workflow
- Consider adding preprocessor for local development feedback
Why Not Both
Running mdbook-lint both as a preprocessor AND standalone in CI is usually redundant and can cause problems:
Problems with Running Both
- Duplicate Work: The same files get linted twice, wasting CI time
- Configuration Drift: Two places to maintain rules can lead to inconsistencies
- Confusing Failures: Issues might be reported twice in different formats
- Maintenance Burden: Updates need to be synchronized in multiple places
Valid Exception: Different Rule Sets
The only scenario where using both makes sense is when you intentionally want different rules:
# CI: Strict linting before build
- name: Strict lint check
run: mdbook-lint lint docs/ --config .mdbook-lint.strict.toml
# Build: Lenient linting during build
- name: Build with lenient linting
run: mdbook build # Uses preprocessor with book.toml config
Migration Strategies
From Preprocessor to Standalone CI
If you're currently using the preprocessor but want to switch to standalone CI:
- Extract configuration from
book.tomlto.mdbook-lint.toml - Remove preprocessor section from
book.toml - Update CI to run mdbook-lint before mdbook build
- Document the change for your team
From Standalone to Preprocessor
If you're using standalone but want to switch to preprocessor:
- Add preprocessor section to
book.toml - Copy configuration from
.mdbook-lint.tomltobook.toml - Remove standalone lint step from CI
- Update documentation for developers
Recommended Configurations
For CI/CD: Use Standalone CLI (Recommended)
# .github/workflows/docs.yml
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install mdbook-lint
run: cargo install mdbook-lint
- name: Lint documentation
run: mdbook-lint docs/src/ --fail-on-warnings
For Local Development: Add Preprocessor (Optional)
# book.toml - for local development feedback only
[preprocessor.mdbook-lint]
fail-on-warnings = false
Combined Setup (Best of Both Worlds)
Use standalone CLI in CI for control and reliability, with optional preprocessor for local development:
# .github/workflows/docs.yml - CI uses standalone
- name: Lint documentation
run: mdbook-lint docs/src/ --fail-on-warnings
- name: Build book
run: mdbook build docs/
# book.toml - local development uses preprocessor (optional)
[preprocessor.mdbook-lint]
fail-on-warnings = false
This approach gives you:
- Reliable CI with clear error output
- Fast feedback during local
mdbook serve - No duplicate configuration (use
.mdbook-lint.tomlfor both)
Common Pitfalls to Avoid
-
Don't duplicate the same rules in both preprocessor and standalone configs
-
Don't run both in CI unless you have a specific reason
-
Don't use
|| trueto ignore failures - fix the issues or disable specific rules -
Don't forget to document which approach you're using for new contributors
Summary
- Use standalone CLI in CI: Better control, clearer errors, fail-fast capability
- Use preprocessor for local development: Optional, provides feedback during
mdbook serve - Avoid using both in CI: Redundant and can cause confusion
- Share configuration: Use
.mdbook-lint.tomlwhich works for both modes - Be consistent: Document your choice and stick with it across your project
Troubleshooting Guide
This guide helps you resolve common issues with mdbook-lint.
Table of Contents
- Installation Issues
- Configuration Problems
- Preprocessor Issues
- Performance Problems
- Rule-Specific Issues
- CI/CD Problems
- Debugging Tips
Installation Issues
Command Not Found
Problem: mdbook-lint: command not found after installation.
Solutions:
-
Verify Cargo bin directory is in PATH:
echo $PATH | grep -q "$HOME/.cargo/bin" || echo "Not in PATH" export PATH="$HOME/.cargo/bin:$PATH" echo 'export PATH="$HOME/.cargo/bin:$PATH"' >> ~/.bashrc -
Check installation location:
find ~ -name mdbook-lint -type f 2>/dev/null -
Reinstall with verbose output:
cargo install mdbook-lint --force --verbose
Version Conflicts
Problem: Different versions between CLI and preprocessor.
Solution:
# Check versions
mdbook-lint --version
cargo install --list | grep mdbook-lint
# Update to latest
cargo install mdbook-lint --force
Build Failures During Installation
Problem: Compilation errors when installing from source.
Solutions:
-
Update Rust toolchain:
rustup update stable rustup default stable -
Clear cargo cache:
cargo clean rm -rf ~/.cargo/registry/cache -
Install with specific version:
cargo install mdbook-lint --version 0.11.1
Configuration Problems
Configuration Not Loading
Problem: Settings in configuration files are ignored.
Debug Steps:
-
Check configuration discovery:
Show which config file is being used
mdbook-lint lint --debug src/ 2>&1 | grep -i config
2. **Validate configuration syntax**:
```bash
# For TOML
cat .mdbook-lint.toml | python -m json.tool > /dev/null 2>&1 || echo "Invalid TOML"
# For JSON
cat .mdbook-lint.json | jq . > /dev/null || echo "Invalid JSON"
# For YAML
cat .mdbook-lint.yaml | python -c "import yaml, sys; yaml.safe_load(sys.stdin)" || echo "Invalid YAML"
-
Test with explicit config:
mdbook-lint lint --config ./my-config.toml src/
Rule Configuration Not Working
Problem: Rule-specific settings aren't applied.
Example Working Configurations:
# .mdbook-lint.toml
[rules.config]
# Correct: Use table syntax for rule config
MD013 = { line_length = 100, tables = false }
MD024 = { siblings_only = true }
# Wrong: Don't use this format
# MD013.line_length = 100 # This won't work
Environment Variables Not Working
Problem: Environment variable overrides aren't applied.
Correct Format:
# Preprocessor settings
export MDBOOK_PREPROCESSOR__MDBOOK_LINT__FAIL_ON_WARNINGS=true
export MDBOOK_PREPROCESSOR__MDBOOK_LINT__DISABLED_RULES='["MD013","MD033"]'
# Note: Use JSON array format for lists
# Wrong: DISABLED_RULES="MD013,MD033"
# Right: DISABLED_RULES='["MD013","MD033"]'
Preprocessor Issues
Preprocessor Not Running
Problem: mdbook-lint doesn't execute during mdbook build.
Comprehensive Check:
#!/bin/bash
# Diagnostic script
echo "1. Checking mdbook-lint installation..."
which mdbook-lint || echo "ERROR: mdbook-lint not found in PATH"
echo "2. Checking book.toml..."
grep -A5 "preprocessor.mdbook-lint" book.toml || echo "ERROR: Preprocessor not configured"
echo "3. Testing preprocessor directly..."
echo '{"root":"","config":{},"renderer":"html","mdbook_version":"0.4.0"}' | mdbook-lint preprocessor
echo "4. Checking mdbook version..."
mdbook --version
echo "5. Testing build with verbose output..."
mdbook build -v 2>&1 | grep -i mdbook-lint
Preprocessor Crashes
Problem: Build fails with preprocessor errors.
Debug Mode:
# Enable debug logging
export RUST_LOG=mdbook_lint=debug
export RUST_BACKTRACE=1
# Run build
mdbook build 2> mdbook-lint-debug.log
# Check error details
grep ERROR mdbook-lint-debug.log
Conflicts with Other Preprocessors
Problem: mdbook-lint conflicts with other preprocessors.
Solution - Control execution order:
# book.toml
[preprocessor.mdbook-lint]
before = ["links"] # Run before links preprocessor
after = ["index"] # Run after index preprocessor
[preprocessor.other-processor]
after = ["mdbook-lint"] # Ensure mdbook-lint runs first
Performance Problems
Slow Builds
Problem: mdbook build takes too long with linting enabled.
Optimization Strategies:
-
Profile the slowdown:
time mdbook build --dest-dir book-without-lint
With linting
time mdbook build --dest-dir book-with-lint
2. **Disable expensive rules**:
```toml
[preprocessor.mdbook-lint]
# Line length and link checking are expensive
disabled-rules = ["MD013", "MD053", "MDBOOK002"]
-
Limit scope:
[preprocessor.mdbook-lint]
Only lint main content
include = ["src/chapters//*.md"] exclude = ["src/appendix/", "src/reference/**"]
4. **Use parallel processing** (if available):
```bash
export RAYON_NUM_THREADS=4
mdbook build
Memory Issues
Problem: Out of memory errors on large books.
Solutions:
-
Process files individually:
Instead of linting everything at once
for file in src/**/*.md; do mdbook-lint lint "$file" done
2. **Increase memory limits**:
```bash
# Linux/macOS
ulimit -v unlimited
# Or specify a limit
ulimit -v 4194304 # 4GB
Rule-Specific Issues
False Positives
Problem: Rules flag valid content as violations.
Solutions:
-
Disable rules inline:
<!-- mdbook-lint-disable MD033 --> <div class="custom-element"> This HTML is intentional </div> <!-- mdbook-lint-enable MD033 --> -
Configure rule parameters:
[rules.config]
Allow specific HTML tags
MD033 = { allowed_elements = ["div", "span", "details", "summary"] }
3. **Report false positives**:
```bash
# Create minimal reproduction
echo "# Test\n<valid-html></valid-html>" > test.md
mdbook-lint lint test.md
# Report issue with output
Rule Conflicts
Problem: Different rules want opposite formatting.
Example Resolution:
# MD047 wants files to end with newline
# MD012 limits consecutive blank lines
# Resolution: Configure both appropriately
[rules.config]
MD047 = true # Require final newline
MD012 = { maximum = 1 } # But only one
CI/CD Problems
GitHub Actions Failures
Problem: CI passes locally but fails in GitHub Actions.
Debug Workflow:
- name: Debug environment
run: |
echo "PATH: $PATH"
which mdbook-lint || echo "mdbook-lint not found"
mdbook-lint --version || echo "Version check failed"
- name: Debug configuration
run: |
cat book.toml
ls -la .mdbook-lint.* 2>/dev/null || echo "No config files"
- name: Test with explicit verbosity
run: |
export RUST_LOG=debug
mdbook build -v
Docker Container Issues
Problem: mdbook-lint fails in Docker containers.
Working Dockerfile:
FROM rust:1.70 AS builder
# Install mdbook and mdbook-lint
RUN cargo install mdbook mdbook-lint
# Runtime stage
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y \
libssl3 \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
COPY --from=builder /usr/local/cargo/bin/mdbook* /usr/local/bin/
WORKDIR /book
CMD ["mdbook", "build"]
Debugging Tips
Enable Verbose Logging
# Maximum verbosity
export RUST_LOG=trace
export RUST_BACKTRACE=full
# Run with timing information
time mdbook-lint lint src/ --verbose
Create Minimal Reproduction
#!/bin/bash
# Create minimal test case
mkdir mdbook-lint-test
cd mdbook-lint-test
# Create minimal book
cat > book.toml << EOF
[book]
title = "Test"
authors = ["Test"]
[preprocessor.mdbook-lint]
fail-on-warnings = true
EOF
mkdir src
echo "# Test\n\nThis is a test." > src/SUMMARY.md
echo "# Chapter 1" > src/chapter_1.md
# Test
mdbook build -v
Check Binary Dependencies
# Linux
ldd $(which mdbook-lint)
# macOS
otool -L $(which mdbook-lint)
# Check for missing libraries
mdbook-lint --version || echo $?
Trace System Calls
# Linux
strace -e open,stat mdbook-lint lint src/ 2>&1 | grep -E "\.(toml|yaml|json)"
# macOS
dtruss -t open mdbook-lint lint src/ 2>&1 | grep -E "\.(toml|yaml|json)"
Getting Help
If these solutions don't resolve your issue:
-
Search existing issues:
gh issue list --repo joshrotenberg/mdbook-lint --search "your error" -
Create detailed bug report:
mdbook-lint --version > bug-report.txt echo "---" >> bug-report.txt mdbook --version >> bug-report.txt echo "---" >> bug-report.txt cat book.toml >> bug-report.txt echo "---" >> bug-report.txt mdbook build -v 2>&1 | tail -50 >> bug-report.txt -
Join discussions:
-
GitHub Issues: https://github.com/joshrotenberg/mdbook-lint/issues
-
Discussions: https://github.com/joshrotenberg/mdbook-lint/discussions
Common Error Messages
"Failed to parse configuration"
Cause: Syntax error in configuration file.
Fix: Validate configuration syntax (see Configuration Problems).
"Rule not found: XXXX"
Cause: Typo in rule ID or using removed rule.
Fix: Check available rules with mdbook-lint rules.
"Preprocessor failed: Input/Output error"
Cause: mdbook-lint crashed or timed out.
Fix: Check system resources and enable debug logging.
"No such file or directory"
Cause: Incorrect paths in configuration.
Fix: Use absolute paths or paths relative to book root:
[preprocessor.mdbook-lint]
include = ["src/**/*.md"] # Relative to book root
exclude = ["/tmp/**"] # Absolute path
Rules Reference
mdbook-lint provides comprehensive markdown linting with two rule categories.
Standard Markdown Rules
59 rules (MD001-MD059) based on the widely-used markdownlint specification. These rules ensure consistent markdown formatting and style.
Categories
- Heading Rules - Heading hierarchy and formatting
- List Rules - List formatting and consistency
- Whitespace Rules - Trailing spaces, blank lines
- Link Rules - URL formatting and link text
- Code Rules - Code block formatting and fencing
- Style Rules - Emphasis and formatting consistency
mdBook-Specific Rules
Rules specifically designed for mdBook projects, validating mdBook-specific syntax and conventions.
Rules
- MDBOOK001 - Code blocks should have language tags
- MDBOOK002 - SUMMARY.md structure validation
- MDBOOK003 - Internal link validation
- MDBOOK004 - Part title formatting
- MDBOOK005 - Chapter path validation
- MDBOOK006 - Draft chapter validation
- MDBOOK007 - Separator syntax validation
Quick Reference
Rules with Automatic Fix Support
The following rules can automatically fix violations:
- MD009 - Remove trailing spaces
- MD010 - Replace hard tabs with spaces
- MD012 - Remove multiple consecutive blank lines
- MD018 - Add space after hash in ATX headings
- MD019 - Fix multiple spaces after hash
- MD020 - Remove spaces inside closed ATX headings
- MD021 - Fix multiple spaces inside closed ATX headings
- MD023 - Remove indentation from headings
- MD027 - Fix multiple spaces after blockquote symbol
- MD030 - Fix spaces after list markers
- MD034 - Wrap bare URLs in angle brackets
- MD047 - Ensure files end with single newline
Disabling Rules
Rules can be disabled globally or for specific files:
# Disable globally
[rules]
MD002 = false
MD041 = false
# Disable for specific files
[ignore]
MD013 = ["CHANGELOG.md", "docs/api/*.md"]
Standard Markdown Rules
mdbook-lint implements 59 standard markdown linting rules based on the markdownlint specification. These rules help maintain consistent, readable, and properly formatted markdown documentation.
Rule Categories
Heading Rules
Rules for heading hierarchy, formatting, and style consistency.
List Rules
Rules for list formatting, indentation, and marker consistency.
Whitespace Rules
Rules for managing spaces, tabs, and blank lines.
Link Rules
Rules for URL formatting, link text, and reference links.
Code Rules
Rules for code blocks, inline code, and fencing style.
Emphasis Rules
Rules for bold, italic, and other emphasis formatting.
Complete Rule List
| Rule ID | Name | Description | Fix |
|---|---|---|---|
| MD001 | heading-increment | Heading levels should only increment by one level at a time | ❌ |
| MD002 | first-heading-h1 | First heading should be a top-level heading | ❌ |
| MD003 | heading-style | Heading style | ❌ |
| MD004 | ul-style | Unordered list style | ❌ |
| MD005 | list-indent | Inconsistent indentation for list items at the same level | ❌ |
| MD006 | ul-start-left | Consider starting lists at the beginning of the line | ❌ |
| MD007 | ul-indent | Unordered list indentation | ❌ |
| MD008 | no-bare-urls | Bare URLs should be wrapped in angle brackets | ❌ |
| MD009 | no-trailing-spaces | Trailing spaces | ✅ |
| MD010 | no-hard-tabs | Hard tabs | ✅ |
| MD011 | no-reversed-links | Reversed link syntax | ❌ |
| MD012 | no-multiple-blanks | Multiple consecutive blank lines | ✅ |
| MD013 | line-length | Line length | ❌ |
| MD014 | commands-show-output | Dollar signs used before commands without showing output | ❌ |
| MD015 | no-missing-space-closed-atx | No space after hash on closed atx style heading | ❌ |
| MD016 | no-reversed-heading-style | Heading levels should only increment | ❌ |
| MD017 | blanks-around-headings | Blank lines around headings | ❌ |
| MD018 | no-missing-space-atx | No space after hash on atx style heading | ✅ |
| MD019 | no-multiple-space-atx | Multiple spaces after hash on atx style heading | ✅ |
| MD020 | no-missing-space-closed-atx | No space inside hashes on closed atx style heading | ✅ |
| MD021 | no-multiple-space-closed-atx | Multiple spaces inside hashes on closed atx style heading | ✅ |
| MD022 | blanks-around-headings | Headings should be surrounded by blank lines | ❌ |
| MD023 | heading-start-left | Headings must start at the beginning of the line | ✅ |
| MD024 | no-duplicate-heading | Multiple headings with the same content | ❌ |
| MD025 | single-h1 | Multiple top-level headings in the same document | ❌ |
| MD026 | no-trailing-punctuation | Trailing punctuation in heading | ❌ |
| MD027 | no-multiple-space-blockquote | Multiple spaces after blockquote symbol | ✅ |
| MD028 | no-blanks-blockquote | Blank line inside blockquote | ❌ |
| MD029 | ol-prefix | Ordered list item prefix | ❌ |
| MD030 | list-marker-space | Spaces after list markers | ✅ |
| MD031 | blanks-around-fences | Fenced code blocks should be surrounded by blank lines | ❌ |
| MD032 | blanks-around-lists | Lists should be surrounded by blank lines | ❌ |
| MD033 | no-inline-html | Inline HTML | ❌ |
| MD034 | no-bare-urls | Bare URL used | ✅ |
| MD035 | hr-style | Horizontal rule style | ❌ |
| MD036 | no-emphasis-as-heading | Emphasis used instead of a heading | ❌ |
| MD037 | no-space-in-emphasis | Spaces inside emphasis markers | ❌ |
| MD038 | no-space-in-code | Spaces inside code span elements | ❌ |
| MD039 | no-space-in-links | Spaces inside link text | ❌ |
| MD040 | fenced-code-language | Fenced code blocks should have a language specified | ❌ |
| MD041 | first-line-h1 | First line in file should be a top-level heading | ❌ |
| MD042 | no-empty-links | No empty links | ❌ |
| MD043 | required-headings | Required heading structure | ❌ |
| MD044 | proper-names | Proper names should have correct capitalization | ❌ |
| MD045 | no-alt-text | Images should have alternate text | ❌ |
| MD046 | code-block-style | Code block style | ❌ |
| MD047 | single-trailing-newline | Files should end with a single newline character | ✅ |
| MD048 | code-fence-style | Code fence style | ❌ |
| MD049 | emphasis-style | Emphasis style should be consistent | ❌ |
| MD050 | strong-style | Strong style should be consistent | ❌ |
| MD051 | link-fragments | Link fragments should be valid | ❌ |
| MD052 | reference-links-images | Reference links and images should use a label that is defined | ❌ |
| MD053 | link-image-reference-definitions | Link and image reference definitions should be needed | ❌ |
| MD054 | link-image-style | Link and image style | ❌ |
| MD055 | table-pipe-style | Table pipe style | ❌ |
| MD056 | table-column-count | Table column count | ❌ |
| MD057 | table-rows | Table rows | ❌ |
| MD058 | blanks-around-tables | Tables should be surrounded by blank lines | ❌ |
| MD059 | table-alignment | Table alignment | ❌ |
Legend:
- ✅ Automatic fix available
- ❌ Manual fix required
Heading Rules
Heading rules ensure proper document structure, hierarchy, and formatting for markdown headings.
Rules in This Category
| Rule | Description | Fix |
|---|---|---|
| MD001 | Heading levels should only increment by one level at a time | ❌ |
| MD002 | First heading should be a top-level heading | ❌ |
| MD003 | Heading style (ATX vs Setext) | ❌ |
| MD018 | No space after hash on ATX style heading | ✅ |
| MD019 | Multiple spaces after hash on ATX style heading | ✅ |
| MD020 | No space inside hashes on closed ATX style heading | ✅ |
| MD021 | Multiple spaces inside hashes on closed ATX style heading | ✅ |
| MD022 | Headings should be surrounded by blank lines | ❌ |
| MD023 | Headings must start at the beginning of the line | ✅ |
| MD024 | Multiple headings with the same content | ❌ |
| MD025 | Multiple top-level headings in the same document | ❌ |
| MD026 | Trailing punctuation in heading | ❌ |
| MD041 | First line in file should be a top-level heading | ❌ |
Best Practices
Document Structure
A well-structured document follows these heading principles:
- Start with H1: Documents should begin with a single H1 heading
- Sequential Levels: Never skip heading levels (H1 → H3 is wrong)
- Logical Hierarchy: Use headings to create a document outline
- Consistent Style: Use either ATX (
#) or Setext style consistently
ATX vs Setext Headings
ATX Style (Recommended):
# Heading 1
## Heading 2
### Heading 3
Setext Style (Limited to H1 and H2):
Heading 1
=========
Heading 2
---------
Closed ATX Headings
Some prefer closed ATX headings for symmetry:
#Heading 1#
##Heading 2##
###Heading 3###
Rules MD020 and MD021 ensure proper formatting of closed headings.
Common Issues and Solutions
Issue: Inconsistent Heading Hierarchy
Problem: Jumping between heading levels disrupts document flow.
# Main Title
### Subsection (skips H2)
## Back to H2
##### Deep section (skips H3 and H4)
Solution: Maintain sequential heading levels.
# Main Title
## Section
### Subsection
## Another Section
### Subsection
#### Deeper Content
##### Deepest Content
Issue: Multiple H1 Headings
Problem: Multiple top-level headings confuse document structure.
# First Title
Content...
# Second Title
More content...
Solution: Use a single H1 with H2s for major sections.
# Document Title
## First Section
Content...
## Second Section
More content...
Issue: Indented Headings
Problem: Headings with leading spaces may not render correctly.
# This might not be a heading
## This is problematic
Solution: Start headings at the beginning of the line.
# Proper Heading
## Another Proper Heading
Accessibility Considerations
Proper heading structure is crucial for accessibility:
- Screen Readers: Use headings to navigate and understand document structure
- Keyboard Navigation: Many tools allow jumping between headings
- Document Outline: Assistive technologies generate outlines from headings
- WCAG Compliance: Proper heading hierarchy is part of WCAG 2.1 guidelines
Integration with mdBook
mdBook relies heavily on proper heading structure:
- Table of Contents: Generated from heading hierarchy
- Search Index: Headings are weighted in search results
- Navigation: Sidebar navigation reflects heading structure
- Anchors: Automatic anchor generation for deep linking
Configuration Examples
Enforce ATX Style Only
[MD003]
style = "atx"
Allow Trailing Punctuation
[MD026]
enabled = false
Require Document to Start with H1
[MD041]
level = 1
front_matter_title = false
MD001 - Heading Increment
Heading levels should only increment by one level at a time.
This rule is triggered when you skip heading levels in a markdown document. For example, a heading level 1 should be followed by level 2, not level 3.
Why This Rule Exists
Proper heading hierarchy improves document structure, accessibility, and navigation. Screen readers and document outlines rely on sequential heading levels to convey the document's organization to users.
Examples
❌ Incorrect (violates rule)
# Title
### Subsection (skips h2)
## Back to h2
##### Deep section (skips h3 and h4)
✅ Correct
# Title
## Section
### Subsection
#### Subsubsection
##### Deep section
Configuration
This rule has no configuration options. It always enforces strict sequential heading levels.
When to Disable
Consider disabling this rule if:
- You're working with generated content that doesn't follow strict hierarchy
- You're importing documentation from external sources with different conventions
- Your project has specific heading level requirements
Rule Details
- Rule ID: MD001
- Aliases: heading-increment
- Category: Structure
- Severity: Warning
- Automatic Fix: Not available
Common Violations and Solutions
Skipping from H1 to H3
Problem:
# Main Title
### Subsection
Solution:
# Main Title
## Section
### Subsection
Deep Nesting Without Intermediate Levels
Problem:
## Chapter
##### Deep Detail
Solution:
## Chapter
### Section
#### Subsection
##### Deep Detail
Rationale for Sequential Headings
- Accessibility: Screen readers rely on proper heading hierarchy to help users navigate documents
- Document Structure: Sequential headings create a logical outline
- SEO: Search engines use heading structure to understand content hierarchy
- Table of Contents: Automated TOC generators expect proper nesting
Integration with mdBook
mdBook's sidebar generation relies on proper heading structure. Violating this rule can lead to:
- Broken navigation in the sidebar
- Incorrect TOC generation
- Poor mobile navigation experience
Related Rules
- MD002 - First heading should be a top-level heading
- MD003 - Heading style consistency
- MD022 - Headings should be surrounded by blank lines
- MD025 - Multiple top-level headings in the same document
References
MD002 - First Heading H1
First heading should be a top-level heading (H1).
Deprecated: This rule is superseded by MD041 which offers an improved implementation. Consider using MD041 instead.
Why This Rule Exists
Documents should start with a top-level heading (H1) to establish proper hierarchy. This ensures consistent document structure and helps screen readers and document outlines understand the content organization.
Examples
Incorrect
## Introduction
This document starts with an H2.
Correct
# Document Title
## Introduction
The document properly starts with an H1.
Configuration
[MD002]
level = 1 # Expected first heading level (default: 1)
When to Disable
- When using MD041 instead (recommended)
- Documents that are fragments or partials
- Auto-generated content with different conventions
Rule Details
- Rule ID: MD002
- Aliases: first-heading-h1
- Category: Structure
- Severity: Warning
- Auto-fix: Yes (can add H1 if missing)
- Deprecated: Yes (use MD041)
Related Rules
- MD001 - Heading increment
- MD041 - First line should be a top-level heading (replacement)
- MD025 - Single top-level heading
MD003 - Heading Style
Heading style should be consistent throughout the document.
Why This Rule Exists
Markdown supports multiple heading styles. Mixing styles within a document creates visual inconsistency and can confuse readers and tooling.
Heading Styles
ATX Style (Recommended)
# Heading 1
## Heading 2
### Heading 3
ATX Closed Style
#Heading 1#
##Heading 2##
###Heading 3###
Setext Style (H1 and H2 only)
Heading 1
=========
Heading 2
---------
Examples
Incorrect
# ATX Heading
Setext Heading
--------------
### Another ATX
Correct
# Main Title
## Section One
### Subsection
Configuration
[MD003]
style = "atx" # Options: "atx", "atx_closed", "setext", "consistent"
| Value | Description |
|---|---|
atx | Use # style headings |
atx_closed | Use # Heading # style |
setext | Use underline style (H1/H2 only) |
consistent | Match the first heading's style |
When to Disable
- Working with legacy documents using mixed styles
- Importing content from multiple sources
Rule Details
- Rule ID: MD003
- Aliases: heading-style
- Category: Structure
- Severity: Warning
- Auto-fix: Yes
Related Rules
- MD001 - Heading increment
- MD018 - Space after hash in ATX headings
- MD019 - Multiple spaces after hash
MD018 - No Space After Hash on ATX Style Heading
Severity: Warning
Category: Headings
Auto-fix: ✓ Available
Rule Description
This rule ensures there's a space after the hash character(s) in ATX-style headings. The space improves readability and is required by many markdown parsers.
Why This Rule Exists
A space after the hash is important because:
- Many markdown parsers require it for proper heading recognition
- Improves readability and consistency
- Follows CommonMark specification
- Prevents confusion with other hash-prefixed content
Examples
❌ Incorrect (violates rule)
# Heading without space
## Another heading missing space
### Third level also needs space
✅ Correct
# Heading with proper space
## Another heading correctly formatted
### Third level with space
Configuration
This rule has no configuration options.
Automatic Fix
This rule supports automatic fixing with --fix. The fix will:
- Add a single space after the hash character(s)
- Preserve the heading level and content
- Handle all heading levels (1-6)
Apply Fix
# Fix heading spacing issues
mdbook-lint lint --fix docs/
# Preview what would be fixed
mdbook-lint lint --fix --dry-run docs/
When to Disable
Consider disabling this rule if:
- You're working with a non-standard markdown parser that doesn't require spaces
- Your content includes hash-prefixed text that isn't meant to be headings
Disable in Config
# .mdbook-lint.toml
disabled_rules = ["MD018"]
Disable Inline
<!-- mdbook-lint-disable MD018 -->
# NoSpaceHeading
<!-- mdbook-lint-enable MD018 -->
Related Rules
- MD019 - Multiple spaces after hash on ATX heading
- MD020 - No space inside hashes on closed ATX heading
- MD021 - Multiple spaces inside hashes on closed ATX heading
- MD022 - Headings should be surrounded by blank lines
- MD023 - Headings must start at the beginning of the line
References
MD019 - Multiple Spaces After Hash on ATX Style Heading
Severity: Warning
Category: Headings
Auto-fix: ✓ Available
Rule Description
This rule ensures there's only a single space after the hash character(s) in ATX-style headings. Multiple spaces are unnecessary and can cause inconsistent formatting.
Why This Rule Exists
Single space after hash is important because:
- Maintains consistent formatting across documents
- Follows standard markdown conventions
- Reduces unnecessary whitespace
- Improves readability and predictability
Examples
❌ Incorrect (violates rule)
# Heading with multiple spaces
## Another heading with extra spaces
### Too many spaces here
✅ Correct
# Heading with single space
## Another heading correctly formatted
### Proper spacing
Configuration
This rule has no configuration options.
Automatic Fix
This rule supports automatic fixing with --fix. The fix will:
- Reduce multiple spaces to a single space after hash character(s)
- Preserve the heading level and content
- Handle all heading levels (1-6)
Apply Fix
# Fix multiple space issues in headings
mdbook-lint lint --fix docs/
# Preview what would be fixed
mdbook-lint lint --fix --dry-run docs/
When to Disable
Consider disabling this rule if:
- Your style guide requires multiple spaces for alignment
- You're maintaining legacy content with specific spacing requirements
Disable in Config
# .mdbook-lint.toml
disabled_rules = ["MD019"]
Disable Inline
<!-- mdbook-lint-disable MD019 -->
## Heading with multiple spaces allowed
<!-- mdbook-lint-enable MD019 -->
Related Rules
- MD018 - No space after hash on ATX heading
- MD020 - No space inside hashes on closed ATX heading
- MD021 - Multiple spaces inside hashes on closed ATX heading
- MD022 - Headings should be surrounded by blank lines
- MD023 - Headings must start at the beginning of the line
References
MD020 - No Space Inside Hashes on Closed ATX Style Heading
Severity: Warning
Category: Headings
Auto-fix: ✓ Available
Rule Description
This rule ensures closed ATX-style headings have no spaces inside the closing hashes. Closed ATX headings use trailing hash characters for symmetry, and spaces inside these markers are incorrect.
Why This Rule Exists
Proper closed heading format is important because:
- Follows the ATX heading specification
- Maintains consistent heading style
- Prevents parsing issues with some markdown processors
- Ensures visual symmetry in closed headings
Examples
❌ Incorrect (violates rule)
#Heading with space before closing#
## Another heading ##
### Heading with spaces ###
✅ Correct
#Heading with proper closing#
##Another heading##
###Correctly closed heading###
# Open heading is also fine
Configuration
This rule has no configuration options.
Automatic Fix
This rule supports automatic fixing with --fix. The fix will:
- Remove spaces between heading content and closing hashes
- Preserve the heading level and content
- Maintain the closed heading style
Apply Fix
# Fix closed heading spacing
mdbook-lint lint --fix docs/
# Preview what would be fixed
mdbook-lint lint --fix --dry-run docs/
When to Disable
Consider disabling this rule if:
- You prefer open ATX headings (without closing hashes)
- Your markdown processor has different requirements for closed headings
Disable in Config
# .mdbook-lint.toml
disabled_rules = ["MD020"]
Disable Inline
<!-- mdbook-lint-disable MD020 -->
# Heading with space #
<!-- mdbook-lint-enable MD020 -->
Related Rules
- MD018 - No space after hash on ATX heading
- MD019 - Multiple spaces after hash on ATX heading
- MD021 - Multiple spaces inside hashes on closed ATX heading
- MD022 - Headings should be surrounded by blank lines
- MD003 - Heading style
References
MD021 - Multiple Spaces Inside Hashes on Closed ATX Style Heading
Severity: Warning
Category: Headings
Auto-fix: ✓ Available
Rule Description
This rule ensures closed ATX-style headings have only a single space inside the hash markers. Multiple spaces create inconsistent formatting and unnecessary whitespace.
Why This Rule Exists
Single space inside closed headings is important because:
- Maintains consistent formatting
- Follows standard markdown conventions
- Improves readability
- Ensures proper rendering across different parsers
Examples
❌ Incorrect (violates rule)
#Heading with multiple spaces#
##Another heading##
###Too many internal spaces###
✅ Correct
#Heading with single spaces#
##Another heading##
###Properly spaced heading###
# Open heading is also fine
Configuration
This rule has no configuration options.
Automatic Fix
This rule supports automatic fixing with --fix. The fix will:
- Reduce multiple spaces to single spaces inside hash markers
- Preserve the heading level and content
- Maintain the closed heading style
Apply Fix
# Fix multiple spaces in closed headings
mdbook-lint lint --fix docs/
# Preview what would be fixed
mdbook-lint lint --fix --dry-run docs/
When to Disable
Consider disabling this rule if:
- Your style guide requires multiple spaces for alignment
- You prefer open ATX headings (without closing hashes)
Disable in Config
# .mdbook-lint.toml
disabled_rules = ["MD021"]
Disable Inline
<!-- mdbook-lint-disable MD021 -->
##Heading with multiple spaces##
<!-- mdbook-lint-enable MD021 -->
Related Rules
- MD018 - No space after hash on ATX heading
- MD019 - Multiple spaces after hash on ATX heading
- MD020 - No space inside hashes on closed ATX heading
- MD022 - Headings should be surrounded by blank lines
- MD003 - Heading style
References
MD022 - Blanks Around Headings
Headings should be surrounded by blank lines.
Why This Rule Exists
Blank lines around headings improve readability and ensure consistent rendering across Markdown parsers. Some parsers require blank lines to properly recognize headings.
Examples
Incorrect
Some paragraph text.
## Heading
More text here.
Correct
Some paragraph text.
## Heading
More text here.
Configuration
[MD022]
lines_above = 1 # Blank lines before heading (default: 1)
lines_below = 1 # Blank lines after heading (default: 1)
When to Disable
- Documents with compact formatting requirements
- Content where headings immediately follow other headings
Rule Details
- Rule ID: MD022
- Aliases: blanks-around-headings
- Category: Structure
- Severity: Warning
- Auto-fix: Yes
Related Rules
- MD023 - Headings start at beginning of line
- MD031 - Blanks around fenced code blocks
- MD032 - Blanks around lists
MD023 - Headings Must Start at the Beginning of the Line
Severity: Warning
Category: Headings
Auto-fix: ✓ Available
Rule Description
This rule ensures headings start at the beginning of the line without any leading spaces or tabs. Indented headings are not valid in standard markdown.
Why This Rule Exists
Headings at line start are important because:
- Indented text with hashes may be interpreted as code or regular text
- Ensures headings are properly recognized by all parsers
- Maintains consistent document structure
- Follows CommonMark specification
Examples
❌ Incorrect (violates rule)
# Indented heading
## Another indented heading
### Tab-indented heading
✅ Correct
# Heading at line start
## Another proper heading
### Correctly positioned heading
Configuration
This rule has no configuration options.
Automatic Fix
This rule supports automatic fixing with --fix. The fix will:
- Remove all leading whitespace (spaces and tabs) before headings
- Preserve the heading level and content
- Maintain proper heading structure
Apply Fix
# Fix indented headings
mdbook-lint lint --fix docs/
# Preview what would be fixed
mdbook-lint lint --fix --dry-run docs/
When to Disable
Consider disabling this rule if:
- You're documenting markdown syntax and showing indented hash examples
- Your content includes code blocks with hash-prefixed comments
Disable in Config
# .mdbook-lint.toml
disabled_rules = ["MD023"]
Disable Inline
<!-- mdbook-lint-disable MD023 -->
# This indented text is intentional
<!-- mdbook-lint-enable MD023 -->
Related Rules
- MD018 - No space after hash on ATX heading
- MD019 - Multiple spaces after hash on ATX heading
- MD022 - Headings should be surrounded by blank lines
- MD025 - Multiple top-level headings in the same document
- MD026 - Trailing punctuation in heading
References
MD024 - No Duplicate Headings
Multiple headings with the same content.
Why This Rule Exists
Duplicate headings can confuse readers and break anchor links. Each heading generates a URL fragment, and duplicates create ambiguous navigation targets.
Examples
Incorrect
# Guide
## Introduction
Some content.
## Introduction
More content with same heading.
Correct
# Guide
## Introduction
Some content.
## Getting Started
Different heading for different section.
Siblings Only Mode
With siblings_only: true, duplicates are allowed in different sections:
# Chapter 1
## Summary
Chapter 1 summary.
# Chapter 2
## Summary
Chapter 2 summary (allowed - different parent).
Configuration
[MD024]
siblings_only = false # Only check sibling headings (default: false)
allow_different_nesting = false # Allow same text at different levels
When to Disable
- Documents with intentionally repeated section names
- Auto-generated content with structured repetition
Rule Details
- Rule ID: MD024
- Aliases: no-duplicate-heading
- Category: Content
- Severity: Warning
- Auto-fix: No
Related Rules
MD025 - Single Top-Level Heading
Multiple top-level headings in the same document.
Why This Rule Exists
A document should have a single H1 heading that serves as its title. Multiple H1 headings suggest the content should be split into separate documents or the heading hierarchy needs adjustment.
Examples
Incorrect
# First Title
Content here.
# Second Title
More content.
Correct
# Document Title
## First Section
Content here.
## Second Section
More content.
Configuration
[MD025]
level = 1 # Heading level to check (default: 1)
front_matter_title = "" # Regex for front matter title
When to Disable
- SUMMARY.md files in mdBook (use MDBOOK025 instead)
- Documents intentionally containing multiple articles
- Changelog files with version headings as H1
Rule Details
- Rule ID: MD025
- Aliases: single-title, single-h1
- Category: Structure
- Severity: Warning
- Auto-fix: No
mdBook Integration
For SUMMARY.md files, this rule is automatically relaxed. Use MDBOOK025 which understands mdBook's multi-section SUMMARY format.
Related Rules
- MD001 - Heading increment
- MD002 - First heading H1
- MD041 - First line top-level heading
- MDBOOK025 - SUMMARY.md heading structure
MD026 - No Trailing Punctuation
Trailing punctuation in headings.
Why This Rule Exists
Headings typically don't end with punctuation like periods or commas. Trailing punctuation can look awkward in tables of contents and navigation menus.
Examples
Incorrect
# Welcome to the Guide.
## Getting Started:
### What is Markdown?
Correct
# Welcome to the Guide
## Getting Started
### What is Markdown
Questions (Configurable)
## Frequently Asked Questions
### How do I install it?
Configuration
[MD026]
punctuation = ".,;:!?" # Characters to flag (default: ".,;:!")
The ? is excluded by default to allow question headings in FAQ sections.
When to Disable
- Documents with headings that are complete sentences
- Stylistic choice to include punctuation
- FAQ sections with question marks (or adjust
punctuationconfig)
Rule Details
- Rule ID: MD026
- Aliases: no-trailing-punctuation
- Category: Formatting
- Severity: Warning
- Auto-fix: Yes (removes trailing punctuation)
Related Rules
MD041 - First Line Top-Level Heading
First line in a file should be a top-level heading.
Why This Rule Exists
Documents should start with a title heading to establish context. This helps with document navigation, accessibility, and table of contents generation.
Examples
Incorrect
Some introductory text before the heading.
# Document Title
Content here.
Correct
# Document Title
Some introductory text.
Content here.
With Front Matter
---
title: My Document
---
# Document Title
Content here.
Configuration
[MD041]
level = 1 # Expected heading level (default: 1)
front_matter_title = "^\\s*title\\s*[:=]" # Regex for front matter title
If front_matter_title matches, the document is considered to have a title
and the rule passes.
When to Disable
- Fragment documents included in larger documents
- Files with front matter providing the title
- Auto-generated content with different structure
Rule Details
- Rule ID: MD041
- Aliases: first-line-heading, first-line-h1
- Category: Structure
- Severity: Warning
- Auto-fix: No
Replaces MD002
This rule supersedes MD002 with improved handling of front matter and more configuration options.
Related Rules
List Rules
List rules ensure consistent formatting, indentation, and structure for both ordered and unordered lists.
Rules in This Category
| Rule | Description | Fix |
|---|---|---|
| MD004 | Unordered list style (consistent markers) | ❌ |
| MD005 | Inconsistent indentation for list items at the same level | ❌ |
| MD006 | Consider starting lists at the beginning of the line | ❌ |
| MD007 | Unordered list indentation | ❌ |
| MD029 | Ordered list item prefix | ❌ |
| MD030 | Spaces after list markers | ✅ |
| MD031 | Fenced code blocks should be surrounded by blank lines | ❌ |
| MD032 | Lists should be surrounded by blank lines | ❌ |
List Basics
Unordered Lists
Markdown supports three markers for unordered lists:
* Item with asterisk
- Item with dash
+ Item with plus
All render the same, but consistency is important.
Ordered Lists
1. First item
2. Second item
3. Third item
Or with lazy numbering:
1. First item
1. Second item
1. Third item
Best Practices
Consistent Markers
Pick one unordered list marker and stick with it:
Good:
* First item
* Second item
* Nested item
* Another nested
* Third item
Bad:
* First item
- Second item
+ Nested item
* Another nested
+ Third item
Proper Indentation
Use consistent indentation for nested lists:
2-space indentation:
* Parent item
* Child item
* Grandchild item
* Another child
* Another parent
4-space indentation:
* Parent item
* Child item
* Grandchild item
* Another child
* Another parent
Spacing After Markers
Maintain consistent spacing after list markers:
Good (single space):
* Item one
* Item two
1. First item
2. Second item
Bad (inconsistent):
*Item one
* Item two
1.First item
2. Second item
Complex List Structures
Multi-line List Items
For list items with multiple paragraphs:
1. First item with multiple paragraphs.
This is still part of the first item. Note the blank line above
and the indentation.
2. Second item.
* Nested list in second item
* Another nested item
3. Third item.
Lists with Code Blocks
Proper indentation for code blocks in lists:
1. Install the package:
```bash
npm install mdbook-lint
-
Configure the linter:
{ "rules": { "MD009": true } } -
Run the linter.
### Task Lists
GitHub Flavored Markdown task lists:
```markdown
- [x] Completed task
- [ ] Incomplete task
- [ ] Another todo
- [x] Completed subtask
- [ ] Incomplete subtask
Common Issues and Solutions
Issue: Inconsistent List Indentation
Problem:
* Item 1
* Nested with 2 spaces
* Deep nested with 4 spaces
* Wrong indentation
* More inconsistency
Solution:
* Item 1
* Nested with 2 spaces
* Consistent 2-space indent
* All items aligned
* Deeper nesting maintains pattern
Issue: Missing Blank Lines Around Lists
Problem:
Some paragraph text
* List starts immediately
* No separation
Paragraph continues here
Solution:
Some paragraph text
* List has blank line before
* Proper separation
Paragraph has blank line after list
Issue: Lazy Numbering Problems
Problem with lazy numbering:
1. First item
1. Second item
5. Oops, wrong number
1. Fourth item
Solution 1 (sequential):
1. First item
2. Second item
3. Third item
4. Fourth item
Solution 2 (all ones):
1. First item
1. Second item
1. Third item
1. Fourth item
Accessibility Considerations
Proper list formatting improves accessibility:
- Screen Readers: Announce list structure and item count
- Navigation: Users can skip between lists
- Context: Proper nesting conveys relationships
- Semantics: Lists convey meaning beyond visual formatting
mdBook-Specific Considerations
In mdBook projects:
- Table of Contents: Lists in SUMMARY.md define book structure
- Navigation: Nested lists create hierarchical navigation
- Rendering: List formatting affects HTML output
- Search: List items are indexed for search
Configuration Examples
Enforce Consistent Unordered List Style
[MD004]
style = "asterisk" # or "dash" or "plus"
Set List Indentation
[MD007]
indent = 2 # or 4, or any consistent value
Configure Ordered List Style
[MD029]
style = "ordered" # or "one" for all 1s
Spaces After List Markers
[MD030]
ul_single = 1 # Spaces after unordered list marker
ol_single = 1 # Spaces after ordered list marker
ul_multi = 1 # Spaces after marker for multi-line items
ol_multi = 1 # Spaces after marker for multi-line items
Related Rules
- MD013 - Line length (affects long list items)
- MD022 - Blank lines (around list blocks)
- MD031 - Code blocks in lists
- MD032 - Blank lines around lists
References
MD004 - Unordered List Style
Unordered list style should be consistent.
Why This Rule Exists
Markdown supports three markers for unordered lists: -, *, and +. Using
different markers inconsistently creates visual noise and can indicate
accidental mixing of content from different sources.
Examples
Incorrect
- Item one
* Item two
+ Item three
Correct
- Item one
- Item two
- Item three
Or consistently using asterisks:
* Item one
* Item two
* Item three
Configuration
[MD004]
style = "dash" # Options: "dash", "asterisk", "plus", "consistent"
| Value | Marker | Example |
|---|---|---|
dash | - | - Item |
asterisk | * | * Item |
plus | + | + Item |
consistent | First used | Matches first list marker |
When to Disable
- Documents intentionally using different markers to distinguish list types
- Importing content from multiple sources
Rule Details
- Rule ID: MD004
- Aliases: ul-style
- Category: Formatting
- Severity: Warning
- Auto-fix: Yes
Related Rules
- MD005 - List item indentation
- MD006 - Lists start at beginning of line
- MD007 - Unordered list indentation
- MD029 - Ordered list prefix style
MD005 - List Item Indentation
List item indentation should be consistent within a list.
Why This Rule Exists
Inconsistent indentation within lists can cause rendering issues and makes documents harder to read in source form. Proper indentation also ensures nested lists render correctly.
Examples
Incorrect
- Item one
- Item two (wrong indentation)
- Item three
Correct
- Item one
- Item two
- Item three
Nested Lists (Correct)
- Item one
- Nested item
- Another nested
- Item two
Configuration
This rule has no configuration options. It enforces consistent indentation within each list.
When to Disable
- Working with auto-generated content that has intentional spacing
- Documents with complex nested structures requiring manual control
Rule Details
- Rule ID: MD005
- Aliases: list-indent
- Category: Formatting
- Severity: Warning
- Auto-fix: Yes
Related Rules
- MD004 - Unordered list style
- MD006 - Lists start at beginning of line
- MD007 - Unordered list indentation depth
- MD030 - Spaces after list markers
MD006 - List Start Left
Consider starting bulleted lists at the beginning of the line.
Why This Rule Exists
Lists that don't start at the beginning of the line can cause unexpected rendering behavior in some Markdown parsers. Starting lists at column 0 ensures consistent rendering across all platforms.
Examples
Incorrect
Some text:
- Indented list item
- Another indented item
Correct
Some text:
- List item at start of line
- Another item at start of line
Nested Lists (Allowed)
- Top level item
- Nested item (indentation is fine for nesting)
- Another nested item
Configuration
This rule has no configuration options.
When to Disable
- Documents with intentionally indented lists for specific formatting
- Content that uses indentation for semantic meaning
Rule Details
- Rule ID: MD006
- Aliases: ul-start-left
- Category: Formatting
- Severity: Warning
- Auto-fix: Yes
Related Rules
- MD004 - Unordered list style
- MD005 - List item indentation consistency
- MD007 - Unordered list indentation
- MD023 - Headings start at beginning of line
MD007 - Unordered List Indentation
Unordered list indentation should use consistent spacing.
Why This Rule Exists
Proper indentation of nested lists ensures correct rendering and improves readability. Different Markdown parsers may interpret inconsistent indentation differently.
Examples
Incorrect (4-space indent when 2 expected)
- Item one
- Nested too far
- Item two
Correct (2-space indent)
- Item one
- Properly nested
- Another nested item
- Item two
Correct (4-space indent with configuration)
- Item one
- Nested with 4 spaces
- Another nested item
- Item two
Configuration
[MD007]
indent = 2 # Spaces per indentation level (default: 2)
start_indented = false # Allow first level to be indented
| Option | Default | Description |
|---|---|---|
indent | 2 | Number of spaces per nesting level |
start_indented | false | Allow top-level items to be indented |
When to Disable
- Documents following a different indentation standard
- Content imported from tools using different conventions
Rule Details
- Rule ID: MD007
- Aliases: ul-indent
- Category: Formatting
- Severity: Warning
- Auto-fix: Yes
Related Rules
- MD004 - Unordered list style
- MD005 - List item indentation consistency
- MD006 - Lists start at beginning of line
- MD029 - Ordered list prefix style
MD029 - Ordered List Prefix
Ordered list item prefix consistency.
Why This Rule Exists
Markdown supports different numbering styles for ordered lists. Consistent style improves readability and makes reordering items easier.
Styles
One-Based (All 1s)
1. First item
1. Second item
1. Third item
Advantage: Easy to reorder without renumbering.
Sequential (Ordered)
1. First item
2. Second item
3. Third item
Advantage: Source reflects rendered numbers.
Zero-Based
0. First item
0. Second item
0. Third item
Examples
Incorrect (Mixed)
1. First item
2. Second item
1. Third item
Correct
1. First item
2. Second item
3. Third item
Configuration
[MD029]
style = "one_or_ordered" # Options: "one", "ordered", "zero", "one_or_ordered"
| Value | Description |
|---|---|
one | All items use 1. |
ordered | Sequential numbering (1, 2, 3...) |
zero | All items use 0. |
one_or_ordered | Allow either 1. or sequential |
When to Disable
- Documents with intentional mixed numbering
- Content using numbers for reference purposes
Rule Details
- Rule ID: MD029
- Aliases: ol-prefix
- Category: Formatting
- Severity: Warning
- Auto-fix: Yes
Related Rules
MD030 - Spaces After List Markers
Severity: Warning
Category: Lists
Auto-fix: ✓ Available
Rule Description
This rule ensures consistent spacing after list markers (*, -, +, or numbers for ordered lists). Proper spacing improves readability and ensures correct parsing.
Why This Rule Exists
Consistent list marker spacing is important because:
- Ensures lists are properly recognized by all parsers
- Maintains uniform formatting across documents
- Improves readability and visual structure
- Some parsers require specific spacing for proper rendering
Examples
❌ Incorrect (violates rule)
*No space after asterisk
-No space after dash
+No space after plus
1.No space after number
* Too many spaces
- Excessive spacing
1. Too much space in ordered list
✅ Correct
* Single space after asterisk
- Single space after dash
+ Single space after plus
1. Single space after number
* Consistent spacing
* Nested items also follow rules
* Multi-level nesting works
Configuration
[MD030]
ul_single = 1 # Spaces after single-line unordered list marker (default: 1)
ul_multi = 1 # Spaces after multi-line unordered list marker (default: 1)
ol_single = 1 # Spaces after single-line ordered list marker (default: 1)
ol_multi = 1 # Spaces after multi-line ordered list marker (default: 1)
Automatic Fix
This rule supports automatic fixing with --fix. The fix will:
- Adjust spacing after list markers to match configuration
- Handle both ordered and unordered lists
- Preserve list content and nesting
- Maintain proper indentation for nested lists
Apply Fix
# Fix list marker spacing
mdbook-lint lint --fix docs/
# Preview what would be fixed
mdbook-lint lint --fix --dry-run docs/
When to Disable
Consider disabling this rule if:
- Your markdown processor has different spacing requirements
- You're working with generated content with specific formatting
- Your style guide requires different spacing patterns
Disable in Config
# .mdbook-lint.toml
disabled_rules = ["MD030"]
Disable Inline
<!-- mdbook-lint-disable MD030 -->
*No space after marker allowed here
<!-- mdbook-lint-enable MD030 -->
Related Rules
- MD004 - Unordered list style
- MD005 - Consistent list indentation
- MD006 - Consider starting lists at the beginning of the line
- MD007 - Unordered list indentation
- MD029 - Ordered list item prefix
- MD032 - Lists surrounded by blank lines
References
MD032 - Blanks Around Lists
Lists should be surrounded by blank lines.
Why This Rule Exists
Blank lines around lists ensure proper parsing and improve readability. Without blank lines, some Markdown parsers may not correctly identify list boundaries.
Examples
Incorrect
Some introductory text.
- Item one
- Item two
Following paragraph.
Correct
Some introductory text.
- Item one
- Item two
Following paragraph.
Configuration
This rule has no configuration options.
When to Disable
- Documents with compact formatting requirements
- Content with tight list-to-text flow
Rule Details
- Rule ID: MD032
- Aliases: blanks-around-lists
- Category: Structure
- Severity: Warning
- Auto-fix: Yes
Related Rules
- MD022 - Blanks around headings
- MD031 - Blanks around fenced code blocks
- MD058 - Blanks around tables
Whitespace Rules
These rules ensure consistent whitespace usage throughout your markdown documents.
Rules in This Category
Auto-fix Available ✓
- MD009 - No trailing spaces
- MD010 - Hard tabs
- MD012 - Multiple consecutive blank lines
- MD027 - Multiple spaces after blockquote symbol
- MD047 - Files should end with a single newline
Why Whitespace Matters
Consistent whitespace usage:
- Improves readability and maintainability
- Prevents version control issues (unnecessary diffs)
- Ensures consistent rendering across different viewers
- Follows standard text file conventions
- Reduces file size
Quick Configuration
# .mdbook-lint.toml
# Configure MD009 - Trailing spaces
[MD009]
br_spaces = 2 # Allow 2 spaces for line breaks
# Configure MD010 - Hard tabs
[MD010]
spaces_per_tab = 4 # Convert tabs to 4 spaces
# Configure MD012 - Multiple blank lines
[MD012]
maximum = 1 # Allow max 1 consecutive blank line
# Configure MD027 - Blockquote spacing
[MD027]
spaces = 1 # Require 1 space after >
Disable All Whitespace Rules
# .mdbook-lint.toml
disabled_rules = ["MD009", "MD010", "MD012", "MD027", "MD047"]
Related Categories
- Heading Rules - Heading formatting and structure
- List Rules - List formatting and indentation
MD009 - No Trailing Spaces
This rule checks for trailing spaces at the end of lines.
Why This Rule Exists
Trailing spaces are usually unintentional and can cause issues:
- They're invisible in most editors, making them hard to spot
- They can cause unexpected behavior in version control systems
- They may render differently across different markdown processors
- They increase file size unnecessarily
Examples
❌ Incorrect (violates rule)
This line has trailing spaces ← spaces
This one has a tab at the end ← tab
Multiple spaces here ← spaces
(Where arrows indicate invisible whitespace characters)
✅ Correct
This line has no trailing spaces
This one is clean too
Two spaces for line break are allowed
when configured (br_spaces = 2)
Configuration
[MD009]
br_spaces = 2 # Number of trailing spaces allowed for line breaks (default: 2)
strict = false # If true, disallow even configured line break spaces (default: false)
Automatic Fix
This rule supports automatic fixing. The fix will:
- Remove all trailing whitespace from lines
- Preserve configured line break spaces (typically 2 spaces)
- Maintain the line's content and structure
When to Disable
Consider disabling this rule if:
- Your project intentionally uses trailing spaces for formatting
Rule Details
- Rule ID: MD009
- Aliases: no-trailing-spaces
- Category: Whitespace
- Severity: Warning
- Automatic Fix: ✅ Available
Configuration Options
[MD009]
br_spaces = 2 # Number of spaces allowed for line breaks (default: 2)
strict = false # If true, disallow even configured line break spaces (default: false)
Understanding Line Breaks
Markdown supports two types of line breaks:
Hard Line Break (Two Spaces)
First line
Second line on new line
Renders as:
First line
Second line on new line
Paragraph Break (Blank Line)
First paragraph
Second paragraph
Renders as:
First paragraph
Second paragraph
Common Issues and Solutions
Invisible Trailing Spaces
Problem: Spaces at line ends are invisible in most editors.
This line has spaces ← invisible spaces
Another line with tab ← invisible tab
Solution: Configure your editor to show whitespace or use the automatic fix.
This line has no trailing spaces
Another clean line
Inconsistent Line Break Handling
Problem: Different markdown processors handle trailing spaces differently.
Some text
More text
Final text
Solution: Use exactly two spaces for line breaks when needed.
Some text
More text
Final text
Editor Configuration
Visual Studio Code
Add to settings.json:
{
"files.trimTrailingWhitespace": true,
"markdown.preview.breaks": true,
"[markdown]": {
"files.trimTrailingWhitespace": false
}
}
Vim
Add to .vimrc:
" Show trailing spaces
set list listchars=trail:·
" Remove trailing spaces on save
autocmd BufWritePre * %s/\s\+$//e
Sublime Text
Add to preferences:
{
"trim_trailing_white_space_on_save": true,
"draw_white_space": ["all"]
}
Version Control Considerations
Git Configuration
Configure Git to warn about trailing whitespace:
git config core.whitespace trailing-space
Pre-commit Hooks
Use a pre-commit hook to catch trailing spaces:
#!/bin/sh
# .git/hooks/pre-commit
exec git diff --check --cached
When Line Break Spaces Are Needed
Sometimes two trailing spaces are intentional:
Poetry and Verses
Roses are red
Violets are blue
Markdown is great
And so are you
Addresses
123 Main Street
Suite 100
Anytown, ST 12345
Preserving Formatting
Name: John Doe
Email: john@example.com
Phone: 555-1234
Performance Impact
Trailing spaces can impact:
- File Size: Unnecessary whitespace increases file size
- Diff Noise: Changes to trailing spaces clutter version control
- Search/Replace: Invisible characters can break patterns
- Copy/Paste: Trailing spaces may cause unexpected behavior
Automatic Fix Behavior
The automatic fix will:
- Remove all trailing whitespace from each line
- Preserve exactly
br_spacesspaces when configured (default: 2) - Handle tabs and mixed whitespace
- Maintain line endings (LF/CRLF)
Fix Examples
Before:
Text with spaces
Text with tab
Text with mixed
After (default config):
Text with spaces
Text with tab
Text with mixed
After (br_spaces=2, preserving line breaks):
Paragraph text
Line break needed
Next line
Related Rules
- MD010 - Hard tabs
- MD012 - Multiple consecutive blank lines
- MD047 - Files should end with a single newline
References
MD010 - Hard Tabs
Severity: Warning
Category: Whitespace
Auto-fix: ✓ Available
Rule Description
This rule checks for hard tab characters in the document and suggests replacing them with spaces for consistency.
Why This Rule Exists
Hard tabs can cause formatting inconsistencies:
- Tab width varies between editors (2, 4, or 8 spaces)
- Mixing tabs and spaces leads to misaligned text
- Different markdown renderers may handle tabs differently
- Code blocks and indentation become unpredictable
Examples
❌ Incorrect (violates rule)
→ This line starts with a tab
-→ List item with tab after marker
```→ Code block with tab indent
(Where → represents a tab character)
✅ Correct
This line uses spaces for indentation
- List item with spaces after marker
``` Code block with space indent
Configuration
[MD010]
code_blocks = true # Check for tabs in code blocks (default: true)
spaces_per_tab = 4 # Number of spaces to replace each tab with (default: 4)
Automatic Fix
This rule supports automatic fixing with --fix. The fix will:
- Replace each tab character with the configured number of spaces
- Preserve the visual indentation of your content
- Handle tabs in all contexts (text, lists, code blocks)
Apply Fix
# Fix all tab issues in your markdown files
mdbook-lint lint --fix docs/
# Preview what would be fixed
mdbook-lint lint --fix --dry-run docs/
When to Disable
Consider disabling this rule if:
- Your project requires hard tabs (e.g., Makefiles in code examples)
- You're working with legacy content that uses tabs consistently
- Your team has standardized on tabs instead of spaces
Disable in Config
# .mdbook-lint.toml
disabled_rules = ["MD010"]
Disable Inline
<!-- mdbook-lint-disable MD010 -->
→ Content with tabs allowed here
<!-- mdbook-lint-enable MD010 -->
Related Rules
- MD009 - No trailing spaces
- MD012 - Multiple consecutive blank lines
- MD047 - Files should end with newline
References
MD012 - Multiple Consecutive Blank Lines
Severity: Warning
Category: Whitespace
Auto-fix: ✓ Available
Rule Description
This rule checks for multiple consecutive blank lines in the document. Excessive blank lines add unnecessary whitespace without improving readability.
Why This Rule Exists
Multiple consecutive blank lines create issues:
- Inconsistent spacing throughout documents
- Unnecessary vertical space in rendered output
- Potential confusion about section boundaries
- Increased file size without benefit
Examples
❌ Incorrect (violates rule)
# Heading
First paragraph.
Second paragraph with too many blank lines above.
Third paragraph with even more blank lines.
✅ Correct
# Heading
First paragraph.
Second paragraph with single blank line.
Third paragraph properly spaced.
Configuration
[MD012]
maximum = 1 # Maximum consecutive blank lines allowed (default: 1)
Automatic Fix
This rule supports automatic fixing with --fix. The fix will:
- Reduce multiple consecutive blank lines to the configured maximum
- Preserve intentional spacing at the maximum level
- Handle blank lines anywhere in the document
Apply Fix
# Fix excessive blank lines
mdbook-lint lint --fix docs/
# Preview what would be fixed
mdbook-lint lint --fix --dry-run docs/
When to Disable
Consider disabling this rule if:
- Your style guide requires multiple blank lines for visual separation
- You're working with generated content that uses specific spacing
- You need extra spacing for ASCII art or diagrams
Disable in Config
# .mdbook-lint.toml
disabled_rules = ["MD012"]
Disable Inline
<!-- mdbook-lint-disable MD012 -->
Content with multiple
blank lines allowed here
<!-- mdbook-lint-enable MD012 -->
Related Rules
References
MD027 - Multiple Spaces After Blockquote Symbol
Severity: Warning
Category: Blockquotes
Auto-fix: ✓ Available
Rule Description
This rule ensures there's only a single space (or no space) after the blockquote marker (>). Multiple spaces create inconsistent formatting.
Why This Rule Exists
Consistent blockquote spacing is important because:
- Maintains uniform appearance across documents
- Reduces unnecessary whitespace
- Follows standard markdown conventions
- Improves readability and predictability
Examples
❌ Incorrect (violates rule)
> Multiple spaces after blockquote marker
>
>
> Even more spaces here
>
>
> Too much spacing
✅ Correct
> Single space after marker
> Consistent spacing throughout
> Clean and readable
>
>
>
>No space is also valid
>When configured appropriately
Configuration
[MD027]
spaces = 1 # Number of spaces after blockquote marker (default: 1, can be 0)
Automatic Fix
This rule supports automatic fixing with --fix. The fix will:
- Adjust spaces after blockquote markers to match configuration
- Preserve blockquote content and nesting
- Handle multi-level blockquotes correctly
Apply Fix
# Fix blockquote spacing
mdbook-lint lint --fix docs/
# Preview what would be fixed
mdbook-lint lint --fix --dry-run docs/
When to Disable
Consider disabling this rule if:
- Your style guide has different blockquote spacing requirements
- You're preserving legacy content with specific formatting
- You need variable spacing for visual emphasis
Disable in Config
# .mdbook-lint.toml
disabled_rules = ["MD027"]
Disable Inline
<!-- mdbook-lint-disable MD027 -->
> Blockquote with custom spacing
<!-- mdbook-lint-enable MD027 -->
Related Rules
References
MD028 - No Blanks in Blockquote
Blank line inside blockquote.
Why This Rule Exists
A blank line inside a blockquote ends the quote in most Markdown parsers. This can cause unexpected rendering where content intended to be quoted appears as regular text.
Examples
Incorrect
> First paragraph of quote.
>
> Second paragraph (this is a new blockquote).
Correct
> First paragraph of quote.
>
> Second paragraph (still in same blockquote).
Multiple Paragraphs
> This is a long quote that spans
> multiple paragraphs.
>
> The blank line has a `>` marker
> to continue the blockquote.
Configuration
This rule has no configuration options.
When to Disable
- Documents intentionally using separate blockquotes
- Content where visual separation is desired
Rule Details
- Rule ID: MD028
- Aliases: no-blanks-blockquote
- Category: Formatting
- Severity: Warning
- Auto-fix: Yes (adds
>to blank lines)
Related Rules
- MD027 - Multiple spaces after blockquote symbol
MD047 - Files Should End with a Single Newline Character
Severity: Warning
Category: Whitespace
Auto-fix: ✓ Available
Rule Description
This rule ensures files end with exactly one newline character. This is a POSIX standard and helps with version control systems.
Why This Rule Exists
Files ending with a newline are important because:
- POSIX standard requires text files to end with a newline
- Git and other VCS show "No newline at end of file" warnings
- Prevents issues when concatenating files
- Ensures consistent file formatting
- Some tools expect the trailing newline
Examples
❌ Incorrect (violates rule)
# Document
Last line without newline```
Or with multiple newlines:
```markdown
# Document
Last line with multiple newlines
✅ Correct
# Document
Last line with single newline
Configuration
This rule has no configuration options.
Automatic Fix
This rule supports automatic fixing with --fix. The fix will:
- Add a newline if the file doesn't end with one
- Remove extra newlines if there are multiple
- Ensure exactly one newline at the end of the file
Apply Fix
# Fix file endings
mdbook-lint lint --fix docs/
# Preview what would be fixed
mdbook-lint lint --fix --dry-run docs/
When to Disable
Consider disabling this rule if:
- You're working with files that intentionally lack final newlines
- Your toolchain doesn't support files with trailing newlines
- You're dealing with generated content that doesn't include newlines
Disable in Config
# .mdbook-lint.toml
disabled_rules = ["MD047"]
Disable for Specific Files
Since this is a file-level rule, you can exclude specific files:
# .mdbook-lint.toml
[[overrides]]
path = "no-newline.md"
disabled_rules = ["MD047"]
Related Rules
References
Link Rules
These rules ensure proper link formatting and validation in markdown documents.
Rules in This Category
Auto-fix Available ✓
- MD034 - Bare URL used
Other Link Rules
- MD011 - Reversed link syntax
- MD039 - Spaces inside link text
- MD042 - No empty links
- MD051 - Link fragments are valid
- MD052 - Reference links and images should use a label
- MD053 - Link and image reference definitions should be needed
- MD054 - Link and image style
- MD059 - Link and image reference style
Why Link Rules Matter
Proper link formatting:
- Ensures links are clickable in all renderers
- Improves accessibility with descriptive text
- Maintains consistent link style
- Prevents broken references
- Enhances document navigation
Common Link Formats
Inline Links
[Link text](https://example.com)
[Relative link](./other-page.md)
[Anchor link](#section-heading)
Reference Links
[Link text][reference]
[Another link][1]
[reference]: https://example.com
[1]: ./other-page.md
Autolinks
<https://example.com>
<user@example.com>
Quick Configuration
# .mdbook-lint.toml
# No configuration for MD034 - it auto-fixes bare URLs
# Disable specific link rules
disabled_rules = ["MD051", "MD052"]
Best Practices
- Use descriptive link text: Avoid "click here" or "link"
- Prefer relative paths: For internal documentation links
- Check anchors: Ensure heading anchors exist
- Use reference style: For frequently used URLs
- Wrap bare URLs: Use
<URL>syntax or proper links
Related Categories
- mdBook Rules - mdBook-specific link validation
- Style Rules - General formatting rules
MD011 - Reversed Link Syntax
Reversed link syntax should be corrected.
Why This Rule Exists
A common typo when writing Markdown links is reversing the bracket order,
writing ](url)[text instead of [text](url). This rule catches these
mistakes before they break your rendered documentation.
Examples
Incorrect
Check out ](https://example.com)[this link for more info.
See ](./other-page.md)[the other page.
Correct
Check out [this link](https://example.com) for more info.
See [the other page](./other-page.md).
Configuration
This rule has no configuration options.
When to Disable
- Generally should not be disabled as reversed links never render correctly
Rule Details
- Rule ID: MD011
- Aliases: no-reversed-links
- Category: Content
- Severity: Error
- Auto-fix: Yes (swaps to correct order)
Related Rules
MD034 - Bare URL Used
Severity: Warning
Category: Links
Auto-fix: ✓ Available
Rule Description
This rule flags bare URLs that should be enclosed in angle brackets or converted to proper markdown links. Bare URLs may not be clickable in all markdown renderers.
Why This Rule Exists
Proper URL formatting is important because:
- Ensures URLs are clickable in all markdown renderers
- Improves document accessibility
- Provides consistent link formatting
- Allows for descriptive link text
Examples
❌ Incorrect (violates rule)
Visit https://example.com for more information.
Check out http://github.com/user/repo
Documentation at www.example.org
✅ Correct
Visit <https://example.com> for more information.
Check out [this repository](http://github.com/user/repo)
Documentation at [example.org](https://www.example.org)
<!-- URLs in code blocks are ignored -->
git clone https://github.com/user/repo.git
Configuration
This rule has no configuration options.
Automatic Fix
This rule supports automatic fixing with --fix. The fix will:
- Wrap bare URLs in angle brackets (
<URL>) - Preserve surrounding text and formatting
- Skip URLs in code blocks and inline code
- Handle both HTTP and HTTPS URLs
Apply Fix
# Fix bare URLs
mdbook-lint lint --fix docs/
# Preview what would be fixed
mdbook-lint lint --fix --dry-run docs/
Manual Enhancement
After auto-fix, consider manually converting to descriptive links:
<!-- After auto-fix -->
Visit <https://example.com> for more information.
<!-- Better: manually add descriptive text -->
Visit [our website](https://example.com) for more information.
When to Disable
Consider disabling this rule if:
- Your markdown renderer automatically links bare URLs
- You're documenting URLs that shouldn't be clickable
- Your content includes many URLs in plain text format
Disable in Config
# .mdbook-lint.toml
disabled_rules = ["MD034"]
Disable Inline
<!-- mdbook-lint-disable MD034 -->
Plain URL: https://example.com
<!-- mdbook-lint-enable MD034 -->
Related Rules
References
MD039 - Spaces Inside Link Text
Spaces inside link text brackets.
Why This Rule Exists
Leading or trailing spaces inside link text brackets create inconsistent formatting and may render with unwanted whitespace in the clickable text.
Examples
Incorrect
[ Click here ](https://example.com)
[ Documentation ](./docs.md)
[ Link ](url)
Correct
[Click here](https://example.com)
[Documentation](./docs.md)
[Link](url)
Configuration
This rule has no configuration options.
When to Disable
- Generally should not be disabled as spaced link text is usually unintentional
Rule Details
- Rule ID: MD039
- Aliases: no-space-in-links
- Category: Links
- Severity: Warning
- Auto-fix: Yes (removes extra spaces)
Related Rules
- MD037 - Spaces inside emphasis
- MD038 - Spaces inside code spans
- MD042 - Empty links
- MD051 - Link fragments
MD042 - No Empty Links
No empty links allowed.
Why This Rule Exists
Empty links with no URL serve no purpose and indicate incomplete content or a mistake during editing. They create broken user experiences when clicked.
Examples
Incorrect
Click [here]() for more information.
See the [documentation]().
[Empty link]()
Correct
Click [here](https://example.com) for more information.
See the [documentation](./docs.md).
[Valid link](https://example.com)
Placeholder Links
If you need placeholders during drafting, use comments:
<!-- TODO: Add link -->
Click [here](#) for more information.
Configuration
This rule has no configuration options.
When to Disable
- Draft documents with intentional placeholders
- Templates where links are filled programmatically
Rule Details
- Rule ID: MD042
- Aliases: no-empty-links
- Category: Links
- Severity: Error
- Auto-fix: No
Related Rules
- MD011 - Reversed link syntax
- MD039 - Spaces inside link text
- MD051 - Link fragments
- MD052 - Reference links and images
MD051 - Link Fragments
Link fragments should be valid.
Why This Rule Exists
Fragment links (anchors) like #section-name should point to actual headings
in the document. Invalid fragments create broken navigation.
Examples
Incorrect
# Introduction
See the [configuration](#config) section.
## Configuration Options
Content here.
The fragment #config doesn't match #configuration-options.
Correct
# Introduction
See the [configuration](#configuration-options) section.
## Configuration Options
Content here.
How Fragments Are Generated
Headings become fragments by:
- Converting to lowercase
- Replacing spaces with hyphens
- Removing special characters
| Heading | Fragment |
|---|---|
## Getting Started | #getting-started |
## API Reference | #api-reference |
## What's New? | #whats-new |
Configuration
This rule has no configuration options.
When to Disable
- Documents with custom anchor IDs
- Content using JavaScript-based navigation
- Files processed by tools that modify anchors
Rule Details
- Rule ID: MD051
- Aliases: link-fragments
- Category: Links
- Severity: Warning
- Auto-fix: No
Performance
This rule is optimized for large documents. It builds a heading index once and validates all fragments efficiently.
Related Rules
- MD024 - Duplicate headings
- MD042 - Empty links
- MD052 - Reference links and images
- MDBOOK002 - Internal link validation
MD052 - Reference Links and Images
Reference links and images should use a label that is defined.
Why This Rule Exists
Reference-style links like [text][label] must have a corresponding definition.
Undefined references render as plain text instead of links.
Examples
Incorrect
Check out the [documentation][docs] for more info.
Visit [our website][site].
<!-- Missing definitions for 'docs' and 'site' -->
Correct
Check out the [documentation][docs] for more info.
Visit [our website][site].
[docs]: https://docs.example.com
[site]: https://example.com
Images
![Logo][logo]
[logo]: ./images/logo.png "Company Logo"
Configuration
This rule has no configuration options.
When to Disable
- Documents where references are defined in included files
- Templates with programmatically injected definitions
Rule Details
- Rule ID: MD052
- Aliases: reference-links-images
- Category: Links
- Severity: Warning
- Auto-fix: No
Reference Link Syntax
<!-- Full reference -->
[Link text][label]
<!-- Collapsed reference (label matches text) -->
[Example][]
<!-- Shortcut reference -->
[Example]
<!-- Definition -->
[label]: url "Optional Title"
Related Rules
MD053 - Link and Image Reference Definitions
Link and image reference definitions should be needed.
Why This Rule Exists
Unused reference definitions clutter documents and may indicate dead links or incomplete edits. Keeping only used definitions improves maintainability.
Examples
Incorrect
Check out the [documentation](https://docs.example.com).
[unused]: https://example.com
[also-unused]: https://other.com
The definitions are never used.
Correct
Check out the [documentation][docs].
[docs]: https://docs.example.com
Or remove unused definitions:
Check out the [documentation](https://docs.example.com).
Configuration
[MD053]
ignored_definitions = [] # Definitions to ignore (e.g., for includes)
Ignoring Definitions
[MD053]
ignored_definitions = ["//", "TODO"]
When to Disable
- Documents with definitions used in included content
- Templates with conditional reference usage
- Files serving as definition libraries
Rule Details
- Rule ID: MD053
- Aliases: link-image-reference-definitions
- Category: Links
- Severity: Warning
- Auto-fix: No
Related Rules
MD054 - Link and Image Style
Link and image style should be consistent.
Why This Rule Exists
Markdown supports inline and reference-style links. Consistent style throughout a document improves readability and maintainability.
Styles
Inline
[Link text](https://example.com)

Reference (Full)
[Link text][ref]
![Alt text][img]
[ref]: https://example.com
[img]: image.png
Reference (Collapsed)
[Example][]
[Example]: https://example.com
Reference (Shortcut)
[Example]
[Example]: https://example.com
Examples
Incorrect (Mixed)
See [inline link](https://example.com) and [reference link][ref].
[ref]: https://other.com
Correct
See [inline link](https://example.com) and [other link](https://other.com).
Configuration
[MD054]
autolink = true # Allow autolinks <https://example.com>
inline = true # Allow inline style
full = true # Allow full reference style
collapsed = true # Allow collapsed reference style
shortcut = true # Allow shortcut reference style
url_inline = true # Allow inline for URLs only
When to Disable
- Documents with intentional style mixing
- Content where different styles serve different purposes
Rule Details
- Rule ID: MD054
- Aliases: link-image-style
- Category: Links
- Severity: Warning
- Auto-fix: No
Related Rules
- MD052 - Reference links must be defined
- MD053 - Unused reference definitions
- MD059 - Descriptive link text
MD059 - Descriptive Link Text
Link text should be descriptive.
Why This Rule Exists
Generic link text like "click here" or "this link" provides no context about the destination. Descriptive text improves accessibility and helps users understand where links lead.
Examples
Incorrect
For more information, [click here](https://docs.example.com).
See [this link](./guide.md) for details.
[Here](https://api.example.com) is the API documentation.
Read more [here](./faq.md).
Correct
For more information, see the [complete documentation](https://docs.example.com).
See the [installation guide](./guide.md) for details.
Read the [API documentation](https://api.example.com).
Read the [frequently asked questions](./faq.md).
Configuration
This rule has no configuration options.
When to Disable
- UI documentation where "click here" matches actual button text
- Content with intentionally brief link descriptions
Rule Details
- Rule ID: MD059
- Aliases: descriptive-link-text
- Category: Accessibility
- Severity: Warning
- Auto-fix: No
Common Non-Descriptive Phrases
The rule flags these common patterns:
- "click here"
- "here"
- "this link"
- "this"
- "link"
- "read more"
Why Descriptive Text Matters
- Screen Readers: Users often navigate by links; "click here" provides no context
- Scanning: Users scan pages looking for relevant links
- SEO: Search engines use link text to understand content relationships
- Mobile: Touch targets benefit from clear labels
Accessibility Standards
This rule helps comply with:
- WCAG 2.1 Success Criterion 2.4.4 (Link Purpose in Context)
- WCAG 2.1 Success Criterion 2.4.9 (Link Purpose Link Only)
Related Rules
Code Rules
These rules ensure proper formatting of code blocks and inline code in markdown documents.
Rules in This Category
- MD040 - Fenced code blocks should have a language specified
- MD014 - Dollar signs used before commands without showing output
- MD031 - Fenced code blocks should be surrounded by blank lines
- MD038 - Spaces inside code span elements
- MD046 - Code block style
- MD048 - Code fence style
Why Code Rules Matter
Proper code formatting:
- Enables syntax highlighting for better readability
- Maintains consistency across code examples
- Improves copy-paste reliability
- Ensures proper rendering in different viewers
- Helps readers identify programming languages quickly
Code Block Styles
Fenced Code Blocks (Recommended)
```javascript
function example() {
return "Hello, world!";
}
```
Indented Code Blocks
function example() {
return "Hello, world!";
}
Inline Code
Use the `console.log()` function to debug.
Language Specifications
Common language tags for syntax highlighting:
| Language | Tags |
|---|---|
| JavaScript | js, javascript |
| TypeScript | ts, typescript |
| Python | py, python |
| Rust | rs, rust |
| Shell | sh, bash, shell |
| JSON | json |
| YAML | yml, yaml |
Quick Configuration
# .mdbook-lint.toml
# Configure MD040 - Require language tags
[MD040]
allowed_languages = ["js", "python", "rust", "bash"]
# Configure MD046 - Code block style
[MD046]
style = "fenced" # Options: "fenced", "indented", "consistent"
# Configure MD048 - Code fence style
[MD048]
style = "backtick" # Options: "backtick", "tilde", "consistent"
Best Practices
- Always specify language: Enables syntax highlighting
- Use fenced blocks: More flexible than indented blocks
- Surround with blank lines: Improves readability
- Be consistent: Use the same style throughout
- Escape special characters: Use backslash when needed
Shell Commands
# Good - shows command without prompt
npm install mdbook-lint
# Avoid - dollar sign without output
npm install mdbook-lint
Related Categories
- mdBook Rules - mdBook-specific code requirements
- Whitespace Rules - General spacing rules
MD014 - Dollar Signs in Commands
Dollar signs used before commands without showing output.
Why This Rule Exists
Including $ prompts in shell code blocks makes it harder for users to copy
and paste commands. If the code block shows command output, the prompt helps
distinguish input from output. Otherwise, it's just noise.
Examples
Incorrect
```bash
$ npm install
$ npm run build
### Correct (no prompts)
```markdown
```bash
npm install
npm run build
### Correct (showing output)
```markdown
```bash
$ echo "Hello"
Hello
$ ls
file1.txt file2.txt
## Configuration
This rule has no configuration options.
## When to Disable
- Tutorial content where prompts help indicate user input
- Documents showing interactive shell sessions
- Content distinguishing between different shell types
## Rule Details
- **Rule ID**: MD014
- **Aliases**: no-dollar-signs, commands-show-output
- **Category**: Content
- **Severity**: Warning
- **Auto-fix**: Yes (removes `$` prefix)
## Related Rules
- [MD040](./md040.md) - Fenced code blocks should have language
- [MD046](./md046.md) - Code block style
MD031 - Blanks Around Fences
Fenced code blocks should be surrounded by blank lines.
Why This Rule Exists
Blank lines around code blocks improve readability and ensure consistent rendering. Some parsers may not correctly identify code blocks without surrounding blank lines.
Examples
Incorrect
Some text here.
```code
let x = 1;
More text here.
### Correct
```markdown
Some text here.
```code
let x = 1;
More text here.
## Configuration
```toml
[MD031]
list_items = true # Apply rule inside list items (default: true)
When to Disable
- Documents with compact formatting requirements
- Content where code blocks intentionally flow with text
Rule Details
- Rule ID: MD031
- Aliases: blanks-around-fences
- Category: Formatting
- Severity: Warning
- Auto-fix: Yes
Related Rules
- MD022 - Blanks around headings
- MD032 - Blanks around lists
- MD040 - Fenced code blocks language
- MD046 - Code block style
MD038 - Spaces Inside Code Spans
Spaces inside code span markers.
Why This Rule Exists
Extra spaces inside backticks create inconsistent code formatting. The spaces become part of the rendered code, which usually isn't intended.
Examples
Incorrect
Use the ` print() ` function.
Run ` npm install ` to install.
Correct
Use the `print()` function.
Run `npm install` to install.
Intentional Spaces
If you need a backtick inside code, use double backticks with spaces:
Use `` `backticks` `` for code.
Configuration
This rule has no configuration options.
When to Disable
- Documents using spaces for specific code formatting
- Content requiring literal spaces in code spans
Rule Details
- Rule ID: MD038
- Aliases: no-space-in-code
- Category: Code
- Severity: Warning
- Auto-fix: Yes (removes extra spaces)
Related Rules
MD040 - Fenced Code Blocks Should Have a Language Specified
Severity: Warning
Category: Code
Auto-fix: Not available
Rule Description
This rule ensures that fenced code blocks specify a language for syntax highlighting. Language tags improve readability and enable proper syntax highlighting in rendered output.
Why This Rule Exists
Language specifications are important because:
- Enables syntax highlighting in rendered markdown
- Improves code readability and comprehension
- Helps readers quickly identify the programming language
- Ensures consistent code block presentation
- Required by many documentation tools (including mdBook)
Examples
❌ Incorrect (violates rule)
```
function hello() {
console.log("Hello, world!");
}
```
```
SELECT * FROM users WHERE active = true;
```
✅ Correct
```javascript
function hello() {
console.log("Hello, world!");
}
```
```sql
SELECT * FROM users WHERE active = true;
```
```bash
echo "Shell commands also benefit from highlighting"
```
```text
Plain text can be explicitly marked
```
Configuration
[MD040]
allowed_languages = [] # List of allowed languages (empty = all allowed)
language_optional = false # Whether language tag is optional (default: false)
Common Language Tags
| Language | Tags |
|---|---|
| JavaScript | js, javascript |
| TypeScript | ts, typescript |
| Python | py, python |
| Rust | rs, rust |
| Shell | sh, bash, shell |
| JSON | json |
| YAML | yml, yaml |
| Markdown | md, markdown |
| Plain Text | text, txt |
| TOML | toml |
| HTML | html |
| CSS | css |
| SQL | sql |
When to Disable
Consider disabling this rule if:
- Your markdown renderer doesn't support syntax highlighting
- You have many code blocks where language is obvious from context
- You're using custom code block processors that don't require language tags
Disable in Config
# .mdbook-lint.toml
disabled_rules = ["MD040"]
Disable Inline
<!-- mdbook-lint-disable MD040 -->
```
Code block without language tag
```
<!-- mdbook-lint-enable MD040 -->
Related Rules
- MD046 - Code block style
- MD048 - Code fence style
- MDBOOK001 - Code blocks should have language tags (mdBook-specific)
References
MD046 - Code Block Style
Code block style should be consistent.
Why This Rule Exists
Markdown supports both fenced code blocks (triple backticks) and indented code blocks (4 spaces). Consistent style improves readability.
Styles
Fenced (Recommended)
```rust
fn main() {
println!("Hello");
}
```
Indented
fn main() {
println!("Hello");
}
Examples
Incorrect (Mixed)
```python
print("Hello")
```
# Indented code block
echo "World"
Correct (Consistent Fenced)
```python
print("Hello")
```
```bash
echo "World"
```
Configuration
[MD046]
style = "fenced" # Options: "fenced", "indented", "consistent"
| Value | Description |
|---|---|
fenced | Use triple backticks |
indented | Use 4-space indentation |
consistent | Match first code block's style |
When to Disable
- Documents mixing styles intentionally
- Legacy content with established patterns
Rule Details
- Rule ID: MD046
- Aliases: code-block-style
- Category: Formatting
- Severity: Warning
- Auto-fix: Yes
Why Fenced is Recommended
- Supports language specification for syntax highlighting
- Clearer visual boundaries
- Easier to copy and paste
- Works better with nested content
Related Rules
MD048 - Code Fence Style
Code fence style should be consistent.
Why This Rule Exists
Markdown supports two fence styles: backticks and tildes. Consistent style throughout a document improves readability and maintainability.
Styles
Backticks (Common)
```rust
let x = 1;
```
Tildes
~~~rust
let x = 1;
~~~
Examples
Incorrect (Mixed)
```python
print("Hello")
```
~~~bash
echo "World"
~~~
Correct
```python
print("Hello")
```
```bash
echo "World"
```
Configuration
[MD048]
style = "backtick" # Options: "backtick", "tilde", "consistent"
| Value | Description |
|---|---|
backtick | Use triple backticks |
tilde | Use triple tildes |
consistent | Match first fence's style |
When to Disable
- Documents with intentional style mixing
- Content with nested code blocks (tildes inside backticks)
Rule Details
- Rule ID: MD048
- Aliases: code-fence-style
- Category: Formatting
- Severity: Warning
- Auto-fix: Yes
Nesting Code Blocks
To show code fences inside code blocks, use different styles:
````markdown
```rust
let x = 1;
```
````
Related Rules
Style Rules
These rules enforce consistent styling choices throughout your markdown documents.
Rules in This Category
- MD013 - Line length
- MD003 - Heading style
- MD035 - Horizontal rule style
- MD036 - Emphasis used instead of a heading
- MD044 - Proper names should have correct capitalization
- MD049 - Emphasis style should be consistent
- MD050 - Strong style should be consistent
Why Style Rules Matter
Consistent style:
- Creates professional, polished documentation
- Improves readability and scanning
- Reduces cognitive load for readers
- Maintains brand and project consistency
- Facilitates team collaboration
Common Style Choices
Heading Styles
# ATX Style Heading (Recommended)
Setext Style Heading
====================
Emphasis Styles
*Italic with asterisks*
_Italic with underscores_
**Bold with asterisks**
__Bold with underscores__
Horizontal Rules
---
---
---
Quick Configuration
# .mdbook-lint.toml
# Configure MD013 - Line length
[MD013]
line_length = 100
code_blocks = false
tables = false
# Configure MD003 - Heading style
[MD003]
style = "atx" # Options: "atx", "setext", "consistent"
# Configure MD035 - Horizontal rule style
[MD035]
style = "---" # Use three hyphens
# Configure MD049 - Emphasis style
[MD049]
style = "asterisk" # Options: "asterisk", "underscore", "consistent"
# Configure MD050 - Strong style
[MD050]
style = "asterisk" # Options: "asterisk", "underscore", "consistent"
Style Guide Template
Create a consistent style guide for your project:
# .mdbook-lint.toml - Project Style Guide
# Line length for readability
[MD013]
line_length = 80
# ATX headings only
[MD003]
style = "atx"
# Consistent emphasis
[MD049]
style = "asterisk"
[MD050]
style = "asterisk"
# Three hyphens for horizontal rules
[MD035]
style = "---"
Best Practices
- Choose and document: Pick a style and document it
- Be consistent: Use the same style throughout
- Consider your audience: Technical vs. general readers
- Think about rendering: How it looks in your target output
- Automate checks: Use CI/CD to enforce style
Related Categories
- Heading Rules - Detailed heading formatting
- Whitespace Rules - Spacing and indentation
- Code Rules - Code formatting standards
MD013 - Line Length
Severity: Warning
Category: Style
Auto-fix: Not available
Rule Description
This rule enforces a maximum line length for markdown files. Long lines can be difficult to read and review, especially in terminals and diff views.
Why This Rule Exists
Line length limits are important because:
- Improves readability in narrow windows and terminals
- Makes diffs easier to review in version control
- Follows traditional text formatting conventions
- Prevents horizontal scrolling in editors
- Facilitates side-by-side comparisons
Examples
❌ Incorrect (violates rule)
This is an extremely long line that goes on and on and on, exceeding the configured maximum line length and making it difficult to read in narrow terminals or when viewing diffs.
✅ Correct
This line is broken up into shorter segments.
It's easier to read and review.
Each line stays within the configured limit.
Configuration
[MD013]
line_length = 80 # Maximum line length (default: 80)
code_blocks = true # Check code blocks (default: true)
tables = true # Check tables (default: true)
headings = true # Check headings (default: true)
strict = false # Strict length (no leniency for URLs) (default: false)
stern = false # Stern length (allow long lines with no spaces) (default: false)
When to Disable
Consider disabling this rule if:
- Your team prefers no line length limits
- You're working with content that requires long lines (tables, URLs)
- Your documentation is primarily viewed in wide screens
- You have many long code examples
Disable in Config
# .mdbook-lint.toml
disabled_rules = ["MD013"]
Disable Inline
<!-- mdbook-lint-disable MD013 -->
This can be a very long line that exceeds the normal limits without triggering a violation.
<!-- mdbook-lint-enable MD013 -->
Disable for Specific Elements
[MD013]
# Keep line length check but exclude certain elements
code_blocks = false # Don't check code blocks
tables = false # Don't check tables
Tips for Compliance
- Break at natural points: Sentences, clauses, or phrases
- Use soft wrapping: Let your editor wrap visually while keeping semantic lines
- Consider semantic line breaks: One sentence per line
- Extract long URLs: Use reference-style links
<!-- Instead of -->
Check out [this very long link text](https://example.com/very/long/path/to/resource)
<!-- Use -->
Check out [this very long link text][1]
[1]: https://example.com/very/long/path/to/resource
Related Rules
References
MD035 - Horizontal Rule Style
Horizontal rule style should be consistent.
Why This Rule Exists
Markdown supports multiple horizontal rule syntaxes. Consistent style throughout a document improves readability and maintainability.
Styles
---
---
---
---
** *
All render as horizontal rules but mixing them is inconsistent.
Examples
Incorrect
Section one content.
---
Section two content.
---
Section three content.
Correct
Section one content.
---
Section two content.
---
Section three content.
Configuration
[MD035]
style = "---" # Options: "---", "***", "___", "consistent"
| Value | Description |
|---|---|
--- | Three dashes |
*** | Three asterisks |
___ | Three underscores |
consistent | Match first occurrence |
When to Disable
- Documents with intentional style variation
- Content imported from multiple sources
Rule Details
- Rule ID: MD035
- Aliases: hr-style
- Category: Formatting
- Severity: Warning
- Auto-fix: Yes
Related Rules
MD043 - Required Heading Structure
Required heading structure not found.
Why This Rule Exists
Some documents must follow a specific heading structure for consistency across a project. This rule enforces a predefined heading pattern.
Examples
Configuration
[MD043]
headings = ["# Title", "## Introduction", "## Usage", "## API", "## License"]
Incorrect
# My Project
## Getting Started
## API Reference
Missing required "Introduction" and has different heading names.
Correct
# Title
## Introduction
## Usage
## API
## License
Configuration 2
[MD043]
headings = [] # Required headings in order
match_case = true # Case-sensitive matching (default: true)
Wildcards
Use * to allow any heading at a position:
[MD043]
headings = ["# *", "## Introduction", "##*"]
When to Disable
- Documents that don't follow a template
- Creative content without structure requirements
- Auto-generated files
Rule Details
- Rule ID: MD043
- Aliases: required-headings
- Category: Structure
- Severity: Warning
- Auto-fix: No
Use Cases
- README templates across repositories
- Documentation standards
- API documentation structure
- Legal documents with required sections
Related Rules
MD044 - Proper Names Capitalization
Proper names should have correct capitalization.
Why This Rule Exists
Brand names, product names, and technical terms often have specific capitalization. Consistent capitalization improves professionalism and readability.
Examples
Configuration
[MD044]
names = ["JavaScript", "GitHub", "macOS", "iOS"]
code_blocks = false # Don't check inside code blocks
Incorrect
Install the package using Github.
This works on MacOS and IOS devices.
Learn javascript programming.
Correct
Install the package using GitHub.
This works on macOS and iOS devices.
Learn JavaScript programming.
Configuration 2
[MD044]
names = [] # List of proper names with correct capitalization
code_blocks = false # Check inside code blocks (default: false)
html_elements = false # Check inside HTML elements (default: false)
Common Names
[MD044]
names = [
"JavaScript", "TypeScript", "Node.js", "npm",
"GitHub", "GitLab", "Bitbucket",
"macOS", "iOS", "iPadOS", "watchOS", "tvOS",
"MySQL", "PostgreSQL", "MongoDB", "SQLite",
"Rust", "Python", "Ruby", "Kotlin"
]
When to Disable
- Documents where capitalization varies intentionally
- Code-heavy content where names appear in identifiers
- Historical documents preserving original text
Rule Details
- Rule ID: MD044
- Aliases: proper-names
- Category: Style
- Severity: Warning
- Auto-fix: No
Notes
The rule is smart about context:
- Ignores text inside code blocks (configurable)
- Ignores text inside inline code spans
- Ignores URLs and link destinations
Related Rules
- MD038 - Spaces inside code spans
Emphasis Rules
Rules for formatting bold and italic text.
Rules in This Category
| Rule | Description | Auto-fix |
|---|---|---|
| MD036 | Emphasis used instead of heading | No |
| MD037 | Spaces inside emphasis markers | Yes |
| MD049 | Emphasis style consistency | Yes |
| MD050 | Strong emphasis style consistency | Yes |
Overview
Emphasis rules ensure consistent formatting of bold (**text**) and italic
(*text*) text throughout your documents.
Common Issues
-
Using
**bold**on a line by itself as a pseudo-heading -
Spaces inside markers like
**bold**that may not render -
Mixing asterisks and underscores inconsistently
Best Practices
- Use real headings (
##) instead of bold text for sections - Keep emphasis markers tight against text (no internal spaces)
- Choose one style (asterisks or underscores) and use it consistently
MD036 - Emphasis Instead of Heading
Emphasis used instead of a heading.
Why This Rule Exists
Using bold or italic text on its own line as a pseudo-heading breaks document structure. Real headings provide proper hierarchy for navigation, accessibility, and table of contents generation.
Examples
Incorrect
**Introduction**
This section introduces the topic.
*Getting Started*
Follow these steps to begin.
Correct
## Introduction
This section introduces the topic.
## Getting Started
Follow these steps to begin.
Configuration
[MD036]
punctuation = ".,;:!?。;:!?" # Punctuation that indicates not a heading
Lines ending with punctuation are assumed to be emphasized text, not pseudo-headings.
When to Disable
- Documents using emphasis for visual styling
- Content where headings aren't appropriate
- Presentations or slides with different formatting needs
Rule Details
- Rule ID: MD036
- Aliases: no-emphasis-as-heading
- Category: Emphasis
- Severity: Warning
- Auto-fix: No
Why This Matters
Pseudo-headings created with emphasis:
- Don't appear in table of contents
- Break accessibility for screen readers
- Can't be linked to with anchors
- Don't contribute to document outline
Related Rules
MD037 - Spaces Inside Emphasis
Spaces inside emphasis markers.
Why This Rule Exists
Spaces immediately inside emphasis markers may prevent proper rendering in some Markdown parsers. The emphasis won't be applied, leaving literal asterisks or underscores in the output.
Examples
Incorrect
This is **bold** text.
This is *italic* text.
Here is __also bold__ text.
Correct
This is **bold** text.
This is *italic* text.
Here is __also bold__ text.
Configuration
This rule has no configuration options.
When to Disable
- Generally should not be disabled as spaced emphasis rarely renders correctly
Rule Details
- Rule ID: MD037
- Aliases: no-space-in-emphasis
- Category: Emphasis
- Severity: Warning
- Auto-fix: Yes (removes spaces inside markers)
Related Rules
- MD038 - Spaces inside code spans
- MD039 - Spaces inside link text
- MD049 - Emphasis style
- MD050 - Strong style
MD049 - Emphasis Style
Emphasis style should be consistent.
Why This Rule Exists
Markdown supports two emphasis markers: asterisks and underscores. Consistent style improves readability and maintainability.
Styles
Asterisks
This is *italic* text.
Underscores
This is _italic_ text.
Examples
Incorrect (Mixed)
This is *italic* and this is _also italic_.
Use *consistent* formatting _throughout_ the document.
Correct
This is *italic* and this is *also italic*.
Use *consistent* formatting *throughout* the document.
Configuration
[MD049]
style = "asterisk" # Options: "asterisk", "underscore", "consistent"
| Value | Description |
|---|---|
asterisk | Use *text* |
underscore | Use _text_ |
consistent | Match first occurrence |
When to Disable
- Documents with intentional style variation
- Content imported from multiple sources
Rule Details
- Rule ID: MD049
- Aliases: emphasis-style
- Category: Formatting
- Severity: Warning
- Auto-fix: Yes
Note on Underscores
Underscores inside words are not treated as emphasis:
some_variable_name <!-- Not italic, just text -->
Related Rules
MD050 - Strong Style
Strong emphasis style should be consistent.
Why This Rule Exists
Markdown supports two strong emphasis markers: double asterisks and double underscores. Consistent style improves readability.
Styles
Asterisks (Common)
This is **bold** text.
Underscores
This is __bold__ text.
Examples
Incorrect (Mixed)
This is **bold** and this is __also bold__.
Correct
This is **bold** and this is **also bold**.
Configuration
[MD050]
style = "asterisk" # Options: "asterisk", "underscore", "consistent"
| Value | Description |
|---|---|
asterisk | Use **text** |
underscore | Use **text** |
| consistent | Match first occurrence |
When to Disable
- Documents with intentional style variation
- Content imported from multiple sources
Rule Details
- Rule ID: MD050
- Aliases: strong-style
- Category: Formatting
- Severity: Warning
- Auto-fix: Yes
Related Rules
Table Rules
Rules for formatting Markdown tables.
Rules in This Category
| Rule | Description | Auto-fix |
|---|---|---|
| MD055 | Table pipe style consistency | Yes |
| MD056 | Table column count | Yes |
| MD058 | Tables surrounded by blank lines | Yes |
Overview
Table rules ensure consistent and valid table formatting. Properly formatted tables render correctly across all Markdown parsers.
Common Issues
- Inconsistent pipe style (leading/trailing pipes)
- Rows with different numbers of columns
- Tables not separated from surrounding content
Best Practices
- Use leading and trailing pipes for clarity
- Ensure all rows have the same number of columns
- Surround tables with blank lines
- Align columns for readable source (optional)
Example Table
| Header 1 | Header 2 | Header 3 |
|----------|----------|----------|
| Cell 1 | Cell 2 | Cell 3 |
| Cell 4 | Cell 5 | Cell 6 |
MD055 - Table Pipe Style
Table pipe style should be consistent.
Why This Rule Exists
Markdown tables can have leading and trailing pipes or omit them. Consistent style improves readability and source formatting.
Styles
Leading and Trailing (Recommended)
| Header 1 | Header 2 |
|----------|----------|
| Cell 1 | Cell 2 |
No Leading/Trailing
Header 1 | Header 2
---------|----------
Cell 1 | Cell 2
Leading Only
| Header 1 | Header 2
|----------|----------
| Cell 1 | Cell 2
Examples
Incorrect (Mixed)
| Header 1 | Header 2 |
|----------|----------|
Cell 1 | Cell 2
Correct
| Header 1 | Header 2 |
|----------|----------|
| Cell 1 | Cell 2 |
Configuration
[MD055]
style = "leading_and_trailing" # Options: see below
| Value | Description |
|---|---|
leading_and_trailing | Pipes on both ends |
leading_only | Only leading pipes |
trailing_only | Only trailing pipes |
no_leading_or_trailing | No outer pipes |
consistent | Match first table's style |
When to Disable
- Documents with tables from different sources
- Content where specific formatting is required
Rule Details
- Rule ID: MD055
- Aliases: table-pipe-style
- Category: Formatting
- Severity: Warning
- Auto-fix: Yes
Related Rules
MD056 - Table Column Count
Table column count should be consistent.
Why This Rule Exists
All rows in a table should have the same number of columns. Mismatched column counts cause rendering issues and indicate data entry errors.
Examples
Incorrect
| Header 1 | Header 2 | Header 3 |
|----------|----------|----------|
| Cell 1 | Cell 2 |
| Cell 1 | Cell 2 | Cell 3 | Cell 4 |
Row 2 has too few columns, row 3 has too many.
Correct
| Header 1 | Header 2 | Header 3 |
|----------|----------|----------|
| Cell 1 | Cell 2 | Cell 3 |
| Cell 4 | Cell 5 | Cell 6 |
Empty Cells
| Header 1 | Header 2 | Header 3 |
|----------|----------|----------|
| Cell 1 | | Cell 3 |
| Cell 4 | Cell 5 | |
Configuration
This rule has no configuration options.
When to Disable
- Tables intentionally using colspan-like behavior
- Content from sources with non-standard table formats
Rule Details
- Rule ID: MD056
- Aliases: table-column-count
- Category: Structure
- Severity: Error
- Auto-fix: Yes (adds empty cells)
Related Rules
MD058 - Blanks Around Tables
Tables should be surrounded by blank lines.
Why This Rule Exists
Blank lines around tables ensure proper parsing and improve readability. Some Markdown parsers require blank lines to correctly identify table boundaries.
Examples
Incorrect
Some text here.
| Header 1 | Header 2 |
|----------|----------|
| Cell 1 | Cell 2 |
More text here.
Correct
Some text here.
| Header 1 | Header 2 |
|----------|----------|
| Cell 1 | Cell 2 |
More text here.
Configuration
This rule has no configuration options.
When to Disable
- Documents with compact formatting requirements
- Content where tables flow tightly with surrounding text
Rule Details
- Rule ID: MD058
- Aliases: blanks-around-tables
- Category: Formatting
- Severity: Warning
- Auto-fix: Yes
Related Rules
- MD022 - Blanks around headings
- MD031 - Blanks around fenced code blocks
- MD032 - Blanks around lists
- MD055 - Table pipe style
- MD056 - Table column count
Image Rules
Rules for image formatting and accessibility.
Rules in This Category
| Rule | Description | Auto-fix |
|---|---|---|
| MD045 | Images should have alt text | Yes |
Overview
Image rules ensure that images are accessible and properly formatted.
Why Alt Text Matters
Alt text (alternative text) serves multiple purposes:
- Accessibility: Screen readers use alt text to describe images
- Fallback: Displays when images fail to load
- SEO: Search engines use alt text to understand image content
Best Practices
- Write descriptive alt text that conveys the image's purpose
- Keep alt text concise but informative
- For decorative images, use empty alt text
 - Describe charts and diagrams with their key data points
Example

MD045 - Images Should Have Alt Text
Images should have alternate text (alt text).
Why This Rule Exists
Alt text is essential for accessibility. Screen readers use it to describe images to visually impaired users. It also displays when images fail to load.
Examples
Incorrect

![][logo]
[logo]: logo.png
Correct

![Company logo][logo]
[logo]: logo.png "Company Logo"
Good Alt Text


Configuration
This rule has no configuration options.
When to Disable
- Decorative images that don't convey information
- Documents where images are supplementary
Rule Details
- Rule ID: MD045
- Aliases: no-alt-text
- Category: Images
- Severity: Warning
- Auto-fix: Yes (adds placeholder alt text)
Writing Good Alt Text
| Image Type | Alt Text Approach |
|---|---|
| Informative | Describe the content and purpose |
| Decorative | Use empty alt  |
| Charts | Summarize the data shown |
| Screenshots | Describe what the screenshot shows |
| Icons | Describe the action or meaning |
Accessibility Standards
This rule helps comply with:
- WCAG 2.1 Success Criterion 1.1.1 (Non-text Content)
- Section 508 accessibility requirements
Related Rules
HTML Rules
Rules for inline HTML usage in Markdown.
Rules in This Category
| Rule | Description | Auto-fix |
|---|---|---|
| MD033 | Inline HTML should be avoided | No |
Overview
HTML rules control the use of raw HTML within Markdown documents. While Markdown supports inline HTML, using it reduces portability and can introduce security concerns.
Why Avoid HTML
- Portability: Not all Markdown renderers support HTML
- Security: HTML can introduce XSS vulnerabilities
- Maintainability: Markdown is easier to read and maintain
- Consistency: Mixing HTML and Markdown creates inconsistent documents
When HTML Is Acceptable
Some features require HTML:
- Collapsible sections (
<details>) - Keyboard shortcuts (
<kbd>) - Subscript/superscript (
<sub>,<sup>) - Complex layouts not possible in Markdown
Configuration
Allow specific HTML elements while blocking others:
[MD033]
allowed_elements = ["details", "summary", "kbd", "br"]
MD033 - No Inline HTML
Inline HTML should be avoided.
Why This Rule Exists
Markdown documents should remain portable and renderable in environments that don't support HTML. Raw HTML also makes documents harder to maintain and can introduce security concerns in some contexts.
Examples
Incorrect
<div class="warning">
This is a warning message.
</div>
Click <a href="https://example.com">here</a> for more.
<br>
<img src="image.png" alt="An image">
Correct
> **Warning**: This is a warning message.
Click [here](https://example.com) for more.

Configuration
[MD033]
allowed_elements = [] # HTML elements to allow (default: none)
Allow Specific Elements
[MD033]
allowed_elements = ["br", "details", "summary"]
When to Disable
- Documents requiring HTML features not in Markdown
- Content using HTML for accessibility features
- mdBook projects using HTML preprocessors
Rule Details
- Rule ID: MD033
- Aliases: no-inline-html
- Category: Content
- Severity: Warning
- Auto-fix: No
Common Allowed Elements
| Element | Use Case |
|---|---|
br | Line breaks within paragraphs |
details | Collapsible sections |
summary | Summary for details |
kbd | Keyboard input |
sub, sup | Subscript/superscript |
Related Rules
- MD045 - Images should have alt text
mdBook-Specific Rules
These rules are specifically designed for mdBook projects, validating mdBook-specific syntax, conventions, and structure.
Rules
| Rule ID | Name | Description |
|---|---|---|
| MDBOOK001 | code-block-language | Code blocks should have language tags |
| MDBOOK002 | summary-structure | SUMMARY.md should follow mdBook structure |
| MDBOOK003 | internal-links | Internal links should be valid |
| MDBOOK004 | part-titles | Part titles should be formatted correctly |
| MDBOOK005 | chapter-paths | Chapter paths should be relative |
| MDBOOK006 | draft-chapters | Draft chapters should have content or be marked |
| MDBOOK007 | separator-syntax | Separator syntax should be correct |
Why mdBook-Specific Rules
mdBook extends standard Markdown with special features:
- SUMMARY.md Structure: Defines book organization
- Include Syntax:
{{#include file.md}} - Playground Links:
{{#playground file.rs}} - Hidden Lines: Lines starting with
#in Rust code blocks - Quiz Support: Interactive quizzes in documentation
- Custom Renderers: Different output formats
These rules ensure your mdBook project:
- Builds correctly
- Renders properly in all output formats
- Maintains consistent structure
- Follows mdBook best practices
SUMMARY.md Structure
The SUMMARY.md file is the backbone of any mdBook project:
# Summary
[Introduction](./introduction.md)
# User Guide
- [Installation](./guide/installation.md)
- [Getting Started](./guide/getting-started.md)
- [Basic Usage](./guide/basic-usage.md)
- [Advanced Usage](./guide/advanced-usage.md)
# Reference
- [Configuration](./reference/configuration.md)
- [API](./reference/api.md)
---
[Contributors](./contributors.md)
Rules MDBOOK002-MDBOOK007 validate various aspects of this structure.
Common mdBook Issues
Missing Language Tags
Problem: Code blocks without language tags don't get syntax highlighting.
fn main() { println!("No highlighting!"); }
Solution: Always specify the language.
```rust
fn main() {
println!("Properly highlighted!");
}
### Broken Internal Links
**Problem**: Links to non-existent chapters break navigation.
```markdown
- [Missing Chapter](./does-not-exist.md)
Solution: Ensure all linked files exist.
Invalid SUMMARY.md Format
Problem: Incorrect indentation or syntax breaks book generation.
- [Chapter 1](./chapter1.md)
- [Wrong indent](./sub.md) # Should be 2 spaces, not 4
[Missing dash](./chapter2.md) # Should be "- [...]"
Solution: Follow mdBook's SUMMARY.md conventions.
Integration with CI/CD
Use these rules in your CI pipeline:
# .github/workflows/mdbook.yml
name: mdBook Checks
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run mdbook-lint
run: |
cargo install mdbook-lint
mdbook-lint check
Configuration
Enable only mdBook rules:
# .mdbook-lint.toml
[rules]
# Disable all standard rules
"MD*" = false
# Enable only mdBook rules
"MDBOOK*" = true
Or enable both sets:
[rules]
# Use defaults (all rules enabled)
# Customize specific mdBook rules
[MDBOOK001]
allow_missing = false
Best Practices
- Run Checks Before Building: Catch issues early
- Include in Pre-commit Hooks: Prevent broken commits
- Document Exceptions: If disabling rules, explain why
- Test Rendering: Lint checks complement, not replace, build tests
- Version Control SUMMARY.md: Track structure changes
Related Standard Rules
Some standard rules are particularly relevant for mdBook:
- MD041 - First line should be a heading (important for chapters)
- MD025 - Single H1 (one main heading per chapter)
- MD051 - Link fragments (for cross-references)
References
MDBOOK001 - Code Blocks Should Have Language Tags
Code blocks should have language tags.
This rule is triggered when code blocks don't have language tags for syntax highlighting. Proper language tags help with documentation clarity and proper rendering in mdBook.
Why This Rule Exists
mdBook uses language tags for:
- Syntax highlighting in rendered output
- Proper code formatting and display
- Enabling language-specific features (like line numbers, highlighting specific lines)
- Improving accessibility for screen readers
- Better SEO and content understanding
Examples
❌ Incorrect (violates rule)
```
fn main() {
println!("Hello, world!");
}
```
✅ Correct
```rust
fn main() {
println!("Hello, world!");
}
```
Other valid examples:
```bash
cargo build --release
```
```toml
[dependencies]
serde = "1.0"
```
```json
{
"name": "example",
"version": "1.0.0"
}
```
Special Language Tags
mdBook supports special language tags:
textorplain- for plain text without highlightingconsole- for command-line outputdiff- for showing differencesignore- for Rust code that shouldn't be testedno_run- for Rust code that compiles but shouldn't runshould_panic- for Rust code expected to panic
Configuration
This rule has no configuration options. All code blocks should have language tags.
When to Disable
Consider disabling this rule if:
- You have many legacy code blocks without language tags
- You're using a custom mdBook renderer that doesn't require language tags
Rule Details
- Rule ID: MDBOOK001
- Category: mdBook-specific
- Severity: Warning
- Automatic Fix: Not available (requires manual language identification)
Why Language Tags Matter in mdBook
Syntax Highlighting
mdBook uses language tags to apply syntax highlighting via highlight.js or similar libraries:
Without language tag:
```
fn main() {
println!("Hello, world!");
}
```
Renders as plain text with no highlighting.
With language tag:
```rust
fn main() {
println!("Hello, world!");
}
```
Renders with proper Rust syntax highlighting.
mdBook-Specific Features
Language tags enable mdBook-specific features:
Rust Playground Integration
fn main() { println!("This code can be run in the Rust Playground!"); }
Hidden Lines in Rust Code
```rust
# fn main() {
println!("Only this line is shown");
# }
```
Test Annotations
```rust,ignore
// This code won't be tested
fn example() {}
```
```rust,no_run
// This code is compiled but not run
fn main() {
loop {} // Would hang if run
}
```
```rust,should_panic
// This code is expected to panic
fn main() {
panic!("This is expected!");
}
```
Common Language Tags
Programming Languages
| Language | Tag | Common Uses |
|---|---|---|
| Rust | rust | Primary language for mdBook documentation |
| JavaScript | javascript or js | Web examples, Node.js code |
| Python | python or py | Scripts, examples |
| Shell | bash or sh | Command-line examples |
| TOML | toml | Configuration files |
| JSON | json | Data structures, APIs |
| YAML | yaml or yml | Configuration, CI/CD |
| HTML | html | Web markup |
| CSS | css | Styling examples |
| SQL | sql | Database queries |
Special Tags
| Tag | Purpose |
|---|---|
text or plain | Plain text without highlighting |
console | Terminal output with prompt highlighting |
diff | Showing differences with +/- highlighting |
markdown or md | Markdown source code |
Examples by Use Case
Configuration Files
TOML (Cargo.toml):
```toml
[package]
name = "my-project"
version = "0.1.0"
[dependencies]
serde = "1.0"
```
JSON (package.json):
```json
{
"name": "my-project",
"version": "1.0.0",
"dependencies": {
"react": "^18.0.0"
}
}
```
Command-Line Examples
Shell commands:
```bash
# Install mdbook-lint
cargo install mdbook-lint
# Run the linter
mdbook-lint check
```
Console output:
```console
$ cargo build
Compiling my-project v0.1.0
Finished dev [unoptimized] target(s) in 2.34s
```
Showing Changes
Diff format:
```diff
- Old line that was removed
+ New line that was added
Unchanged line
```
Multi-language Examples
HTML with embedded CSS and JavaScript:
```html
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: sans-serif; }
</style>
</head>
<body>
<h1>Hello</h1>
<script>
console.log('Hello, world!');
</script>
</body>
</html>
```
Choosing the Right Language Tag
Decision Tree
- Is it code? → Use appropriate language tag
- Is it terminal output? → Use
console - Is it a diff? → Use
diff - Is it plain text? → Use
textorplain - Is it data? → Use format tag (
json,yaml,toml) - Not sure? → Use
textrather than no tag
Language Detection Tips
Look for characteristic syntax:
- Rust:
fn,let,mut,impl,:: - Python:
def,import,:for blocks, no semicolons - JavaScript:
function,const,=>,var - Shell:
$,#for comments, command names - JSON:
{,},:, quoted keys - TOML:
[sections],key = value,#comments
Edge Cases
Mixed Language Blocks
For templates or mixed content, choose the primary language:
```html
<!-- This is primarily HTML even though it contains CSS -->
<div style="color: red;">Content</div>
```
Unknown or Custom Languages
For unsupported languages, use text:
```text
CUSTOM_SYNTAX {
nonstandard = syntax
}
```
File Names as Context
Sometimes include the filename for context:
```rust
// src/main.rs
fn main() {
println!("Hello!");
}
```
Impact on mdBook Features
Search Indexing
Code blocks with language tags are better indexed for search.
Syntax Theme Support
Language tags enable proper theme application:
- Light themes show appropriate colors
- Dark themes adjust for readability
- Contrast themes maintain accessibility
Copy Button
mdBook's copy button works better with properly tagged code blocks.
Line Numbers
Some themes show line numbers only for tagged code blocks:
```rust,linenos
fn main() {
println!("Line 1");
println!("Line 2");
}
```
Configuration 2
This rule has no configuration options. All code blocks should have language tags for optimal mdBook rendering.
Related Rules
- MD040 - Fenced code blocks should have a language specified (standard rule)
- MD046 - Code block style
- MD048 - Code fence style
References
MDBOOK002 - Invalid Internal Link
Severity: Error
Category: mdBook-specific
Auto-fix: Not available
Rule Description
This rule validates internal links within mdBook projects, ensuring they point to valid files and anchors. Broken internal links create a poor reading experience and navigation issues.
Why This Rule Exists
Valid internal links are crucial because:
- Ensures readers can navigate between chapters
- Prevents 404 errors in generated documentation
- Maintains documentation integrity
- Enables proper mdBook navigation features
- Helps identify renamed or moved files
Examples
❌ Incorrect (violates rule)
<!-- Link to non-existent file -->
See [configuration](./configs.md) for details.
<!-- Link to non-existent anchor -->
Check the [installation section](./setup.md#install)
<!-- Broken relative path -->
Read more in [the guide](../guides/intro.md)
✅ Correct
<!-- Valid file link -->
See [configuration](./configuration.md) for details.
<!-- Valid anchor link -->
Check the [installation section](./getting-started.md#installation)
<!-- Correct relative path -->
Read more in [the introduction](./introduction.md)
<!-- External links are not checked -->
Visit [Rust website](https://www.rust-lang.org)
What This Rule Checks
- File existence: Verifies linked
.mdfiles exist - Anchor validity: Confirms heading anchors are present
- Path resolution: Validates relative paths from current file
- SUMMARY.md links: Ensures all chapter links are valid
Configuration
[MDBOOK002]
check_anchors = true # Validate heading anchors (default: true)
allow_external = true # Skip external URLs (default: true)
check_images = false # Also validate image paths (default: false)
Common Issues and Solutions
Issue: File Renamed
<!-- Before -->
[Old name](./old-filename.md)
<!-- After -->
[New name](./new-filename.md)
Issue: Heading Changed
<!-- Heading changed from "## Installation" to "## Setup" -->
<!-- Before -->
[Install](./guide.md#installation)
<!-- After -->
[Install](./guide.md#setup)
Issue: File Moved
<!-- File moved to subdirectory -->
<!-- Before -->
[Guide](./guide.md)
<!-- After -->
[Guide](./user-guide/guide.md)
When to Disable
Consider disabling this rule if:
- You're in the middle of a major restructuring
- Your build process generates files dynamically
- You have external link checking handled separately
Disable in Config
# .mdbook-lint.toml
disabled_rules = ["MDBOOK002"]
Disable Inline
<!-- mdbook-lint-disable MDBOOK002 -->
[Temporarily broken link](./todo.md)
<!-- mdbook-lint-enable MDBOOK002 -->
Tips for Compliance
- Use relative paths: More maintainable than absolute paths
- Update links when renaming: Use search and replace
- Test navigation: Click through links after changes
- Use anchor generation tools: Ensure correct anchor format
Anchor Format
mdBook generates anchors from headings using these rules:
- Convert to lowercase
- Replace spaces with hyphens
- Remove special characters
- Handle duplicates with numbers
## Hello World! <!-- #hello-world -->
## User's Guide <!-- #users-guide -->
## 1.2.3 Version <!-- #123-version -->
Related Rules
- MD042 - No empty links
- MD051 - Link fragments are valid
- MDBOOK003 - SUMMARY.md structure
- MDBOOK006 - Cross-reference validation
References
MDBOOK003 - Invalid SUMMARY.md Structure
Severity: Error
Category: mdBook-specific
Auto-fix: Not available
Rule Description
This rule validates that SUMMARY.md follows mdBook's required structure and conventions. The SUMMARY.md file defines your book's table of contents and must follow specific formatting rules.
Why This Rule Exists
Proper SUMMARY.md structure is essential because:
- mdBook uses it to generate navigation
- Incorrect structure causes build failures
- Defines the reading order of chapters
- Controls the book's hierarchical organization
- Enables proper sidebar navigation
Examples
❌ Incorrect (violates rule)
# Summary
[Introduction](./introduction.md)
- Part 1
- [Chapter 1](./chapter1.md)
* [Chapter 2](./chapter2.md) <!-- Mixed list markers -->
- [Chapter 3](./chapter3.md) <!-- Incorrect indentation -->
[](./empty.md) <!-- Empty link text -->
- - [Double nested](./nested.md) <!-- Invalid nesting -->
✅ Correct
# Summary
[Introduction](./introduction.md)
# User Guide
- [Getting Started](./getting-started.md)
- [Installation](./installation.md)
- [Configuration](./configuration.md)
- [Advanced Usage](./advanced.md)
# Reference
- [API Documentation](./api.md)
- [Configuration Reference](./config-ref.md)
---
[Contributors](./contributors.md)
SUMMARY.md Structure Rules
Required Elements
- Title: Must start with
# Summary - Prefix Chapter: Optional
[Introduction](./intro.md)before numbered chapters - Numbered Chapters: Use consistent list markers (
-or*) - Suffix Chapters: Optional unnumbered chapters after separator
Formatting Rules
- Consistent indentation: Use 2 or 4 spaces per level
- Consistent list markers: Use either
-or*throughout - Valid links: All links must have text and valid paths
- Proper nesting: Child chapters indented under parents
- Part headers: Use
# Part Namefor sections
Special Elements
# Summary
[Preface](./preface.md) <!-- Prefix chapter -->
# Part I
- [Chapter 1](./ch1.md) <!-- Numbered chapter -->
- [Section 1.1](./ch1-1.md) <!-- Nested chapter -->
- [Chapter 2](./ch2.md)
- [Draft]() <!-- Draft chapter (no link) -->
--- <!-- Separator -->
[Appendix A](./appendix-a.md) <!-- Suffix chapter -->
Configuration
[MDBOOK003]
allow_draft_chapters = true # Allow chapters without links (default: true)
require_part_headers = false # Require part headers (default: false)
max_depth = 3 # Maximum nesting depth (default: 3)
Common Issues and Solutions
Issue: Mixed List Markers
<!-- Wrong -->
- [Chapter 1](./ch1.md)
* [Chapter 2](./ch2.md)
<!-- Correct -->
- [Chapter 1](./ch1.md)
- [Chapter 2](./ch2.md)
Issue: Incorrect Indentation
<!-- Wrong -->
- [Chapter 1](./ch1.md)
- [Section](./sec.md) <!-- 3 spaces -->
<!-- Correct -->
- [Chapter 1](./ch1.md)
- [Section](./sec.md) <!-- 2 spaces -->
Issue: Invalid Draft Syntax
<!-- Wrong -->
- [TODO](.)
- [Draft](/)
<!-- Correct -->
- [Draft]()
When to Disable
Consider disabling this rule if:
- You're using a custom mdBook theme with different requirements
- Your build process generates SUMMARY.md dynamically
- You're migrating from another documentation system
Disable in Config
# .mdbook-lint.toml
disabled_rules = ["MDBOOK003"]
Tips for Compliance
- Use consistent indentation: Pick 2 or 4 spaces and stick with it
- Order matters: Chapters appear in the order listed
- Test the build: Run
mdbook buildto verify structure - Use draft chapters: For work-in-progress sections
Related Rules
- MDBOOK002 - Invalid internal link
- MDBOOK005 - Orphaned files
- MDBOOK025 - SUMMARY.md heading structure
References
MDBOOK004 - No Duplicate Chapter Titles
Chapter titles should be unique across the book.
Why This Rule Exists
Duplicate chapter titles create confusion in navigation and can cause issues with mdBook's URL generation. Each chapter should have a distinct, identifiable title.
Examples
Incorrect (SUMMARY.md)
# Summary
- [Introduction](./intro.md)
- [Getting Started](./start.md)
- [Introduction](./advanced-intro.md) <!-- Duplicate -->
Correct
# Summary
- [Introduction](./intro.md)
- [Getting Started](./start.md)
- [Advanced Introduction](./advanced-intro.md)
Configuration
This rule has no configuration options.
When to Disable
- Books with intentionally repeated section names
- Multi-part books where repetition is meaningful
Rule Details
- Rule ID: MDBOOK004
- Aliases: no-duplicate-chapter-titles
- Category: MdBook
- Severity: Warning
- Auto-fix: No
Impact
Duplicate titles can cause:
- Confusing navigation sidebar
- Ambiguous URL paths
- Search result confusion
- Poor user experience
Related Rules
- MD024 - No duplicate headings
- MDBOOK003 - SUMMARY.md structure
- MDBOOK025 - SUMMARY.md heading structure
MDBOOK005 - Orphaned Files
Severity: Warning
Category: mdBook-specific
Auto-fix: Not available
Rule Description
This rule detects markdown files in your mdBook source directory that are not referenced in SUMMARY.md. Orphaned files won't be included in the built book and represent unused or forgotten content.
Why This Rule Exists
Detecting orphaned files is important because:
- Identifies forgotten or lost content
- Helps maintain a clean project structure
- Prevents confusion about what's included in the book
- Finds files that should be deleted or added to SUMMARY.md
- Reduces repository size by identifying unused files
Examples
❌ Problematic Structure
src/
├── SUMMARY.md
├── introduction.md ✓ (in SUMMARY.md)
├── chapter1.md ✓ (in SUMMARY.md)
├── chapter2.md ✓ (in SUMMARY.md)
├── old-chapter.md ✗ (orphaned)
├── todo.md ✗ (orphaned)
└── notes.md ✗ (orphaned)
✅ Clean Structure
src/
├── SUMMARY.md
├── introduction.md ✓ (in SUMMARY.md)
├── chapter1.md ✓ (in SUMMARY.md)
├── chapter2.md ✓ (in SUMMARY.md)
└── appendix.md ✓ (in SUMMARY.md)
What This Rule Checks
The rule scans for:
- All
.mdfiles in the source directory - Files referenced in SUMMARY.md
- Reports files not in SUMMARY.md as orphaned
Special Cases
Files that are not considered orphaned:
SUMMARY.mditselfREADME.md(often used as index)- Files in directories excluded by configuration
- Files matching ignore patterns
Configuration
[MDBOOK005]
ignore_patterns = ["drafts/**", "*.backup.md"] # Patterns to ignore
check_nested = true # Check subdirectories (default: true)
exclude_readme = true # Don't report README.md (default: true)
Common Scenarios
Scenario 1: Renamed File
You renamed a chapter but forgot to update SUMMARY.md:
# Old file still exists but not in SUMMARY.md
src/old-name.md → orphaned
src/new-name.md → in SUMMARY.md
Solution: Delete the old file or update SUMMARY.md
Scenario 2: Work in Progress
You're drafting new content not ready for inclusion:
src/draft-chapter.md → orphaned (intentionally)
Solution: Move to a drafts folder or add ignore pattern
Scenario 3: Included Files
You have files that are included by other files:
src/snippets/example.md → orphaned (but included via {{#include}})
Solution: Add to ignore patterns or move to non-source directory
Handling Orphaned Files
Option 1: Add to SUMMARY.md
# Summary
- [Existing Chapter](./existing.md)
- [Previously Orphaned](./orphaned.md) <!-- Add this line -->
Option 2: Delete the File
rm src/orphaned-file.md
Option 3: Move Outside Source Directory
mkdir archived
mv src/orphaned.md archived/
Option 4: Add to Ignore Patterns
[MDBOOK005]
ignore_patterns = ["drafts/**", "work-in-progress.md"]
When to Disable
Consider disabling this rule if:
- You intentionally keep reference files in the source directory
- Your build process dynamically generates SUMMARY.md
- You use many include files that aren't directly referenced
- You're in the middle of major restructuring
Disable in Config
# .mdbook-lint.toml
disabled_rules = ["MDBOOK005"]
Disable for Specific Directories
[[overrides]]
path = "src/reference/**"
disabled_rules = ["MDBOOK005"]
Best Practices
- Regular cleanup: Periodically review and remove orphaned files
- Use drafts folder: Keep work-in-progress in a separate directory
- Document intentional orphans: Add comments explaining why files are kept
- Version control: Check git history before deleting orphaned files
Related Rules
- MDBOOK002 - Invalid internal link
- MDBOOK003 - SUMMARY.md structure
- MDBOOK006 - Cross-reference validation
References
MDBOOK006 - Internal Cross-References
Internal cross-reference links must point to valid headings in target files.
Why This Rule Exists
Cross-references between chapters using anchor fragments must resolve to actual headings in the target file. Invalid fragments create broken navigation.
Examples
Incorrect
See the [configuration section](./config.md#settings) for details.
Where config.md has no ## Settings heading.
Correct
See the [configuration section](./config.md#configuration-options) for details.
Where config.md contains:
## Configuration Options
Content here.
How Fragments Are Generated
mdBook generates fragments from headings:
| Heading | Fragment |
|---|---|
## Getting Started | #getting-started |
## API Reference | #api-reference |
## What's New? | #whats-new |
Configuration
This rule has no configuration options.
When to Disable
- Books using custom anchor IDs
- Content with JavaScript-based navigation
Rule Details
- Rule ID: MDBOOK006
- Aliases: internal-cross-references
- Category: MdBook
- Severity: Warning
- Auto-fix: No
Related Rules
MDBOOK007 - Include Validation
Include directives must point to existing files with valid syntax.
Why This Rule Exists
mdBook's {{#include}} directive embeds content from other files. Invalid
paths or syntax cause build failures or missing content.
Examples
Incorrect
{{#include missing-file.rs}}
{{#include ../src/lib.rs:nonexistent_anchor}}
\{{include src/main.rs}} <!-- Missing # -->
Correct
{{#include ../src/lib.rs}}
{{#include ../src/lib.rs:main_function}}
{{#include ./snippets/example.rs:5:10}}
Include Syntax
<!-- Full file -->
{{#include path/to/file.rs}}
<!-- Line range -->
{{#include path/to/file.rs:5:10}}
<!-- From line to end -->
{{#include path/to/file.rs:5:}}
<!-- Named anchor -->
{{#include path/to/file.rs:anchor_name}}
Configuration
This rule has no configuration options.
When to Disable
- Files with includes resolved at a different build stage
- Templates with dynamic include paths
Rule Details
- Rule ID: MDBOOK007
- Aliases: include-validation
- Category: MdBook
- Severity: Error
- Auto-fix: No
Related Rules
MDBOOK008 - Rustdoc Include Validation
Invalid {{#rustdoc_include}} paths or syntax.
Why This Rule Exists
The {{#rustdoc_include}} directive is similar to \{{#include}} but hides
lines starting with # (used for rustdoc hidden lines). Invalid paths or
syntax cause build failures.
Examples
Incorrect
{{#rustdoc_include missing-file.rs}}
{{#rustdoc_include ../src/lib.rs:bad_anchor}}
\{{rustdoc_include src/main.rs}} <!-- Missing # -->
Correct
{{#rustdoc_include ../src/lib.rs}}
{{#rustdoc_include ../src/lib.rs:example}}
{{#rustdoc_include ./snippets/demo.rs:5:20}}
Rustdoc Include Syntax
<!-- Full file, hiding # lines -->
{{#rustdoc_include path/to/file.rs}}
<!-- Line range -->
{{#rustdoc_include path/to/file.rs:5:10}}
<!-- Named anchor -->
{{#rustdoc_include path/to/file.rs:anchor_name}}
Hidden Lines
In the source file, lines starting with # are hidden:
fn main() { println!("This line is visible"); }
Renders as just:
#![allow(unused)] fn main() { println!("This line is visible"); }
Configuration
This rule has no configuration options.
Rule Details
- Rule ID: MDBOOK008
- Aliases: rustdoc-include-validation
- Category: MdBook
- Severity: Error
- Stability: Experimental
- Auto-fix: No
Related Rules
MDBOOK009 - Playground Validation
Invalid {{#playground}} configuration.
Why This Rule Exists
The {{#playground}} directive creates interactive Rust code examples. Invalid
paths or configuration cause build failures or non-functional playgrounds.
Examples
Incorrect
{{#playground missing-file.rs}}
{{#playground ../src/example.rs invalid_option}}
\{{playground src/demo.rs}} <!-- Missing # -->
Correct
{{#playground ../src/example.rs}}
{{#playground ../src/example.rs editable}}
{{#playground ../src/example.rs editable hide_lines=1-3}}
Playground Options
<!-- Basic playground -->
{{#playground path/to/file.rs}}
<!-- Editable playground -->
{{#playground path/to/file.rs editable}}
<!-- Hide specific lines -->
{{#playground path/to/file.rs hide_lines=1-3}}
<!-- Multiple options -->
{{#playground path/to/file.rs editable no_run}}
Available Options
| Option | Description |
|---|---|
editable | Allow users to edit the code |
no_run | Show code but disable running |
ignore | Don't test this code |
hide_lines | Hide specific line ranges |
Configuration
This rule has no configuration options.
Rule Details
- Rule ID: MDBOOK009
- Aliases: playground-validation
- Category: MdBook
- Severity: Warning
- Stability: Experimental
- Auto-fix: No
Related Rules
MDBOOK010 - Preprocessor Validation
Missing or invalid preprocessor configuration.
Why This Rule Exists
mdBook preprocessors transform content before rendering. Using preprocessor directives without proper configuration causes silent failures or build errors.
Examples
Incorrect
Using a directive without configuring the preprocessor:
{{#katex}}
E = mc^2
\{{/katex}}
Without [preprocessor.katex] in book.toml.
Correct
First, configure in book.toml:
[preprocessor.katex]
Then use the directive:
{{#katex}}
E = mc^2
\{{/katex}}
Common Preprocessors
| Preprocessor | Purpose |
|---|---|
katex | Math equations |
mermaid | Diagrams |
toc | Table of contents |
template | Template expansion |
admonish | Callout boxes |
Configuration
This rule has no configuration options.
Rule Details
- Rule ID: MDBOOK010
- Aliases: preprocessor-validation
- Category: MdBook
- Severity: Warning
- Stability: Experimental
- Auto-fix: No
Related Rules
- MDBOOK011 - Template validation
MDBOOK011 - Template Validation
Invalid {{#template}} syntax.
Why This Rule Exists
The {{#template}} directive expands templates with variable substitution.
Invalid syntax or missing variables cause build failures.
Examples
Incorrect
{{#template missing-template.md}}
{{#template ./template.md var1=value}} <!-- Missing closing -->
\{{template ./template.md}} <!-- Missing # -->
Correct
{{#template ./templates/note.md}}
{{#template ./templates/warning.md title="Important" content="Read carefully"}}
Template Syntax
<!-- Basic template -->
{{#template path/to/template.md}}
<!-- With variables -->
{{#template path/to/template.md var1="value1" var2="value2"}}
Template File
<!-- templates/note.md -->
> **\{{title}}**
>
> \{{content}}
Usage
{{#template templates/note.md title="Note" content="This is important."}}
Configuration
This rule has no configuration options.
Rule Details
- Rule ID: MDBOOK011
- Aliases: template-validation
- Category: MdBook
- Severity: Warning
- Stability: Experimental
- Auto-fix: No
Related Rules
MDBOOK012 - Include Line Range Validation
Broken {{#include}} line ranges.
Why This Rule Exists
Include directives with line ranges must reference valid line numbers. Ranges that exceed the file length or have invalid syntax cause build failures or unexpected content.
Examples
Incorrect
<!-- File has only 50 lines -->
{{#include ../src/lib.rs:100:150}}
<!-- Invalid range (end before start) -->
{{#include ../src/lib.rs:20:10}}
<!-- Non-numeric range -->
{{#include ../src/lib.rs:start:end}}
Correct
{{#include ../src/lib.rs:1:10}}
{{#include ../src/lib.rs:5:}}
{{#include ../src/lib.rs::20}}
Line Range Syntax
<!-- Lines 5 through 10 -->
{{#include file.rs:5:10}}
<!-- Line 5 to end of file -->
{{#include file.rs:5:}}
<!-- Start of file through line 10 -->
{{#include file.rs::10}}
<!-- Single line (line 5 only) -->
{{#include file.rs:5:5}}
Configuration
This rule has no configuration options.
Rule Details
- Rule ID: MDBOOK012
- Aliases: include-line-range-validation
- Category: MdBook
- Severity: Error
- Stability: Experimental
- Auto-fix: No
Related Rules
MDBOOK025 - Multiple H1 Headings Allowed in SUMMARY.md
Severity: Info
Category: mdBook-specific
Auto-fix: Not available
Rule Description
This rule specifically allows multiple H1 headings in SUMMARY.md files while still enforcing the single H1 rule (MD025) in regular chapter files. SUMMARY.md uses H1 headings to define part separators in the book structure.
Why This Rule Exists
SUMMARY.md has special requirements because:
- H1 headings define book parts/sections
- Multiple parts are common in large books
- mdBook treats these H1s as structural elements
- They don't represent document headings but navigation structure
- Standard MD025 rule would incorrectly flag valid SUMMARY.md files
Examples
✅ Correct SUMMARY.md Structure
# Summary
[Introduction](./introduction.md)
# Part I: Getting Started
- [Installation](./chapter1/installation.md)
- [Configuration](./chapter1/configuration.md)
# Part II: User Guide
- [Basic Usage](./chapter2/basic-usage.md)
- [Advanced Features](./chapter2/advanced.md)
# Part III: Reference
- [API Documentation](./chapter3/api.md)
- [Configuration Reference](./chapter3/config-ref.md)
---
[Appendix A](./appendix-a.md)
[Appendix B](./appendix-b.md)
❌ What This Rule Prevents
In regular chapter files (not SUMMARY.md):
# First Heading
Content...
# Second H1 Heading <!-- MD025 violation in regular files -->
More content...
SUMMARY.md Structure Rules
Part Headers
- H1 headings (
# Part Name) create part divisions - Parts group related chapters
- Part headers appear in the rendered navigation
- No limit on number of parts
Special Elements
# Summary <!-- Required first line -->
[Prefix Chapter](./preface.md) <!-- Before numbered chapters -->
# Part Name <!-- Part header -->
- [Chapter](./ch.md) <!-- Numbered chapters -->
- [Section](./sect.md) <!-- Nested chapters -->
--- <!-- Separator -->
[Suffix Chapter](./appendix.md) <!-- After numbered chapters -->
Configuration
[MDBOOK025]
# This rule has no configuration options
# It automatically applies only to SUMMARY.md
How It Works
This rule:
- Detects if the file is SUMMARY.md
- Allows multiple H1 headings in SUMMARY.md
- Defers to MD025 for all other files
- Validates proper SUMMARY.md structure
Common Patterns
Book with Multiple Parts
# Summary
[Preface](./preface.md)
# Part I: Fundamentals
- [Chapter 1](./ch1.md)
- [Chapter 2](./ch2.md)
# Part II: Intermediate
- [Chapter 3](./ch3.md)
- [Chapter 4](./ch4.md)
# Part III: Advanced
- [Chapter 5](./ch5.md)
- [Chapter 6](./ch6.md)
Book without Parts
# Summary
[Introduction](./intro.md)
- [Chapter 1](./ch1.md)
- [Chapter 2](./ch2.md)
- [Chapter 3](./ch3.md)
---
[Conclusion](./conclusion.md)
Mixed Structure
# Summary
- [Getting Started](./start.md)
# Core Concepts
- [Fundamentals](./fundamentals.md)
- [Architecture](./architecture.md)
# Advanced Topics
- [Performance](./performance.md)
- [Security](./security.md)
---
[Glossary](./glossary.md)
Best Practices
- Use parts for organization: Group related chapters
- Keep part names concise: They appear in navigation
- Order matters: Parts appear in sequence
- Be consistent: Use similar naming patterns
- Consider reader flow: Logical progression through parts
Part Naming Conventions
<!-- Numbered parts -->
# Part I: Introduction
# Part II: Core Concepts
# Part III: Advanced Topics
<!-- Descriptive parts -->
# Getting Started
# User Guide
# API Reference
# Appendices
<!-- Module-based -->
# Core Modules
# Extension Modules
# Utility Modules
Interaction with Other Rules
Works With
- MDBOOK003: Validates overall SUMMARY.md structure
- MD022: Headings surrounded by blank lines
- MD026: No trailing punctuation in headings
Overrides
- MD025: Multiple top-level headings (in SUMMARY.md only)
When to Disable
Consider disabling this rule if:
- You use a custom book structure
- You have a different table of contents format
- You don't use SUMMARY.md
- You prefer strict MD025 enforcement everywhere
Disable in Config
# .mdbook-lint.toml
disabled_rules = ["MDBOOK025"]
# Or disable both MDBOOK025 and MD025
disabled_rules = ["MDBOOK025", "MD025"]
Tips
- Part headers are optional: Not every book needs parts
- Unnumbered chapters: Can exist before first part
- Separator sections: Use
---for appendices - Draft chapters: Use
[Chapter]()for placeholders - Nested structure: Indent with 2 or 4 spaces consistently
Related Rules
- MDBOOK003 - SUMMARY.md structure validation
- MD025 - Multiple top-level headings (general rule)
- MD001 - Heading levels should increment
References
Configuration Reference
Complete reference for all configuration options in mdbook-lint.
Global Configuration Options
fail-on-warnings
- Type:
boolean - Default:
false - Description: Exit with error code when warnings are found
fail-on-errors
- Type:
boolean - Default:
true - Description: Exit with error code when errors are found
disabled-rules
- Type:
array<string> - Default:
[] - Description: List of rule IDs to disable globally
- Example:
["MD013", "MD033"]
enabled-rules
- Type:
array<string> - Default:
[] - Description: List of rule IDs to explicitly enable
- Example:
["MD001", "MD002"]
enabled-categories
- Type:
array<string> - Default:
[] - Description: List of rule categories to enable
- Valid values:
headings,lists,whitespace,code,style,links,mdbook
disabled-categories
- Type:
array<string> - Default:
[] - Description: List of rule categories to disable
markdownlint-compatible
- Type:
boolean - Default:
false - Description: Enable markdownlint compatibility mode (disables rules that are disabled by default in markdownlint)
deprecated-warning
- Type:
string - Default:
"warn" - Description: How to handle deprecated rule warnings
- Valid values:
"warn","info","silent"
malformed-markdown
- Type:
string - Default:
"warn" - Description: How to handle malformed markdown
- Valid values:
"error","warn","skip"
Rules Section Configuration
rules.default
- Type:
boolean - Default:
true - Description: Whether rules are enabled by default
rules.enabled
- Type:
table<string, boolean> - Description: Map of rule IDs to enable when
default = false
rules.disabled
- Type:
table<string, boolean> - Description: Map of rule IDs to disable when
default = true
Example:
[rules]
default = false
[rules.enabled]
MD001 = true
MD002 = true
MD009 = true
Rule-Specific Configuration
MD002 - First heading should be a top-level heading
[MD002]
level = 1 # Expected level of first heading (default: 1)
MD003 - Heading style
[MD003]
style = "consistent" # Options: "consistent", "atx", "atx_closed", "setext"
MD004 - Unordered list style
[MD004]
style = "consistent" # Options: "consistent", "asterisk", "plus", "dash"
MD007 - Unordered list indentation
[MD007]
indent = 2 # Spaces for indentation (default: 2)
start_indented = false # Allow first level to be indented
MD009 - Trailing spaces
[MD009]
br_spaces = 2 # Spaces for line breaks (default: 2)
list_item_empty_lines = false # Allow spaces in empty list items
strict = false # Strict mode for all trailing spaces
MD010 - Hard tabs
[MD010]
code_blocks = true # Include code blocks (default: true)
spaces_per_tab = 4 # Spaces per tab for reporting (default: 4)
MD012 - Multiple consecutive blank lines
[MD012]
maximum = 1 # Maximum consecutive blank lines (default: 1)
MD013 - Line length
[MD013]
line_length = 80 # Maximum line length (default: 80)
code_blocks = false # Check code blocks
tables = false # Check tables
headings = true # Check headings
heading_line_length = 80 # Separate limit for headings
strict = false # Strict length checking
stern = false # Stern length checking
MD024 - Multiple headings with same content
[MD024]
siblings_only = false # Only check sibling headings (default: false)
MD025 - Multiple top-level headings
[MD025]
level = 1 # Heading level to check (default: 1)
front_matter_title = true # Use front matter title
MD026 - Trailing punctuation in heading
[MD026]
punctuation = ".,;:!?" # Punctuation to check (default: ".,;:!?")
MD029 - Ordered list item prefix
[MD029]
style = "one_or_ordered" # Options: "one", "ordered", "one_or_ordered", "zero"
MD030 - Spaces after list markers
[MD030]
ul_single = 1 # Spaces after single-line unordered list marker
ul_multi = 1 # Spaces after multi-line unordered list marker
ol_single = 1 # Spaces after single-line ordered list marker
ol_multi = 1 # Spaces after multi-line ordered list marker
MD035 - Horizontal rule style
[MD035]
style = "consistent" # Style to enforce or "consistent"
MD036 - Emphasis used instead of heading
[MD036]
punctuation = ".,;:!?" # Punctuation at end (default: ".,;:!?")
MD043 - Required heading structure
[MD043]
headings = ["# Summary", "## Overview"] # Required headings in order
required_headings = ["# Summary", "## Overview"] # Alternative name
headers = ["# Summary", "## Overview"] # Alternative name (deprecated)
MD044 - Proper names should have correct capitalization
[MD044]
names = ["JavaScript", "GitHub", "TypeScript"] # Proper names
code_blocks = false # Include code blocks
html_elements = false # Include HTML elements
MD046 - Code block style
[MD046]
style = "consistent" # Options: "consistent", "fenced", "indented"
MD048 - Code fence style
[MD048]
style = "consistent" # Options: "consistent", "backtick", "tilde"
MD049 - Emphasis style
[MD049]
style = "consistent" # Options: "consistent", "asterisk", "underscore"
MD050 - Strong style
[MD050]
style = "consistent" # Options: "consistent", "asterisk", "underscore"
MD051 - Link fragments should be valid
[MD051]
# No configuration options
MD052 - Reference links and images should use label
[MD052]
shortcut_syntax = false # Allow shortcut syntax
MD053 - Link and image reference definitions need labels
[MD053]
ignored_definitions = ["//"] # Definitions to ignore
MD054 - Link and image reference definitions should be used
[MD054]
# No configuration options
MD055 - Table pipe style
[MD055]
style = "consistent" # Options: "consistent", "leading_only", "trailing_only", "leading_and_trailing", "no_leading_or_trailing"
MD056 - Table column count
[MD056]
# No configuration options
MD058 - Tables should be surrounded by blank lines
[MD058]
# No configuration options
MD059 - Tables should not have empty cells
[MD059]
allowed_corner_cells = false # Allow empty corner cells
mdBook-Specific Rules
mdBook-specific rules (MDBOOK001-MDBOOK025) generally don't have configuration options, as they check for mdBook-specific patterns and conventions.
Configuration File Examples
Minimal Configuration
disabled-rules = ["MD013", "MD033"]
Comprehensive Configuration
fail-on-warnings = true
fail-on-errors = true
markdownlint-compatible = false
deprecated-warning = "warn"
malformed-markdown = "error"
enabled-categories = ["headings", "lists"]
disabled-rules = ["MD041"]
[MD002]
level = 1
[MD003]
style = "atx"
[MD007]
indent = 2
start_indented = false
[MD009]
br_spaces = 2
strict = false
[MD013]
line_length = 100
code_blocks = false
tables = false
[MD024]
siblings_only = true
[MD029]
style = "ordered"
[MD030]
ul_single = 1
ol_single = 1
[MD044]
names = ["JavaScript", "TypeScript", "GitHub", "mdBook"]
code_blocks = false
Using Rules Section
[rules]
default = false
[rules.enabled]
MD001 = true
MD002 = true
MD003 = true
MD009 = true
MD047 = true
[MD003]
style = "atx"
[MD009]
br_spaces = 2
Example Configuration
This page provides a complete, fully-commented example configuration file for mdbook-lint.
Quick Start
- Copy the configuration below to
.mdbook-lint.tomlin your project root - Uncomment and modify only the settings you want to change
- All settings are optional - mdbook-lint works with sensible defaults
Complete Example Configuration
# mdbook-lint Example Configuration
#
# This is a comprehensive example configuration file for mdbook-lint.
# Copy this file to `.mdbook-lint.toml` in your project root and customize as needed.
#
# All settings shown here are optional - mdbook-lint works with sensible defaults.
# Uncomment and modify only the settings you want to change.
# ============================================================================
# GLOBAL SETTINGS
# ============================================================================
# Exit with error code if warnings are found
# fail-on-warnings = false
# List of rules to disable globally
# disabled_rules = ["MD013", "MD033"]
# Default severity level for all rules (error, warning, info)
# default_severity = "warning"
# Paths to ignore when linting
# ignore_paths = ["target/", "vendor/", "*.backup.md"]
# ============================================================================
# STANDARD MARKDOWN RULES (MD001-MD059)
# ============================================================================
# ----------------------------------------------------------------------------
# Heading Rules
# ----------------------------------------------------------------------------
# MD001 - Heading levels should only increment by one level at a time
# [MD001]
# No configuration options
# MD002 - First heading should be a top-level heading
# DEPRECATED: Use MD041 instead. This rule is disabled by default.
# [MD002]
# level = 1 # Expected first heading level
# MD003 - Heading style
# [MD003]
# style = "atx" # Options: "atx", "setext", "atx_closed", "consistent"
# MD018 - No space after hash on atx style heading (auto-fix)
# [MD018]
# No configuration options
# MD019 - Multiple spaces after hash on atx style heading (auto-fix)
# [MD019]
# No configuration options
# MD020 - No space inside hashes on closed atx style heading (auto-fix)
# [MD020]
# No configuration options
# MD021 - Multiple spaces inside hashes on closed atx style heading (auto-fix)
# [MD021]
# No configuration options
# MD022 - Headings should be surrounded by blank lines
# [MD022]
# lines_above = 1 # Blank lines above heading
# lines_below = 1 # Blank lines below heading
# MD023 - Headings must start at the beginning of the line (auto-fix)
# [MD023]
# No configuration options
# MD024 - Multiple headings with the same content
# [MD024]
# siblings_only = false # Only check sibling headings
# MD025 - Multiple top-level headings in the same document
# [MD025]
# level = 1 # Heading level to check
# front_matter_title = true # Consider front matter title as heading
# MD026 - Trailing punctuation in heading
# [MD026]
# punctuation = ".,;:!。,;:!" # Punctuation to check
# ----------------------------------------------------------------------------
# List Rules
# ----------------------------------------------------------------------------
# MD004 - Unordered list style
# [MD004]
# style = "consistent" # Options: "asterisk", "dash", "plus", "consistent"
# MD005 - Consistent list indentation
# [MD005]
# No configuration options
# MD006 - Consider starting lists at the beginning of the line
# DEPRECATED: This rule conflicts with nested list handling. Disabled by default.
# [MD006]
# No configuration options
# MD007 - Unordered list indentation
# [MD007]
# indent = 2 # Spaces per indentation level
# start_indented = false # Allow first level to be indented
# MD029 - Ordered list item prefix
# [MD029]
# style = "one_or_ordered" # Options: "one", "ordered", "one_or_ordered"
# MD030 - Spaces after list markers (auto-fix)
# [MD030]
# ul_single = 1 # Spaces after single-line unordered list marker
# ul_multi = 1 # Spaces after multi-line unordered list marker
# ol_single = 1 # Spaces after single-line ordered list marker
# ol_multi = 1 # Spaces after multi-line ordered list marker
# MD032 - Lists should be surrounded by blank lines
# [MD032]
# No configuration options
# ----------------------------------------------------------------------------
# Whitespace Rules
# ----------------------------------------------------------------------------
# MD009 - Trailing spaces (auto-fix)
# [MD009]
# br_spaces = 2 # Number of spaces for line break
# strict = false # Strict mode (no line break spaces)
# list_item_empty_lines = false # Allow empty list items
# MD010 - Hard tabs (auto-fix)
# [MD010]
# code_blocks = true # Check code blocks
# spaces_per_tab = 4 # Spaces to replace each tab
# MD012 - Multiple consecutive blank lines (auto-fix)
# [MD012]
# maximum = 1 # Maximum consecutive blank lines
# MD027 - Multiple spaces after blockquote symbol (auto-fix)
# [MD027]
# spaces = 1 # Number of spaces after blockquote marker
# MD028 - Blank line inside blockquote
# [MD028]
# No configuration options
# MD047 - Files should end with a single newline character (auto-fix)
# [MD047]
# No configuration options
# ----------------------------------------------------------------------------
# Code Rules
# ----------------------------------------------------------------------------
# MD014 - Dollar signs used before commands without showing output
# [MD014]
# No configuration options
# MD031 - Fenced code blocks should be surrounded by blank lines
# [MD031]
# list_items = true # Check code blocks in lists
# MD038 - Spaces inside code span elements
# [MD038]
# No configuration options
# MD040 - Fenced code blocks should have a language specified
# [MD040]
# allowed_languages = [] # List of allowed languages (empty = all)
# language_optional = false # Language tag is optional
# MD046 - Code block style
# [MD046]
# style = "fenced" # Options: "fenced", "indented", "consistent"
# MD048 - Code fence style
# [MD048]
# style = "backtick" # Options: "backtick", "tilde", "consistent"
# ----------------------------------------------------------------------------
# Link and Image Rules
# ----------------------------------------------------------------------------
# MD011 - Reversed link syntax
# [MD011]
# No configuration options
# MD034 - Bare URL used (auto-fix)
# [MD034]
# No configuration options
# MD039 - Spaces inside link text
# [MD039]
# No configuration options
# MD042 - No empty links
# [MD042]
# No configuration options
# MD045 - Images should have alternate text (alt text)
# [MD045]
# No configuration options
# MD051 - Link fragments should be valid
# [MD051]
# No configuration options
# MD052 - Reference links and images should use a label that is defined
# [MD052]
# No configuration options
# MD053 - Link and image reference definitions should be needed
# [MD053]
# ignored_definitions = [] # Definitions to ignore
# MD054 - Link and image style
# [MD054]
# autolink = true # Allow autolinks
# inline = true # Allow inline links
# full = true # Allow full reference links
# collapsed = true # Allow collapsed reference links
# shortcut = true # Allow shortcut reference links
# url_inline = true # Allow URLs as inline links
# MD059 - Link and image reference definitions should be sorted
# [MD059]
# No configuration options (coming soon)
# ----------------------------------------------------------------------------
# Style Rules
# ----------------------------------------------------------------------------
# MD013 - Line length
# [MD013]
# line_length = 80 # Maximum line length
# length_mode = "strict" # Options: "strict" (count all chars), "visual" (exclude URLs)
# code_blocks = true # Check code blocks
# tables = true # Check tables
# headings = true # Check headings
# strict = false # Strict mode (no leniency)
# stern = false # Stern mode (allow long lines without spaces)
# MD035 - Horizontal rule style
# [MD035]
# style = "---" # Horizontal rule style
# MD036 - Emphasis used instead of a heading
# [MD036]
# punctuation = ".,;:!?。,;:!?" # Punctuation at end
# MD037 - Spaces inside emphasis markers
# [MD037]
# No configuration options
# MD041 - First line in a file should be a top-level heading
# [MD041]
# level = 1 # Required heading level
# front_matter_title = true # Consider front matter title
# MD043 - Required heading structure
# [MD043]
# headings = [] # Required heading structure
# match_case = false # Case-sensitive matching
# MD044 - Proper names should have the correct capitalization
# [MD044]
# names = [] # List of proper names
# code_blocks = true # Check code blocks
# html_elements = true # Check HTML elements
# MD049 - Emphasis style should be consistent
# [MD049]
# style = "asterisk" # Options: "asterisk", "underscore", "consistent"
# MD050 - Strong style should be consistent
# [MD050]
# style = "asterisk" # Options: "asterisk", "underscore", "consistent"
# ----------------------------------------------------------------------------
# Table Rules
# ----------------------------------------------------------------------------
# MD055 - Table pipe style
# [MD055]
# style = "leading_and_trailing" # Options: "leading_only", "trailing_only", "leading_and_trailing", "no_leading_or_trailing"
# MD056 - Table column count
# [MD056]
# No configuration options
# MD057 - Relative links should point to existing files
# [MD057]
# No configuration options
# MD058 - Tables should be surrounded by blank lines
# [MD058]
# No configuration options
# MD060 - Table column alignment style
# [MD060]
# style = "consistent" # Options: "aligned", "compact", "tight", "any", "consistent"
# ----------------------------------------------------------------------------
# HTML Rules
# ----------------------------------------------------------------------------
# MD033 - Inline HTML
# [MD033]
# allowed_elements = [] # HTML elements to allow
# ============================================================================
# CONTENT RULES
# ============================================================================
# Rules for checking document content quality and structure
# CONTENT001 - Document should have a title (H1 heading)
# [CONTENT001]
# No configuration options
# CONTENT002 - Document should have an introduction/summary
# [CONTENT002]
# min_words = 10 # Minimum words in introduction paragraph
# CONTENT003 - Headings should be descriptive
# [CONTENT003]
# min_length = 3 # Minimum heading length in characters
# max_length = 80 # Maximum heading length in characters
# CONTENT004 - Code blocks should have explanatory text
# [CONTENT004]
# require_before = true # Require text before code block
# require_after = false # Require text after code block
# CONTENT005 - Document should have adequate content
# [CONTENT005]
# min_words = 50 # Minimum word count for document
# min_headings = 1 # Minimum number of headings
# ============================================================================
# MDBOOK-SPECIFIC RULES
# ============================================================================
# MDBOOK001 - Code blocks should have language tags for syntax highlighting
# [MDBOOK001]
# No configuration options
# MDBOOK002 - Internal link validation
# [MDBOOK002]
# check_anchors = true # Validate heading anchors
# allow_external = true # Skip external URLs
# check_images = false # Also validate image paths
# MDBOOK003 - SUMMARY.md structure validation
# [MDBOOK003]
# allow_draft_chapters = true # Allow chapters without links
# require_part_headers = false # Require part headers
# max_depth = 3 # Maximum nesting depth
# MDBOOK004 - No duplicate chapter titles
# [MDBOOK004]
# case_sensitive = false # Case-sensitive comparison
# ignore_prefixes = ["Chapter", "Part"] # Prefixes to ignore
# MDBOOK005 - Orphaned files detection
# [MDBOOK005]
# ignore_patterns = ["drafts/**", "*.backup.md"] # Patterns to ignore
# check_nested = true # Check subdirectories
# exclude_readme = true # Don't report README.md
# MDBOOK006 - Cross-reference validation
# [MDBOOK006]
# check_external = false # Don't check external URLs
# ignore_missing = false # Report missing files
# case_sensitive = false # Case-sensitive anchor matching
# MDBOOK007 - Include directive syntax validation
# [MDBOOK007]
# check_line_ranges = true # Validate line numbers exist
# allow_external = false # Allow includes outside src/
# max_depth = 3 # Maximum directory traversal depth
# MDBOOK008 - Rustdoc include validation
# [MDBOOK008]
# check_compilation = false # Try to compile included code
# allow_external = false # Allow includes outside project
# validate_anchors = true # Check anchor existence
# MDBOOK009 - Playground directive syntax
# [MDBOOK009]
# require_editable = false # Require editable attribute
# validate_compilation = false # Check if code compiles
# MDBOOK010 - Preprocessor configuration validation
# [MDBOOK010]
# check_math = true # Validate math syntax
# check_mermaid = true # Validate mermaid syntax
# allowed_preprocessors = ["katex", "mermaid", "admonish"]
# MDBOOK011 - Template syntax validation
# [MDBOOK011]
# template_dir = "./templates" # Default template directory
# check_variables = true # Validate variable substitution
# allow_missing_vars = false # Allow undefined variables
# MDBOOK012 - Include line range validation
# [MDBOOK012]
# validate_bounds = true # Check if line numbers exist
# warn_large_ranges = true # Warn for ranges > 100 lines
# max_range_size = 100 # Maximum lines in a range
# prefer_anchors = true # Suggest anchors over line numbers
# MDBOOK016 - Rust code blocks should use valid mdBook/rustdoc attributes
# [MDBOOK016]
# No configuration options
# MDBOOK017 - Rust code blocks should use # prefix to hide boilerplate
# [MDBOOK017]
# No configuration options
# MDBOOK021 - {{#title}} directive should appear only once per chapter
# [MDBOOK021]
# No configuration options
# MDBOOK022 - {{#title}} directive should appear near the top of the file
# [MDBOOK022]
# max_line = 10 # Maximum line number for title directive
# MDBOOK023 - Chapter titles in SUMMARY.md should match H1 headers
# [MDBOOK023]
# No configuration options
# MDBOOK025 - Multiple H1 headings allowed in SUMMARY.md
# [MDBOOK025]
# No configuration options
# ============================================================================
# FILE-SPECIFIC OVERRIDES
# ============================================================================
# Override rules for specific files or patterns
# [[overrides]]
# path = "README.md"
# disabled_rules = ["MD041"] # README doesn't need H1 as first line
# [[overrides]]
# path = "CHANGELOG.md"
# disabled_rules = ["MD024", "MD025"] # Allow duplicate headings in changelog
# [[overrides]]
# path = "examples/**/*.md"
# disabled_rules = ["MD013"] # No line length limit in examples
# [overrides.rules.MD009]
# br_spaces = 0 # No trailing spaces allowed in examples
# [[overrides]]
# path = "docs/api/**"
# [overrides.rules.MD013]
# line_length = 120 # Longer lines for API docs
# ============================================================================
# PREPROCESSOR CONFIGURATION
# ============================================================================
# Configuration for mdBook preprocessor mode
# [preprocessor]
# fail_on_warnings = true # Fail the build on warnings
# renderer = ["html", "pdf"] # Run for specific renderers
# ============================================================================
# OUTPUT CONFIGURATION
# ============================================================================
# [output]
# format = "github" # Options: "github", "json", "checkstyle"
# file = "lint-report.json" # Output to file instead of stdout
# verbose = false # Verbose output
# quiet = false # Suppress non-error output
# color = "auto" # Options: "auto", "always", "never"
Common Configuration Patterns
Minimal Configuration
For most projects, a minimal configuration is sufficient:
# .mdbook-lint.toml
fail-on-warnings = true
disabled_rules = ["MD013"] # Disable line length if not needed
Strict Configuration
For projects requiring strict markdown compliance:
# Fail on any issues
fail-on-warnings = true
# Strict whitespace rules
[MD009]
strict = true # No trailing spaces at all
[MD010]
code_blocks = true # Check tabs in code blocks
# Require code block languages
[MD040]
language_optional = false
# Strict line length
[MD013]
line_length = 80
strict = true
Documentation Project
For technical documentation or mdBook projects:
# mdBook-specific checks
[MDBOOK002]
check_anchors = true
check_images = true
[MDBOOK005]
ignore_patterns = ["drafts/**", "archive/**"]
# Allow longer lines for documentation
[MD013]
line_length = 100
code_blocks = false # Don't check code block line length
tables = false # Don't check table line length
# Require proper code highlighting
[MD040]
language_optional = false
Blog or Content Site
For blogs or content-heavy sites:
# Relaxed rules for content
disabled_rules = [
"MD013", # No line length limit
"MD033", # Allow inline HTML
"MD041" # First line doesn't need to be H1
]
# Allow emphasis for styling
[MD036]
punctuation = "" # Don't check for punctuation
# Consistent emphasis style
[MD049]
style = "asterisk"
[MD050]
style = "asterisk"
File-Specific Overrides
Example: Different Rules for Different Directories
# Strict rules for source documentation
[[overrides]]
path = "src/**/*.md"
[overrides.rules.MD013]
line_length = 80
# Relaxed rules for examples
[[overrides]]
path = "examples/**/*.md"
disabled_rules = ["MD013", "MD009"]
# Special rules for CHANGELOG
[[overrides]]
path = "CHANGELOG.md"
disabled_rules = [
"MD024", # Allow duplicate version headings
"MD025" # Allow multiple H1s for versions
]
Integration Configurations
GitHub Actions
# For CI/CD pipelines
fail-on-warnings = true
[output]
format = "github" # GitHub Actions annotations
mdBook Preprocessor
[preprocessor]
fail_on_warnings = false # Warning but don't fail build
renderer = ["html"] # Only run for HTML output
Editor Integration
# For real-time feedback
[output]
format = "json" # Machine-readable output
verbose = false # Minimal output
Rule Categories Quick Reference
Disable All Rules in a Category
# Disable all heading rules
disabled_rules = [
"MD001", "MD002", "MD003", "MD018", "MD019",
"MD020", "MD021", "MD022", "MD023", "MD024",
"MD025", "MD026"
]
# Disable all whitespace rules
disabled_rules = [
"MD009", "MD010", "MD012", "MD027", "MD028", "MD047"
]
# Disable all list rules
disabled_rules = [
"MD004", "MD005", "MD006", "MD007", "MD029",
"MD030", "MD032"
]
Tips
- Start minimal: Begin with defaults and add configuration as needed
- Use overrides: Apply different rules to different parts of your project
- Document choices: Comment why certain rules are disabled
- Version control: Commit
.mdbook-lint.tomlto your repository - Team agreement: Discuss and agree on rules with your team
Next Steps
- See Configuration Reference for detailed options
- Check Rules Reference for all available rules
- Learn about mdBook Integration for book projects
API Documentation
This page provides comprehensive API documentation for mdbook-lint's core libraries and rule implementations.
Core Library (mdbook-lint-core)
The mdbook-lint-core crate provides the foundational infrastructure for markdown linting.
Overview
The mdbook-lint-core crate provides:
- Plugin-based architecture for extensible rule sets
- AST and text-based linting with efficient document processing
- Violation reporting with detailed position tracking and severity levels
- Automatic fix infrastructure for correctable violations
- Configuration system for customizing rule behavior
- Document abstraction with markdown parsing via comrak
Architecture
The core follows a plugin-based architecture where rules are provided by external crates:
┌─────────────────┐
│ Application │
└────────┬────────┘
│
┌────────▼────────┐
│ PluginRegistry │ ◄─── Registers rule providers
└────────┬────────┘
│
┌────────▼────────┐
│ LintEngine │ ◄─── Orchestrates linting
└────────┬────────┘
│
┌────────▼────────┐
│ Rules │ ◄─── Individual rule implementations
└─────────────────┘
Key Components
Document Processing
Document- Represents a markdown file with content and metadataPosition- Tracks line/column positions for violationsNodeContext- Provides AST node information during linting
Rule System
Ruletrait - Core interface for all linting rulesAstRule- Rules that process markdown AST nodesTextRule- Rules that process raw text contentRuleProvider- Plugin interface for rule registration
Violation Reporting
Violation- Represents a linting issue with location and severitySeverity- Error, Warning, or Info levelsFix- Automatic fix information for violations
Engine and Registry
LintEngine- Main orchestrator for document lintingPluginRegistry- Manages rule providers and configuration
Rulesets Library (mdbook-lint-rulesets)
The mdbook-lint-rulesets crate implements all linting rules.
Rule Implementation
The mdbook-lint-rulesets crate implements the actual linting rules used by mdbook-lint.
It provides:
- 55 standard markdown rules (MD001-MD060) based on the markdownlint specification
- 18 mdBook-specific rules (MDBOOK001-MDBOOK025) for mdBook project validation
- 10 content rules (CONTENT001-CONTENT011) for content quality checks
- Automatic fix support for many rules to correct issues automatically
- Configurable rules with sensible defaults
Rule Categories
Standard Markdown Rules (MD001-MD059)
These rules cover common markdown style and formatting issues:
- Heading rules (MD001-MD003, MD018-MD025): Heading hierarchy, style, and formatting
- List rules (MD004-MD007, MD029-MD032): List formatting, indentation, and consistency
- Whitespace rules (MD009-MD012, MD027-MD028): Trailing spaces, blank lines, tabs
- Link rules (MD034, MD039, MD042): URL formatting and link text
- Code rules (MD038, MD040, MD046, MD048): Code block formatting and fencing
- Emphasis rules (MD036-MD037, MD049-MD050): Bold and italic formatting
mdBook-Specific Rules (MDBOOK001-MDBOOK012, MDBOOK025)
These rules validate mdBook-specific requirements:
- MDBOOK001: Code blocks should have language tags for proper syntax highlighting
- MDBOOK002: Validate internal link paths and anchors
- MDBOOK003: SUMMARY.md should follow proper mdBook structure
- MDBOOK005: Detect orphaned files not referenced in SUMMARY.md
- MDBOOK006: Validate cross-reference links between chapters
- MDBOOK007: Validate file include syntax and paths
- MDBOOK008: Check rustdoc_include directive usage
- MDBOOK009: Validate playground directive syntax
- MDBOOK010: Check for invalid math block syntax
- MDBOOK011: Validate template include syntax
- MDBOOK012: Check file include range syntax
- MDBOOK025: Ensure proper heading structure in SUMMARY.md
Automatic Fixes
Many rules support automatic fixing to correct violations:
Whitespace and Formatting
- MD009: Remove trailing spaces while preserving line breaks
- MD010: Convert tabs to spaces with configurable spacing
- MD012: Remove excessive consecutive blank lines
- MD018: Add space after hash in headings
- MD019: Remove multiple spaces after hash in headings
- MD020: Remove spaces inside hash-surrounded headings
- MD021: Remove multiple spaces inside hash-surrounded headings
- MD023: Remove indentation from headings
- MD027: Remove spaces after blockquote markers
- MD030: Ensure proper spacing after list markers
- MD034: Replace bare URLs with proper link syntax
- MD047: Ensure files end with single newline
Coming Soon
- Additional formatting rules
- More sophisticated content restructuring
- Context-aware fixes for complex violations
Usage Examples
Basic Library Usage
#![allow(unused)] fn main() { use mdbook_lint_core::{PluginRegistry, LintEngine}; use mdbook_lint_rulesets::{StandardRuleProvider, MdBookRuleProvider}; // Create a registry with standard rules let mut registry = PluginRegistry::new(); registry.register(StandardRuleProvider::new())?; registry.register(MdBookRuleProvider::new())?; // Create engine and lint a document let engine = LintEngine::from_registry(registry); let document = Document::from_file("README.md")?; let violations = engine.lint_document(&document)?; }
Custom Rule Implementation
#![allow(unused)] fn main() { use mdbook_lint_core::{Document, AstRule, Violation, Position}; use comrak::nodes::{AstNode, NodeValue}; pub struct MyCustomRule; impl AstRule for MyCustomRule { fn id(&self) -> &'static str { "CUSTOM001" } fn description(&self) -> &'static str { "My custom rule" } fn lint_ast(&self, document: &Document, node: &AstNode) -> Vec<Violation> { // Your custom linting logic here Vec::new() } } }
Configuration Usage
#![allow(unused)] fn main() { use mdbook_lint_core::Config; let config = Config::from_file(".mdbook-lint.toml")?; let engine = LintEngine::from_config(config)?; }
Individual Rule Documentation
Each rule provides detailed documentation including:
- Purpose and rationale - Why the rule exists
- Examples - Correct and incorrect markdown
- Configuration options - Customizable behavior
- Automatic fixes - What fixes are available
- Related rules - Connected or overlapping rules
Featured Rules
MD001 - Heading Increment
Ensures heading levels increment sequentially for proper document structure.
MD009 - No Trailing Spaces (Auto-fix)
Removes trailing whitespace while preserving intentional line breaks.
MDBOOK001 - Code Block Language Tags
Ensures code blocks have language tags for proper syntax highlighting in mdBook.
Quick Rule Reference
| Rule | Description | Auto-fix |
|---|---|---|
| MD001 | Heading increment | |
| MD009 | No trailing spaces | ✓ |
| MD010 | Hard tabs | ✓ |
| MD012 | Multiple blank lines | ✓ |
| MD018 | No space after hash | ✓ |
| MD019 | Multiple spaces after hash | ✓ |
| MD020 | No space in closed headings | ✓ |
| MD021 | Multiple spaces in closed headings | ✓ |
| MD023 | Headings start at beginning | ✓ |
| MD027 | Multiple spaces after blockquote | ✓ |
| MD030 | Spaces after list markers | ✓ |
| MD034 | Bare URL used | ✓ |
| MD047 | Files should end with newline | ✓ |
✓ indicates automatic fix support
Full API Reference
For complete API documentation with all types, traits, and functions:
Generate Documentation
# Generate and open documentation
cargo doc --open
# Generate documentation for all features
cargo doc --all-features --open
# Generate documentation without dependencies
cargo doc --no-deps --open
Online Documentation
- crates.io: mdbook-lint-core, mdbook-lint-rulesets
- Repository: GitHub documentation
Integration Patterns
mdBook Preprocessor
# book.toml
[preprocessor.mdbook-lint]
fail-on-warnings = true
CI/CD Integration
# Fail build on any violations
mdbook-lint lint --fail-on-warnings docs/
# Auto-fix and commit changes
mdbook-lint lint --fix docs/
git add docs/
git commit -m "docs: auto-fix markdown violations"
Editor Integration
Configure your editor to run mdbook-lint on save for real-time feedback.
Tool Comparison: mdbook-lint vs markdownlint
This document analyzes the differences in linting behavior between mdbook-lint and markdownlint when run on the same documentation.
Performance Metrics
| Metric | mdbook-lint | markdownlint | Difference |
|---|---|---|---|
| Execution Time | 0.101s | 0.283s | mdbook-lint is 2.8x faster |
| Total Violations | 1,390 | 818 | mdbook-lint finds 70% more |
| Standard Rules Only | 1,018 | 818 | mdbook-lint finds 24% more |
Rule-by-Rule Analysis
Rules with Similar Detection (±10% difference)
These rules show consistent behavior between both tools:
| Rule | Description | mdbook-lint | markdownlint |
|---|---|---|---|
| MD022 | Headings surrounded by blanks | 177 | 177 |
| MD031 | Code blocks surrounded by blanks | 200 | 202 |
| MD032 | Lists surrounded by blanks | 181 | 174 |
| MD047 | Files end with newline | 37 | 37 |
| MD051 | Link fragments | 47 | 47 |
Major Discrepancies
Rules Where mdbook-lint Finds More Violations
| Rule | Description | mdbook-lint | markdownlint | Analysis |
|---|---|---|---|---|
| MD013 | Line length | 179 | 115 | mdbook-lint is 55% stricter |
| MD007 | List indentation | 46 | 0 | markdownlint doesn't check inside code blocks |
| MD052 | Reference links | 24 | 0 | mdbook-lint validates undefined references |
| MD006 | Lists start at beginning | 21 | 0 | mdbook-lint enforces list positioning |
| MD058 | Tables surrounded by blanks | 11 | 2 | mdbook-lint has stricter table detection |
Rules Only Detected by mdbook-lint
These standard markdown rules are caught by mdbook-lint but not markdownlint in our docs:
- MD014 (1): Dollar signs in shell code
- MD018 (8): No space after hash in headings
- MD019 (10): Multiple spaces after hash
- MD020 (14): No space in closed headings
- MD021 (8): Multiple spaces in closed headings
- MD023 (12): Headings not at line beginning
- MD027 (4): Multiple spaces after blockquote
- MD028 (1): Blank line inside blockquote
- MD030 (1): Spaces after list markers
- MD033 (6): Inline HTML
- MD035 (2): Horizontal rule style
- MD039 (1): Spaces inside link text
- MD044 (10): Proper names capitalization
- MD050 (1): Strong style consistency
Root Cause Analysis
1. Code Block Processing (MD007)
Issue: mdbook-lint reports 46 MD007 violations, markdownlint reports 0
Example:
steps:
- uses: actions/checkout@v4 # mdbook-lint flags this indentation
Analysis: mdbook-lint appears to be checking list indentation rules inside code blocks, which is incorrect. Code blocks should be treated as literal content.
2. Line Length Strictness (MD013)
Issue: mdbook-lint finds 179 violations vs markdownlint's 115
Analysis: Both tools use 80-character default, but mdbook-lint may:
- Count differently (e.g., including/excluding certain characters)
- Check more contexts (e.g., inside certain structures)
- Have different handling of Unicode or special characters
3. Reference Link Validation (MD052)
Issue: mdbook-lint finds 24 violations, markdownlint finds 0
Example from contributing.md:335-337:
[ ] # mdbook-lint flags as undefined reference
Analysis: mdbook-lint validates that reference links actually have definitions, while markdownlint may only check syntax.
4. Whitespace Rules (MD018-MD021, MD023, MD027)
Issue: mdbook-lint finds 42 total violations across these rules, markdownlint finds 0
Analysis: mdbook-lint has more comprehensive whitespace checking around:
- Heading markers (MD018-MD021)
- Heading indentation (MD023)
- Blockquote markers (MD027)
mdBook-Specific Rules
mdbook-lint includes 372 additional violations from mdBook-specific rules:
| Rule | Count | Purpose |
|---|---|---|
| MDBOOK002 | 157 | Internal link validation |
| MDBOOK007 | 75 | File include syntax |
| MDBOOK005 | 46 | Orphaned files detection |
| MDBOOK001 | 30 | Code blocks need language tags |
| MDBOOK012 | 21 | File include ranges |
| MDBOOK008 | 13 | Rustdoc include validation |
| Others | 30 | Various mdBook features |
Recommendations
For mdbook-lint
- Fix MD007: Should not check list indentation inside code blocks
- Document MD013: Clarify how line length is calculated
- Configuration alignment: Consider a
markdownlint-compatiblemode that matches behavior exactly
For Users
- Choose based on needs:
-
mdbook-lint: Better for mdBook projects, stricter checking, faster performance
-
markdownlint: Better for general markdown, more mature, wider ecosystem
- Configuration tips:
-
Disable MD007 in mdbook-lint if false positives in code blocks are problematic
-
Adjust MD013 line length if 80 characters is too strict
-
Use
markdownlint-compatibleflag for closer behavior matching
Conclusion
mdbook-lint is significantly stricter and faster than markdownlint, finding 70% more violations overall. The main differences stem from:
- Bug: MD007 checking inside code blocks (should be fixed)
- Design: Stricter validation of references, whitespace, and formatting
- Feature: mdBook-specific rules add valuable checks for mdBook projects
For mdBook projects, mdbook-lint provides superior coverage. For general markdown, the choice depends on whether stricter checking is desired.
Library API
mdbook-lint is designed as a library-first project, making it easy to integrate markdown linting capabilities into your own Rust applications.
Overview
The core library (mdbook-lint-core) provides a clean, well-documented API for:
- Creating lint engines with different rule sets
- Processing markdown documents programmatically
- Configuring rules and behavior
- Handling violations and results
Quick Start
Add mdbook-lint to your Cargo.toml:
[dependencies]
mdbook-lint-core = "0.3.0"
Basic usage:
use mdbook_lint_core::{Document, create_engine_with_all_rules}; use std::path::PathBuf; fn main() -> Result<(), Box<dyn std::error::Error>> { // Create a lint engine with all available rules let engine = create_engine_with_all_rules(); // Create a document from content and path let content = "# My Document\n\nSome content here."; let document = Document::new(content.to_string(), PathBuf::from("example.md"))?; // Lint the document let violations = engine.lint_document(&document)?; // Process results for violation in violations { println!("{}:{}: {} - {}", violation.line, violation.column, violation.rule_id, violation.message ); } Ok(()) }
Core Types
Document
Represents a markdown document with content and metadata:
#![allow(unused)] fn main() { use mdbook_lint_core::Document; use std::path::PathBuf; let document = Document::new( "# Title\n\nContent".to_string(), PathBuf::from("my-file.md") )?; }
LintEngine
The main interface for linting operations:
#![allow(unused)] fn main() { use mdbook_lint_core::{LintEngine, create_engine_with_all_rules, create_standard_engine}; // Engine with all rules (standard + mdBook-specific) let all_engine = create_engine_with_all_rules(); // Engine with only standard markdown rules (MD001-MD059) let standard_engine = create_standard_engine(); // Engine with only mdBook-specific rules let mdbook_engine = create_mdbook_engine(); }
Configuration
Control which rules are enabled and configure their behavior:
#![allow(unused)] fn main() { use mdbook_lint_core::Config; let mut config = Config::default(); // Enable specific rules only config.enabled_rules = vec!["MD001".to_string(), "MD013".to_string()]; // Disable specific rules config.disabled_rules = vec!["MD002".to_string()]; // Enable specific categories config.enabled_categories = vec!["structure".to_string()]; // Lint with configuration let violations = engine.lint_document_with_config(&document, &config)?; }
Violations
Results from linting operations:
#![allow(unused)] fn main() { use mdbook_lint_core::{Violation, Severity}; // Violations contain detailed information about issues found for violation in violations { println!("Rule: {}", violation.rule_id); println!("Message: {}", violation.message); println!("Location: {}:{}", violation.line, violation.column); match violation.severity { Severity::Error => println!("This is an error"), Severity::Warning => println!("This is a warning"), Severity::Info => println!("This is informational"), } } }
Advanced Usage
Custom Rule Providers
Create engines with specific rule sets:
#![allow(unused)] fn main() { use mdbook_lint_core::{PluginRegistry, StandardRuleProvider}; let mut registry = PluginRegistry::new(); registry.register_provider(Box::new(StandardRuleProvider))?; let engine = registry.create_engine()?; }
Error Handling
The library uses anyhow for comprehensive error handling:
#![allow(unused)] fn main() { use mdbook_lint_core::error::Result; fn lint_file(path: &Path) -> Result<Vec<Violation>> { let content = std::fs::read_to_string(path)?; let document = Document::new(content, path.to_path_buf())?; let engine = create_engine_with_all_rules(); engine.lint_document(&document) } }
Batch Processing
Process multiple documents efficiently:
#![allow(unused)] fn main() { use walkdir::WalkDir; let engine = create_engine_with_all_rules(); let mut all_violations = Vec::new(); for entry in WalkDir::new("src/") { let entry = entry?; if entry.path().extension().map_or(false, |ext| ext == "md") { let content = std::fs::read_to_string(entry.path())?; let document = Document::new(content, entry.path().to_path_buf())?; let violations = engine.lint_document(&document)?; all_violations.extend(violations); } } }
Rule Categories
Rules are organized into logical categories:
- Structure: Document structure and hierarchy (MD001, MD003, etc.)
- Formatting: Code blocks, lists, emphasis (MD004, MD005, etc.)
- Content: Language, spelling, accessibility (MD044, MD045, etc.)
- Links: URL validation and formatting (MD034, MD039, etc.)
- MdBook: mdBook-specific checks (MDBOOK001-004)
Enable categories programmatically:
#![allow(unused)] fn main() { let mut config = Config::default(); config.enabled_categories = vec![ "structure".to_string(), "formatting".to_string() ]; }
Integration Examples
mdBook Preprocessor
#![allow(unused)] fn main() { use mdbook::preprocess::{Preprocessor, PreprocessorContext}; use mdbook::book::Book; use mdbook_lint_core::create_engine_with_all_rules; struct MyLinter { engine: LintEngine, } impl Preprocessor for MyLinter { fn run(&self, ctx: &PreprocessorContext, book: Book) -> mdbook::errors::Result<Book> { // Lint each chapter // Return book unchanged (linting only) Ok(book) } } }
CI/CD Integration
use mdbook_lint_core::{create_engine_with_all_rules, Severity}; fn main() -> std::process::ExitCode { let engine = create_engine_with_all_rules(); let mut has_errors = false; // Process all markdown files in repository // Set exit code based on results if has_errors { std::process::ExitCode::FAILURE } else { std::process::ExitCode::SUCCESS } }
API Documentation
For complete API documentation with examples, see the rustdoc documentation:
- Online: https://docs.rs/mdbook-lint-core/latest/mdbook_lint_core/
- Local: Run
cargo doc --open --no-depsin the repository
The rustdoc includes:
- Complete API reference with examples
- Module-level documentation
- Implementation details and internal architecture
- Links between related types and functions
Performance Considerations
- Single-pass parsing: Documents are parsed once and reused across all rules
- Lazy evaluation: Rules are only applied to relevant document sections
- Memory efficient: Minimal AST retention, streaming for large files
- Parallel processing: Use
rayonor similar for batch operations
Error Types
The library defines specific error types for different failure modes:
ConfigError: Configuration parsing and validation issuesDocumentError: Document creation and parsing problemsRuleError: Rule execution failuresIoError: File system access problems
Next Steps
- See Configuration for detailed configuration options
- Check Rules Reference for available rules
- Review Architecture for internal design details
- Browse the source code on GitHub
Contributing to mdbook-lint
Thank you for your interest in contributing to mdbook-lint! This guide covers everything you need to know to contribute effectively.
Quick Start
Prerequisites
- Rust 1.88.0 or later
- Git
Development Setup
# Fork and clone the repository
git clone https://github.com/YOUR_USERNAME/mdbook-lint.git
cd mdbook-lint
# Set up development environment
cargo build
cargo test
cargo fmt
cargo clippy
Making Your First Contribution
- Create a branch:
git checkout -b feature/your-change - Make changes: Follow the guidelines below
- Test thoroughly: Ensure all tests pass
- Submit PR: Use conventional commit format
Project Structure
mdbook-lint/
├── src/
│ ├── main.rs # CLI entry point
│ ├── lib.rs # Library entry point
│ ├── engine.rs # Core linting engine
│ ├── config.rs # Configuration system
│ ├── document.rs # Markdown document processing
│ ├── rule.rs # Rule trait definitions
│ ├── rules/ # Rule implementations
│ │ ├── standard/ # Standard markdown rules (MD001-MD059)
│ │ ├── mdbook001.rs # mdBook-specific rules
│ │ └── ...
│ └── preprocessor.rs # mdBook preprocessor integration
├── tests/ # Integration tests
├── docs/ # This documentation site
└── scripts/ # Development utilities
Code Standards
Rust Style
- Formatting: Use
cargo fmt(enforced in CI) - Linting: Fix all
cargo clippywarnings - Error Handling: Use
Result<T>types, avoid.unwrap() - Documentation: Document all public APIs with rustdoc
- Testing: Comprehensive unit and integration tests required
Commit Format
We use Conventional Commits:
<type>[scope]: <description>
feat(rules): add MD040 rule for fenced code blocks
fix(cli): handle empty files correctly
docs: update installation instructions
test: add edge cases for rule validation
refactor: simplify config parsing logic
Types: feat, fix, docs, test, refactor, perf, chore, ci
Scopes: rules, cli, config, engine, docs, tests
Branch Naming
<type>/<description>
feature/md040-code-block-language
fix/empty-file-handling
docs/contributing-guide
refactor/rule-registry-cleanup
Adding New Rules
Rule Types
Line-based Rules (implement Rule trait):
- Simple checks on raw text lines
- Faster execution, lower memory usage
- Good for formatting rules
AST-based Rules (implement AstRule trait):
- Complex semantic analysis
- Full markdown structure access
- Required for structural rules
Implementation Example
#![allow(unused)] fn main() { use crate::rule::{AstRule, RuleCategory, RuleMetadata}; use crate::{Document, violation::{Severity, Violation}}; use comrak::nodes::{AstNode, NodeValue}; pub struct MD999; impl AstRule for MD999 { fn id(&self) -> &'static str { "MD999" } fn name(&self) -> &'static str { "example-rule" } fn description(&self) -> &'static str { "Example rule for demonstration" } fn metadata(&self) -> RuleMetadata { RuleMetadata::stable(RuleCategory::Structure) } fn check_ast<'a>( &self, document: &Document, ast: &'a AstNode<'a>, ) -> crate::error::Result<Vec<Violation>> { let mut violations = Vec::new(); // Walk AST and check for violations for node in ast.descendants() { if let NodeValue::Heading(heading) = &node.data.borrow().value { if heading.level > 6 { if let Some((line, col)) = document.node_position(node) { violations.push(self.create_violation( "Heading level exceeds maximum".to_string(), line, col, Severity::Error, )); } } } } Ok(violations) } } [cfg(test)] mod tests { use super::*; use crate::rule::Rule; use std::path::PathBuf; [test] fn test_md999_valid_headings() { let content = "# H1\n## H2\n### H3\n"; let document = Document::new(content.to_string(), PathBuf::from("test.md")).unwrap(); let rule = MD999; let violations = rule.check(&document).unwrap(); assert_eq!(violations.len(), 0); } [test] fn test_md999_detects_violations() { let content = "####### Invalid heading level"; let document = Document::new(content.to_string(), PathBuf::from("test.md")).unwrap(); let rule = MD999; let violations = rule.check(&document).unwrap(); assert_eq!(violations.len(), 1); assert_eq!(violations[0].rule_id, "MD999"); } } }
Rule Registration
- Add module: Include your rule in
src/rules/standard/mod.rs - Register rule: Add to
StandardRuleProvider::register_rules() - Add to rule list: Include ID in
StandardRuleProvider::rule_ids()
Testing Requirements
Comprehensive testing is required:
- Test valid cases (no violations)
- Test violation detection
- Test edge cases (empty files, very long lines, unicode)
- Test configuration options (if applicable)
- Test error conditions
Configuration System
Rule Configuration
Rules can accept configuration through rule_config:
#![allow(unused)] fn main() { fn check_ast(&self, document: &Document, ast: &AstNode) -> Result<Vec<Violation>> { let config = document.config.rule_config .get(self.id()) .and_then(|v| v.as_object()); let max_length = config .and_then(|c| c.get("max-length")) .and_then(|v| v.as_u64()) .unwrap_or(100) as usize; // Use configuration in rule logic } }
Supported Formats
Configuration files can be TOML, YAML, or JSON:
# .mdbook-lint.toml
fail-on-warnings = true
enabled-rules = ["MD001", "MD013"]
[MD013]
line-length = 120
ignore-code-blocks = true
CLI Development
Adding Commands
Add new commands to the Commands enum in src/main.rs:
#![allow(unused)] fn main() { [derive(Subcommand)] enum Commands { /// Lint markdown files Lint { files: Vec<String>, [arg(short, long)] config: Option<String>, }, /// Your new command NewCommand { input: PathBuf, [arg(long)] option: bool, }, } }
Command Implementation
Create handler functions with proper error handling:
#![allow(unused)] fn main() { fn run_new_command(input: PathBuf, option: bool) -> Result<()> { // Validate input if !input.exists() { return Err(MdBookLintError::config_error( format!("Path does not exist: {}", input.display()) )); } // Implementation Ok(()) } }
Testing
Running Tests
# Unit tests
cargo test --lib
# Integration tests
cargo test --test '*'
# All tests
cargo test
# Specific test
cargo test test_name -- --exact
# With output
cargo test test_name -- --nocapture
Test Organization
- Unit tests: In same file with
#[cfg(test)] - Integration tests: In
tests/directory - CLI tests: Use
assert_cmdcrate - Fixtures: Test data in
tests/fixtures/
Writing Good Tests
#![allow(unused)] fn main() { [test] fn test_descriptive_name() { // Arrange let input = "test input"; let expected = "expected output"; // Act let result = function_under_test(input); // Assert assert_eq!(result, expected); } }
Pull Request Guidelines
Before Submitting
-
All tests pass:
cargo test -
Code is formatted:
cargo fmt -
No clippy warnings:
cargo clippy - Documentation updated (if needed)
- Commit follows conventional format
PR Template
## Description
Brief description of changes and motivation
## Type of Change
- [ ] Bug fix
- [ ] New feature
- [ ] Documentation update
- [ ] Refactoring
## Testing
- [ ] Tests pass locally
- [ ] Added tests for new functionality
- [ ] Manual testing completed
## Checklist
- [ ] Code follows project style
- [ ] Self-review completed
- [ ] Documentation updated
Review Process
- Automated checks run on all PRs
- Maintainer review for code quality
- Testing to ensure functionality
- Merge when approved and passing
Architecture Overview
Core Components
LintEngine (src/engine.rs):
- Orchestrates linting process
- Manages rule execution
- Aggregates results
Rule System (src/rule.rs, src/rules/):
- Defines
RuleandAstRuletraits - Implements linting logic
- Categorizes by type and stability
Document Processing (src/document.rs):
- Parses markdown using comrak
- Provides position tracking
- Handles various formats
Configuration (src/config.rs):
- Multi-format support (TOML/YAML/JSON)
- Rule-specific settings
- Precedence handling
Data Flow
Input Files → Document Parser → Lint Engine → Rules → Violations → Output
↓ ↓ ↓ ↓ ↓ ↓
.md files AST + Lines Rule Registry Checks Results CLI/JSON
Common Tasks
Debug Rule Issues
# Enable debug logging
export RUST_LOG=mdbook_lint=debug
# Run with backtrace
export RUST_BACKTRACE=1
# Test specific rule
cargo test md001 -- --nocapture
Profile Performance
# Install tools
cargo install flamegraph
# Profile application
cargo build --release
sudo flamegraph -- ./target/release/mdbook-lint lint large-file.md
Update Dependencies
# Check for updates
cargo outdated
# Update Cargo.lock
cargo update
# Update Cargo.toml versions
cargo upgrade
Project Conventions
Naming Standards
- Files:
snake_case.rs - Structs/Enums:
PascalCase - Functions/Variables:
snake_case - Constants:
SCREAMING_SNAKE_CASE - Rules:
MD###orMDBOOK###
Documentation Style
- Simple, clear, and factual
- Include working code examples
- No marketing language
- Professional tone throughout
- Link to related documentation
Error Messages
- Clear and actionable
- Include relevant context
- Suggest fixes when possible
- Consistent formatting
Example:
"Missing language tag for code block at line 15, column 1
Consider adding a language identifier: ```rust"
Release Process
Versioning
We use Semantic Versioning:
- MAJOR: Breaking changes
- MINOR: New features (backward compatible)
- PATCH: Bug fixes
Release Workflow
- Changes merged to main via PR
- Release-please creates release PR automatically
- Merge release PR to trigger release
- GitHub Actions publishes to crates.io
Getting Help
Resources
- Documentation: joshrotenberg.github.io/mdbook-lint
- Repository: github.com/joshrotenberg/mdbook-lint
- Issues: Report bugs and request features
- Discussions: Ask questions and get help
Common Questions
Q: How do I add a new rule? A: Follow the "Adding New Rules" section above. Start with the rule template and add comprehensive tests.
Q: Why did my PR fail CI?
A: Check that cargo test, cargo fmt, and cargo clippy all pass locally.
Q: How do I test mdBook integration?
A: Use the MdBookLint preprocessor in your tests. See existing integration tests for examples.
Q: Can I contribute documentation improvements?
A: Absolutely! Documentation improvements are highly valued. Edit the files in docs/src/.
Community Guidelines
- Be respectful and constructive
- Help others learn and contribute
- Follow our professional standards
- Ask questions when unclear
- Provide helpful feedback in reviews
Project Conventions 2
Commit Format 2
2
We use Conventional Commits:
<type>[scope]: <description>
feat(rules): add MD040 rule for fenced code blocks
fix(cli): handle empty files correctly
docs: update installation instructions
refactor(engine): simplify rule registry
Types: feat, fix, docs, test, refactor, perf, chore, ci
Scopes: rules, cli, config, engine, docs
Branch Naming
2
2
<type>/<description>
feature/md040-code-block-language
fix/empty-file-handling
docs/contributing-guide
refactor/rule-registry
Code Naming
- Files:
snake_case.rs - Structs/Enums:
PascalCase - Functions:
snake_case - Variables:
snake_case - Constants:
SCREAMING_SNAKE_CASE
Rule Naming
- Standard rules:
MD###(MD001, MD040, etc.) - mdBook rules:
MDBOOK###(MDBOOK001, MDBOOK002, etc.) - Rule files:
md###.rsormdbook###.rs - Rule names:
kebab-case(heading-increment, code-block-language)
Configuration Keys
Use kebab-case for all configuration keys:
fail-on-warnings = true
enabled-rules = ["MD001", "MD013"]
disabled-categories = ["style"]
[MD013]
line-length = 100
ignore-code-blocks = true
Documentation Style 2
2
- Simple, clear, and factual
- No marketing language or emojis
- Include working code examples
- Professional tone throughout
Thank you for contributing to mdbook-lint! Your efforts help make documentation better for everyone.
Architecture
This page provides an overview of mdbook-lint's internal architecture and design decisions.
High-Level Architecture
mdbook-lint is built as a modular Rust application with clear separation of concerns:
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ CLI Interface │ │ mdBook Plugin │ │ Library Core │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
└───────────────────────┼───────────────────────┘
│
┌─────────────────┐
│ Lint Engine │
└─────────────────┘
│
┌──────────────────┼──────────────────┐
│ │ │
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ Rule Registry │ │ Configuration │ │ File Parser │
└───────────────┘ └───────────────┘ └───────────────┘
│
┌────┴────┐
│ Rules │
└─────────┘
Core Components
Lint Engine
The central component that orchestrates the linting process:
- Loads and validates configuration
- Discovers and parses markdown files
- Applies rules to parsed content
- Collects and formats results
Rule System
A plugin-like architecture for linting rules:
- Each rule implements a common
Ruletrait - Rules are automatically registered at compile time
- Configurable rule parameters
- Extensible for new rule types
Parser
Handles markdown parsing and AST generation:
- Uses
comrakfor CommonMark compliance - Maintains source position information
- Provides structured access to document elements
Configuration
Flexible configuration system:
- TOML-based configuration files
- Command-line argument override
- Environment variable support
- Validation and error reporting
Data Flow
- Input Processing
-
Command-line arguments parsed
-
Configuration files loaded and merged
-
File paths resolved and validated
- Document Processing
-
Markdown files parsed into AST
-
Source position tracking maintained
-
Document metadata extracted
- Rule Application
-
Enabled rules identified
-
Rules applied to document AST
-
Violations collected with positions
- Output Generation
-
Results formatted for display
-
Exit codes determined
-
Statistics calculated
Rule Implementation
Rules follow a consistent pattern:
#![allow(unused)] fn main() { pub struct ExampleRule { config: ExampleConfig, } impl Rule for ExampleRule { fn id(&self) -> &'static str { "MD001" } fn description(&self) -> &'static str { "Rule description" } fn check(&self, document: &Document) -> Vec<Violation> { // Rule logic here } } }
Rule Categories
- Standard Rules (MD001-MD059): Based on markdownlint
- mdBook Rules (MDBOOK001-004): mdBook-specific checks
- Custom Rules: Extensible for project-specific needs
Performance Considerations
Parsing Strategy
- Single-pass parsing per document
- AST reuse across multiple rules
- Lazy evaluation where possible
Memory Management
- Streaming file processing for large projects
- Minimal AST retention
- Efficient string handling
Concurrency
- Parallel file processing
- Thread-safe rule application
- Configurable worker threads
Error Handling
Error Categories
- Configuration Errors: Invalid settings, missing files
- Parse Errors: Malformed markdown, encoding issues
- Rule Errors: Internal rule failures
- IO Errors: File system access problems
Error Recovery
- Graceful degradation on individual file failures
- Detailed error context and suggestions
- Configurable error tolerance levels
Extension Points
Custom Rules
#![allow(unused)] fn main() { // Plugin-style rule loading pub fn register_custom_rule<R: Rule + 'static>(rule: R) { RULE_REGISTRY.register(Box::new(rule)); } }
Output Formats
- JSON for machine consumption
- SARIF for integration tools
- Custom formatters via traits
Configuration Sources
- Environment variables
- External configuration services
- Runtime configuration updates
Testing Architecture
Test Categories
- Unit Tests: Individual rule logic
- Integration Tests: End-to-end CLI testing
- Corpus Tests: Real-world markdown validation
- Performance Tests: Benchmarking and profiling
Test Infrastructure
- Automated test case generation
- Snapshot testing for output validation
- Property-based testing for edge cases
Dependencies
Core Dependencies
comrak: Markdown parsingserde: Configuration serializationclap: Command-line interfaceanyhow: Error handling
Development Dependencies
criterion: Performance benchmarkingtempfile: Test file managementassert_cmd: CLI testing
Future Architecture Considerations
Planned Enhancements
- Plugin system for external rules
- Language server protocol support
- Real-time linting capabilities
- Integration with popular editors
Scalability
- Distributed linting for large repositories
- Caching and incremental analysis
- Cloud-based rule execution
Contributing to Architecture
When making architectural changes:
- Maintain backward compatibility
- Document design decisions
- Consider performance implications
- Ensure testability
- Follow Rust best practices
Next Steps
- See Contributing for development guidelines
- Check Rules Reference for rule implementation details
- Review source code for implementation specifics
Testing Strategy
This document describes mdbook-lint's comprehensive testing approach, including our simplified corpus testing and performance validation framework.
Overview
mdbook-lint uses a multi-layered testing strategy designed for speed, reliability, and comprehensive coverage:
- Unit Tests: Fast, focused tests for individual components
- Integration Tests: End-to-end testing of CLI and preprocessor functionality
- Corpus Tests: Real-world validation using diverse markdown content
- Performance Tests: Regression testing for known performance issues
- Property Tests: Fuzzing-style tests ensuring stability on any input
Corpus Testing
Our corpus testing validates correctness and stability using carefully selected real-world content.
Essential Corpus Files
Located in tests/corpus/essential/, these files test key scenarios:
empty_file.md: Edge case handling for empty filesunicode_content.md: International text and special characterslarge_file.md: Performance testing with substantial contentmixed_line_endings.md: Cross-platform line ending handlingknown_violations.md: Files with intentional violations for rule validation
Property-Based Testing
We use property-based testing to ensure mdbook-lint never crashes on any input:
#![allow(unused)] fn main() { // Example: Test with various malformed markdown let malformed_cases = [ "[unclosed link", "**unclosed emphasis", "`unclosed code", "```\nunclosed code block", ]; for case in &malformed_cases { assert_no_crash(case, &format!("Malformed: {}", case)); } }
This approach tests:
- Random/invalid UTF-8 sequences
- Malformed markdown syntax
- Pathological nesting patterns
- Mixed valid/invalid content
- Binary-like content
Performance Testing
Performance tests prevent regressions in known problem areas and ensure consistent speed.
Regression Tests
Located in simple_performance_tests.rs, these tests target historical issues:
MD051 Performance Fix
Tests the O(n²) → O(n) optimization for HTML fragment validation:
#![allow(unused)] fn main() { // Previously caused exponential slowdown let html_content = r##" <a href="#section1">Link 1</a> <a href="#section2">Link 2</a> # Section 1 {#section1} # Section 2 {#section2} "##; assert_completes_quickly(&document, Duration::from_millis(100)); }
MD049 Infinite Loop Fix
Tests the emphasis parsing fix that prevented infinite loops:
#![allow(unused)] fn main() { // Previously caused hangs with patterns like wrapping_* let emphasis_content = r##" - `wrapping_*` function calls in code - `checked_*` operations in backticks - Normal *emphasis* outside code should work "##; assert_completes_quickly(&document, Duration::from_millis(50)); }
Performance Targets
- Small files (< 1KB): Complete in < 50ms
- Medium files (1KB-10KB): Complete in < 100ms
- Large files (> 10KB): Complete in < 500ms
- Pathological cases: Complete in < 200ms
Test Execution
Local Testing
Run the full test suite:
# All tests
cargo test --all-features
# Specific test categories
cargo test --test simple_corpus_tests # Corpus validation
cargo test --test simple_performance_tests # Performance regressions
cargo test --lib # Unit tests
CI Testing
Our CI runs tests across platforms:
- Ubuntu, macOS, Windows: Cross-platform compatibility
- Stable, Beta Rust: Forward compatibility
- Essential corpus tests: Real-world validation
- Performance benchmarks: Regression detection
Design Philosophy
Simplified Approach
We prioritize accuracy and stability over absolute benchmarks because mdbook-lint is already fast compared to other tools. Our testing focuses on:
- Correctness: Rules work as intended
- Stability: Never crash on any input
- Performance: Prevent regressions
- Maintainability: Simple, focused tests
Property Testing Benefits
Property-based testing provides confidence that mdbook-lint handles the unpredictable nature of real-world markdown:
- User-generated content with encoding issues
- Generated markdown from various tools
- Partially corrupted files
- Mixed content types
Performance Testing Strategy
Rather than complex benchmarking, we use targeted regression tests:
- Known issues: Test specific problems that were fixed
- Pathological inputs: Ensure reasonable performance on edge cases
- Real content: Validate speed on actual documentation
Historical Context
This testing approach was simplified from a more complex framework that included:
- External corpus downloads (The Rust Book, etc.)
- markdownlint compatibility testing
- Extensive generated edge cases
- Complex nightly CI workflows
The current approach maintains essential coverage while being:
- 10x faster to run locally
- No external dependencies
- Easier to understand and maintain
- More reliable in CI environments
Contributing to Tests
When adding new functionality:
- Add unit tests for the specific feature
- Add integration tests if it affects CLI/preprocessor behavior
- Add corpus tests if it might affect stability
- Add performance tests if it's performance-sensitive
For bug fixes:
- Add a regression test that would have caught the bug
- Ensure it fails before your fix
- Verify it passes after your fix
See Contributing for detailed guidelines.