rust_diff_analyzer/analysis/
extractor.rs

1// SPDX-FileCopyrightText: 2025 RAprogramm <andrey.rozanov.vl@gmail.com>
2// SPDX-License-Identifier: MIT
3
4use std::{fs, path::Path};
5
6use masterror::AppError;
7
8use super::ast_visitor::SemanticUnitVisitor;
9use crate::{
10    error::{FileReadError, ParseError},
11    types::SemanticUnit,
12};
13
14/// Extracts semantic units from a Rust source file
15///
16/// # Arguments
17///
18/// * `path` - Path to the Rust source file
19///
20/// # Returns
21///
22/// Vector of semantic units or error
23///
24/// # Errors
25///
26/// Returns error if file cannot be read or parsed
27///
28/// # Examples
29///
30/// ```no_run
31/// use std::path::Path;
32///
33/// use rust_diff_analyzer::analysis::extract_semantic_units;
34///
35/// let units = extract_semantic_units(Path::new("src/lib.rs"));
36/// ```
37pub fn extract_semantic_units(path: &Path) -> Result<Vec<SemanticUnit>, AppError> {
38    let content =
39        fs::read_to_string(path).map_err(|e| AppError::from(FileReadError::new(path, e)))?;
40
41    extract_semantic_units_from_str(&content, path)
42}
43
44/// Extracts semantic units from Rust source code string
45///
46/// # Arguments
47///
48/// * `content` - Rust source code as string
49/// * `path` - Path for error reporting
50///
51/// # Returns
52///
53/// Vector of semantic units or error
54///
55/// # Errors
56///
57/// Returns error if code cannot be parsed
58///
59/// # Examples
60///
61/// ```
62/// use std::path::Path;
63///
64/// use rust_diff_analyzer::analysis::extractor::extract_semantic_units_from_str;
65///
66/// let code = "fn main() {}";
67/// let units = extract_semantic_units_from_str(code, Path::new("main.rs")).unwrap();
68/// assert_eq!(units.len(), 1);
69/// ```
70pub fn extract_semantic_units_from_str(
71    content: &str,
72    path: &Path,
73) -> Result<Vec<SemanticUnit>, AppError> {
74    let file = syn::parse_file(content)
75        .map_err(|e| AppError::from(ParseError::new(path, e.to_string())))?;
76
77    Ok(SemanticUnitVisitor::extract(&file))
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    fn test_extract_from_str() {
86        let code = r#"
87            pub fn main() {}
88            struct Config {}
89            impl Config {
90                pub fn new() -> Self { Config {} }
91            }
92        "#;
93
94        let units = extract_semantic_units_from_str(code, Path::new("test.rs"))
95            .expect("extraction should succeed");
96
97        assert!(units.len() >= 3);
98    }
99
100    #[test]
101    fn test_parse_error() {
102        let bad_code = "fn broken( {}";
103        let result = extract_semantic_units_from_str(bad_code, Path::new("bad.rs"));
104        assert!(result.is_err());
105    }
106}