use std::path::{Path, PathBuf};
use rustc_hash::FxHashSet;
use fallow_config::{ExternalPluginDef, PluginDetection, UsedClassMemberRule};
use super::super::{PathRule, Plugin, PluginResult, PluginUsedExportRule, UsedExportRule};
use super::AggregatedPluginResult;
pub fn process_static_patterns(
plugin: &dyn Plugin,
root: &Path,
result: &mut AggregatedPluginResult,
) {
result.active_plugins.push(plugin.name().to_string());
let pname = plugin.name().to_string();
result
.entry_point_roles
.insert(pname.clone(), plugin.entry_point_role());
for rule in plugin.entry_pattern_rules() {
result.entry_patterns.push((rule, pname.clone()));
}
for pat in plugin.config_patterns() {
result.config_patterns.push((*pat).to_string());
}
for pat in plugin.always_used() {
result.always_used.push(((*pat).to_string(), pname.clone()));
}
for rule in plugin.used_export_rules() {
result
.used_exports
.push(PluginUsedExportRule::new(pname.clone(), rule));
}
for member in plugin.used_class_members() {
result
.used_class_members
.push(UsedClassMemberRule::from(*member));
}
for dep in plugin.tooling_dependencies() {
result.tooling_dependencies.push((*dep).to_string());
}
for prefix in plugin.virtual_module_prefixes() {
result.virtual_module_prefixes.push((*prefix).to_string());
}
for pattern in plugin.generated_import_patterns() {
result
.generated_import_patterns
.push((*pattern).to_string());
}
for (prefix, replacement) in plugin.path_aliases(root) {
result.path_aliases.push((prefix.to_string(), replacement));
}
for pat in plugin.fixture_glob_patterns() {
result
.fixture_patterns
.push(((*pat).to_string(), pname.clone()));
}
}
pub fn process_external_plugins(
external_plugins: &[ExternalPluginDef],
all_deps: &[String],
root: &Path,
discovered_files: &[PathBuf],
result: &mut AggregatedPluginResult,
) {
let all_dep_refs: Vec<&str> = all_deps.iter().map(String::as_str).collect();
for ext in external_plugins {
let is_active = if let Some(detection) = &ext.detection {
check_plugin_detection(detection, &all_dep_refs, root, discovered_files)
} else if !ext.enablers.is_empty() {
ext.enablers.iter().any(|enabler| {
if enabler.ends_with('/') {
all_deps.iter().any(|d| d.starts_with(enabler))
} else {
all_deps.iter().any(|d| d == enabler)
}
})
} else {
false
};
if is_active {
result.active_plugins.push(ext.name.clone());
result
.entry_point_roles
.insert(ext.name.clone(), ext.entry_point_role);
result.entry_patterns.extend(
ext.entry_points
.iter()
.map(|p| (PathRule::new(p.clone()), ext.name.clone())),
);
result.config_patterns.extend(ext.config_patterns.clone());
result.always_used.extend(
ext.config_patterns
.iter()
.chain(ext.always_used.iter())
.map(|p| (p.clone(), ext.name.clone())),
);
result
.tooling_dependencies
.extend(ext.tooling_dependencies.clone());
for ue in &ext.used_exports {
result.used_exports.push(PluginUsedExportRule::new(
ext.name.clone(),
UsedExportRule::new(ue.pattern.clone(), ue.exports.clone()),
));
}
result
.used_class_members
.extend(ext.used_class_members.iter().cloned());
}
}
}
pub fn discover_json_config_files<'a>(
config_matchers: &[(&'a dyn Plugin, Vec<globset::GlobMatcher>)],
resolved_plugins: &FxHashSet<&str>,
relative_files: &[(&PathBuf, String)],
root: &Path,
) -> Vec<(PathBuf, &'a dyn Plugin)> {
let mut json_configs: Vec<(PathBuf, &'a dyn Plugin)> = Vec::new();
for (plugin, _) in config_matchers {
if resolved_plugins.contains(plugin.name()) {
continue;
}
for pat in plugin.config_patterns() {
let has_glob = pat.contains("**") || pat.contains('*') || pat.contains('?');
if has_glob {
let filename = std::path::Path::new(pat)
.file_name()
.and_then(|n| n.to_str())
.unwrap_or(pat);
let matcher = globset::Glob::new(pat).ok().map(|g| g.compile_matcher());
if let Some(matcher) = matcher {
let mut checked_dirs: FxHashSet<&Path> = FxHashSet::default();
checked_dirs.insert(root);
for (abs_path, _) in relative_files {
if let Some(parent) = abs_path.parent() {
checked_dirs.insert(parent);
}
}
for dir in checked_dirs {
let candidate = dir.join(filename);
if candidate.is_file() {
let rel = candidate
.strip_prefix(root)
.map(|p| p.to_string_lossy())
.unwrap_or_default();
if matcher.is_match(rel.as_ref()) {
json_configs.push((candidate, *plugin));
}
}
}
}
} else {
let abs_path = root.join(pat);
if abs_path.is_file() {
json_configs.push((abs_path, *plugin));
}
}
}
}
json_configs
}
pub fn process_config_result(
plugin_name: &str,
plugin_result: PluginResult,
result: &mut AggregatedPluginResult,
) {
let pname = plugin_name.to_string();
if plugin_result.replace_entry_patterns && !plugin_result.entry_patterns.is_empty() {
result.entry_patterns.retain(|(_, name)| name != &pname);
}
if plugin_result.replace_used_export_rules && !plugin_result.used_exports.is_empty() {
result.used_exports.retain(|rule| rule.plugin_name != pname);
}
result.entry_patterns.extend(
plugin_result
.entry_patterns
.into_iter()
.map(|rule| (rule, pname.clone())),
);
result.used_exports.extend(
plugin_result
.used_exports
.into_iter()
.map(|rule| PluginUsedExportRule::new(pname.clone(), rule)),
);
result
.used_class_members
.extend(plugin_result.used_class_members);
result
.referenced_dependencies
.extend(plugin_result.referenced_dependencies);
result.discovered_always_used.extend(
plugin_result
.always_used_files
.into_iter()
.map(|p| (p, pname.clone())),
);
for (prefix, replacement) in plugin_result.path_aliases {
result
.path_aliases
.retain(|(existing_prefix, _)| existing_prefix != &prefix);
result.path_aliases.push((prefix, replacement));
}
result.setup_files.extend(
plugin_result
.setup_files
.into_iter()
.map(|p| (p, pname.clone())),
);
result.fixture_patterns.extend(
plugin_result
.fixture_patterns
.into_iter()
.map(|p| (p, pname.clone())),
);
result
.scss_include_paths
.extend(plugin_result.scss_include_paths);
}
pub fn check_has_config_file(
plugin: &dyn Plugin,
config_matchers: &[(&dyn Plugin, Vec<globset::GlobMatcher>)],
relative_files: &[(&PathBuf, String)],
) -> bool {
!plugin.config_patterns().is_empty()
&& config_matchers.iter().any(|(p, matchers)| {
p.name() == plugin.name()
&& relative_files
.iter()
.any(|(_, rel)| matchers.iter().any(|m| m.is_match(rel.as_str())))
})
}
pub fn check_plugin_detection(
detection: &PluginDetection,
all_deps: &[&str],
root: &Path,
discovered_files: &[PathBuf],
) -> bool {
match detection {
PluginDetection::Dependency { package } => all_deps.iter().any(|d| *d == package),
PluginDetection::FileExists { pattern } => {
if let Ok(matcher) = globset::Glob::new(pattern).map(|g| g.compile_matcher()) {
for file in discovered_files {
let relative = file.strip_prefix(root).unwrap_or(file);
if matcher.is_match(relative) {
return true;
}
}
}
let full_pattern = root.join(pattern).to_string_lossy().to_string();
glob::glob(&full_pattern)
.ok()
.is_some_and(|mut g| g.next().is_some())
}
PluginDetection::All { conditions } => conditions
.iter()
.all(|c| check_plugin_detection(c, all_deps, root, discovered_files)),
PluginDetection::Any { conditions } => conditions
.iter()
.any(|c| check_plugin_detection(c, all_deps, root, discovered_files)),
}
}