fallow_config/workspace/missing_pattern_dirs.rs
1//! Declared workspace patterns whose directory does not exist.
2
3use std::path::Path;
4
5use super::{DeclaredPattern, collect_declared_patterns};
6
7/// The positive declared patterns that stay inside the root and whose
8/// directory part does not exist on disk.
9///
10/// The directory part is the literal prefix of the pattern
11/// ([`fallow_types::workspace::workspace_pattern_literal_dir`]). A pattern
12/// without one never qualifies, and a directory that exists but holds no
13/// package does not qualify either. The rule stays conservative because a
14/// match suppresses the unused-catalog-entries check for the run.
15pub(super) fn missing_dir_patterns<'a>(
16 root: &'a Path,
17 declared: &'a [DeclaredPattern],
18) -> impl Iterator<Item = &'a DeclaredPattern> {
19 declared.iter().filter(move |declared| {
20 !declared.pattern.starts_with('!')
21 && !crate::glob_validation::escapes_project_root(&declared.pattern)
22 && fallow_types::workspace::workspace_pattern_literal_dir(&declared.pattern)
23 .is_some_and(|dir| !root.join(dir).exists())
24 })
25}
26
27/// Whether a declared workspace pattern names a directory that does not
28/// exist, the condition of the `workspace-pattern-dir-missing` diagnostic.
29///
30/// The catalog analysis reads this to skip the unused-catalog-entries check,
31/// because the packages in that directory can consume catalog entries. The
32/// audit reads it on the base run to treat those head findings as inherited
33/// (`fallow_engine::unprovable`).
34/// Discovery and the catalog analysis share one predicate, so the
35/// diagnostic and the skip cannot disagree. A root manifest that does not
36/// parse gives `false`; discovery reports it.
37#[must_use]
38pub fn has_missing_workspace_pattern_dirs(root: &Path, extra_patterns: &[String]) -> bool {
39 collect_declared_patterns(root, extra_patterns)
40 .is_ok_and(|declared| missing_dir_patterns(root, &declared).next().is_some())
41}