code-buster 0.11.0

Repository-aware static analysis for AI-assisted development
Documentation
use std::collections::{BTreeMap, BTreeSet};

use crate::config::Config;
use crate::finding_controls;
use crate::findings::{Finding, Severity};
use crate::rule_catalog;

type FindingsByPath<'a> = BTreeMap<&'a str, Vec<&'a Finding>>;

struct QualifiedGroup<'a> {
    path: &'a str,
    findings: Vec<&'a Finding>,
    rule_ids: BTreeSet<&'a str>,
}

pub fn analyze(findings: &[Finding], config: &Config) -> Vec<Finding> {
    let grouped = group_findings(findings, config);
    let qualified = qualify_groups(grouped);
    emit_findings(qualified, concentration_severity(config))
}

fn group_findings<'a>(findings: &'a [Finding], config: &Config) -> FindingsByPath<'a> {
    let mut by_path = FindingsByPath::new();
    for finding in findings {
        if finding.path.is_empty()
            || finding.code == "yagni-concentration"
            || !is_active_yagni_rule(&finding.code, config)
        {
            continue;
        }
        by_path
            .entry(finding.path.as_str())
            .or_default()
            .push(finding);
    }
    by_path
}

fn qualify_groups<'a>(grouped: FindingsByPath<'a>) -> impl Iterator<Item = QualifiedGroup<'a>> {
    grouped
        .into_iter()
        .filter_map(|(path, findings)| qualify_group(path, findings))
}

fn qualify_group<'a>(path: &'a str, mut findings: Vec<&'a Finding>) -> Option<QualifiedGroup<'a>> {
    findings.sort_by(|left, right| {
        left.line
            .cmp(&right.line)
            .then_with(|| left.code.cmp(&right.code))
    });
    let rule_ids = findings
        .iter()
        .map(|finding| finding.code.as_str())
        .collect::<BTreeSet<_>>();
    (findings.len() >= 3 && rule_ids.len() >= 2).then_some(QualifiedGroup {
        path,
        findings,
        rule_ids,
    })
}

fn emit_findings<'a>(
    groups: impl Iterator<Item = QualifiedGroup<'a>>,
    severity: Severity,
) -> Vec<Finding> {
    groups
        .into_iter()
        .map(|group| {
            let rule_list = format_rule_list(&group.rule_ids);
            Finding {
                code: "yagni-concentration".to_owned(),
                severity,
                path: group.path.to_owned(),
                line: group.findings[0].line,
                end_line: 0,
                message: format!(
                    "{} YAGNI findings from {} rule families are concentrated in this file: {}",
                    group.findings.len(),
                    group.rule_ids.len(),
                    rule_list
                ),
                confidence: "medium".to_owned(),
                why: "Several independent signs of unused flexibility or one-use indirection in one file suggest the design should be reviewed as a whole instead of fixing each symptom separately.".to_owned(),
                suggestion: "Start from the actual callers and required behavior, then remove unused variation and collapse one-use layers together; retain boundaries that own policy, validation, lifecycle, or an external contract.".to_owned(),
                related_files: Vec::new(),
                snippet: String::new(),
                code_flow: Vec::new(),
            }
        })
        .collect()
}

fn format_rule_list(rule_ids: &BTreeSet<&str>) -> String {
    let mut rule_list = String::new();
    for rule_id in rule_ids {
        if !rule_list.is_empty() {
            rule_list.push_str(", ");
        }
        rule_list.push_str(rule_id);
    }
    rule_list
}

fn concentration_severity(config: &Config) -> Severity {
    config
        .severity_overrides
        .get("yagni-concentration")
        .and_then(|value| Severity::parse(value))
        .unwrap_or(Severity::Info)
}

fn is_active_yagni_rule(code: &str, config: &Config) -> bool {
    if config.disabled_rules.contains(code)
        || rule_catalog::metadata(code).is_none_or(|metadata| metadata.legacy_group != "yagni")
    {
        return false;
    }
    let group =
        rule_catalog::descriptor(code).map_or("maintainability", |descriptor| descriptor.group);
    finding_controls::mode_for_code(code, config, group) != "off"
}

#[cfg(test)]
mod tests {
    use super::*;

    fn finding(code: &str, line: usize) -> Finding {
        Finding {
            code: code.to_owned(),
            severity: Severity::Info,
            path: "lib/service.dart".to_owned(),
            line,
            end_line: line,
            message: String::new(),
            confidence: String::new(),
            why: String::new(),
            suggestion: String::new(),
            related_files: Vec::new(),
            snippet: String::new(),
            code_flow: Vec::new(),
        }
    }

    #[test]
    fn requires_density_diversity_and_active_source_rules() {
        let wrappers = vec![
            finding("single-use-trivial-wrapper", 2),
            finding("single-use-trivial-wrapper", 4),
            finding("single-use-trivial-wrapper", 6),
        ];
        let constant = finding("constant-argument-parameter", 8);
        let config = Config::defaults(".".into());

        assert!(analyze(&wrappers, &config).is_empty());
        assert!(analyze(&[wrappers[0].clone(), constant.clone()], &config).is_empty());
        assert_eq!(
            analyze(
                &[wrappers[0].clone(), wrappers[1].clone(), constant.clone()],
                &config,
            )
            .len(),
            1
        );

        let mut disabled = config;
        disabled
            .disabled_rules
            .insert("constant-argument-parameter".to_owned());
        assert!(
            analyze(
                &[wrappers[0].clone(), wrappers[1].clone(), constant],
                &disabled,
            )
            .is_empty()
        );
    }
}