Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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: 54 standard markdown rules plus 13 mdBook-specific rules
  • 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.

Installation

mdbook-lint can be installed through several methods depending on your needs.

The easiest way to install mdbook-lint is through Cargo:

cargo install mdbook-lint

This will install the latest stable version from crates.io.

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:

  1. Add to book.toml:

    [preprocessor.mdbook-lint]
    
  2. 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:

  1. Initial setup: Add configuration file and run first lint
  2. Auto-fix simple issues: Use --fix to handle common problems
  3. Fix remaining issues: Address structural problems manually
  4. Customize rules: Disable rules that don't fit your style
  5. Integrate with build: Add to mdBook or CI pipeline
  6. 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

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.toml or mdbook-lint.toml (recommended)
  • YAML: .mdbook-lint.yaml or .mdbook-lint.yml
  • JSON: .mdbook-lint.json
  • markdownlint: .markdownlint.json (for compatibility)

Configuration Discovery

mdbook-lint searches for configuration files in the following order:

  1. Current directory
  2. Parent directories (recursively up to root)
  3. Custom path via MDBOOK_LINT_CONFIG environment variable

The first configuration file found is used.

Basic Configuration

# 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 rules
  • lists - List formatting rules
  • whitespace - Whitespace and blank line rules
  • code - Code block and inline code rules
  • style - General style rules
  • links - Link and reference rules
  • mdbook - 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

SettingTypeDefaultDescription
fail-on-warningsbooleanfalseExit with error code on warnings
fail-on-errorsbooleantrueExit with error code on errors
disabled-rulesarray[]List of rule IDs to disable
enabled-rulesarray[]List of rule IDs to explicitly enable
enabled-categoriesarray[]List of categories to enable
disabled-categoriesarray[]List of categories to disable
markdownlint-compatiblebooleanfalseEnable markdownlint compatibility
deprecated-warningstring"warn"How to handle deprecated rules ("warn", "info", "silent")
malformed-markdownstring"warn"How to handle malformed markdown ("error", "warn", "skip")

Configuration Precedence

Configuration is resolved in the following order (later overrides earlier):

  1. Built-in defaults
  2. Configuration file (.mdbook-lint.toml, etc.)
  3. mdBook preprocessor config (in book.toml)
  4. Environment variables (MDBOOK_LINT_*)
  5. Command-line arguments

Environment Variables

  • MDBOOK_LINT_CONFIG - Path to custom configuration file
  • MDBOOK_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

Configuration Validation

To validate your configuration:

# Check if configuration is valid
mdbook-lint lint --config .mdbook-lint.toml --dry-run

# Show which rules are enabled
mdbook-lint rules --config .mdbook-lint.toml

Next Steps

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)
  • --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

Rules Options

  • --detailed: Show detailed rule descriptions
  • --enabled: Show only enabled rules
  • --format <FORMAT>: Output format (text, json)

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 found
  • 2: Invalid arguments or configuration

Next Steps

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

Note: If you're unsure whether to use mdbook-lint as a preprocessor or standalone in CI, see our CI vs Preprocessor guide.

Installation

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 formatting
  • lists - List formatting and consistency
  • whitespace - Spacing, indentation, line breaks
  • code - Code blocks and inline code
  • links - Link validation and formatting
  • html - HTML usage in markdown
  • mdbook - 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:

  1. Verify installation:

    mdbook-lint --version
    which mdbook-lint
    
  2. Check book.toml has the preprocessor section:

    [preprocessor.mdbook-lint]
    
  3. Run with verbose output:

    mdbook build -v 2>&1 | grep mdbook-lint
    
  4. 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):

  1. Environment variables (e.g., MDBOOK_PREPROCESSOR__MDBOOK_LINT__FAIL_ON_WARNINGS)
  2. book.toml preprocessor settings
  3. .mdbook-lint.toml file in project root
  4. 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:

  1. Syntax errors in configuration files
  2. Invalid rule IDs in enabled/disabled lists
  3. 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:

  1. Exclude unnecessary files:

    [preprocessor.mdbook-lint]
    exclude = ["src/generated/**", "src/vendor/**"]
    
  2. Disable expensive rules:

    disabled-rules = ["MD013", "MD053"]  # Line length checks can be slow
    
  3. 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:

  1. Explicit --config flag (CLI mode only)
  2. Environment variable: MDBOOK_LINT_CONFIG
  3. book.toml preprocessor configuration
  4. 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

CI vs Preprocessor: Choosing Your Integration Strategy

This guide helps you choose between running mdbook-lint as an mdBook preprocessor or as a standalone tool in CI, and explains why you typically want one approach but not both.

Quick Decision Guide

Use CaseRecommended ApproachWhy
mdBook project with regular buildsPreprocessorAutomatic linting on every build
Pure markdown documentation (no mdBook)Standalone CINo mdBook dependency needed
Want fastest CI buildsStandalone CICan fail fast before building
Need SARIF/GitHub integrationStandalone CIBetter tool integration options
Want immediate feedback during developmentPreprocessorCatches issues during mdbook serve
Complex CI pipeline with multiple checksStandalone CIMore control over when/how linting runs

Integration Approaches

When to use:

  • You have an mdBook project
  • You want linting integrated into your normal workflow
  • You want consistent behavior between local development and CI
  • You prefer configuration in book.toml

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:

  • ✅ Single source of truth for configuration
  • ✅ Linting happens automatically during builds
  • ✅ Works with mdbook serve for live feedback
  • ✅ No duplicate configuration needed

Disadvantages:

  • ❌ Can't fail fast in CI (must start book build first)
  • ❌ Limited output format options
  • ❌ No SARIF output for GitHub Security tab

Approach 2: Standalone CI Tool

When to use:

  • You don't use mdBook (just markdown files)
  • You want to fail fast in CI before other expensive operations
  • You need SARIF output for GitHub Security integration
  • You want different linting rules in different CI contexts

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
  • ✅ SARIF output for GitHub Security tab
  • ✅ More control over when linting happens
  • ✅ Can run in parallel with other checks
  • ✅ Works without mdBook

Disadvantages:

  • ❌ No automatic linting during local mdbook serve
  • ❌ Need to maintain separate configuration
  • ❌ Developers might forget to run locally

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:

  1. Duplicate Work: The same files get linted twice, wasting CI time
  2. Configuration Drift: Two places to maintain rules can lead to inconsistencies
  3. Confusing Failures: Issues might be reported twice in different formats
  4. 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:

  1. Extract configuration from book.toml to .mdbook-lint.toml
  2. Remove preprocessor section from book.toml
  3. Update CI to run mdbook-lint before mdbook build
  4. Document the change for your team

From Standalone to Preprocessor

If you're using standalone but want to switch to preprocessor:

  1. Add preprocessor section to book.toml
  2. Copy configuration from .mdbook-lint.toml to book.toml
  3. Remove standalone lint step from CI
  4. Update documentation for developers

For mdBook Projects: Use Preprocessor

# book.toml
[preprocessor.mdbook-lint]
fail-on-warnings = false  # true in CI via env var

# .github/workflows/docs.yml
env:
  MDBOOK_PREPROCESSOR__MDBOOK_LINT__FAIL_ON_WARNINGS: true

For Non-mdBook Projects: Use Standalone

# .github/workflows/lint.yml
- uses: joshrotenberg/mdbook-lint-action@v1
  with:
    files: '**/*.md'
    fail-on-warnings: true

For Maximum Flexibility: Standalone with Optional Preprocessor

# book.toml - minimal config for local development
[preprocessor.mdbook-lint]
fail-on-warnings = false

# CI runs standalone for proper control
# Developers get feedback during mdbook serve

Common Pitfalls to Avoid

  1. Don't duplicate the same rules in both preprocessor and standalone configs
  2. Don't run both in CI unless you have a specific reason
  3. Don't use || true to ignore failures - fix the issues or disable specific rules
  4. Don't forget to document which approach you're using for new contributors

Summary

  • Use preprocessor: When you have an mdBook project and want integrated linting
  • Use standalone: When you need CI flexibility or don't use mdBook
  • Avoid using both: Unless you specifically need different rule sets
  • 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

Command Not Found

Problem: mdbook-lint: command not found after installation.

Solutions:

  1. 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
    
  2. Check installation location:

    find ~ -name mdbook-lint -type f 2>/dev/null
    
  3. 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:

  1. Update Rust toolchain:

    rustup update stable
    rustup default stable
    
  2. Clear cargo cache:

    cargo clean
    rm -rf ~/.cargo/registry/cache
    
  3. 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:

  1. 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"
  1. 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:

  1. 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"]
  1. 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:

  1. 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:

  1. Disable rules inline:

    <!-- mdbook-lint-disable MD033 -->
    <div class="custom-element">
      This HTML is intentional
    </div>
    <!-- mdbook-lint-enable MD033 -->
    
  2. 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:

  1. Search existing issues:

    gh issue list --repo joshrotenberg/mdbook-lint --search "your error"
    
  2. 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
    
  3. Join 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 categories of rules:

Standard Markdown Rules

59 rules (MD001-MD059) based on the widely-used markdownlint specification. These rules ensure consistent markdown formatting and style across your documentation.

Categories

mdBook-Specific Rules

7 rules (MDBOOK001-MDBOOK007) specifically designed for mdBook projects. These rules validate mdBook-specific syntax and conventions.

Rules

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.

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 IDNameDescriptionFix
MD001heading-incrementHeading levels should only increment by one level at a time
MD002first-heading-h1First heading should be a top-level heading
MD003heading-styleHeading style
MD004ul-styleUnordered list style
MD005list-indentInconsistent indentation for list items at the same level
MD006ul-start-leftConsider starting lists at the beginning of the line
MD007ul-indentUnordered list indentation
MD008no-bare-urlsBare URLs should be wrapped in angle brackets
MD009no-trailing-spacesTrailing spaces
MD010no-hard-tabsHard tabs
MD011no-reversed-linksReversed link syntax
MD012no-multiple-blanksMultiple consecutive blank lines
MD013line-lengthLine length
MD014commands-show-outputDollar signs used before commands without showing output
MD015no-missing-space-closed-atxNo space after hash on closed atx style heading
MD016no-reversed-heading-styleHeading levels should only increment
MD017blanks-around-headingsBlank lines around headings
MD018no-missing-space-atxNo space after hash on atx style heading
MD019no-multiple-space-atxMultiple spaces after hash on atx style heading
MD020no-missing-space-closed-atxNo space inside hashes on closed atx style heading
MD021no-multiple-space-closed-atxMultiple spaces inside hashes on closed atx style heading
MD022blanks-around-headingsHeadings should be surrounded by blank lines
MD023heading-start-leftHeadings must start at the beginning of the line
MD024no-duplicate-headingMultiple headings with the same content
MD025single-h1Multiple top-level headings in the same document
MD026no-trailing-punctuationTrailing punctuation in heading
MD027no-multiple-space-blockquoteMultiple spaces after blockquote symbol
MD028no-blanks-blockquoteBlank line inside blockquote
MD029ol-prefixOrdered list item prefix
MD030list-marker-spaceSpaces after list markers
MD031blanks-around-fencesFenced code blocks should be surrounded by blank lines
MD032blanks-around-listsLists should be surrounded by blank lines
MD033no-inline-htmlInline HTML
MD034no-bare-urlsBare URL used
MD035hr-styleHorizontal rule style
MD036no-emphasis-as-headingEmphasis used instead of a heading
MD037no-space-in-emphasisSpaces inside emphasis markers
MD038no-space-in-codeSpaces inside code span elements
MD039no-space-in-linksSpaces inside link text
MD040fenced-code-languageFenced code blocks should have a language specified
MD041first-line-h1First line in file should be a top-level heading
MD042no-empty-linksNo empty links
MD043required-headingsRequired heading structure
MD044proper-namesProper names should have correct capitalization
MD045no-alt-textImages should have alternate text
MD046code-block-styleCode block style
MD047single-trailing-newlineFiles should end with a single newline character
MD048code-fence-styleCode fence style
MD049emphasis-styleEmphasis style should be consistent
MD050strong-styleStrong style should be consistent
MD051link-fragmentsLink fragments should be valid
MD052reference-links-imagesReference links and images should use a label that is defined
MD053link-image-reference-definitionsLink and image reference definitions should be needed
MD054link-image-styleLink and image style
MD055table-pipe-styleTable pipe style
MD056table-column-countTable column count
MD057table-rowsTable rows
MD058blanks-around-tablesTables should be surrounded by blank lines
MD059table-alignmentTable 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

RuleDescriptionFix
MD001Heading levels should only increment by one level at a time
MD002First heading should be a top-level heading
MD003Heading style (ATX vs Setext)
MD018No space after hash on ATX style heading
MD019Multiple spaces after hash on ATX style heading
MD020No space inside hashes on closed ATX style heading
MD021Multiple spaces inside hashes on closed ATX style heading
MD022Headings should be surrounded by blank lines
MD023Headings must start at the beginning of the line
MD024Multiple headings with the same content
MD025Multiple top-level headings in the same document
MD026Trailing punctuation in heading
MD041First line in file should be a top-level heading

Best Practices

Document Structure

A well-structured document follows these heading principles:

  1. Start with H1: Documents should begin with a single H1 heading
  2. Sequential Levels: Never skip heading levels (H1 → H3 is wrong)
  3. Logical Hierarchy: Use headings to create a document outline
  4. 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:

  1. Table of Contents: Generated from heading hierarchy
  2. Search Index: Headings are weighted in search results
  3. Navigation: Sidebar navigation reflects heading structure
  4. 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

  1. Accessibility: Screen readers rely on proper heading hierarchy to help users navigate documents
  2. Document Structure: Sequential headings create a logical outline
  3. SEO: Search engines use heading structure to understand content hierarchy
  4. 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
  • 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

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 -->
  • 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 -->
  • 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 -->
  • 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 -->
  • 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

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 -->
  • 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

List Rules

List rules ensure consistent formatting, indentation, and structure for both ordered and unordered lists.

Rules in This Category

RuleDescriptionFix
MD004Unordered list style (consistent markers)
MD005Inconsistent indentation for list items at the same level
MD006Consider starting lists at the beginning of the line
MD007Unordered list indentation
MD029Ordered list item prefix
MD030Spaces after list markers
MD031Fenced code blocks should be surrounded by blank lines
MD032Lists 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
  1. Configure the linter:

    {
      "rules": {
        "MD009": true
      }
    }
    
  2. 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:

  1. Screen Readers: Announce list structure and item count
  2. Navigation: Users can skip between lists
  3. Context: Proper nesting conveys relationships
  4. Semantics: Lists convey meaning beyond visual formatting

mdBook-Specific Considerations

In mdBook projects:

  1. Table of Contents: Lists in SUMMARY.md define book structure
  2. Navigation: Nested lists create hierarchical navigation
  3. Rendering: List formatting affects HTML output
  4. 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
  • MD013 - Line length (affects long list items)
  • MD022 - Blank lines (around list blocks)
  • MD031 - Code blocks in lists
  • MD032 - Blank lines around lists

References

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 -->
  • 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

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"]

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:

  1. File Size: Unnecessary whitespace increases file size
  2. Diff Noise: Changes to trailing spaces clutter version control
  3. Search/Replace: Invisible characters can break patterns
  4. Copy/Paste: Trailing spaces may cause unexpected behavior

Automatic Fix Behavior

The automatic fix will:

  1. Remove all trailing whitespace from each line
  2. Preserve exactly br_spaces spaces when configured (default: 2)
  3. Handle tabs and mixed whitespace
  4. 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
  • 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 -->
  • 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 -->
  • MD009 - No trailing spaces
  • MD010 - Hard tabs
  • MD047 - Files should end with newline

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 -->
  • MD028 - Blank line inside blockquote
  • MD032 - Lists surrounded by blank lines

References

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"]
  • MD009 - No trailing spaces
  • MD010 - Hard tabs
  • MD012 - Multiple consecutive blank lines

References

Link Rules

These rules ensure proper link formatting and validation in markdown documents.

Rules in This Category

Auto-fix Available ✓

  • 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

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
[Link text](https://example.com)
[Relative link](./other-page.md)
[Anchor link](#section-heading)
[Link text][reference]
[Another link][1]

[reference]: https://example.com
[1]: ./other-page.md
<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

  1. Use descriptive link text: Avoid "click here" or "link"
  2. Prefer relative paths: For internal documentation links
  3. Check anchors: Ensure heading anchors exist
  4. Use reference style: For frequently used URLs
  5. Wrap bare URLs: Use <URL> syntax or proper links

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 -->
  • MD011 - Reversed link syntax
  • MD039 - Spaces inside link text
  • MD042 - No empty links

References

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

```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:

LanguageTags
JavaScriptjs, javascript
TypeScriptts, typescript
Pythonpy, python
Rustrs, rust
Shellsh, bash, shell
JSONjson
YAMLyml, 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

  1. Always specify language: Enables syntax highlighting
  2. Use fenced blocks: More flexible than indented blocks
  3. Surround with blank lines: Improves readability
  4. Be consistent: Use the same style throughout
  5. 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

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

LanguageTags
JavaScriptjs, javascript
TypeScriptts, typescript
Pythonpy, python
Rustrs, rust
Shellsh, bash, shell
JSONjson
YAMLyml, yaml
Markdownmd, markdown
Plain Texttext, txt
TOMLtoml
HTMLhtml
CSScss
SQLsql

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 -->
  • MD046 - Code block style
  • MD048 - Code fence style
  • MDBOOK001 - Code blocks should have language tags (mdBook-specific)

References

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

  1. Choose and document: Pick a style and document it
  2. Be consistent: Use the same style throughout
  3. Consider your audience: Technical vs. general readers
  4. Think about rendering: How it looks in your target output
  5. Automate checks: Use CI/CD to enforce style

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

  1. Break at natural points: Sentences, clauses, or phrases
  2. Use soft wrapping: Let your editor wrap visually while keeping semantic lines
  3. Consider semantic line breaks: One sentence per line
  4. 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

References

mdBook-Specific Rules

These rules are specifically designed for mdBook projects, validating mdBook-specific syntax, conventions, and structure.

Rules

Rule IDNameDescription
MDBOOK001code-block-languageCode blocks should have language tags
MDBOOK002summary-structureSUMMARY.md should follow mdBook structure
MDBOOK003internal-linksInternal links should be valid
MDBOOK004part-titlesPart titles should be formatted correctly
MDBOOK005chapter-pathsChapter paths should be relative
MDBOOK006draft-chaptersDraft chapters should have content or be marked
MDBOOK007separator-syntaxSeparator syntax should be correct

Why mdBook-Specific Rules?

mdBook extends standard Markdown with special features:

  1. SUMMARY.md Structure: Defines book organization
  2. Include Syntax: {{#include file.md}}
  3. Playground Links: {{#playground file.rs}}
  4. Hidden Lines: Lines starting with # in Rust code blocks
  5. Quiz Support: Interactive quizzes in documentation
  6. 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

  1. Run Checks Before Building: Catch issues early
  2. Include in Pre-commit Hooks: Prevent broken commits
  3. Document Exceptions: If disabling rules, explain why
  4. Test Rendering: Lint checks complement, not replace, build tests
  5. Version Control SUMMARY.md: Track structure changes

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:

  • text or plain - for plain text without highlighting
  • console - for command-line output
  • diff - for showing differences
  • ignore - for Rust code that shouldn't be tested
  • no_run - for Rust code that compiles but shouldn't run
  • should_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

LanguageTagCommon Uses
RustrustPrimary language for mdBook documentation
JavaScriptjavascript or jsWeb examples, Node.js code
Pythonpython or pyScripts, examples
Shellbash or shCommand-line examples
TOMLtomlConfiguration files
JSONjsonData structures, APIs
YAMLyaml or ymlConfiguration, CI/CD
HTMLhtmlWeb markup
CSScssStyling examples
SQLsqlDatabase queries

Special Tags

TagPurpose
text or plainPlain text without highlighting
consoleTerminal output with prompt highlighting
diffShowing differences with +/- highlighting
markdown or mdMarkdown 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

  1. Is it code? → Use appropriate language tag
  2. Is it terminal output? → Use console
  3. Is it a diff? → Use diff
  4. Is it plain text? → Use text or plain
  5. Is it data? → Use format tag (json, yaml, toml)
  6. Not sure? → Use text rather 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

This rule has no configuration options. All code blocks should have language tags for optimal mdBook rendering.

  • 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

  1. File existence: Verifies linked .md files exist
  2. Anchor validity: Confirms heading anchors are present
  3. Path resolution: Validates relative paths from current file
  4. 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

  1. Use relative paths: More maintainable than absolute paths
  2. Update links when renaming: Use search and replace
  3. Test navigation: Click through links after changes
  4. 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 -->

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

  1. Title: Must start with # Summary
  2. Prefix Chapter: Optional [Introduction](./intro.md) before numbered chapters
  3. Numbered Chapters: Use consistent list markers (- or *)
  4. 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 Name for 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

  1. Use consistent indentation: Pick 2 or 4 spaces and stick with it
  2. Order matters: Chapters appear in the order listed
  3. Test the build: Run mdbook build to verify structure
  4. Use draft chapters: For work-in-progress sections

References

MDBOOK004 - No Duplicate Chapter Titles

Severity: Warning
Category: mdBook-specific
Auto-fix: Not available

Rule Description

This rule ensures that chapter titles are unique across the entire mdBook project. Duplicate titles can confuse readers and make navigation difficult.

Why This Rule Exists

Unique chapter titles are important because:

  • Prevents reader confusion when navigating the book
  • Ensures clear distinction between different chapters
  • Improves search functionality and indexing
  • Makes cross-references unambiguous
  • Helps maintain organized documentation structure

Examples

❌ Incorrect (violates rule)

In chapter1.md:

# Introduction
Content for first introduction...

In chapter5.md:

# Introduction
Different content but same title...

✅ Correct

In chapter1.md:

# Getting Started
Introduction content...

In chapter5.md:

# API Introduction
API-specific introduction...

Or use more specific titles:

# Project Overview
# Installation Guide
# Configuration Reference
# API Documentation

What This Rule Checks

  1. First-level headings: Checks all H1 headings across files
  2. Case sensitivity: Treats "Introduction" and "introduction" as duplicates
  3. Cross-file validation: Compares titles across all book chapters
  4. SUMMARY.md entries: Validates chapter titles in the table of contents

Configuration

[MDBOOK004]
case_sensitive = false  # Case-sensitive comparison (default: false)
ignore_prefixes = ["Chapter", "Part"]  # Prefixes to ignore

Common Issues and Solutions

Issue: Generic Titles

Many chapters use generic titles like "Introduction" or "Overview":

<!-- Bad: Too generic -->
# Introduction
# Overview
# Configuration
# Usage

<!-- Good: More specific -->
# Project Introduction
# Architecture Overview
# Database Configuration
# CLI Usage

Issue: Section Titles as Chapter Titles

Using section-level titles as chapter titles:

<!-- Bad: Section-like titles -->
# Installing
# Configuring
# Running

<!-- Good: Complete chapter titles -->
# Installation Guide
# Configuration Reference
# Running the Application

When to Disable

Consider disabling this rule if:

  • Your book intentionally uses duplicate titles (e.g., multiple "Introduction" sections)
  • You're generating content dynamically with potential duplicates
  • You have a large multi-part book where context makes duplicates clear

Disable in Config

# .mdbook-lint.toml
disabled_rules = ["MDBOOK004"]

Disable for Specific Files

[[overrides]]
path = "appendices/**"
disabled_rules = ["MDBOOK004"]

Best Practices

  1. Be specific: Use descriptive, unique titles for each chapter
  2. Add context: Include the subject area in the title
  3. Use hierarchy: Let SUMMARY.md structure provide context
  4. Consider prefixes: Use part/section prefixes for clarity

Title Patterns

# [Subject] + [Type]
# Database Configuration
# API Reference
# Testing Guide

# [Action] + [Target]
# Installing Dependencies
# Configuring the Server
# Building from Source

# [Module] + [Aspect]
# Authentication Overview
# Storage Implementation
# Network Architecture
  • MDBOOK003 - SUMMARY.md structure validation
  • MD024 - Multiple headings with same content
  • MD025 - Multiple top-level headings

References

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:

  1. All .md files in the source directory
  2. Files referenced in SUMMARY.md
  3. Reports files not in SUMMARY.md as orphaned

Special Cases

Files that are not considered orphaned:

  • SUMMARY.md itself
  • README.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

  1. Regular cleanup: Periodically review and remove orphaned files
  2. Use drafts folder: Keep work-in-progress in a separate directory
  3. Document intentional orphans: Add comments explaining why files are kept
  4. Version control: Check git history before deleting orphaned files

References

MDBOOK006 - Invalid Cross-Reference Links

Severity: Error
Category: mdBook-specific
Auto-fix: Not available

Rule Description

This rule validates internal cross-reference links that point to headings in other files. It ensures that both the target file exists and contains the referenced heading anchor.

Why This Rule Exists

Valid cross-references are crucial because:

  • Ensures readers can navigate between related sections
  • Prevents broken links in published documentation
  • Maintains documentation integrity across chapters
  • Enables proper mdBook navigation features
  • Helps identify outdated references after refactoring

Examples

❌ Incorrect (violates rule)

<!-- Target heading doesn't exist -->
See [configuration details](./config.md#database-setup)

<!-- File exists but anchor is wrong -->
Check the [API reference](./api.md#rest-endpoints)

<!-- Typo in anchor -->
Read about [authentication](./auth.md#authentification)

✅ Correct

<!-- Valid file and anchor -->
See [configuration details](./config.md#database-configuration)

<!-- Correct anchor format -->
Check the [API reference](./api.md#rest-api-endpoints)

<!-- Valid cross-reference -->
Read about [authentication](./auth.md#authentication)

What This Rule Checks

  1. File existence: Target .md file must exist
  2. Heading presence: Referenced heading must exist in target file
  3. Anchor format: Anchor must match mdBook's heading ID generation
  4. Case sensitivity: Anchors are case-insensitive but should match

Anchor Generation Rules

mdBook generates anchors from headings:

  • Convert to lowercase
  • Replace spaces with hyphens
  • Remove special characters (except hyphens and underscores)
  • Handle duplicate anchors with numeric suffixes
## Hello World!           → #hello-world
## User's Guide          → #users-guide
## API Reference (v2)    → #api-reference-v2
## 1.2.3 Version Notes   → #123-version-notes

Configuration

[MDBOOK006]
check_external = false   # Don't check external URLs (default: false)
ignore_missing = false   # Report missing files (default: false)
case_sensitive = false   # Case-sensitive anchor matching (default: false)

Common Issues and Solutions

Issue: Heading Text Changed

When heading text changes, anchors break:

<!-- Original heading in config.md -->
## Database Setup

<!-- Link that breaks after heading change -->
[See database setup](./config.md#database-setup)

<!-- After heading renamed to "Database Configuration" -->
[See database configuration](./config.md#database-configuration)

Solution: Update all cross-references when changing headings

Issue: Special Characters in Headings

Special characters affect anchor generation:

<!-- Heading with special characters -->
## What's New? (2024)

<!-- Wrong anchor -->
[What's new](./changelog.md#whats-new-2024)  ✗

<!-- Correct anchor -->
[What's new](./changelog.md#whats-new-2024)   ✓

Issue: Duplicate Headings

Multiple headings with same text get numeric suffixes:

<!-- In api.md -->
## Authentication
...
## Authentication  <!-- Gets #authentication-1 -->

<!-- Linking to second occurrence -->
[Second auth section](./api.md#authentication-1)

When to Disable

Consider disabling this rule if:

  • You're in the middle of major documentation restructuring
  • Your build process generates files dynamically
  • You use external tools that create anchors differently
  • You have many legacy cross-references to update

Disable in Config

# .mdbook-lint.toml
disabled_rules = ["MDBOOK006"]

Disable Inline

<!-- mdbook-lint-disable MDBOOK006 -->
[Temporarily broken cross-reference](./future.md#todo-section)
<!-- mdbook-lint-enable MDBOOK006 -->

Best Practices

  1. Use descriptive anchors: Make headings clear and unique
  2. Test after refactoring: Verify links after changing headings
  3. Maintain a link registry: Document important cross-references
  4. Use consistent heading style: Makes anchors predictable
  5. Avoid special characters: Simplifies anchor generation

Cross-Reference Patterns

<!-- Good: Clear, specific references -->
[See configuration options](./config.md#available-options)
[Authentication guide](./auth.md#oauth2-setup)
[API error codes](./api.md#error-handling)

<!-- Avoid: Vague or fragile references -->
[See here](./config.md#options)
[More info](./auth.md#setup)
[Errors](./api.md#errors)

Tools and Tips

Finding Broken Cross-References

# Run linter to find all cross-reference issues
mdbook-lint lint --rules MDBOOK006 docs/

# Test all links with mdBook
mdbook test

Generating Anchor Lists

# List all headings and their anchors
grep "^#" docs/**/*.md | sed 's/#\+//'
  • MDBOOK002 - Invalid internal link
  • MD051 - Link fragments are valid
  • MD052 - Reference links and images

References

MDBOOK007 - Invalid Include Directive

Severity: Error
Category: mdBook-specific
Auto-fix: Not available

Rule Description

This rule validates {{#include}} directives used to include content from other files. It ensures the syntax is correct and the referenced files exist.

Why This Rule Exists

Valid include directives are essential because:

  • Prevents build failures from broken includes
  • Ensures included content is available
  • Maintains documentation modularity
  • Enables content reuse across chapters
  • Helps identify moved or renamed files

Examples

❌ Incorrect (violates rule)

<!-- File doesn't exist -->
{{#include ./missing-file.md}}

<!-- Invalid syntax -->
{{include ./file.md}}
{{#includes ./file.md}}

<!-- Malformed path -->
{{#include file.md}}  <!-- Missing ./ prefix -->
{{#include ../../../outside-project.md}}

<!-- Invalid line range syntax -->
{{#include ./file.md:1-}}
{{#include ./file.md:a-b}}

✅ Correct

<!-- Include entire file -->
{{#include ./examples/sample.md}}

<!-- Include specific lines -->
{{#include ./code.rs:1:10}}
{{#include ./code.rs:5:}}
{{#include ./code.rs::10}}

<!-- Include with anchor -->
{{#include ./file.md:anchor}}

<!-- Include from parent directory -->
{{#include ../shared/header.md}}

Include Directive Syntax

Basic Include

{{#include filepath}}

Line Range Selection

{{#include filepath:start:end}}  <!-- Lines start to end -->
{{#include filepath:start:}}     <!-- From start to EOF -->
{{#include filepath::end}}       <!-- From beginning to end -->
{{#include filepath:line}}       <!-- Single line -->

Anchor-based Include

{{#include filepath:anchor}}     <!-- Include anchor section -->

In the source file:

<!-- ANCHOR: example -->
Content to include
<!-- ANCHOR_END: example -->

Configuration

[MDBOOK007]
check_line_ranges = true    # Validate line numbers exist (default: true)
allow_external = false      # Allow includes outside src/ (default: false)
max_depth = 3              # Maximum directory traversal depth (default: 3)

Common Issues and Solutions

Issue: Relative Path Confusion

<!-- Current file: src/chapter1/section.md -->

<!-- Wrong: Assumes path from src/ -->
{{#include examples/code.rs}}  ✗

<!-- Correct: Relative to current file -->
{{#include ../examples/code.rs}}  ✓

Issue: Line Numbers Out of Range

<!-- file.md has 50 lines -->

<!-- Error: End line exceeds file length -->
{{#include ./file.md:1:100}}

<!-- Fix: Use open-ended range -->
{{#include ./file.md:1:}}

Issue: Platform-Specific Paths

<!-- Windows-style path (avoid) -->
{{#include .\examples\code.rs}}

<!-- Unix-style path (preferred) -->
{{#include ./examples/code.rs}}

Advanced Usage

Including Code with Hidden Lines

#![allow(unused)]
fn main() {
{{#include ./examples/main.rs}}
}

In main.rs:

#![allow(unused)]
fn main() {
fn hidden_function() {
    // This line is hidden in mdBook output
}

fn visible_function() {
    // This is shown
}
}

Nested Includes

Includes can contain other includes:

<!-- chapter.md -->
{{#include ./sections/intro.md}}

<!-- sections/intro.md -->
This is the introduction.
{{#include ../examples/sample.md}}

When to Disable

Consider disabling this rule if:

  • You're generating included files during the build process
  • You're migrating content with many broken includes
  • You use a custom preprocessor that handles includes differently
  • You're prototyping with placeholder includes

Disable in Config

# .mdbook-lint.toml
disabled_rules = ["MDBOOK007"]

Disable Inline

<!-- mdbook-lint-disable MDBOOK007 -->
{{#include ./todo-file.md}}
<!-- mdbook-lint-enable MDBOOK007 -->

Best Practices

  1. Use relative paths: More maintainable than absolute paths
  2. Keep includes close: Avoid deep directory traversal
  3. Document anchors: Comment what each anchor contains
  4. Test after moving files: Update include paths when reorganizing
  5. Prefer anchors over line numbers: More stable than line ranges

Include Organization

book/
├── src/
│   ├── chapter1.md
│   ├── chapter2.md
│   └── includes/        <!-- Dedicated includes directory -->
│       ├── header.md
│       ├── footer.md
│       └── examples/
│           └── code.rs

Anchor Documentation

<!-- ANCHOR: configuration_example -->
<!-- This anchor includes a complete configuration example -->
```toml
[book]
title = "My Book"

## Error Messages

Common error messages and their solutions:

| Error | Solution |
|-------|----------|
| "File not found" | Check file path and spelling |
| "Invalid line range" | Verify line numbers exist |
| "Anchor not found" | Ensure anchor tags match |
| "Path traversal too deep" | Simplify directory structure |

## Related Rules

- [MDBOOK008](./mdbook008.html) - Rustdoc include validation
- [MDBOOK011](./mdbook011.html) - Template include syntax
- [MDBOOK012](./mdbook012.html) - Include line ranges

## References

- [mdBook - Including Files](https://rust-lang.github.io/mdBook/format/markdown.html#including-files)
- [mdBook Preprocessors](https://rust-lang.github.io/mdBook/format/configuration/preprocessors.html)

MDBOOK008 - Invalid Rustdoc Include

Severity: Error
Category: mdBook-specific
Auto-fix: Not available

Rule Description

This rule validates {{#rustdoc_include}} directives used to include Rust code from external files with proper rustdoc handling. It ensures correct syntax and that referenced files exist.

Why This Rule Exists

Valid rustdoc includes are important because:

  • Ensures Rust code examples compile and run correctly
  • Preserves rustdoc annotations and attributes
  • Maintains testable documentation
  • Enables code reuse from actual Rust projects
  • Prevents broken code examples in documentation

Examples

❌ Incorrect (violates rule)

<!-- File doesn't exist -->
{{#rustdoc_include ./missing.rs}}

<!-- Invalid syntax -->
{{#rustdoc_includes ./file.rs}}
{{#rust_doc_include ./file.rs}}

<!-- Invalid line range -->
{{#rustdoc_include ./main.rs:a:b}}
{{#rustdoc_include ./main.rs:-10}}

✅ Correct

<!-- Include entire Rust file -->
{{#rustdoc_include ./examples/main.rs}}

<!-- Include specific lines -->
{{#rustdoc_include ./src/lib.rs:1:20}}
{{#rustdoc_include ./src/lib.rs:10:}}
{{#rustdoc_include ./src/lib.rs::15}}

<!-- Include with anchor -->
{{#rustdoc_include ./src/lib.rs:example_function}}

Rustdoc Include vs Regular Include

Key Differences

Feature{{#include}}{{#rustdoc_include}}
Hidden lines (# )Shown as-isHidden in output
Doc commentsShown as-isProcessed correctly
Rust syntax highlightingManualAutomatic
Test annotationsIgnoredPreserved

Example Comparison

Source file example.rs:

#![allow(unused)]
fn main() {
use std::collections::HashMap;
/// Creates a new cache
fn create_cache() -> HashMap<String, String> {
    let mut cache = HashMap::new();
    cache.insert("key".into(), "value".into());
    cache
}
}

With {{#include ./example.rs}}:

#![allow(unused)]
fn main() {
use std::collections::HashMap;
/// Creates a new cache
fn create_cache() -> HashMap<String, String> {
    let mut cache = HashMap::new();
    cache.insert("key".into(), "value".into());
    cache
}
}

With {{#rustdoc_include ./example.rs}}:

#![allow(unused)]
fn main() {
/// Creates a new cache
fn create_cache() -> HashMap<String, String> {
    cache
}
}

Configuration

[MDBOOK008]
check_compilation = false  # Try to compile included code (default: false)
allow_external = false    # Allow includes outside project (default: false)
validate_anchors = true   # Check anchor existence (default: true)

Common Issues and Solutions

Issue: Hidden Lines Not Working

<!-- Wrong: Using regular include -->
{{#include ./example.rs}}  <!-- Shows # lines -->

<!-- Correct: Using rustdoc_include -->
{{#rustdoc_include ./example.rs}}  <!-- Hides # lines -->

Issue: Doc Tests Not Running

#![allow(unused)]
fn main() {
// example.rs
/// ```
/// assert_eq!(2 + 2, 4);
/// ```
fn documented_function() {}
}
<!-- Use rustdoc_include to preserve doc tests -->
{{#rustdoc_include ./example.rs}}

Issue: Anchor Mismatch

#![allow(unused)]
fn main() {
// ANCHOR: my_example
fn example() {
    // code
}
// ANCHOR_END: my_example
}
<!-- Wrong anchor name -->
{{#rustdoc_include ./code.rs:myexample}}  ✗

<!-- Correct anchor name -->
{{#rustdoc_include ./code.rs:my_example}}  ✓

Best Practices

  1. Use for Rust code: Only use rustdoc_include for .rs files
  2. Hide setup code: Use # prefix for boilerplate
  3. Include from src/: Reference actual project code when possible
  4. Document anchors: Explain what each anchor demonstrates
  5. Test included code: Ensure examples compile and run

Rust File Organization

// examples/complete_example.rs

// ANCHOR: imports
use std::collections::HashMap;
use std::error::Error;
// ANCHOR_END: imports

// ANCHOR: main_example
/// Main example function
pub fn example() -> Result<(), Box<dyn Error>> {
    let data = HashMap::new();
    // Visible implementation
    println!("Processing data...");
    Ok(())
}
// ANCHOR_END: main_example

fn main() {
    example().unwrap();
}

Including in Documentation

## Complete Example

Here's how to use the library:

{{#rustdoc_include ./examples/complete_example.rs:main_example}}

The necessary imports are:

{{#rustdoc_include ./examples/complete_example.rs:imports}}

When to Disable

Consider disabling this rule if:

  • You're generating Rust files during the build
  • You're migrating from regular includes gradually
  • You use a custom preprocessor for Rust code
  • Your examples are in a separate repository

Disable in Config

# .mdbook-lint.toml
disabled_rules = ["MDBOOK008"]

Disable Inline

<!-- mdbook-lint-disable MDBOOK008 -->
{{#rustdoc_include ./generated/example.rs}}
<!-- mdbook-lint-enable MDBOOK008 -->

Testing Included Code

Verify Examples Compile

# Test all Rust code blocks
mdbook test

# Test specific chapter
mdbook test -c "Chapter Name"

Extract and Test Separately

# Extract code examples
for file in examples/*.rs; do
    rustc --test "$file"
done

References

MDBOOK009 - Invalid Playground Configuration

Severity: Warning
Category: mdBook-specific
Auto-fix: Not available

Rule Description

This rule validates {{#playground}} directives and playground configuration in code blocks. It ensures proper syntax for making code examples editable and runnable in the Rust Playground.

Why This Rule Exists

Valid playground configuration is important because:

  • Enables interactive code examples for readers
  • Ensures code can run in the Rust Playground
  • Provides hands-on learning experiences
  • Validates that examples are executable
  • Maintains consistent playground behavior

Examples

❌ Incorrect (violates rule)

<!-- Invalid directive syntax -->
{{#playgrounds ./file.rs}}
{{playground ./file.rs}}

<!-- Invalid playground attributes -->
```rust,playground
fn main() {
    println!("Hello");
}
```

<!-- Missing file -->
{{#playground ./missing.rs}}

✅ Correct

<!-- Include file as playground -->
{{#playground ./examples/hello.rs}}

<!-- Inline editable code -->
```rust,editable
fn main() {
    println!("Hello, world!");
}
```

<!-- Non-editable but runnable -->
```rust,no_run
fn main() {
    // This compiles but won't run
}
```

<!-- Editable with hidden lines -->
```rust,editable
# fn hidden_setup() {}
fn main() {
    println!("Visible code");
}
```

Playground Attributes

Code Block Attributes

AttributeDescription
editableMakes code block editable in browser
no_runCode compiles but doesn't run
compile_failExample that should fail compilation
ignoreCode block is not tested
should_panicCode should panic when run

Examples with Attributes

<!-- Editable example -->
```rust,editable
fn main() {
    let x = 5;
    println!("x = {}", x);
}
```

<!-- Example that should fail -->
```rust,compile_fail
fn main() {
    let x: i32 = "not a number";
}
```

<!-- Example that panics -->
```rust,should_panic
fn main() {
    panic!("This is expected!");
}
```

Configuration

# book.toml
[output.html.playground]
editable = true         # Make code blocks editable by default
copyable = true         # Add copy button to code blocks
copy-js = true          # Include JavaScript for copy functionality
line-numbers = false    # Show line numbers in code blocks

[MDBOOK009]
require_editable = false  # Require editable attribute (default: false)
validate_compilation = false  # Check if code compiles (default: false)

Common Issues and Solutions

Issue: Playground Not Enabled

# book.toml - Enable playground
[output.html.playground]
editable = true

Issue: Code Doesn't Compile

<!-- Missing main function -->
```rust,editable
println!("Hello");  // Error: not in a function
```

<!-- Fixed -->
```rust,editable
fn main() {
    println!("Hello");
}
```

Issue: Hidden Lines Shown

<!-- Wrong: Hidden lines visible in playground -->
```rust,editable
# use std::io;
# fn setup() {}
fn main() {
    // User code
}
```

<!-- Note: Hidden lines work but are still editable -->

Best Practices

  1. Provide complete examples: Include all necessary code
  2. Use hidden lines sparingly: Only for necessary boilerplate
  3. Test in playground: Verify examples work in actual playground
  4. Add context: Explain what users should try changing
  5. Keep examples focused: One concept per playground

Interactive Example Template

## Try It Yourself

Modify the code below to experiment with different values:

```rust,editable
fn calculate_area(width: f64, height: f64) -> f64 {
    width * height
}

fn main() {
    // Try changing these values!
    let width = 10.0;
    let height = 5.0;
    
    let area = calculate_area(width, height);
    println!("Area: {} square units", area);
    
    // Challenge: Add a perimeter calculation
}
```

**Suggestions to try:**
- Change the dimensions
- Add a perimeter function
- Handle negative values

Advanced Usage

Custom Playground URL

# book.toml
[output.html.playground]
editable = true
site = "https://play.rust-lang.org"

Playground with Dependencies

<!-- Note: Dependencies must be available in playground -->
```rust,editable
// Available in playground: rand, regex, lazy_static, etc.
use rand::Rng;

fn main() {
    let mut rng = rand::thread_rng();
    let n: u32 = rng.gen_range(0..10);
    println!("Random number: {}", n);
}
```

When to Disable

Consider disabling this rule if:

  • Your book doesn't use playground features
  • You're targeting offline readers
  • You have custom code execution environment
  • Your examples require local dependencies

Disable in Config

# .mdbook-lint.toml
disabled_rules = ["MDBOOK009"]

Disable Inline

<!-- mdbook-lint-disable MDBOOK009 -->
{{#playground ./complex-example.rs}}
<!-- mdbook-lint-enable MDBOOK009 -->

Troubleshooting

Playground Not Working

  1. Check book.toml configuration
  2. Verify code compiles standalone
  3. Test in actual Rust Playground
  4. Check browser console for errors

Common Error Messages

ErrorSolution
"Playground not configured"Enable in book.toml
"Code doesn't compile"Add missing imports/main function
"File not found"Check file path in directive
"Invalid attribute"Use supported attributes only

References

MDBOOK010 - Invalid Preprocessor Configuration

Severity: Warning
Category: mdBook-specific
Auto-fix: Not available

Rule Description

This rule validates preprocessor directives and configuration, including math blocks, mermaid diagrams, and other mdBook preprocessor syntax. It ensures proper formatting for preprocessor features.

Why This Rule Exists

Valid preprocessor configuration is important because:

  • Prevents build failures from malformed directives
  • Ensures special content renders correctly
  • Maintains compatibility with mdBook preprocessors
  • Validates math notation and diagram syntax
  • Helps identify configuration issues early

Examples

❌ Incorrect (violates rule)

<!-- Invalid math block syntax -->
$$
x = \frac{-b \pm \sqrt{b^2 - 4ac}{2a}  <!-- Missing closing brace -->
$$

<!-- Wrong delimiter -->
$$$
E = mc^2
$$$

<!-- Invalid mermaid block -->
```mermaid
graph LR
    A --> B  <!-- Missing semicolons in some cases -->
```

<!-- Malformed preprocessor directive -->
{{#math}}x^2{{/math}}  <!-- Wrong syntax -->

✅ Correct

<!-- Valid inline math -->
When $a \ne 0$, there are two solutions

<!-- Valid math block -->
$$
x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}
$$

<!-- Valid KaTeX block -->
```math
\int_{-\infty}^{\infty} e^{-x^2} dx = \sqrt{\pi}
```

<!-- Valid mermaid diagram -->
```mermaid
graph LR
    A[Start] --> B{Decision}
    B -->|Yes| C[Result 1]
    B -->|No| D[Result 2]
```

Supported Preprocessors

Math Rendering (mdbook-katex)

<!-- Inline math -->
The equation $a^2 + b^2 = c^2$ is famous.

<!-- Block math with $$ -->
$$
\begin{aligned}
\nabla \cdot \vec{E} &= \frac{\rho}{\epsilon_0} \\
\nabla \cdot \vec{B} &= 0
\end{aligned}
$$

<!-- Block math with code fence -->
```math
\sum_{i=1}^{n} i = \frac{n(n+1)}{2}
```

Mermaid Diagrams (mdbook-mermaid)

```mermaid
sequenceDiagram
    participant A as Alice
    participant B as Bob
    A->>B: Hello Bob!
    B->>A: Hi Alice!
```

Other Common Preprocessors

<!-- mdbook-admonish -->
```admonish warning
This is a warning message.
```

<!-- mdbook-toc -->
{{#toc}}

<!-- mdbook-plantuml -->
```plantuml
@startuml
Alice -> Bob: Hello
@enduml
```

Configuration

# book.toml - Preprocessor configuration
[preprocessor.katex]
after = ["links"]

[preprocessor.mermaid]
command = "mdbook-mermaid"

[preprocessor.admonish]
command = "mdbook-admonish"

# .mdbook-lint.toml - Rule configuration
[MDBOOK010]
check_math = true       # Validate math syntax (default: true)
check_mermaid = true    # Validate mermaid syntax (default: true)
allowed_preprocessors = ["katex", "mermaid", "admonish"]

Common Issues and Solutions

Issue: Math Not Rendering

# book.toml - Ensure preprocessor is installed
[preprocessor.katex]
# Install the preprocessor
cargo install mdbook-katex

Issue: Delimiter Conflicts

<!-- Problem: Dollar signs in code -->
```bash
echo "Price: $10"  # $ conflicts with math
```

<!-- Solution: Use code fence -->
```text
Price: $10
```

<!-- Or escape in inline code -->
The price is `$10` (literal dollar sign)

Issue: Complex Math Expressions

<!-- Break complex expressions for readability -->
$$
\begin{aligned}
f(x) &= \int_{0}^{x} t^2 dt \\
     &= \left[ \frac{t^3}{3} \right]_{0}^{x} \\
     &= \frac{x^3}{3}
\end{aligned}
$$

Best Practices

  1. Test preprocessors locally: Verify rendering before committing
  2. Use appropriate delimiters: Choose based on content type
  3. Document requirements: List required preprocessors in README
  4. Escape special characters: Prevent delimiter conflicts
  5. Keep it simple: Break complex expressions into parts

Math Block Guidelines

<!-- Good: Clear, well-formatted -->
$$
E = mc^2
$$

<!-- Better: With context -->
Einstein's famous equation:
$$
E = mc^2
$$
where $E$ is energy, $m$ is mass, and $c$ is the speed of light.

Diagram Guidelines

<!-- Good: Simple, clear diagram -->
```mermaid
graph TD
    A[Input] --> B[Process]
    B --> C[Output]
```

<!-- Better: With descriptive labels -->
```mermaid
graph TD
    A[User Input] --> B{Validate}
    B -->|Valid| C[Process Data]
    B -->|Invalid| D[Show Error]
    C --> E[Return Result]
```

When to Disable

Consider disabling this rule if:

  • You use custom preprocessors with different syntax
  • Your documentation doesn't use math or diagrams
  • You're migrating from another documentation system
  • You handle preprocessing externally

Disable in Config

# .mdbook-lint.toml
disabled_rules = ["MDBOOK010"]

Disable Inline

<!-- mdbook-lint-disable MDBOOK010 -->
$$
\custom{syntax}
$$
<!-- mdbook-lint-enable MDBOOK010 -->

Testing Preprocessor Output

# Build and check output
mdbook build
open book/index.html

# Test specific preprocessor
mdbook test

# Clean build
mdbook clean && mdbook build
  • MD040 - Code block language tags
  • MD031 - Fenced code blocks surrounded by blank lines

References

MDBOOK011 - Invalid Template Syntax

Severity: Error
Category: mdBook-specific
Auto-fix: Not available

Rule Description

This rule validates {{#template}} directives used with the mdbook-template preprocessor. It ensures correct syntax for template inclusion and variable substitution.

Why This Rule Exists

Valid template syntax is important because:

  • Prevents build failures from malformed templates
  • Ensures consistent content across chapters
  • Enables proper variable substitution
  • Maintains template reusability
  • Helps identify template errors early

Examples

❌ Incorrect (violates rule)

<!-- Invalid directive syntax -->
{{template file.md}}
{{#templates file.md}}

<!-- Missing template file -->
{{#template ./missing-template.md}}

<!-- Invalid variable syntax -->
{{#template ./template.md
    name=value with spaces
    key = value
}}

<!-- Unclosed directive -->
{{#template ./template.md
    var1=value1

✅ Correct

<!-- Basic template inclusion -->
{{#template ./templates/header.md}}

<!-- Template with variables -->
{{#template ./templates/api-doc.md
    method=GET
    endpoint=/api/users
    description=Retrieves all users
}}

<!-- Multi-line variables -->
{{#template ./templates/example.md
    title=Introduction
    content=This is the content
    footer=Copyright 2024
}}

Template File Syntax

Template Definition

<!-- templates/api-doc.md -->
## {{method}} {{endpoint}}

{{description}}

### Request
```http
{{method}} {{endpoint}} HTTP/1.1
Host: api.example.com

Response

{
    "status": "success",
    "data": {{response_data}}
}

### Using the Template

```markdown
{{#template ./templates/api-doc.md
    method=POST
    endpoint=/api/users
    description=Creates a new user
    response_data={"id": 123, "name": "John"}
}}

Configuration

# book.toml
[preprocessor.template]

# .mdbook-lint.toml
[MDBOOK011]
template_dir = "./templates"  # Default template directory
check_variables = true         # Validate variable substitution
allow_missing_vars = false     # Allow undefined variables

Common Issues and Solutions

Issue: Spaces in Variable Values

<!-- Wrong: Unquoted spaces -->
{{#template ./template.md
    title=My Great Title
}}

<!-- Correct: Use quotes or underscores -->
{{#template ./template.md
    title="My Great Title"
}}

<!-- Or use underscores -->
{{#template ./template.md
    title=My_Great_Title
}}

Issue: Variable Not Replaced

<!-- Template -->
Hello {{name}}!

<!-- Wrong variable name -->
{{#template ./greeting.md
    username=Alice  <!-- Should be 'name' -->
}}

<!-- Correct -->
{{#template ./greeting.md
    name=Alice
}}

Issue: Nested Templates

<!-- templates/outer.md -->
# {{title}}

{{#template ./inner.md
    content={{inner_content}}
}}

<!-- Using nested templates -->
{{#template ./templates/outer.md
    title=Documentation
    inner_content=Details here
}}

Best Practices

  1. Organize templates: Keep in dedicated directory
  2. Name variables clearly: Use descriptive names
  3. Document variables: List required variables in template
  4. Provide defaults: Handle missing variables gracefully
  5. Test substitution: Verify all variables are replaced

Template Organization

book/
├── src/
│   ├── chapter1.md
│   └── chapter2.md
├── templates/
│   ├── api/
│   │   ├── request.md
│   │   └── response.md
│   ├── components/
│   │   ├── header.md
│   │   └── footer.md
│   └── examples/
│       └── code-block.md

Self-Documenting Templates

<!-- templates/documented.md -->
<!--
Template: API Endpoint Documentation
Required variables:
  - method: HTTP method (GET, POST, etc.)
  - path: API endpoint path
  - description: What this endpoint does
Optional variables:
  - params: Query parameters
  - body: Request body example
-->

## {{method}} {{path}}

{{description}}

{{#if params}}
### Parameters
{{params}}
{{/if}}

{{#if body}}
### Request Body
```json
{{body}}

{{/if}}


## Advanced Usage

### Conditional Content

```markdown
<!-- Template with conditional sections -->
{{#if premium}}
This content is only for premium users.
{{/if}}

{{#unless beta}}
This feature is available in stable release.
{{/unless}}

Loops and Lists

<!-- Template with repeated content -->
{{#each items}}
- {{this.name}}: {{this.description}}
{{/each}}

Default Values

<!-- Template with defaults -->
# {{title|Default Title}}

Author: {{author|Anonymous}}
Date: {{date|TBD}}

When to Disable

Consider disabling this rule if:

  • You don't use the template preprocessor
  • You use a different template system
  • Your templates are generated dynamically
  • You're migrating template syntax

Disable in Config

# .mdbook-lint.toml
disabled_rules = ["MDBOOK011"]

Disable Inline

<!-- mdbook-lint-disable MDBOOK011 -->
{{#template ./experimental-template.md}}
<!-- mdbook-lint-enable MDBOOK011 -->

Error Messages

ErrorSolution
"Template file not found"Check file path and existence
"Invalid variable syntax"Use key=value format
"Undefined variable"Ensure all variables are provided
"Unclosed template directive"Add closing }}

References

MDBOOK012 - Invalid Include Line Ranges

Severity: Error
Category: mdBook-specific
Auto-fix: Not available

Rule Description

This rule validates line range specifications in {{#include}} and {{#rustdoc_include}} directives. It ensures line numbers are valid and the specified ranges exist in the target files.

Why This Rule Exists

Valid line ranges are important because:

  • Prevents build failures from invalid ranges
  • Ensures included content is accurate
  • Maintains documentation accuracy
  • Helps identify outdated line references
  • Prevents missing content in output

Examples

❌ Incorrect (violates rule)

<!-- Invalid range syntax -->
{{#include ./file.md:1-10}}      <!-- Should use colons -->
{{#include ./file.md:10:5}}      <!-- End before start -->
{{#include ./file.md:0:10}}      <!-- Line numbers start at 1 -->
{{#include ./file.md:-5:10}}     <!-- Negative line number -->

<!-- Out of bounds (file has 50 lines) -->
{{#include ./file.md:45:60}}     <!-- End exceeds file length -->
{{#include ./file.md:100}}       <!-- Line doesn't exist -->

<!-- Invalid format -->
{{#include ./file.md:a:b}}       <!-- Non-numeric -->
{{#include ./file.md::}}         <!-- Empty range -->

✅ Correct

<!-- Include specific lines -->
{{#include ./file.md:1:10}}      <!-- Lines 1-10 -->
{{#include ./file.md:5}}         <!-- Only line 5 -->

<!-- Open-ended ranges -->
{{#include ./file.md:10:}}       <!-- From line 10 to end -->
{{#include ./file.md::20}}       <!-- From start to line 20 -->

<!-- Using anchors instead of lines -->
{{#include ./file.md:example}}   <!-- More stable than line numbers -->

Line Range Syntax

Syntax Options

SyntaxDescriptionExample
:start:endLines from start to end:1:10
:lineSingle line:42
:start:From start to end of file:10:
::endFrom beginning to end::25
:anchorInclude anchor section:my_example

Examples

<!-- Include lines 5-10 -->
{{#include ./example.rs:5:10}}

<!-- Include from line 20 to end -->
{{#include ./example.rs:20:}}

<!-- Include first 15 lines -->
{{#include ./example.rs::15}}

<!-- Include single line -->
{{#include ./example.rs:7}}

Configuration

[MDBOOK012]
validate_bounds = true    # Check if line numbers exist (default: true)
warn_large_ranges = true  # Warn for ranges > 100 lines (default: true)
max_range_size = 100      # Maximum lines in a range (default: 100)
prefer_anchors = true     # Suggest anchors over line numbers (default: true)

Common Issues and Solutions

Issue: File Changes Break Ranges

<!-- Original: function at lines 10-15 -->
{{#include ./code.rs:10:15}}

<!-- After refactoring: function moved to lines 25-30 -->
<!-- BROKEN: Still references old location -->
{{#include ./code.rs:10:15}}

<!-- Solution: Use anchors -->
{{#include ./code.rs:my_function}}

Issue: Off-by-One Errors

<!-- Want to include lines with function (lines 5-8) -->

<!-- Wrong: Missing first line -->
{{#include ./code.rs:6:8}}

<!-- Correct: Include all lines -->
{{#include ./code.rs:5:8}}

Issue: Including Too Much

<!-- Bad: Including entire file when only need part -->
{{#include ./large-file.md:1:500}}

<!-- Better: Include specific section -->
{{#include ./large-file.md:45:67}}

<!-- Best: Use anchor -->
{{#include ./large-file.md:relevant_section}}

Best Practices

  1. Prefer anchors over line numbers: More stable across edits
  2. Keep ranges small: Include only what's necessary
  3. Document ranges: Comment what's being included
  4. Update after refactoring: Check includes after moving code
  5. Test includes: Verify correct content is included

Using Anchors Instead

#![allow(unused)]
fn main() {
// code.rs
// ANCHOR: database_connection
fn connect_to_database() -> Result<Connection, Error> {
    let url = env::var("DATABASE_URL")?;
    Connection::new(&url)
}
// ANCHOR_END: database_connection
}
<!-- More stable than line numbers -->
{{#include ./code.rs:database_connection}}

Documenting Includes

<!-- Include the main function (lines 15-25) -->
{{#include ./example.rs:15:25}}

<!-- Include error handling example -->
{{#include ./errors.rs:error_handling}}

Line Counting Rules

  1. Line numbers start at 1: First line is line 1, not 0
  2. Inclusive ranges: Both start and end lines are included
  3. Empty lines count: Blank lines are counted in numbering
  4. Comments count: Comment lines are included in count

Example File Numbering

// Line 1: Comment
// Line 2: Another comment
                          // Line 3: Empty line
fn main() {               // Line 4
    println!("Hello");    // Line 5
}                         // Line 6

When to Disable

Consider disabling this rule if:

  • Your files are generated and line numbers are stable
  • You're migrating content with many includes
  • You use a different include mechanism
  • Your build process validates includes separately

Disable in Config

# .mdbook-lint.toml
disabled_rules = ["MDBOOK012"]

Disable Inline

<!-- mdbook-lint-disable MDBOOK012 -->
{{#include ./generated.md:1:1000}}
<!-- mdbook-lint-enable MDBOOK012 -->

Debugging Line Ranges

View File with Line Numbers

# Show file with line numbers
cat -n file.md

# Show specific lines
sed -n '10,20p' file.md

# Count total lines
wc -l file.md

Test Include Output

# Build and check included content
mdbook build
grep -A5 -B5 "included content" book/chapter.html

Error Messages

ErrorSolution
"Line number out of range"Check file length
"Invalid range syntax"Use correct format :start:end
"End before start"Ensure start < end
"File not found"Verify file path

References

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:

  1. Detects if the file is SUMMARY.md
  2. Allows multiple H1 headings in SUMMARY.md
  3. Defers to MD025 for all other files
  4. 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

  1. Use parts for organization: Group related chapters
  2. Keep part names concise: They appear in navigation
  3. Order matters: Parts appear in sequence
  4. Be consistent: Use similar naming patterns
  5. 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

  1. Part headers are optional: Not every book needs parts
  2. Unnumbered chapters: Can exist before first part
  3. Separator sections: Use --- for appendices
  4. Draft chapters: Use [Chapter]() for placeholders
  5. Nested structure: Indent with 2 or 4 spaces consistently
  • 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]
# No configuration options
[MD052]
shortcut_syntax = false  # Allow shortcut syntax
[MD053]
ignored_definitions = ["//"]  # Definitions to ignore
[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

  1. Copy the configuration below to .mdbook-lint.toml in your project root
  2. Uncomment and modify only the settings you want to change
  3. 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)
# [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
# [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
# 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

# MD058 - Tables should have rows
# [MD058]
# No configuration options

# ----------------------------------------------------------------------------
# HTML Rules
# ----------------------------------------------------------------------------

# MD033 - Inline HTML
# [MD033]
# allowed_elements = []  # HTML elements to allow

# ============================================================================
# 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

# 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

  1. Start minimal: Begin with defaults and add configuration as needed
  2. Use overrides: Apply different rules to different parts of your project
  3. Document choices: Comment why certain rules are disabled
  4. Version control: Commit .mdbook-lint.toml to your repository
  5. Team agreement: Discuss and agree on rules with your team

Next Steps

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 metadata
  • Position - Tracks line/column positions for violations
  • NodeContext - Provides AST node information during linting

Rule System

  • Rule trait - Core interface for all linting rules
  • AstRule - Rules that process markdown AST nodes
  • TextRule - Rules that process raw text content
  • RuleProvider - Plugin interface for rule registration

Violation Reporting

  • Violation - Represents a linting issue with location and severity
  • Severity - Error, Warning, or Info levels
  • Fix - Automatic fix information for violations

Engine and Registry

  • LintEngine - Main orchestrator for document linting
  • PluginRegistry - 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:

  • 59 standard markdown rules (MD001-MD059) based on the markdownlint specification
  • 13 mdBook-specific rules (MDBOOK001-MDBOOK012, MDBOOK025) for mdBook project validation
  • 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

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

RuleDescriptionAuto-fix
MD001Heading increment
MD009No trailing spaces
MD010Hard tabs
MD012Multiple blank lines
MD018No space after hash
MD019Multiple spaces after hash
MD020No space in closed headings
MD021Multiple spaces in closed headings
MD023Headings start at beginning
MD027Multiple spaces after blockquote
MD030Spaces after list markers
MD034Bare URL used
MD047Files 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

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

Metricmdbook-lintmarkdownlintDifference
Execution Time0.101s0.283smdbook-lint is 2.8x faster
Total Violations1,390818mdbook-lint finds 70% more
Standard Rules Only1,018818mdbook-lint finds 24% more

Rule-by-Rule Analysis

Rules with Similar Detection (±10% difference)

These rules show consistent behavior between both tools:

RuleDescriptionmdbook-lintmarkdownlint
MD022Headings surrounded by blanks177177
MD031Code blocks surrounded by blanks200202
MD032Lists surrounded by blanks181174
MD047Files end with newline3737
MD051Link fragments4747

Major Discrepancies

Rules Where mdbook-lint Finds More Violations

RuleDescriptionmdbook-lintmarkdownlintAnalysis
MD013Line length179115mdbook-lint is 55% stricter
MD007List indentation460markdownlint doesn't check inside code blocks
MD052Reference links240mdbook-lint validates undefined references
MD006Lists start at beginning210mdbook-lint enforces list positioning
MD058Tables surrounded by blanks112mdbook-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

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:

RuleCountPurpose
MDBOOK002157Internal link validation
MDBOOK00775File include syntax
MDBOOK00546Orphaned files detection
MDBOOK00130Code blocks need language tags
MDBOOK01221File include ranges
MDBOOK00813Rustdoc include validation
Others30Various mdBook features

Recommendations

For mdbook-lint

  1. Fix MD007: Should not check list indentation inside code blocks
  2. Document MD013: Clarify how line length is calculated
  3. Configuration alignment: Consider a markdownlint-compatible mode that matches behavior exactly

For Users

  1. Choose based on needs:

    • mdbook-lint: Better for mdBook projects, stricter checking, faster performance
    • markdownlint: Better for general markdown, more mature, wider ecosystem
  2. 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-compatible flag for closer behavior matching

Conclusion

mdbook-lint is significantly stricter and faster than markdownlint, finding 70% more violations overall. The main differences stem from:

  1. Bug: MD007 checking inside code blocks (should be fixed)
  2. Design: Stricter validation of references, whitespace, and formatting
  3. 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:

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 rayon or similar for batch operations

Error Types

The library defines specific error types for different failure modes:

  • ConfigError: Configuration parsing and validation issues
  • DocumentError: Document creation and parsing problems
  • RuleError: Rule execution failures
  • IoError: File system access problems

Next Steps

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

  1. Create a branch: git checkout -b feature/your-change
  2. Make changes: Follow the guidelines below
  3. Test thoroughly: Ensure all tests pass
  4. 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 clippy warnings
  • 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

  1. Add module: Include your rule in src/rules/standard/mod.rs
  2. Register rule: Add to StandardRuleProvider::register_rules()
  3. 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_cmd crate
  • 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

  1. Automated checks run on all PRs
  2. Maintainer review for code quality
  3. Testing to ensure functionality
  4. 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 Rule and AstRule traits
  • 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### or MDBOOK###

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

  1. Changes merged to main via PR
  2. Release-please creates release PR automatically
  3. Merge release PR to trigger release
  4. GitHub Actions publishes to crates.io

Getting Help

Resources

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

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
refactor(engine): simplify rule registry

Types: feat, fix, docs, test, refactor, perf, chore, ci Scopes: rules, cli, config, engine, docs

Branch Naming

<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###.rs or mdbook###.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

  • 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 Rule trait
  • Rules are automatically registered at compile time
  • Configurable rule parameters
  • Extensible for new rule types

Parser

Handles markdown parsing and AST generation:

  • Uses comrak for 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

  1. Input Processing

    • Command-line arguments parsed
    • Configuration files loaded and merged
    • File paths resolved and validated
  2. Document Processing

    • Markdown files parsed into AST
    • Source position tracking maintained
    • Document metadata extracted
  3. Rule Application

    • Enabled rules identified
    • Rules applied to document AST
    • Violations collected with positions
  4. 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 parsing
  • serde: Configuration serialization
  • clap: Command-line interface
  • anyhow: Error handling

Development Dependencies

  • criterion: Performance benchmarking
  • tempfile: Test file management
  • assert_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:

  1. Maintain backward compatibility
  2. Document design decisions
  3. Consider performance implications
  4. Ensure testability
  5. 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 files
  • unicode_content.md: International text and special characters
  • large_file.md: Performance testing with substantial content
  • mixed_line_endings.md: Cross-platform line ending handling
  • known_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:

  1. Correctness: Rules work as intended
  2. Stability: Never crash on any input
  3. Performance: Prevent regressions
  4. 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:

  1. Add unit tests for the specific feature
  2. Add integration tests if it affects CLI/preprocessor behavior
  3. Add corpus tests if it might affect stability
  4. Add performance tests if it's performance-sensitive

For bug fixes:

  1. Add a regression test that would have caught the bug
  2. Ensure it fails before your fix
  3. Verify it passes after your fix

See Contributing for detailed guidelines.