Skip to main content

arborist/
lib.rs

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