arborist/lib.rs
1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4pub mod error;
5pub mod languages;
6pub mod metrics;
7pub mod types;
8pub mod walker;
9
10pub use error::ArboristError;
11pub use languages::LanguageProfile;
12pub use types::{AnalysisConfig, FileReport, FunctionMetrics, Language};
13
14use std::path::Path;
15
16/// Analyze a source file, auto-detecting language from its extension.
17///
18/// The language is determined from the file extension (e.g., `.rs` → Rust,
19/// `.py` → Python). Uses default configuration (no threshold, methods included).
20///
21/// # Errors
22///
23/// - [`ArboristError::FileNotFound`] if the path does not exist.
24/// - [`ArboristError::UnrecognizedExtension`] if the extension is unknown.
25/// - [`ArboristError::LanguageNotEnabled`] if the language feature flag is off.
26///
27/// # Examples
28///
29/// ```no_run
30/// use arborist::analyze_file;
31///
32/// let report = analyze_file("src/main.rs")?;
33/// println!("{}: cognitive={}", report.path, report.file_cognitive);
34/// for func in &report.functions {
35/// println!(" {} cognitive={}", func.name, func.cognitive);
36/// }
37/// # Ok::<(), arborist::ArboristError>(())
38/// ```
39pub fn analyze_file(path: impl AsRef<Path>) -> Result<FileReport, ArboristError> {
40 analyze_file_with_config(path, &AnalysisConfig::default())
41}
42
43/// Analyze a source file with custom configuration.
44///
45/// Like [`analyze_file`], but accepts an [`AnalysisConfig`] to control
46/// threshold flagging and method inclusion.
47///
48/// # Errors
49///
50/// Same as [`analyze_file`].
51///
52/// # Examples
53///
54/// ```no_run
55/// use arborist::{analyze_file_with_config, AnalysisConfig};
56///
57/// let config = AnalysisConfig {
58/// cognitive_threshold: Some(8),
59/// ..Default::default()
60/// };
61/// let report = analyze_file_with_config("src/lib.rs", &config)?;
62/// for func in &report.functions {
63/// if func.exceeds_threshold == Some(true) {
64/// eprintln!("WARNING: {} has cognitive complexity {}", func.name, func.cognitive);
65/// }
66/// }
67/// # Ok::<(), arborist::ArboristError>(())
68/// ```
69pub fn analyze_file_with_config(
70 path: impl AsRef<Path>,
71 config: &AnalysisConfig,
72) -> Result<FileReport, ArboristError> {
73 let path = path.as_ref();
74
75 if !path.exists() {
76 return Err(ArboristError::FileNotFound {
77 path: path.display().to_string(),
78 });
79 }
80
81 let ext = path
82 .extension()
83 .and_then(|e| e.to_str())
84 .ok_or_else(|| ArboristError::UnrecognizedExtension {
85 extension: String::new(),
86 })?;
87
88 let (language, profile) = languages::profile_for_extension(ext)?;
89 let source = std::fs::read_to_string(path)?;
90
91 let mut report = walker::walk_source(&source, language, profile.as_ref(), config)?;
92 report.path = path.display().to_string();
93 Ok(report)
94}
95
96/// Analyze source code provided as a string, with explicit language.
97///
98/// Use this when the source code is already in memory (e.g., from an editor
99/// buffer or a network response). The returned [`FileReport`] will have an
100/// empty `path`.
101///
102/// # Errors
103///
104/// - [`ArboristError::LanguageNotEnabled`] if the language feature flag is off.
105///
106/// # Examples
107///
108/// ```
109/// use arborist::{analyze_source, Language};
110///
111/// let source = r#"
112/// fn add(a: i32, b: i32) -> i32 {
113/// a + b
114/// }
115/// "#;
116///
117/// let report = analyze_source(source, Language::Rust)?;
118/// assert_eq!(report.functions.len(), 1);
119/// assert_eq!(report.functions[0].name, "add");
120/// assert_eq!(report.functions[0].cognitive, 0);
121/// # Ok::<(), arborist::ArboristError>(())
122/// ```
123pub fn analyze_source(source: &str, language: Language) -> Result<FileReport, ArboristError> {
124 analyze_source_with_config(source, language, &AnalysisConfig::default())
125}
126
127/// Analyze source code with explicit language and custom configuration.
128///
129/// Like [`analyze_source`], but accepts an [`AnalysisConfig`] to control
130/// threshold flagging and method inclusion.
131///
132/// # Errors
133///
134/// Same as [`analyze_source`].
135///
136/// # Examples
137///
138/// ```
139/// use arborist::{analyze_source_with_config, AnalysisConfig, Language};
140///
141/// let source = r#"
142/// fn complex(x: i32) -> i32 {
143/// if x > 0 {
144/// if x > 10 {
145/// x * 2
146/// } else {
147/// x + 1
148/// }
149/// } else {
150/// 0
151/// }
152/// }
153/// "#;
154///
155/// let config = AnalysisConfig {
156/// cognitive_threshold: Some(1),
157/// ..Default::default()
158/// };
159/// let report = analyze_source_with_config(source, Language::Rust, &config)?;
160/// assert_eq!(report.functions[0].exceeds_threshold, Some(true));
161/// # Ok::<(), arborist::ArboristError>(())
162/// ```
163pub fn analyze_source_with_config(
164 source: &str,
165 language: Language,
166 config: &AnalysisConfig,
167) -> Result<FileReport, ArboristError> {
168 let (_lang, profile) = languages::profile_for_language(language)?;
169 walker::walk_source(source, language, profile.as_ref(), config)
170}