use std::path::PathBuf;
use fallow_config::ResolvedConfig;
use fallow_types::discover::{DiscoveredFile, EntryPoint, EntryPointSource};
use rustc_hash::{FxHashMap, FxHashSet};
use super::CategorizedEntryPoints;
use super::entry_points::relative_paths_for;
use crate::results::EntryPointSummary;
pub fn summarize_entry_points(entry_points: &[EntryPoint]) -> EntryPointSummary {
let mut counts: FxHashMap<String, usize> = FxHashMap::default();
for ep in entry_points {
let category = match &ep.source {
EntryPointSource::PackageJsonMain
| EntryPointSource::PackageJsonModule
| EntryPointSource::PackageJsonExports
| EntryPointSource::PackageJsonBin
| EntryPointSource::PackageJsonScript => "package.json",
EntryPointSource::Plugin { .. } => "plugin",
EntryPointSource::TestFile => "test file",
EntryPointSource::DefaultIndex => "default index",
EntryPointSource::ManualEntry => "manual entry",
EntryPointSource::InfrastructureConfig => "config",
EntryPointSource::DynamicallyLoaded => "dynamically loaded",
};
*counts.entry(category.to_string()).or_insert(0) += 1;
}
let mut by_source: Vec<(String, usize)> = counts.into_iter().collect();
by_source.sort_by(|a, b| b.1.cmp(&a.1).then_with(|| a.0.cmp(&b.0)));
EntryPointSummary {
total: entry_points.len(),
by_source,
}
}
pub(super) fn non_manual_paths(entries: &[EntryPoint]) -> FxHashSet<PathBuf> {
entries
.iter()
.filter(|entry| !matches!(entry.source, EntryPointSource::ManualEntry))
.map(|entry| entry.path.clone())
.collect()
}
pub fn record_entry_pattern_effect(
config: &ResolvedConfig,
files: &[DiscoveredFile],
entry_points: &CategorizedEntryPoints,
mut other_sources: FxHashSet<PathBuf>,
) {
if config.entry_patterns.is_empty() {
return;
}
other_sources.extend(non_manual_paths(&entry_points.runtime));
config
.entry_pattern_effect
.record(manual_entry_pattern_effect(config, files, &other_sources));
}
#[expect(
clippy::expect_used,
reason = "entry glob patterns are validated before entry point discovery"
)]
fn manual_entry_pattern_effect(
config: &ResolvedConfig,
files: &[DiscoveredFile],
non_manual_runtime_paths: &FxHashSet<PathBuf>,
) -> Vec<bool> {
let relative_paths = relative_paths_for(files, &config.root);
config
.entry_patterns
.iter()
.map(|pattern| {
let matcher = fallow_config::compile_path_glob(pattern)
.expect("entry pattern was validated at config load time")
.compile_matcher();
let mut matched = relative_paths
.iter()
.zip(files)
.filter(|(relative, _)| matcher.is_match(relative.as_str()))
.map(|(_, file)| &file.path)
.peekable();
matched.peek().is_none() || matched.any(|path| !non_manual_runtime_paths.contains(path))
})
.collect()
}