Skip to main content

dupes_rust/
lib.rs

1//! Rust language analyzer for the `dupes-core` duplicate detection framework.
2//!
3//! This crate provides [`RustAnalyzer`], which implements the
4//! [`dupes_core::analyzer::LanguageAnalyzer`] trait using `syn` for AST parsing
5//! and normalization.
6
7pub mod normalizer;
8pub mod parser;
9
10use std::path::Path;
11
12use dupes_core::analyzer::LanguageAnalyzer;
13use dupes_core::code_unit::CodeUnit;
14use dupes_core::config::AnalysisConfig;
15
16/// Rust language analyzer using syn for AST parsing.
17pub struct RustAnalyzer;
18
19impl RustAnalyzer {
20    #[must_use]
21    pub const fn new() -> Self {
22        Self
23    }
24}
25
26impl Default for RustAnalyzer {
27    fn default() -> Self {
28        Self::new()
29    }
30}
31
32impl LanguageAnalyzer for RustAnalyzer {
33    fn file_extensions(&self) -> &[&str] {
34        &["rs"]
35    }
36
37    fn parse_file(
38        &self,
39        path: &Path,
40        source: &str,
41        config: &AnalysisConfig,
42    ) -> Result<Vec<CodeUnit>, Box<dyn std::error::Error + Send + Sync>> {
43        parser::parse_source(path, source, config.min_nodes, config.min_lines)
44            .map_err(|e| -> Box<dyn std::error::Error + Send + Sync> { e.into() })
45    }
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51    use std::path::PathBuf;
52
53    #[test]
54    fn rust_analyzer_through_trait() {
55        let analyzer = RustAnalyzer::new();
56        let config = AnalysisConfig {
57            min_nodes: 1,
58            min_lines: 0,
59        };
60        let source = r#"
61            fn foo(x: i32) -> i32 {
62                let y = x + 1;
63                y * 2
64            }
65            #[test]
66            fn test_foo() {
67                let z = 1;
68                let w = z + 1;
69                assert_eq!(w, 2);
70            }
71        "#;
72        let path = PathBuf::from("test.rs");
73        let units = analyzer.parse_file(&path, source, &config).unwrap();
74
75        // Both units should be present (filtering is done by analyze())
76        assert!(units.len() >= 2);
77
78        // Production code should not be tagged as test
79        let prod: Vec<_> = units.iter().filter(|u| u.name == "foo").collect();
80        assert_eq!(prod.len(), 1);
81        assert!(!prod[0].is_test);
82
83        // Test code should be tagged
84        let test: Vec<_> = units.iter().filter(|u| u.name == "test_foo").collect();
85        assert_eq!(test.len(), 1);
86        assert!(test[0].is_test);
87
88        // Default is_test_code() delegates to is_test field
89        assert!(!analyzer.is_test_code(prod[0]));
90    }
91}