cargo-mend 0.16.1

Opinionated visibility auditing for Rust crates and workspaces
use std::collections::BTreeSet;

use super::StoredFinding;
use super::StoredReport;
use crate::config::DiagnosticCode;

pub(super) fn apply_visibility_narrowing_priority(reports: &mut [StoredReport]) {
    let mut unused_pub_keys = BTreeSet::new();
    for report in reports.iter() {
        for finding in &report.findings {
            if finding.diagnostic_code == DiagnosticCode::UnusedPub {
                unused_pub_keys.insert(visibility_priority_key(finding));
            }
        }
    }

    if unused_pub_keys.is_empty() {
        return;
    }

    for report in reports.iter_mut() {
        report.findings.retain(|finding| {
            if !matches!(
                finding.diagnostic_code,
                DiagnosticCode::NarrowToPubCrate | DiagnosticCode::SuspiciousPub
            ) {
                return true;
            }
            !unused_pub_keys.contains(&visibility_priority_key(finding))
        });
    }
}

fn visibility_priority_key(finding: &StoredFinding) -> (String, usize, usize) {
    (finding.path.clone(), finding.line, finding.column)
}

#[cfg(test)]
mod tests {
    use std::path::Path;

    use super::apply_visibility_narrowing_priority;
    use crate::compiler::constants::FINDINGS_SCHEMA_VERSION;
    use crate::compiler::persistence::StoredFinding;
    use crate::compiler::persistence::StoredReport;
    use crate::compiler::settings;
    use crate::config::DiagnosticCode;
    use crate::reporting::CompilerWarningFacts;
    use crate::reporting::FixSupport;
    use crate::reporting::Severity;

    const CONFIG_FINGERPRINT: &str = "config-fingerprint";

    #[test]
    fn visibility_priority_prefers_unused_pub_for_same_item() {
        let mut reports = vec![StoredReport {
            findings: vec![
                stored_finding(
                    DiagnosticCode::UnusedPub,
                    Path::new("/package/src/lib.rs"),
                    "item",
                    7,
                ),
                stored_finding(
                    DiagnosticCode::NarrowToPubCrate,
                    Path::new("/package/src/lib.rs"),
                    "item",
                    7,
                ),
            ],
            ..report_for_test()
        }];

        apply_visibility_narrowing_priority(&mut reports);

        assert_eq!(reports[0].findings.len(), 1);
        assert_eq!(
            reports[0].findings[0].diagnostic_code,
            DiagnosticCode::UnusedPub
        );
    }

    fn stored_finding(
        diagnostic_code: DiagnosticCode,
        path: &Path,
        item: &str,
        line: usize,
    ) -> StoredFinding {
        StoredFinding {
            severity: Severity::Warning,
            diagnostic_code,
            path: path.to_string_lossy().into_owned(),
            line,
            column: 1,
            highlight_len: 3,
            source_line: "pub fn item() {}".to_string(),
            item: Some(item.to_string()),
            message: format!("{item} should change visibility"),
            suggestion: Some("use narrower visibility".to_string()),
            fix_support: FixSupport::None,
            related: None,
            item_def_path: None,
            narrower_scope_def_path: None,
        }
    }

    fn report_for_test() -> StoredReport {
        StoredReport {
            version:                FINDINGS_SCHEMA_VERSION,
            analysis_fingerprint:   settings::current_analysis_fingerprint(),
            scope_fingerprint:      "scope".to_string(),
            package_root:           "/package".to_string(),
            crate_root_file:        "/package/src/lib.rs".to_string(),
            config_fingerprint:     CONFIG_FINGERPRINT.to_string(),
            findings:               Vec::new(),
            pub_use_fix_facts:      Vec::new(),
            compiler_warning_facts: CompilerWarningFacts::None,
            use_sites:              Vec::new(),
        }
    }
}