Skip to main content

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}