cargo-mend 0.16.0

Opinionated visibility auditing for Rust crates and workspaces
use std::fmt::Write as _;

use super::ColorMode;
use super::color;
use crate::compiler::DIAGNOSTIC_SEVERITY_ERROR_PREFIX;
use crate::compiler::DIAGNOSTIC_SEVERITY_WARNING_PREFIX;
use crate::reporting::constants::ANSI_BOLD;
use crate::reporting::constants::ANSI_BOLD_RED;
use crate::reporting::constants::ANSI_BOLD_YELLOW;
use crate::reporting::constants::RUSTC_LEVEL_HELP;
use crate::reporting::constants::RUSTC_LEVEL_NOTE;
use crate::reporting::diagnostics;
use crate::reporting::diagnostics::Finding;
use crate::reporting::diagnostics::Severity;

pub(super) fn render_finding(output: &mut String, finding: &Finding, color_mode: ColorMode) {
    let severity = severity_label(finding.severity, color_mode);
    let headline = diagnostics::finding_headline(finding);
    let line_label = finding.line.to_string();
    let gutter_width = line_label.len();
    let gutter_pad = " ".repeat(gutter_width + 1);
    let arrow_pad = " ".repeat(gutter_width);
    let _ = writeln!(output, "{severity} {headline}");
    let _ = writeln!(
        output,
        "{arrow_pad}{} {}:{}:{}",
        color::blue_bold("-->", color_mode),
        finding.path,
        finding.line,
        finding.column
    );
    let _ = writeln!(output, "{gutter_pad}{}", color::blue_bold("|", color_mode));
    let _ = writeln!(
        output,
        "{:>width$} {} {}",
        color::blue_bold(&line_label, color_mode),
        color::blue_bold("|", color_mode),
        finding.source_line,
        width = gutter_width
    );
    let _ = writeln!(
        output,
        "{gutter_pad}{} {}",
        color::blue_bold("|", color_mode),
        severity_marker(
            finding.severity,
            finding.column,
            finding.highlight_len,
            color_mode
        )
    );
    if let Some(inline_help) = diagnostics::custom_inline_help_text(finding)
        .or_else(|| diagnostics::inline_help_text(finding))
    {
        let _ = writeln!(output, "{gutter_pad}{}", color::blue_bold("|", color_mode));
        let _ = writeln!(
            output,
            "{gutter_pad}{} {}",
            color::blue_bold("|", color_mode),
            color::blue_bold(&format!("help: {inline_help}"), color_mode)
        );
    }

    let reasons = diagnostics::detail_reasons(finding);
    if diagnostics::custom_inline_help_text(finding).is_some()
        || diagnostics::inline_help_text(finding).is_some()
        || !reasons.is_empty()
    {
        let _ = writeln!(output, "{gutter_pad}{}", color::blue_bold("|", color_mode));
    }
    if !reasons.is_empty() {
        for reason in reasons {
            let _ = writeln!(
                output,
                "{gutter_pad}{} {}",
                diagnostic_label(RUSTC_LEVEL_NOTE, color_mode),
                reason
            );
        }
    }
    let help_url = diagnostics::finding_help_url(finding);
    let _ = writeln!(
        output,
        "{gutter_pad}{} for further information visit {help_url}",
        diagnostic_label(RUSTC_LEVEL_HELP, color_mode)
    );
    let _ = writeln!(output);
}

fn severity_label(severity: Severity, color_mode: ColorMode) -> String {
    match severity {
        Severity::Error => {
            color::paint(DIAGNOSTIC_SEVERITY_ERROR_PREFIX, ANSI_BOLD_RED, color_mode)
        },
        Severity::Warning => color::paint(
            DIAGNOSTIC_SEVERITY_WARNING_PREFIX,
            ANSI_BOLD_YELLOW,
            color_mode,
        ),
    }
}

fn severity_marker(
    severity: Severity,
    column: usize,
    highlight_len: usize,
    color_mode: ColorMode,
) -> String {
    let indent = " ".repeat(column.saturating_sub(1));
    let carets = "^".repeat(highlight_len.max(1));
    let code = match severity {
        Severity::Error => ANSI_BOLD_RED,
        Severity::Warning => ANSI_BOLD_YELLOW,
    };
    format!("{indent}{}", color::paint(&carets, code, color_mode))
}

fn diagnostic_label(kind: &str, color_mode: ColorMode) -> String {
    let prefix = color::blue_bold("=", color_mode);
    let label = match kind {
        RUSTC_LEVEL_HELP => color::paint(RUSTC_LEVEL_HELP, ANSI_BOLD, color_mode),
        RUSTC_LEVEL_NOTE => color::paint(RUSTC_LEVEL_NOTE, ANSI_BOLD, color_mode),
        other => other.to_string(),
    };
    format!("{prefix} {label}:")
}