fallow-cli 3.13.0

CLI for fallow, codebase intelligence for TypeScript and JavaScript
Documentation
use crate::report::sink::outln;
use std::path::Path;

use fallow_api::ResultGroup;
use fallow_types::duplicates::DuplicationReport;
use fallow_types::results::AnalysisResults;

pub(super) fn print_markdown(results: &AnalysisResults, root: &Path) {
    outln!("{}", fallow_api::build_markdown(results, root));
}

pub(super) fn print_grouped_markdown(groups: &[ResultGroup], root: &Path) {
    outln!("{}", fallow_api::build_grouped_markdown(groups, root));
}

pub(super) fn print_duplication_markdown(report: &DuplicationReport, root: &Path) {
    outln!("{}", fallow_api::build_duplication_markdown(report, root));
}

pub(super) fn print_health_markdown(report: &fallow_output::HealthReport, root: &Path) {
    outln!("{}", fallow_api::build_health_markdown(report, root));
}

pub(super) fn print_type_aware_markdown(
    type_aware: Option<&fallow_types::envelope::TypeAwareMeta>,
    scope: Option<&str>,
) {
    let Some(meta) = type_aware else {
        return;
    };
    let completeness = meta
        .identity
        .as_ref()
        .and_then(|identity| serde_json::to_value(identity.completeness).ok())
        .and_then(|value| value.as_str().map(str::to_owned))
        .unwrap_or_else(|| "unknown".to_string());
    let capabilities = meta.identity.as_ref().map_or_else(
        || "none".to_string(),
        |identity| {
            identity
                .capabilities
                .iter()
                .filter_map(|capability| serde_json::to_value(capability).ok())
                .filter_map(|value| value.as_str().map(|label| format!("`{label}`")))
                .collect::<Vec<_>>()
                .join(", ")
        },
    );
    let heading = type_aware_heading(scope);
    outln!(
        "\n## {heading}\n\n- Executed: `{}`\n- Backend: `{}@{}`\n- Completeness: `{}`\n- Capabilities: {}\n- Confirmed used: {}\n- Contract preserved: {}\n- No static references: {}\n- Guarded fixes available: {}\n- Unresolved: {}\n- Abstained: {}\n- Warnings: {}",
        meta.executed,
        meta.backend,
        meta.backend_version.as_deref().unwrap_or("not-run"),
        completeness,
        capabilities,
        meta.confirmed_used_count,
        meta.contract_preserved_count,
        meta.no_static_references_count,
        meta.fix_eligible_count,
        meta.unresolved_count,
        meta.abstained_count,
        meta.warning_count
    );
}

fn type_aware_heading(scope: Option<&str>) -> String {
    scope.map_or_else(
        || "Type-aware evidence".to_string(),
        |scope| format!("Type-aware {scope} evidence"),
    )
}

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

    #[test]
    fn type_aware_heading_labels_combined_scope() {
        assert_eq!(
            type_aware_heading(Some("dead-code")),
            "Type-aware dead-code evidence"
        );
        assert_eq!(
            type_aware_heading(Some("health")),
            "Type-aware health evidence"
        );
    }
}