Skip to content

Implementation Plan: Ruff Integration for Python Linting & Formatting

Overview

Replace flake8 (checker) and black (formatter) with ruff for Python file processing in linthis. Ruff is an extremely fast Python linter and formatter written in Rust, offering 10-100x speed improvements.

Research Summary

Ruff Capabilities

Feature flake8+black ruff
Linting flake8 ruff check
Formatting black ruff format
Speed Baseline 10-100x faster
Language Python Rust
Rule Coverage flake8 + plugins 800+ built-in rules
Auto-fix Limited Comprehensive
Config .flake8, pyproject.toml pyproject.toml, ruff.toml

Ruff CLI Commands

Linting:

ruff check path/to/file.py              # Check file
ruff check --output-format json         # JSON output
ruff check --fix                        # Auto-fix

Formatting:

ruff format path/to/file.py             # Format file
ruff format --check                     # Check without modifying
ruff format --diff                      # Show diff

Ruff JSON Output Structure

{
  "cell": null,
  "code": "F401",
  "end_location": {"column": 10, "row": 1},
  "filename": "test.py",
  "fix": {
    "applicability": "safe",
    "edits": [{"content": "", "end_location": {...}, "location": {...}}],
    "message": "Remove unused import: `os`"
  },
  "location": {"column": 8, "row": 1},
  "message": "`os` imported but unused",
  "noqa_row": 1,
  "url": "https://docs.astral.sh/ruff/rules/unused-import"
}

Technical Design

Phase 1: Replace Python Checker (flake8 -> ruff check)

File: src/checkers/python.rs

Changes: 1. Replace flake8 command with ruff check --output-format json 2. Parse JSON output instead of text parsing 3. Map ruff error codes to Severity (same prefix logic works) 4. Update name() to return "ruff" 5. Update is_available() to check for ruff

New parsing logic:

fn parse_ruff_json_output(&self, output: &str, file_path: &Path) -> Vec<LintIssue> {
    // Parse JSON array of issues
    // Extract: filename, code, message, location.row, location.column
    // Map code prefix to severity (E/F -> Error, W -> Warning, etc.)
}

Phase 2: Replace Python Formatter (black -> ruff format)

File: src/formatters/python.rs

Changes: 1. Replace black command with ruff format 2. Replace black --check with ruff format --check 3. Update name() to return "ruff" 4. Update is_available() to check for ruff 5. Remove hard-coded --line-length 120 (use ruff config)

Phase 3: Configuration Updates

File: defaults/config.toml

Add ruff-specific configuration section:

[python]
linter = "ruff"       # or "flake8" for backwards compatibility
formatter = "ruff"    # or "black" for backwards compatibility
line_length = 120

File: src/main.rs (--init-configs)

Update to generate ruff.toml or pyproject.toml with ruff config:

[tool.ruff]
line-length = 120

[tool.ruff.lint]
select = ["E", "F", "W"]

Phase 4: Speed Comparison Feature

Add optional benchmark mode to compare linter/formatter performance:

CLI:

linthis --benchmark --lang python    # Compare ruff vs flake8+black

Implementation: 1. Add --benchmark flag to CLI 2. When enabled, run both tool sets and measure time 3. Output comparison table

Output example:

Python Linting/Formatting Benchmark (100 files)
┌──────────────┬─────────────┬─────────────┬──────────┐
│ Tool         │ Lint (ms)   │ Format (ms) │ Total    │
├──────────────┼─────────────┼─────────────┼──────────┤
│ flake8+black │ 5,234       │ 3,421       │ 8,655ms  │
│ ruff         │ 312         │ 198         │ 510ms    │
├──────────────┼─────────────┼─────────────┼──────────┤
│ Speedup      │ 16.8x       │ 17.3x       │ 17.0x    │
└──────────────┴─────────────┴─────────────┴──────────┘

Implementation Tasks

Task 1: Update Python Checker for Ruff

  • [ ] Modify src/checkers/python.rs
  • [ ] Add JSON parsing with serde
  • [ ] Update command invocation
  • [ ] Add tests

Task 2: Update Python Formatter for Ruff

  • [ ] Modify src/formatters/python.rs
  • [ ] Update command invocation
  • [ ] Add tests

Task 3: Update Configuration

  • [ ] Update defaults/config.toml
  • [ ] Update --init-configs to generate ruff config
  • [ ] Document new options

Task 4: Add Benchmark Mode (Optional)

  • [ ] Add --benchmark CLI flag
  • [ ] Implement dual-tool timing
  • [ ] Format comparison output

Task 5: Testing & Documentation

  • [ ] Integration tests with ruff
  • [ ] Update README
  • [ ] Test backwards compatibility

Data Model

Ruff JSON Issue Structure

#[derive(Debug, Deserialize)]
struct RuffIssue {
    filename: String,
    code: String,
    message: String,
    location: RuffLocation,
    end_location: RuffLocation,
    fix: Option<RuffFix>,
    url: Option<String>,
}

#[derive(Debug, Deserialize)]
struct RuffLocation {
    row: usize,
    column: usize,
}

#[derive(Debug, Deserialize)]
struct RuffFix {
    message: String,
    applicability: String,
    edits: Vec<RuffEdit>,
}

#[derive(Debug, Deserialize)]
struct RuffEdit {
    content: String,
    location: RuffLocation,
    end_location: RuffLocation,
}

Severity Mapping

Ruff Code Prefix Severity
E (Error) Error
F (Pyflakes) Error
W (Warning) Warning
C (Convention) Info
R (Refactor) Info
I (Import) Info
N (Naming) Warning
D (Docstring) Info
UP (pyupgrade) Info
B (bugbear) Warning
S (bandit/security) Warning
A (builtins) Warning
Others Info

API Contracts

Checker Trait (unchanged)

pub trait Checker: Send + Sync {
    fn name(&self) -> &str;                              // Returns "ruff"
    fn supported_languages(&self) -> &[Language];        // [Language::Python]
    fn check(&self, path: &Path) -> Result<Vec<LintIssue>>;
    fn is_available(&self) -> bool;
}

Formatter Trait (unchanged)

pub trait Formatter: Send + Sync {
    fn name(&self) -> &str;                              // Returns "ruff"
    fn supported_languages(&self) -> &[Language];        // [Language::Python]
    fn format(&self, path: &Path) -> Result<FormatResult>;
    fn check(&self, path: &Path) -> Result<bool>;
    fn is_available(&self) -> bool;
}

Quickstart

Prerequisites

# Install ruff
pip install ruff
# or
uv pip install ruff
# or
brew install ruff

Verify Installation

ruff --version
# ruff 0.8.x

Basic Usage (after implementation)

# Run linthis on Python files (uses ruff)
linthis --lang python

# Benchmark comparison
linthis --benchmark --lang python

Risk Assessment

Risk Mitigation
Ruff not installed Fall back to flake8+black if ruff unavailable
Different rule sets Document rule mapping, provide migration guide
Config format change Support both ruff.toml and legacy .flake8

Timeline Estimate

This plan covers all implementation details. The actual implementation can proceed task-by-task.

References