oxc/crates/oxc_cli/src/walk.rs
2023-10-29 13:30:20 +08:00

113 lines
3.7 KiB
Rust

use std::{
path::{Path, PathBuf},
sync::mpsc,
};
use ignore::{overrides::OverrideBuilder, DirEntry};
use oxc_span::VALID_EXTENSIONS;
use crate::IgnoreOptions;
pub struct Walk {
inner: ignore::WalkParallel,
}
struct WalkBuilder {
sender: mpsc::Sender<Vec<Box<Path>>>,
}
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() })
}
}
struct WalkCollector {
paths: Vec<Box<Path>>,
sender: mpsc::Sender<Vec<Box<Path>>>,
}
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.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 }
}
pub fn paths(self) -> Vec<Box<Path>> {
let (sender, receiver) = mpsc::channel::<Vec<Box<Path>>>();
let mut builder = WalkBuilder { sender };
self.inner.visit(&mut builder);
drop(builder);
receiver.into_iter().flatten().collect()
}
fn is_wanted_entry(dir_entry: &DirEntry) -> 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 };
VALID_EXTENSIONS.contains(&extension.to_string_lossy().as_ref())
}
}