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}