Repository: astral-sh/ruff
File: crates/ty_project/src/glob/exclude.rs (Rust)

Code before:
    ignore: Gitignore,
}

impl ExcludeFilter {
    /// Returns `true` if the path to a directory is definitely excluded and `false` otherwise.
    pub(crate) fn match_directory(&self, path: &SystemPath, mode: GlobFilterCheckMode) -> bool {
        self.matches(path, mode, true)
    }

    /// Returns `true` if the path to a file is definitely excluded and `false` otherwise.
    pub(crate) fn match_file(&self, path: &SystemPath, mode: GlobFilterCheckMode) -> bool {
        self.matches(path, mode, false)
    }

    fn matches(&self, path: &SystemPath, mode: GlobFilterCheckMode, directory: bool) -> bool {
        match mode {
            GlobFilterCheckMode::TopDown => {
                match self.ignore.matched(path, directory) {
                    // No hit or an allow hit means the file or directory is not excluded.
                    Match::None | Match::Allow => false,
                    Match::Ignore => true,
                }
            }
            GlobFilterCheckMode::Adhoc => {
                for ancestor in path.ancestors() {
                    match self.ignore.matched(ancestor, directory) {
                        // If the path is allowlisted or there's no hit, try the parent to ensure we don't return false
                        // for a folder where there's an exclude for a parent.
                        Match::None | Match::Allow => {}
                        Match::Ignore => return true,
                    }
                }

                false
            }
        }
    }
}

impl std::fmt::Display for ExcludeFilter {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        f.debug_list().entries(&self.ignore.globs).finish()
    }
}

pub(crate) struct ExcludeFilterBuilder {
    ignore: GitignoreBuilder,
}

impl ExcludeFilterBuilder {
    pub(crate) fn new() -> Self {

Review comment (bug):
We incorrectly passed the value `directory` here; it should always be true when testing the ancestor paths.

Code after:
    ignore: Gitignore,
}

impl ExcludeFilter {
    /// Returns `true` if the path to a directory is definitely excluded and `false` otherwise.
    pub(crate) fn match_directory(&self, path: &SystemPath, mode: GlobFilterCheckMode) -> bool {
        self.matches(path, mode, true)
    }

    /// Returns `true` if the path to a file is definitely excluded and `false` otherwise.
    pub(crate) fn match_file(&self, path: &SystemPath, mode: GlobFilterCheckMode) -> bool {
        self.matches(path, mode, false)
    }

    fn matches(&self, path: &SystemPath, mode: GlobFilterCheckMode, directory: bool) -> bool {
        // If the path is excluded, return `ignore`
        if self.ignore.matched(path, directory).is_ignore() {
            return true;
        }

        match mode {
            GlobFilterCheckMode::TopDown => {
                // No hit or an allow hit means the file or directory is not excluded.
                false
            }
            GlobFilterCheckMode::Adhoc => {
                // If the path is allowlisted or there's no hit, try the parent to ensure we don't return false
                // for a folder where there's an exclude for a parent.
                path.ancestors()
                    .skip(1)
                    .any(|ancestor| self.ignore.matched(ancestor, true).is_ignore())
            }
        }
    }
}

impl std::fmt::Display for ExcludeFilter {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        f.debug_list().entries(&self.ignore.globs).finish()
    }
}

pub(crate) struct ExcludeFilterBuilder {
    ignore: GitignoreBuilder,
}

impl ExcludeFilterBuilder {
    pub(crate) fn new() -> Self {
        Self {
            ignore: GitignoreBuilder::new(),
        }