use std::path::{Path, PathBuf};
use fallow_config::{ExternalPluginDef, PackageJson};
use crate::core_backend;
pub use crate::core_backend::{
CheckWarning, ManifestResult, RuleReport, WarningKind, check_manifest_entries,
is_external_plugin_active,
};
pub mod registry {
use crate::core_backend;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PluginRegexValidationError {
message: String,
}
impl From<core_backend::BackendPluginRegexValidationError> for PluginRegexValidationError {
fn from(inner: core_backend::BackendPluginRegexValidationError) -> Self {
Self {
message: inner.message(),
}
}
}
#[must_use]
pub fn builtin_plugin_names() -> Vec<&'static str> {
core_backend::builtin_plugin_names()
}
#[must_use]
pub fn format_plugin_regex_errors(errors: &[PluginRegexValidationError]) -> String {
let joined = errors
.iter()
.map(|error| error.message.as_str())
.collect::<Vec<_>>();
format!(
"invalid plugin regex configuration:\n - {}\n\nRewrite the plugin config with Rust-compatible regex syntax, or remove unsupported constructs such as JavaScript lookahead and lookbehind.",
joined.join("\n - ")
)
}
}
#[derive(Debug, Clone, Default)]
pub struct AggregatedPluginResult {
inner: core_backend::BackendAggregatedPluginResult,
}
impl AggregatedPluginResult {
#[must_use]
pub fn active_plugins(&self) -> &[String] {
self.inner.active_plugins()
}
pub(crate) fn merge_active_plugins_from(&mut self, other: &Self) {
self.inner.merge_active_plugins_from(&other.inner);
}
pub(crate) fn backend(&self) -> &core_backend::BackendAggregatedPluginResult {
&self.inner
}
}
impl From<core_backend::BackendAggregatedPluginResult> for AggregatedPluginResult {
fn from(inner: core_backend::BackendAggregatedPluginResult) -> Self {
Self { inner }
}
}
pub struct PluginRegistry {
inner: core_backend::BackendPluginRegistry,
}
impl PluginRegistry {
#[must_use]
pub(crate) fn new(external: Vec<ExternalPluginDef>) -> Self {
Self {
inner: core_backend::BackendPluginRegistry::new(external),
}
}
#[must_use]
pub(crate) fn discovery_hidden_dirs(&self, pkg: &PackageJson, root: &Path) -> Vec<String> {
self.inner.discovery_hidden_dirs(pkg, root)
}
pub(crate) fn try_run(
&self,
pkg: &PackageJson,
root: &Path,
discovered_files: &[PathBuf],
) -> Result<AggregatedPluginResult, Vec<registry::PluginRegexValidationError>> {
self.inner
.try_run(pkg, root, discovered_files)
.map(Into::into)
.map_err(|errors| errors.into_iter().map(Into::into).collect())
}
}
impl Default for PluginRegistry {
fn default() -> Self {
Self::new(vec![])
}
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use super::{AggregatedPluginResult, PluginRegistry};
#[test]
fn plugin_registry_try_run_returns_engine_result() {
let registry = PluginRegistry::default();
let result = registry
.try_run(
&fallow_config::PackageJson::default(),
&PathBuf::from("/repo"),
&[],
)
.expect("empty package should not produce regex errors");
assert!(result.active_plugins().is_empty());
}
#[test]
fn aggregated_plugin_result_merges_active_plugins() {
let mut base = AggregatedPluginResult::default();
base.inner.push_active_plugin_for_test("nextjs");
let mut incoming = AggregatedPluginResult::default();
incoming.inner.push_active_plugin_for_test("nextjs");
incoming.inner.push_active_plugin_for_test("vitest");
base.merge_active_plugins_from(&incoming);
assert_eq!(base.active_plugins(), ["nextjs", "vitest"]);
}
}
#[cfg(test)]
mod roster_tests {
#[test]
fn roster_carries_every_registered_plugin() {
let names = super::registry::builtin_plugin_names();
assert!(
names.contains(&"deno"),
"deno is registered in core and must reach the roster"
);
assert!(
names.len() > 100,
"roster looks truncated: {} names",
names.len()
);
}
}