gather_all_code_from_crates/
process_file.rs

1crate::ix!();
2
3pub fn process_file(path: &PathBuf, criteria: &AstFilterCriteria) -> Result<String, AppError> {
4    let content = fs::read_to_string(path).map_err(|e|AppError::Io{code:e.kind()})?;
5    let parsed = ra_ap_syntax::SourceFile::parse(&content, ra_ap_syntax::Edition::CURRENT);
6    let parse_errors = parsed.errors();
7    if !parse_errors.is_empty() {
8        for err in parse_errors {
9            eprintln!("Parsing error in {}: {:?}", path.display(), err);
10        }
11        return Err(AppError::Parse { reason: ErrorReason::Parse });
12    }
13
14    let syntax = parsed.tree().syntax().clone();
15    let items  = extract_items_from_ast(&syntax, *criteria.remove_doc_comments());
16
17    // Apply filtering
18    let mut filtered_items = items;
19
20    // Filter by include_tests
21    if !criteria.include_tests() {
22        filtered_items = filtered_items.into_iter().filter(|item| {
23            match item {
24                ItemInfo::Function(f) => !f.is_test(),
25                ItemInfo::ImplBlock { methods, .. } => {
26                    // Remove test methods
27                    let filtered: Vec<FunctionInfo> = methods.iter().filter(|m| !m.is_test()).cloned().collect();
28                    filtered.len() > 0 // Keep impl block only if there's something left (or return all methods filtered)
29                }
30                _ => true // Non-function items are not tests
31            }
32        }).map(|mut item| {
33            if let ItemInfo::ImplBlock { methods, .. } = &mut item {
34                *methods = methods.iter().filter(|m| !m.is_test()).cloned().collect();
35            }
36            item
37        }).collect();
38    }
39
40    // Filter by single_test_name
41    if let Some(test_name) = criteria.single_test_name() {
42        filtered_items = filtered_items.into_iter().filter(|item| {
43            match item {
44                ItemInfo::Function(f) => *f.is_test() && f.name() == test_name,
45                ItemInfo::ImplBlock { methods, .. } => {
46                    // Keep only methods matching test_name and is_test
47                    methods.iter().any(|m| *m.is_test() && m.name() == test_name)
48                }
49                _ => true
50            }
51        }).map(|mut item| {
52            if let ItemInfo::ImplBlock { methods, .. } = &mut item {
53                *methods = methods.iter().filter(|m| *m.is_test() && m.name() == test_name).cloned().collect();
54            }
55            item
56        }).collect();
57    }
58
59    // Filter by omit_private
60    if *criteria.omit_private() {
61        filtered_items = filtered_items.into_iter().filter(|item| {
62            match item {
63                ItemInfo::Function(f) => *f.is_public(),
64                ItemInfo::Struct { is_public, .. } => *is_public,
65                ItemInfo::Enum { is_public, .. } => *is_public,
66                ItemInfo::TypeAlias { is_public, .. } => *is_public,
67                ItemInfo::ImplBlock { is_public, methods, ..} => {
68                    // Impl blocks themselves have no pub, but we can decide to keep them if they have public methods
69                    methods.iter().any(|m| *m.is_public())
70                }
71            }
72        }).map(|mut item| {
73            if let ItemInfo::ImplBlock { methods, .. } = &mut item {
74                *methods = methods.iter().filter(|m| *m.is_public()).cloned().collect();
75            }
76            item
77        }).collect();
78    }
79
80    // Filter by single_function_name
81    if let Some(func_name) = criteria.single_function_name() {
82        filtered_items = filtered_items.into_iter().filter(|item| {
83            match item {
84                ItemInfo::Function(f) => f.name() == func_name,
85                ItemInfo::ImplBlock { methods, .. } => methods.iter().any(|m| m.name() == func_name),
86                _ => true
87            }
88        }).map(|mut item| {
89            if let ItemInfo::ImplBlock { methods, .. } = &mut item {
90                *methods = methods.iter().filter(|m| m.name() == func_name).cloned().collect();
91            }
92            item
93        }).collect();
94    }
95
96    let omit_bodies = *criteria.omit_bodies() && criteria.single_function_name().is_none();
97
98    let reconstructed = reconstruct_code_from_filtered_items(&filtered_items, omit_bodies);
99
100    Ok(reconstructed)
101}
102
103#[cfg(test)]
104mod process_file_tests {
105    use super::*;
106
107    #[test]
108    fn test_process_file_basic() {
109        let code = r#"
110            pub fn visible() {}
111            fn hidden() {}
112            #[test]
113            fn test_something() {}
114        "#;
115        let tmp_dir = TempDir::new().unwrap();
116        let file_path = tmp_dir.path().join("test.rs");
117        {
118            let mut f = File::create(&file_path).unwrap();
119            f.write_all(code.as_bytes()).unwrap();
120        }
121
122        let criteria = AstFilterCriteriaBuilder::default()
123            .include_tests(false)
124            .omit_private(true)
125            .build().unwrap();
126
127        let result = process_file(&file_path, &criteria).unwrap();
128        // include_tests=false: test_something filtered out
129        // omit_private=true: hidden filtered out
130        // only visible remains
131        assert!(result.contains("pub fn visible()"));
132        assert!(!result.contains("test_something"));
133        assert!(!result.contains("hidden"));
134    }
135
136    #[test]
137    fn test_process_file_single_function_name() {
138        let code = r#"
139            pub fn visible() {}
140            pub fn visible2() {}
141        "#;
142        let tmp_dir = TempDir::new().unwrap();
143        let file_path = tmp_dir.path().join("test2.rs");
144        {
145            let mut f = File::create(&file_path).unwrap();
146            f.write_all(code.as_bytes()).unwrap();
147        }
148
149        let criteria = AstFilterCriteriaBuilder::default()
150            .single_function_name(Some("visible".to_string()))
151            .build().unwrap();
152
153        let result = process_file(&file_path, &criteria).unwrap();
154        assert!(result.contains("fn visible("));
155        assert!(!result.contains("visible2"));
156    }
157
158
159    #[test]
160    fn test_process_file_remove_doc_comments() {
161        let code = r#"
162            #[inline]
163            #[test]
164            fn mytest() {}
165        "#;
166        let tmp_dir = TempDir::new().unwrap();
167        let file_path = tmp_dir.path().join("test3.rs");
168            {
169                let mut f = File::create(&file_path).unwrap();
170                f.write_all(code.as_bytes()).unwrap();
171            }
172
173            // Enable tests so that `#[test]` functions are not filtered out.
174            let criteria = AstFilterCriteriaBuilder::default()
175                .remove_doc_comments(true)
176                .include_tests(true) // <-- Add this line
177                .build().unwrap();
178
179        println!("criteria: {:#?}", criteria);
180
181        let result = process_file(&file_path, &criteria).unwrap();
182        println!("result: {:#?}", result);
183
184        // Now that tests are included, the function `mytest` will remain, as will its attributes.
185        assert!(result.contains("#[inline]"));
186        assert!(result.contains("#[test]"));
187    }
188
189    #[test]
190    fn test_process_file_syntax_error() {
191        let code = r#"
192            fn broken( {}
193        "#;
194        let tmp_dir = TempDir::new().unwrap();
195        let file_path = tmp_dir.path().join("broken.rs");
196        {
197            let mut f = File::create(&file_path).unwrap();
198            f.write_all(code.as_bytes()).unwrap();
199        }
200
201        let criteria = AstFilterCriteriaBuilder::default().build().unwrap();
202        let result = process_file(&file_path, &criteria);
203        // This should fail due to parse errors
204        match result {
205            Err(AppError::Parse { reason: ErrorReason::Parse }) => (),
206            _ => panic!("Expected parse error"),
207        }
208    }
209}