1use std::path::{Path, PathBuf};
4
5use fallow_config::{ExternalPluginDef, PackageJson};
6
7use crate::core_backend;
8
9pub use crate::core_backend::{
11 CheckWarning, ManifestResult, RuleReport, WarningKind, check_manifest_entries,
12 is_external_plugin_active,
13};
14
15pub mod registry {
17 use crate::core_backend;
18
19 #[derive(Debug, Clone, PartialEq, Eq)]
21 pub struct PluginRegexValidationError {
22 message: String,
23 }
24
25 impl From<core_backend::BackendPluginRegexValidationError> for PluginRegexValidationError {
26 fn from(inner: core_backend::BackendPluginRegexValidationError) -> Self {
27 Self {
28 message: inner.message(),
29 }
30 }
31 }
32
33 #[must_use]
39 pub fn builtin_plugin_names() -> Vec<&'static str> {
40 core_backend::builtin_plugin_names()
41 }
42
43 #[must_use]
45 pub fn format_plugin_regex_errors(errors: &[PluginRegexValidationError]) -> String {
46 let joined = errors
47 .iter()
48 .map(|error| error.message.as_str())
49 .collect::<Vec<_>>();
50 format!(
51 "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.",
52 joined.join("\n - ")
53 )
54 }
55}
56
57#[derive(Debug, Clone, Default)]
59pub struct AggregatedPluginResult {
60 inner: core_backend::BackendAggregatedPluginResult,
61}
62
63impl AggregatedPluginResult {
64 #[must_use]
66 pub fn active_plugins(&self) -> &[String] {
67 self.inner.active_plugins()
68 }
69
70 pub(crate) fn merge_active_plugins_from(&mut self, other: &Self) {
72 self.inner.merge_active_plugins_from(&other.inner);
73 }
74
75 pub(crate) fn backend(&self) -> &core_backend::BackendAggregatedPluginResult {
76 &self.inner
77 }
78}
79
80impl From<core_backend::BackendAggregatedPluginResult> for AggregatedPluginResult {
81 fn from(inner: core_backend::BackendAggregatedPluginResult) -> Self {
82 Self { inner }
83 }
84}
85
86pub struct PluginRegistry {
88 inner: core_backend::BackendPluginRegistry,
89}
90
91impl PluginRegistry {
92 #[must_use]
94 pub(crate) fn new(external: Vec<ExternalPluginDef>) -> Self {
95 Self {
96 inner: core_backend::BackendPluginRegistry::new(external),
97 }
98 }
99
100 #[must_use]
102 pub(crate) fn discovery_hidden_dirs(&self, pkg: &PackageJson, root: &Path) -> Vec<String> {
103 self.inner.discovery_hidden_dirs(pkg, root)
104 }
105
106 pub(crate) fn try_run(
108 &self,
109 pkg: &PackageJson,
110 root: &Path,
111 discovered_files: &[PathBuf],
112 ) -> Result<AggregatedPluginResult, Vec<registry::PluginRegexValidationError>> {
113 self.inner
114 .try_run(pkg, root, discovered_files)
115 .map(Into::into)
116 .map_err(|errors| errors.into_iter().map(Into::into).collect())
117 }
118}
119
120impl Default for PluginRegistry {
121 fn default() -> Self {
122 Self::new(vec![])
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use std::path::PathBuf;
129
130 use super::{AggregatedPluginResult, PluginRegistry};
131
132 #[test]
133 fn plugin_registry_try_run_returns_engine_result() {
134 let registry = PluginRegistry::default();
135 let result = registry
136 .try_run(
137 &fallow_config::PackageJson::default(),
138 &PathBuf::from("/repo"),
139 &[],
140 )
141 .expect("empty package should not produce regex errors");
142
143 assert!(result.active_plugins().is_empty());
144 }
145
146 #[test]
147 fn aggregated_plugin_result_merges_active_plugins() {
148 let mut base = AggregatedPluginResult::default();
149 base.inner.push_active_plugin_for_test("nextjs");
150 let mut incoming = AggregatedPluginResult::default();
151 incoming.inner.push_active_plugin_for_test("nextjs");
152 incoming.inner.push_active_plugin_for_test("vitest");
153
154 base.merge_active_plugins_from(&incoming);
155
156 assert_eq!(base.active_plugins(), ["nextjs", "vitest"]);
157 }
158}
159
160#[cfg(test)]
161mod roster_tests {
162 #[test]
170 fn roster_carries_every_registered_plugin() {
171 let names = super::registry::builtin_plugin_names();
172 assert!(
173 names.contains(&"deno"),
174 "deno is registered in core and must reach the roster"
175 );
176 assert!(
177 names.len() > 100,
178 "roster looks truncated: {} names",
179 names.len()
180 );
181 }
182}