rrubocop 0.8.14

10x faster RuboCop drop-in. Same output format and configs, without maintaining RuboCop gems.
use std::io::Write;

use crate::diagnostic::Diagnostic;
use crate::formatter::color::Color;
use crate::formatter::Formatter;

pub struct TextFormatter {
    pub color: Color,
}

impl Formatter for TextFormatter {
    fn format_to(&self, diagnostics: &[Diagnostic], files: &[std::path::PathBuf], out: &mut dyn Write) {
        for d in diagnostics {
            let _ = writeln!(out, "{}", d.render(self.color));
        }
        write_summary(self.color, diagnostics, files.len(), out);
    }
}

fn noun(n: usize, one: &'static str, many: &'static str) -> &'static str {
    if n == 1 { one } else { many }
}

fn count_fix_states(diagnostics: &[Diagnostic]) -> (usize, usize) {
    (
        diagnostics.iter().filter(|d| d.corrected).count(),
        diagnostics
            .iter()
            .filter(|d| d.correctable && !d.corrected)
            .count(),
    )
}

fn colored_offense_count(color: Color, n: usize) -> String {
    let text = format!("{n} {}", noun(n, "offense", "offenses"));
    if n == 0 {
        color.green(&text)
    } else {
        color.red(&text)
    }
}

fn write_with_corrected(
    color: Color,
    files: &str,
    offenses: &str,
    n: usize,
    corrected: usize,
    correctable: usize,
    out: &mut dyn Write,
) {
    let corr_text = format!("{corrected} {}", noun(corrected, "offense", "offenses"));
    let corr = if corrected == n {
        color.green(&corr_text)
    } else {
        color.cyan(&corr_text)
    };
    if correctable == 0 {
        let _ = writeln!(out, "\n{files} inspected, {offenses} detected, {corr} corrected");
        return;
    }
    let more = format!("{correctable} {}", noun(correctable, "offense", "offenses"));
    let _ = writeln!(
        out,
        "\n{files} inspected, {offenses} detected, {corr} corrected, {} autocorrectable",
        color.yellow(&more)
    );
}

pub(crate) fn write_summary(
    color: Color,
    diagnostics: &[Diagnostic],
    file_count: usize,
    out: &mut dyn Write,
) {
    let n = diagnostics.len();
    let offenses = colored_offense_count(color, n);
    let files = format!("{file_count} {}", noun(file_count, "file", "files"));
    let (corrected, correctable) = count_fix_states(diagnostics);
    if corrected == 0 && correctable == 0 {
        if n == 0 {
            let clean = color.green("no offenses");
            let _ = writeln!(out, "\n{files} inspected, {clean} detected");
        } else {
            let _ = writeln!(out, "\n{files} inspected, {offenses} detected");
        }
    } else if corrected > 0 {
        write_with_corrected(color, &files, &offenses, n, corrected, correctable, out);
    } else {
        let more = format!("{correctable} {}", noun(correctable, "offense", "offenses"));
        let _ = writeln!(
            out,
            "\n{files} inspected, {offenses} detected, {} autocorrectable",
            color.yellow(&more)
        );
    }
}