Skip to main content

arborist_cli/
analysis.rs

1use std::io::Read;
2use std::path::{Path, PathBuf};
3
4use arborist::{AnalysisConfig, FileReport, Language};
5
6use crate::cli::AnalyzeArgs;
7use crate::error::ArboristError;
8use crate::traversal;
9
10pub fn build_config(args: &AnalyzeArgs) -> AnalysisConfig {
11    AnalysisConfig {
12        cognitive_threshold: args.threshold,
13        include_methods: !args.no_methods,
14    }
15}
16
17pub fn analyze_file(path: &Path, config: &AnalysisConfig) -> Result<FileReport, ArboristError> {
18    arborist::analyze_file_with_config(path, config)
19        .map_err(|e| ArboristError::Analysis(e.to_string()))
20}
21
22pub fn analyze_stdin(language: &str, config: &AnalysisConfig) -> Result<FileReport, ArboristError> {
23    let mut source = String::new();
24    std::io::stdin()
25        .read_to_string(&mut source)
26        .map_err(ArboristError::Io)?;
27
28    let lang: Language = language
29        .parse()
30        .map_err(|e: String| ArboristError::Analysis(e))?;
31
32    arborist::analyze_source_with_config(&source, lang, config)
33        .map_err(|e| ArboristError::Analysis(e.to_string()))
34}
35
36pub fn analyze_paths(
37    paths: &[PathBuf],
38    config: &AnalysisConfig,
39    args: &AnalyzeArgs,
40) -> Result<(Vec<FileReport>, Vec<String>), ArboristError> {
41    let files = traversal::collect_files(paths, args.languages.as_deref(), args.gitignore)?;
42
43    let mut reports = Vec::new();
44    let mut errors = Vec::new();
45
46    for file in &files {
47        match analyze_file(file, config) {
48            Ok(report) => reports.push(report),
49            Err(e) => errors.push(format!("{}: {e}", file.display())),
50        }
51    }
52
53    Ok((reports, errors))
54}
55
56pub fn apply_filters(reports: &[FileReport], args: &AnalyzeArgs) -> (Vec<FileReport>, bool) {
57    let mut threshold_exceeded = false;
58    let mut filtered: Vec<FileReport> = Vec::new();
59
60    for report in reports {
61        let mut report = report.clone();
62
63        if let Some(threshold) = args.threshold {
64            let has_exceeding = report.functions.iter().any(|f| f.cognitive > threshold);
65
66            if has_exceeding {
67                threshold_exceeded = true;
68            }
69
70            if args.exceeds_only {
71                report.functions.retain(|f| f.cognitive > threshold);
72                if report.functions.is_empty() {
73                    continue;
74                }
75            }
76        }
77
78        filtered.push(report);
79    }
80
81    (filtered, threshold_exceeded)
82}
83
84#[derive(Debug, Clone)]
85pub struct FlatFunction {
86    pub name: String,
87    pub file_path: String,
88    pub language: String,
89    pub line_start: usize,
90    pub line_end: usize,
91    pub cognitive: u64,
92    pub cyclomatic: u64,
93    pub sloc: u64,
94}
95
96pub fn flatten_reports(reports: &[FileReport]) -> Vec<FlatFunction> {
97    let mut flat = Vec::new();
98    for report in reports {
99        for func in &report.functions {
100            flat.push(FlatFunction {
101                name: func.name.clone(),
102                file_path: report.path.clone(),
103                language: report.language.to_string(),
104                line_start: func.start_line,
105                line_end: func.end_line,
106                cognitive: func.cognitive,
107                cyclomatic: func.cyclomatic,
108                sloc: func.sloc,
109            });
110        }
111    }
112    flat
113}
114
115pub fn sort_and_top(
116    flat: &mut Vec<FlatFunction>,
117    sort: &crate::cli::SortMetric,
118    top: Option<usize>,
119) {
120    use crate::cli::SortMetric;
121
122    match sort {
123        SortMetric::Cognitive => flat.sort_by(|a, b| b.cognitive.cmp(&a.cognitive)),
124        SortMetric::Cyclomatic => flat.sort_by(|a, b| b.cyclomatic.cmp(&a.cyclomatic)),
125        SortMetric::Sloc => flat.sort_by(|a, b| b.sloc.cmp(&a.sloc)),
126        SortMetric::Name => flat.sort_by(|a, b| a.name.cmp(&b.name)),
127    }
128
129    if let Some(n) = top {
130        flat.truncate(n);
131    }
132}