mirror of
https://github.com/danbulant/oxc
synced 2026-05-25 04:42:10 +00:00
167 lines
5.2 KiB
Rust
167 lines
5.2 KiB
Rust
use std::{
|
|
path::{Path, PathBuf},
|
|
sync::mpsc,
|
|
};
|
|
|
|
use ignore::{overrides::OverrideBuilder, DirEntry};
|
|
use oxc_span::VALID_EXTENSIONS;
|
|
|
|
use crate::IgnoreOptions;
|
|
|
|
#[derive(Clone)]
|
|
pub struct Extensions(pub Vec<&'static str>);
|
|
|
|
impl Default for Extensions {
|
|
fn default() -> Self {
|
|
Self(VALID_EXTENSIONS.to_vec())
|
|
}
|
|
}
|
|
|
|
pub struct Walk {
|
|
inner: ignore::WalkParallel,
|
|
/// The file extensions to include during the traversal.
|
|
extensions: Extensions,
|
|
}
|
|
|
|
struct WalkBuilder {
|
|
sender: mpsc::Sender<Vec<Box<Path>>>,
|
|
extensions: Extensions,
|
|
}
|
|
|
|
impl<'s> ignore::ParallelVisitorBuilder<'s> for WalkBuilder {
|
|
fn build(&mut self) -> Box<dyn ignore::ParallelVisitor + 's> {
|
|
Box::new(WalkCollector {
|
|
paths: vec![],
|
|
sender: self.sender.clone(),
|
|
extensions: self.extensions.clone(),
|
|
})
|
|
}
|
|
}
|
|
|
|
struct WalkCollector {
|
|
paths: Vec<Box<Path>>,
|
|
sender: mpsc::Sender<Vec<Box<Path>>>,
|
|
extensions: Extensions,
|
|
}
|
|
|
|
impl Drop for WalkCollector {
|
|
fn drop(&mut self) {
|
|
let paths = std::mem::take(&mut self.paths);
|
|
self.sender.send(paths).unwrap();
|
|
}
|
|
}
|
|
|
|
impl ignore::ParallelVisitor for WalkCollector {
|
|
fn visit(&mut self, entry: Result<ignore::DirEntry, ignore::Error>) -> ignore::WalkState {
|
|
match entry {
|
|
Ok(entry) => {
|
|
if entry.file_type().is_some_and(|ft| !ft.is_dir())
|
|
&& Walk::is_wanted_entry(&entry, &self.extensions)
|
|
{
|
|
self.paths.push(entry.path().to_path_buf().into_boxed_path());
|
|
}
|
|
ignore::WalkState::Continue
|
|
}
|
|
Err(_err) => ignore::WalkState::Skip,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Walk {
|
|
/// Will not canonicalize paths.
|
|
/// # Panics
|
|
pub fn new(paths: &[PathBuf], options: &IgnoreOptions) -> Self {
|
|
assert!(!paths.is_empty(), "At least one path must be provided to Walk::new");
|
|
|
|
let mut inner = ignore::WalkBuilder::new(
|
|
paths
|
|
.iter()
|
|
.next()
|
|
.expect("Expected paths parameter to Walk::new() to contain at least one path."),
|
|
);
|
|
|
|
if let Some(paths) = paths.get(1..) {
|
|
for path in paths {
|
|
inner.add(path);
|
|
}
|
|
}
|
|
|
|
if !options.no_ignore {
|
|
inner.add_custom_ignore_filename(&options.ignore_path);
|
|
|
|
if !options.ignore_pattern.is_empty() {
|
|
let mut override_builder = OverrideBuilder::new(Path::new("/"));
|
|
for pattern in &options.ignore_pattern {
|
|
// Meaning of ignore pattern is reversed
|
|
// <https://docs.rs/ignore/latest/ignore/overrides/struct.OverrideBuilder.html#method.add>
|
|
let pattern = format!("!{pattern}");
|
|
override_builder.add(&pattern).unwrap();
|
|
}
|
|
let overrides = override_builder.build().unwrap();
|
|
inner.overrides(overrides);
|
|
}
|
|
}
|
|
// Turning off `follow_links` because:
|
|
// * following symlinks is a really slow syscall
|
|
// * it is super rare to have symlinked source code
|
|
let inner = inner.ignore(false).git_global(false).follow_links(false).build_parallel();
|
|
Self { inner, extensions: Extensions::default() }
|
|
}
|
|
|
|
pub fn paths(self) -> Vec<Box<Path>> {
|
|
let (sender, receiver) = mpsc::channel::<Vec<Box<Path>>>();
|
|
let mut builder = WalkBuilder { sender, extensions: self.extensions };
|
|
self.inner.visit(&mut builder);
|
|
drop(builder);
|
|
receiver.into_iter().flatten().collect()
|
|
}
|
|
|
|
pub fn with_extensions(mut self, extensions: Extensions) -> Self {
|
|
self.extensions = extensions;
|
|
self
|
|
}
|
|
|
|
fn is_wanted_entry(dir_entry: &DirEntry, extensions: &Extensions) -> bool {
|
|
let Some(file_type) = dir_entry.file_type() else { return false };
|
|
if file_type.is_dir() {
|
|
return false;
|
|
}
|
|
let Some(file_name) = dir_entry.path().file_name() else { return false };
|
|
if [".min.", "-min.", "_min."].iter().any(|e| file_name.to_string_lossy().contains(e)) {
|
|
return false;
|
|
}
|
|
let Some(extension) = dir_entry.path().extension() else { return false };
|
|
let extension = extension.to_string_lossy();
|
|
extensions.0.contains(&extension.as_ref())
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use std::{env, ffi::OsString};
|
|
|
|
use crate::IgnoreOptions;
|
|
|
|
use super::{Extensions, Walk};
|
|
|
|
#[test]
|
|
fn test_walk_with_extensions() {
|
|
let fixture = env::current_dir().unwrap().join("fixtures/walk_dir");
|
|
let fixtures = vec![fixture.clone()];
|
|
let ignore_options = IgnoreOptions {
|
|
no_ignore: false,
|
|
ignore_path: OsString::from(".gitignore"),
|
|
ignore_pattern: vec![],
|
|
};
|
|
|
|
let mut paths = Walk::new(&fixtures, &ignore_options)
|
|
.with_extensions(Extensions(["js", "vue"].to_vec()))
|
|
.paths()
|
|
.into_iter()
|
|
.map(|path| path.strip_prefix(&fixture).unwrap().to_string_lossy().to_string())
|
|
.collect::<Vec<_>>();
|
|
paths.sort();
|
|
|
|
assert_eq!(paths, vec!["bar.vue", "foo.js"]);
|
|
}
|
|
}
|