fallow-engine 3.0.0

Typed analysis engine facade for fallow consumers
Documentation
//! Health core pipeline preparation.

use std::process::ExitCode;

use fallow_config::ResolvedConfig;

use crate::{duplicates::DuplicationReport, results::DeadCodeAnalysisArtifacts};

use super::analysis_data::{
    HealthAnalysisData, HealthAnalysisDataInput, prepare_health_analysis_data,
};
use super::coverage_settings::{HealthCoverageSettings, prepare_health_coverage_settings};
use super::findings_pipeline::{HealthFindingsData, HealthFindingsInput, prepare_health_findings};
use super::pipeline::HealthScope;
use super::runtime_sections::{
    HealthRuntimeSections, HealthRuntimeSectionsInput, prepare_health_runtime_sections,
};
use super::{HealthDerivedSections, HealthOptions, HealthSeams, HealthVitalData, scoring};

pub(super) struct HealthCoreSectionsInput<'a, R> {
    pub(super) opts: &'a HealthOptions<'a>,
    pub(super) config: &'a ResolvedConfig,
    pub(super) files: &'a [fallow_types::discover::DiscoveredFile],
    pub(super) modules: &'a [crate::source::ModuleInfo],
    pub(super) scope: &'a HealthScope<'a, R>,
    pub(super) pre_computed_analysis: Option<DeadCodeAnalysisArtifacts>,
    pub(super) pre_computed_duplication: Option<DuplicationReport>,
    pub(super) seams: &'a HealthSeams<'a>,
}

struct HealthAnalysisPreludeInput<'a, R> {
    opts: &'a HealthOptions<'a>,
    config: &'a ResolvedConfig,
    modules: &'a [crate::source::ModuleInfo],
    scope: &'a HealthScope<'a, R>,
    pre_computed_analysis: Option<DeadCodeAnalysisArtifacts>,
    seams: &'a HealthSeams<'a>,
}

struct HealthScopedFindingsInput<'a, R> {
    opts: &'a HealthOptions<'a>,
    config: &'a ResolvedConfig,
    modules: &'a [crate::source::ModuleInfo],
    scope: &'a HealthScope<'a, R>,
    score_output: Option<&'a scoring::FileScoreOutput>,
}

struct HealthAnalysisPrelude {
    analysis_data: HealthAnalysisData,
    report_coverage_gaps: bool,
    enforce_coverage_gaps: bool,
    has_istanbul_coverage: bool,
    needs_file_scores: bool,
}

pub(super) struct HealthPreparedCore {
    pub(super) findings_data: HealthFindingsData,
    pub(super) analysis_data: HealthAnalysisData,
    pub(super) derived_sections: HealthDerivedSections,
    pub(super) vital_data: HealthVitalData,
    pub(super) report_coverage_gaps: bool,
    pub(super) enforce_coverage_gaps: bool,
    pub(super) has_istanbul_coverage: bool,
    pub(super) needs_file_scores: bool,
}

pub(super) fn prepare_health_core_sections<R>(
    input: HealthCoreSectionsInput<'_, R>,
) -> Result<HealthPreparedCore, ExitCode> {
    let HealthCoreSectionsInput {
        opts,
        config,
        files,
        modules,
        scope,
        pre_computed_analysis,
        pre_computed_duplication,
        seams,
    } = input;

    let HealthAnalysisPrelude {
        analysis_data,
        report_coverage_gaps,
        enforce_coverage_gaps,
        has_istanbul_coverage,
        needs_file_scores,
    } = prepare_health_analysis_prelude(HealthAnalysisPreludeInput {
        opts,
        config,
        modules,
        scope,
        pre_computed_analysis,
        seams,
    })?;

    let findings_data = prepare_health_scoped_findings(&HealthScopedFindingsInput {
        opts,
        config,
        modules,
        scope,
        score_output: analysis_data.score_output.as_ref(),
    })?;

    let HealthRuntimeSections {
        analysis_data,
        derived_sections,
        vital_data,
    } = prepare_health_runtime_sections(
        opts,
        HealthRuntimeSectionsInput {
            config,
            files,
            modules,
            file_paths: &scope.file_paths,
            ignore_set: &scope.ignore_set,
            changed_files: scope.changed_files.as_ref(),
            ws_roots: scope.ws_roots.as_deref(),
            diff_index: scope.diff_index,
            loaded_baseline: findings_data.loaded_baseline.as_ref(),
            findings: &findings_data.findings,
            analysis_data,
            pre_computed_duplication,
            has_istanbul_coverage,
            needs_file_scores,
        },
    )?;

    Ok(HealthPreparedCore {
        findings_data,
        analysis_data,
        derived_sections,
        vital_data,
        report_coverage_gaps,
        enforce_coverage_gaps,
        has_istanbul_coverage,
        needs_file_scores,
    })
}

fn prepare_health_analysis_prelude<R>(
    input: HealthAnalysisPreludeInput<'_, R>,
) -> Result<HealthAnalysisPrelude, ExitCode> {
    let HealthCoverageSettings {
        report_coverage_gaps,
        enforce_coverage_gaps,
        istanbul_coverage,
    } = prepare_health_coverage_settings(input.opts, input.config)?;

    let needs_file_scores = needs_health_file_scores(
        input.opts,
        report_coverage_gaps,
        enforce_coverage_gaps,
        input.scope.enforce_crap,
    );
    let analysis_data = prepare_health_analysis_data(HealthAnalysisDataInput {
        opts: input.opts,
        config: input.config,
        modules: input.modules,
        file_paths: &input.scope.file_paths,
        ignore_set: &input.scope.ignore_set,
        changed_files: input.scope.changed_files.as_ref(),
        ws_roots: input.scope.ws_roots.as_deref(),
        istanbul_coverage: istanbul_coverage.as_ref(),
        pre_computed_analysis: input.pre_computed_analysis,
        needs_file_scores,
        seams: input.seams,
    })?;

    Ok(HealthAnalysisPrelude {
        analysis_data,
        report_coverage_gaps,
        enforce_coverage_gaps,
        has_istanbul_coverage: istanbul_coverage.is_some(),
        needs_file_scores,
    })
}

fn prepare_health_scoped_findings<R>(
    input: &HealthScopedFindingsInput<'_, R>,
) -> Result<HealthFindingsData, ExitCode> {
    prepare_health_findings(HealthFindingsInput {
        opts: input.opts,
        config: input.config,
        modules: input.modules,
        file_paths: &input.scope.file_paths,
        ignore_set: &input.scope.ignore_set,
        changed_files: input.scope.changed_files.as_ref(),
        ws_roots: input.scope.ws_roots.as_deref(),
        diff_index: input.scope.diff_index,
        max_cyclomatic: input.scope.max_cyclomatic,
        max_cognitive: input.scope.max_cognitive,
        max_crap: input.scope.max_crap,
        enforce_crap: input.scope.enforce_crap,
        score_output: input.score_output,
    })
}

fn needs_health_file_scores(
    opts: &HealthOptions<'_>,
    report_coverage_gaps: bool,
    enforce_coverage_gaps: bool,
    enforce_crap: bool,
) -> bool {
    opts.file_scores
        || report_coverage_gaps
        || enforce_coverage_gaps
        || opts.hotspots
        || opts.targets
        || opts.force_full
        || enforce_crap
}