-
Notifications
You must be signed in to change notification settings - Fork 190
Expand file tree
/
Copy pathfile_walker.rs
More file actions
196 lines (155 loc) · 5.92 KB
/
file_walker.rs
File metadata and controls
196 lines (155 loc) · 5.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
use std::path::{Path, PathBuf};
use ignore::WalkBuilder;
use vite_error::Error;
// TODO: only support esm files for now
/// File extensions to process for import rewriting
const TS_JS_EXTENSIONS: &[&str] = &["ts", "tsx", "mts", "js", "jsx", "mjs"];
/// Result of walking TypeScript/JavaScript files
#[derive(Debug)]
pub struct WalkResult {
/// List of file paths found
pub files: Vec<PathBuf>,
}
/// Find all TypeScript/JavaScript files in a directory, respecting gitignore
///
/// This function walks the directory tree starting from `root` and finds all files
/// with TypeScript or JavaScript extensions (.ts, .tsx, .mts, .cts, .js, .jsx, .mjs, .cjs).
///
/// The walk respects:
/// - `.gitignore` files in the directory tree
/// - Global gitignore configuration
/// - `.git/info/exclude` files
/// - Hidden files and directories are skipped
///
/// # Arguments
///
/// * `root` - The root directory to start searching from
///
/// # Returns
///
/// Returns a `WalkResult` containing the list of found files, or an error if
/// the directory walk fails.
///
/// # Example
///
/// ```ignore
/// use std::path::Path;
/// use vite_migration::find_ts_files;
///
/// let result = find_ts_files(Path::new("./src"))?;
/// for file in result.files {
/// println!("Found: {}", file.display());
/// }
/// ```
pub fn find_ts_files(root: &Path) -> Result<WalkResult, Error> {
let mut files = Vec::new();
let walker = WalkBuilder::new(root)
.hidden(true) // Skip hidden files/dirs
.git_ignore(true) // Respect .gitignore
.git_global(true) // Respect global gitignore
.git_exclude(true) // Respect .git/info/exclude
.require_git(false) // Work even if not a git repo
.build();
for entry in walker {
let entry = entry?;
let path = entry.path();
// Skip directories
if path.is_dir() {
continue;
}
// Check extension
if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
if TS_JS_EXTENSIONS.contains(&ext) {
files.push(path.to_path_buf());
}
}
}
Ok(WalkResult { files })
}
#[cfg(test)]
mod tests {
use std::fs;
use tempfile::tempdir;
use super::*;
#[test]
fn test_find_ts_files_basic() {
let temp = tempdir().unwrap();
// Create test files
fs::write(temp.path().join("app.ts"), "").unwrap();
fs::write(temp.path().join("utils.tsx"), "").unwrap();
fs::write(temp.path().join("config.js"), "").unwrap();
fs::write(temp.path().join("readme.md"), "").unwrap();
let result = find_ts_files(temp.path()).unwrap();
// Should find ts, tsx, js but not md
assert_eq!(result.files.len(), 3);
}
#[test]
fn test_find_ts_files_nested() {
let temp = tempdir().unwrap();
// Create nested directory
fs::create_dir(temp.path().join("src")).unwrap();
fs::write(temp.path().join("src/index.ts"), "").unwrap();
fs::write(temp.path().join("src/utils.tsx"), "").unwrap();
// Create deeper nesting
fs::create_dir_all(temp.path().join("src/components")).unwrap();
fs::write(temp.path().join("src/components/Button.tsx"), "").unwrap();
let result = find_ts_files(temp.path()).unwrap();
assert_eq!(result.files.len(), 3);
}
#[test]
fn test_find_ts_files_respects_gitignore() {
let temp = tempdir().unwrap();
// Create test files
fs::write(temp.path().join("app.ts"), "").unwrap();
// Create node_modules (should be ignored via gitignore)
fs::create_dir(temp.path().join("node_modules")).unwrap();
fs::write(temp.path().join("node_modules/pkg.ts"), "").unwrap();
// Create dist (should be ignored via gitignore)
fs::create_dir(temp.path().join("dist")).unwrap();
fs::write(temp.path().join("dist/bundle.js"), "").unwrap();
// Create .gitignore
fs::write(temp.path().join(".gitignore"), "node_modules/\ndist/").unwrap();
let result = find_ts_files(temp.path()).unwrap();
// Should only find app.ts, not node_modules or dist files
assert_eq!(result.files.len(), 1);
assert!(result.files[0].ends_with("app.ts"));
}
#[test]
fn test_find_ts_files_all_extensions() {
let temp = tempdir().unwrap();
// Create files with all supported extensions
fs::write(temp.path().join("a.ts"), "").unwrap();
fs::write(temp.path().join("b.tsx"), "").unwrap();
fs::write(temp.path().join("c.mts"), "").unwrap();
fs::write(temp.path().join("d.cts"), "").unwrap();
fs::write(temp.path().join("e.js"), "").unwrap();
fs::write(temp.path().join("f.jsx"), "").unwrap();
fs::write(temp.path().join("g.mjs"), "").unwrap();
fs::write(temp.path().join("h.cjs"), "").unwrap();
// Create non-matching files
fs::write(temp.path().join("i.json"), "").unwrap();
fs::write(temp.path().join("j.css"), "").unwrap();
fs::write(temp.path().join("k.html"), "").unwrap();
let result = find_ts_files(temp.path()).unwrap();
assert_eq!(result.files.len(), 6);
}
#[test]
fn test_find_ts_files_empty_directory() {
let temp = tempdir().unwrap();
let result = find_ts_files(temp.path()).unwrap();
assert!(result.files.is_empty());
}
#[test]
fn test_find_ts_files_skips_hidden() {
let temp = tempdir().unwrap();
// Create visible file
fs::write(temp.path().join("visible.ts"), "").unwrap();
// Create hidden directory with ts file
fs::create_dir(temp.path().join(".hidden")).unwrap();
fs::write(temp.path().join(".hidden/secret.ts"), "").unwrap();
let result = find_ts_files(temp.path()).unwrap();
// Should only find visible.ts
assert_eq!(result.files.len(), 1);
assert!(result.files[0].ends_with("visible.ts"));
}
}