rustqual 1.2.3

Comprehensive Rust code quality analyzer — seven dimensions: IOSP, Complexity, DRY, SRP, Coupling, Test Quality, Architecture
Documentation
//! JSON DRY-section builders: duplicates, dead_code, fragments,
//! wildcards, boilerplate, repeated_matches. Each typed `DryFinding`
//! variant projects to its corresponding JSON section type.

use std::collections::HashSet;

use super::super::json_types::{
    JsonBoilerplateFind, JsonDeadCodeWarning, JsonDuplicateEntry, JsonDuplicateGroup,
    JsonFragmentEntry, JsonFragmentGroup, JsonRepeatedMatchEntry, JsonRepeatedMatchGroup,
    JsonWildcardWarning,
};
use crate::domain::findings::{DryFinding, DryFindingDetails, DryFindingKind};

pub(super) fn build_duplicates(findings: &[DryFinding]) -> Vec<JsonDuplicateGroup> {
    let mut seen: HashSet<Vec<(String, usize)>> = HashSet::new();
    let mut groups = Vec::new();
    findings
        .iter()
        .filter(|f| !f.common.suppressed)
        .for_each(|f| {
            if let DryFindingDetails::Duplicate {
                participants,
                similarity,
            } = &f.details
            {
                let key: Vec<(String, usize)> = participants
                    .iter()
                    .map(|p| (p.file.clone(), p.line))
                    .collect();
                if !seen.insert(key) {
                    return;
                }
                groups.push(JsonDuplicateGroup {
                    kind: f.kind.meta().json_label.to_string(),
                    similarity: *similarity,
                    entries: participants
                        .iter()
                        .map(|p| JsonDuplicateEntry {
                            name: p.function_name.clone(),
                            qualified_name: p.function_name.clone(),
                            file: p.file.clone(),
                            line: p.line,
                        })
                        .collect(),
                });
            }
        });
    groups
}

pub(super) fn build_dead_code(findings: &[DryFinding]) -> Vec<JsonDeadCodeWarning> {
    findings
        .iter()
        .filter(|f| !f.common.suppressed)
        .filter_map(|f| match (&f.kind, &f.details) {
            (
                DryFindingKind::DeadCodeUncalled | DryFindingKind::DeadCodeTestOnly,
                DryFindingDetails::DeadCode {
                    qualified_name,
                    suggestion,
                },
            ) => Some(JsonDeadCodeWarning {
                function_name: qualified_name.clone(),
                qualified_name: qualified_name.clone(),
                file: f.common.file.clone(),
                line: f.common.line,
                kind: f.kind.meta().json_label.to_string(),
                suggestion: suggestion.clone().unwrap_or_default(),
            }),
            _ => None,
        })
        .collect()
}

pub(super) fn build_fragments(findings: &[DryFinding]) -> Vec<JsonFragmentGroup> {
    let mut seen: HashSet<Vec<(String, usize)>> = HashSet::new();
    let mut groups = Vec::new();
    findings
        .iter()
        .filter(|f| !f.common.suppressed)
        .for_each(|f| {
            if let DryFindingDetails::Fragment {
                participants,
                statement_count,
            } = &f.details
            {
                let key: Vec<(String, usize)> = participants
                    .iter()
                    .map(|p| (p.file.clone(), p.line))
                    .collect();
                if !seen.insert(key) {
                    return;
                }
                groups.push(JsonFragmentGroup {
                    statement_count: *statement_count,
                    entries: participants
                        .iter()
                        .map(|p| JsonFragmentEntry {
                            function_name: p.function_name.clone(),
                            qualified_name: p.function_name.clone(),
                            file: p.file.clone(),
                            start_line: p.line,
                            end_line: p.end_line,
                        })
                        .collect(),
                });
            }
        });
    groups
}

pub(super) fn build_wildcards(findings: &[DryFinding]) -> Vec<JsonWildcardWarning> {
    findings
        .iter()
        .filter(|f| !f.common.suppressed)
        .filter_map(|f| match &f.details {
            DryFindingDetails::Wildcard { module_path } => Some(JsonWildcardWarning {
                file: f.common.file.clone(),
                line: f.common.line,
                module_path: module_path.clone(),
            }),
            _ => None,
        })
        .collect()
}

pub(super) fn build_boilerplate(findings: &[DryFinding]) -> Vec<JsonBoilerplateFind> {
    findings
        .iter()
        .filter(|f| !f.common.suppressed)
        .filter_map(|f| match &f.details {
            DryFindingDetails::Boilerplate {
                pattern_id,
                struct_name,
                suggestion,
            } => Some(JsonBoilerplateFind {
                pattern_id: pattern_id.clone(),
                file: f.common.file.clone(),
                line: f.common.line,
                struct_name: struct_name.clone(),
                description: f.common.message.clone(),
                suggestion: suggestion.clone(),
            }),
            _ => None,
        })
        .collect()
}

pub(super) fn build_repeated_matches(findings: &[DryFinding]) -> Vec<JsonRepeatedMatchGroup> {
    // Dedup key: enum_name + sorted participant locations. Keying on
    // enum_name alone collapses two distinct repeated patterns over
    // the same enum into one group.
    let mut seen: HashSet<(String, Vec<(String, usize)>)> = HashSet::new();
    let mut groups = Vec::new();
    findings
        .iter()
        .filter(|f| !f.common.suppressed)
        .for_each(|f| {
            if let DryFindingDetails::RepeatedMatch {
                enum_name,
                participants,
            } = &f.details
            {
                let mut locs: Vec<(String, usize)> = participants
                    .iter()
                    .map(|p| (p.file.clone(), p.line))
                    .collect();
                locs.sort();
                if !seen.insert((enum_name.clone(), locs)) {
                    return;
                }
                groups.push(JsonRepeatedMatchGroup {
                    enum_name: enum_name.clone(),
                    entries: participants
                        .iter()
                        .map(|p| JsonRepeatedMatchEntry {
                            file: p.file.clone(),
                            line: p.line,
                            function_name: p.function_name.clone(),
                            arm_count: p.arm_count,
                        })
                        .collect(),
                });
            }
        });
    groups
}