ruff/crates/ty_project/src/glob.rs

98 lines
3.3 KiB
Rust

use ruff_db::system::SystemPath;
pub(crate) use exclude::{ExcludeFilter, ExcludeFilterBuilder};
pub(crate) use include::{IncludeFilter, IncludeFilterBuilder};
pub(crate) use portable::{
AbsolutePortableGlobPattern, PortableGlobError, PortableGlobKind, PortableGlobPattern,
};
mod exclude;
mod include;
mod portable;
/// Path filtering based on an exclude and include glob pattern set.
///
/// Exclude patterns take precedence over includes.
#[derive(Clone, Debug, Eq, PartialEq, get_size2::GetSize)]
pub struct IncludeExcludeFilter {
include: IncludeFilter,
exclude: ExcludeFilter,
}
impl IncludeExcludeFilter {
pub(crate) fn new(include: IncludeFilter, exclude: ExcludeFilter) -> Self {
Self { include, exclude }
}
/// Returns whether this directory is included in this filter.
///
/// Note, this function never returns [`IncludeResult::Included`] for a path that is not included or excluded.
/// However, it may return [`IncludeResult::Included`] for directories that are not excluded, but where
/// it requires traversal to decide if any of its subdirectories or files are included. This, for example,
/// is the case when using wildcard include-patterns like `**/test`. Prefix wildcards require to traverse `src`
/// because it can't be known ahead of time whether it contains a `test` directory or file.
pub(crate) fn is_directory_maybe_included(
&self,
path: &SystemPath,
mode: GlobFilterCheckMode,
) -> IncludeResult {
if self.exclude.match_directory(path, mode) {
IncludeResult::Excluded
} else if self.include.match_directory(path) {
IncludeResult::Included
} else {
IncludeResult::NotIncluded
}
}
pub(crate) fn is_file_included(
&self,
path: &SystemPath,
mode: GlobFilterCheckMode,
) -> IncludeResult {
if self.exclude.match_file(path, mode) {
IncludeResult::Excluded
} else if self.include.match_file(path) {
IncludeResult::Included
} else {
IncludeResult::NotIncluded
}
}
}
impl std::fmt::Display for IncludeExcludeFilter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "include={}, exclude={}", &self.include, &self.exclude)
}
}
#[derive(Copy, Clone, Eq, PartialEq)]
pub(crate) enum GlobFilterCheckMode {
/// The paths are checked top-to-bottom and inclusion is determined
/// for each path during the traversal.
TopDown,
/// An adhoc test if a single file or directory is included.
///
/// This is more expensive than a [`Self::TopDown`] check
/// because it may require testing every ancestor path in addition to the
/// path itself to ensure no ancestor path matches an exclude rule.
Adhoc,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(crate) enum IncludeResult {
/// The path matches or at least is a prefix of an include pattern.
///
/// For directories: This isn't a guarantee that any file in this directory gets included
/// but we need to traverse it to make this decision.
Included,
/// The path matches an exclude pattern.
Excluded,
/// The path matches neither an include nor an exclude pattern and, therefore,
/// isn't included.
NotIncluded,
}