1#![expect(
2 unused_results,
3 reason = "WalkBuilder configuration uses fluent setters only for their mutation side effects."
4)]
5
6use ignore::{DirEntry, WalkBuilder};
13use std::path::Path;
14
15use crate::exclusions::{DEFAULT_EXCLUDED_DIRS, VTCODE_IGNORE_FILE};
16
17pub fn build_default_walker(root: &Path) -> WalkBuilder {
26 let mut builder = WalkBuilder::new(root);
27 apply_defaults(&mut builder);
28 builder
29}
30
31pub fn build_walker_single_threaded(root: &Path) -> WalkBuilder {
38 let mut builder = WalkBuilder::new(root);
39 builder.threads(1);
40 apply_defaults(&mut builder);
41 builder
42}
43
44pub fn apply_defaults(builder: &mut WalkBuilder) {
50 builder.git_ignore(true);
52 builder.git_global(true);
53 builder.git_exclude(true);
54 builder.ignore(true);
55 builder.parents(true);
56
57 builder.add_custom_ignore_filename(VTCODE_IGNORE_FILE);
62
63 builder.follow_links(false);
65
66 builder.hidden(false);
70}
71
72pub fn is_excluded_dir(entry: &DirEntry) -> bool {
81 if !entry.file_type().is_some_and(|ft| ft.is_dir()) {
82 return false;
83 }
84
85 entry
86 .file_name()
87 .to_str()
88 .is_some_and(|name| DEFAULT_EXCLUDED_DIRS.contains(&name))
89}
90
91#[cfg(test)]
92mod tests {
93 use std::fs;
94
95 use tempfile::tempdir;
96
97 use super::*;
98
99 fn collected_paths(root: &Path) -> Vec<String> {
100 let walker = build_default_walker(root).build();
101 let mut paths = walker
102 .filter_map(Result::ok)
103 .filter(|entry| entry.path() != root)
104 .map(|entry| {
105 entry
106 .path()
107 .strip_prefix(root)
108 .unwrap_or_else(|_| entry.path())
109 .to_string_lossy()
110 .into_owned()
111 })
112 .collect::<Vec<_>>();
113 paths.sort();
114 paths
115 }
116
117 #[test]
118 fn default_walker_respects_vtcodegitignore() {
119 let temp = tempdir().expect("tempdir");
120 let root = temp.path();
121 fs::write(root.join(".vtcodegitignore"), "ignored_dir/\nignored_file.txt\n").expect("write ignore file");
122 fs::create_dir(root.join("ignored_dir")).expect("mkdir ignored_dir");
123 fs::write(root.join("ignored_dir/secret.rs"), "x").expect("write secret");
124 fs::write(root.join("ignored_file.txt"), "x").expect("write ignored file");
125 fs::write(root.join("kept.rs"), "x").expect("write kept file");
126
127 let paths = collected_paths(root);
128
129 assert!(paths.contains(&"kept.rs".to_owned()), "kept file should remain: {paths:?}");
130 assert!(!paths.iter().any(|p| p.contains("ignored_file.txt")), "ignored file leaked: {paths:?}");
131 assert!(!paths.iter().any(|p| p.contains("ignored_dir")), "ignored dir leaked: {paths:?}");
132 }
133
134 #[test]
135 fn default_walker_vtcodegitignore_negation_reincludes() {
136 let temp = tempdir().expect("tempdir");
137 let root = temp.path();
138 fs::write(root.join(".vtcodegitignore"), "*.log\n!important.log\n").expect("write ignore file");
142 fs::write(root.join("important.log"), "x").expect("write important log");
143 fs::write(root.join("noise.log"), "x").expect("write noise log");
144 fs::write(root.join("kept.rs"), "x").expect("write kept file");
145
146 let paths = collected_paths(root);
147
148 assert!(paths.contains(&"important.log".to_owned()), "negated file should be re-included: {paths:?}");
149 assert!(!paths.iter().any(|p| p.ends_with("noise.log")), "non-negated file leaked: {paths:?}");
150 assert!(paths.contains(&"kept.rs".to_owned()), "unrelated file should remain: {paths:?}");
151 }
152}