fallow_core/plugins/
mod.rs1use std::path::{Path, PathBuf};
13
14use fallow_config::PackageJson;
15
16#[derive(Debug, Default)]
18pub struct PluginResult {
19 pub entry_patterns: Vec<String>,
21 pub referenced_dependencies: Vec<String>,
23 pub always_used_files: Vec<String>,
25 pub setup_files: Vec<PathBuf>,
27}
28
29impl PluginResult {
30 pub const fn is_empty(&self) -> bool {
31 self.entry_patterns.is_empty()
32 && self.referenced_dependencies.is_empty()
33 && self.always_used_files.is_empty()
34 && self.setup_files.is_empty()
35 }
36}
37
38pub trait Plugin: Send + Sync {
40 fn name(&self) -> &'static str;
42
43 fn enablers(&self) -> &'static [&'static str] {
46 &[]
47 }
48
49 fn is_enabled(&self, pkg: &PackageJson, root: &Path) -> bool {
52 let deps = pkg.all_dependency_names();
53 self.is_enabled_with_deps(&deps, root)
54 }
55
56 fn is_enabled_with_deps(&self, deps: &[String], _root: &Path) -> bool {
59 let enablers = self.enablers();
60 if enablers.is_empty() {
61 return false;
62 }
63 enablers.iter().any(|enabler| {
64 if enabler.ends_with('/') {
65 deps.iter().any(|d| d.starts_with(enabler))
67 } else {
68 deps.iter().any(|d| d == enabler)
69 }
70 })
71 }
72
73 fn entry_patterns(&self) -> &'static [&'static str] {
75 &[]
76 }
77
78 fn config_patterns(&self) -> &'static [&'static str] {
80 &[]
81 }
82
83 fn always_used(&self) -> &'static [&'static str] {
85 &[]
86 }
87
88 fn used_exports(&self) -> Vec<(&'static str, &'static [&'static str])> {
90 vec![]
91 }
92
93 fn tooling_dependencies(&self) -> &'static [&'static str] {
96 &[]
97 }
98
99 fn virtual_module_prefixes(&self) -> &'static [&'static str] {
104 &[]
105 }
106
107 fn path_aliases(&self, _root: &Path) -> Vec<(&'static str, String)> {
117 vec![]
118 }
119
120 fn resolve_config(&self, _config_path: &Path, _source: &str, _root: &Path) -> PluginResult {
125 PluginResult::default()
126 }
127
128 fn package_json_config_key(&self) -> Option<&'static str> {
133 None
134 }
135}
136
137macro_rules! define_plugin {
172 (
173 struct $name:ident => $display:expr,
174 enablers: $enablers:expr
175 $(, entry_patterns: $entry:expr)?
176 $(, config_patterns: $config:expr)?
177 $(, always_used: $always:expr)?
178 $(, tooling_dependencies: $tooling:expr)?
179 $(, virtual_module_prefixes: $virtual:expr)?
180 $(, used_exports: [$( ($pat:expr, $exports:expr) ),* $(,)?])?
181 $(,)?
182 ) => {
183 pub struct $name;
184
185 impl Plugin for $name {
186 fn name(&self) -> &'static str {
187 $display
188 }
189
190 fn enablers(&self) -> &'static [&'static str] {
191 $enablers
192 }
193
194 $( fn entry_patterns(&self) -> &'static [&'static str] { $entry } )?
195 $( fn config_patterns(&self) -> &'static [&'static str] { $config } )?
196 $( fn always_used(&self) -> &'static [&'static str] { $always } )?
197 $( fn tooling_dependencies(&self) -> &'static [&'static str] { $tooling } )?
198 $( fn virtual_module_prefixes(&self) -> &'static [&'static str] { $virtual } )?
199
200 $(
201 fn used_exports(&self) -> Vec<(&'static str, &'static [&'static str])> {
202 vec![$( ($pat, $exports) ),*]
203 }
204 )?
205 }
206 };
207}
208
209pub mod config_parser;
210pub mod registry;
211mod tooling;
212
213pub use registry::{AggregatedPluginResult, PluginRegistry};
214pub use tooling::is_known_tooling_dependency;
215
216mod angular;
217mod astro;
218mod ava;
219mod babel;
220mod biome;
221mod bun;
222mod c8;
223mod capacitor;
224mod changesets;
225mod commitizen;
226mod commitlint;
227mod cspell;
228mod cucumber;
229mod cypress;
230mod dependency_cruiser;
231mod docusaurus;
232mod drizzle;
233mod electron;
234mod eslint;
235mod expo;
236mod gatsby;
237mod graphql_codegen;
238mod husky;
239mod i18next;
240mod jest;
241mod karma;
242mod knex;
243mod kysely;
244mod lefthook;
245mod lint_staged;
246mod markdownlint;
247mod mocha;
248mod msw;
249mod nestjs;
250mod next_intl;
251mod nextjs;
252mod nitro;
253mod nodemon;
254mod nuxt;
255mod nx;
256mod nyc;
257mod openapi_ts;
258mod oxlint;
259mod parcel;
260mod playwright;
261mod plop;
262mod pm2;
263mod postcss;
264mod prettier;
265mod prisma;
266mod react_native;
267mod react_router;
268mod relay;
269mod remark;
270mod remix;
271mod rolldown;
272mod rollup;
273mod rsbuild;
274mod rspack;
275mod sanity;
276mod semantic_release;
277mod sentry;
278mod simple_git_hooks;
279mod storybook;
280mod stylelint;
281mod sveltekit;
282mod svgo;
283mod svgr;
284mod swc;
285mod syncpack;
286mod tailwind;
287mod tanstack_router;
288mod tsdown;
289mod tsup;
290mod turborepo;
291mod typedoc;
292mod typeorm;
293mod typescript;
294mod vite;
295mod vitepress;
296mod vitest;
297mod webdriverio;
298mod webpack;
299mod wrangler;
300
301#[cfg(test)]
302mod tests {
303 use super::*;
304 use std::path::Path;
305
306 #[test]
309 fn is_enabled_with_deps_exact_match() {
310 let plugin = nextjs::NextJsPlugin;
311 let deps = vec!["next".to_string()];
312 assert!(plugin.is_enabled_with_deps(&deps, Path::new("/project")));
313 }
314
315 #[test]
316 fn is_enabled_with_deps_no_match() {
317 let plugin = nextjs::NextJsPlugin;
318 let deps = vec!["react".to_string()];
319 assert!(!plugin.is_enabled_with_deps(&deps, Path::new("/project")));
320 }
321
322 #[test]
323 fn is_enabled_with_deps_empty_deps() {
324 let plugin = nextjs::NextJsPlugin;
325 let deps: Vec<String> = vec![];
326 assert!(!plugin.is_enabled_with_deps(&deps, Path::new("/project")));
327 }
328
329 #[test]
332 fn plugin_result_is_empty_when_default() {
333 let r = PluginResult::default();
334 assert!(r.is_empty());
335 }
336
337 #[test]
338 fn plugin_result_not_empty_with_entry_patterns() {
339 let r = PluginResult {
340 entry_patterns: vec!["*.ts".to_string()],
341 ..Default::default()
342 };
343 assert!(!r.is_empty());
344 }
345
346 #[test]
347 fn plugin_result_not_empty_with_referenced_deps() {
348 let r = PluginResult {
349 referenced_dependencies: vec!["lodash".to_string()],
350 ..Default::default()
351 };
352 assert!(!r.is_empty());
353 }
354
355 #[test]
356 fn plugin_result_not_empty_with_setup_files() {
357 let r = PluginResult {
358 setup_files: vec![PathBuf::from("/setup.ts")],
359 ..Default::default()
360 };
361 assert!(!r.is_empty());
362 }
363}