Skip to main content

cli/lib/compiler/plugins/
plugins_loader.rs

1//! Rust-native compiler plugins are handled by Rust crates, so legacy transformer
2//! plugin declarations are recorded as unresolved instead of being emulated through Node.
3
4use std::path::PathBuf;
5
6use serde_json::Value;
7
8use crate::configuration::Plugin;
9
10pub const PLUGIN_ENTRY_FILENAME: &str = "plugin";
11
12#[derive(Clone, Debug, Default, PartialEq, Eq)]
13pub struct NestCompilerPlugin {
14    pub name: String,
15    pub resolved_path: PathBuf,
16    pub options: serde_json::Map<String, Value>,
17    pub has_before_hook: bool,
18    pub has_after_hook: bool,
19    pub has_after_declarations_hook: bool,
20    pub has_readonly_visitor: bool,
21    pub path_to_source: Option<PathBuf>,
22}
23
24#[derive(Clone, Debug, Default, PartialEq, Eq)]
25pub struct MultiNestCompilerPlugins {
26    pub before_hooks: Vec<NestCompilerPlugin>,
27    pub after_hooks: Vec<NestCompilerPlugin>,
28    pub after_declarations_hooks: Vec<NestCompilerPlugin>,
29    pub readonly_visitors: Vec<NestCompilerPlugin>,
30    pub unresolved_plugins: Vec<String>,
31}
32
33impl MultiNestCompilerPlugins {
34    pub fn is_any_plugin_registered(&self) -> bool {
35        !self.before_hooks.is_empty()
36            || !self.after_hooks.is_empty()
37            || !self.after_declarations_hooks.is_empty()
38    }
39}
40
41#[derive(Clone, Debug, PartialEq, Eq)]
42pub struct PluginsLoader {
43    cwd: PathBuf,
44    module_paths: Vec<PathBuf>,
45}
46
47impl Default for PluginsLoader {
48    fn default() -> Self {
49        Self::new(std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")))
50    }
51}
52
53impl PluginsLoader {
54    pub fn new(cwd: impl Into<PathBuf>) -> Self {
55        let cwd = cwd.into();
56        let module_paths = vec![cwd.join("node_modules")];
57        Self { cwd, module_paths }
58    }
59
60    pub fn with_module_paths(cwd: impl Into<PathBuf>, module_paths: Vec<PathBuf>) -> Self {
61        Self {
62            cwd: cwd.into(),
63            module_paths,
64        }
65    }
66
67    pub fn load(
68        &self,
69        plugins: &[Plugin],
70        path_to_source: Option<PathBuf>,
71    ) -> Result<MultiNestCompilerPlugins, String> {
72        let mut multi = MultiNestCompilerPlugins::default();
73        for plugin in plugins {
74            let (name, _) = plugin_entry_parts(plugin);
75            multi.unresolved_plugins.push(name);
76        }
77        let _ = path_to_source;
78        Ok(multi)
79    }
80
81    pub fn resolve_plugin_reference(&self, name: &str) -> Option<PathBuf> {
82        self.node_module_paths()
83            .into_iter()
84            .flat_map(|module_path| {
85                [
86                    module_path.join(name).join(PLUGIN_ENTRY_FILENAME),
87                    module_path
88                        .join(name)
89                        .join(format!("{PLUGIN_ENTRY_FILENAME}.js")),
90                    module_path.join(name),
91                    self.cwd.join(name),
92                ]
93            })
94            .find(|candidate| candidate.exists())
95    }
96
97    pub fn node_module_paths(&self) -> Vec<PathBuf> {
98        let mut paths = vec![self.cwd.join("node_modules")];
99        paths.extend(self.module_paths.clone());
100        paths.sort();
101        paths.dedup();
102        paths
103    }
104}
105
106fn plugin_entry_parts(plugin: &Plugin) -> (String, serde_json::Map<String, Value>) {
107    match plugin {
108        Plugin::Name(name) => (name.clone(), serde_json::Map::new()),
109        Plugin::Options(options) => {
110            let mut merged_options = serde_json::Map::new();
111            for option_group in &options.options {
112                for (key, value) in option_group {
113                    merged_options.insert(key.clone(), value.clone());
114                }
115            }
116            (options.name.clone(), merged_options)
117        }
118    }
119}