Skip to main content

darklua_core/rules/bundle/
mod.rs

1pub(crate) mod path_require_mode;
2mod rename_type_declaration;
3mod require_mode;
4
5use std::path::Path;
6
7use wax::Program;
8
9use crate::frontend::LoaderConfiguration;
10use crate::nodes::Block;
11use crate::rules::{
12    Context, Rule, RuleConfiguration, RuleConfigurationError, RuleMetadata, RuleProcessResult,
13    RuleProperties,
14};
15use crate::Parser;
16
17pub(crate) use rename_type_declaration::RenameTypeDeclarationProcessor;
18pub use require_mode::BundleRequireMode;
19
20pub const BUNDLER_RULE_NAME: &str = "bundler";
21
22#[derive(Debug)]
23pub(crate) struct BundleOptions {
24    parser: Parser,
25    modules_identifier: String,
26    excludes: Option<wax::Any<'static>>,
27    loaders: LoaderConfiguration,
28}
29
30impl BundleOptions {
31    fn new<'a>(
32        parser: Parser,
33        modules_identifier: impl Into<String>,
34        excludes: impl Iterator<Item = &'a str>,
35        loaders: LoaderConfiguration,
36    ) -> Self {
37        let excludes: Vec<_> = excludes
38            .filter_map(|exclusion| match wax::Glob::new(exclusion) {
39                Ok(glob) => Some(glob.into_owned()),
40                Err(err) => {
41                    log::warn!(
42                        "unable to create exclude matcher from `{}`: {}",
43                        exclusion,
44                        err
45                    );
46                    None
47                }
48            })
49            .collect();
50        Self {
51            parser,
52            modules_identifier: modules_identifier.into(),
53            excludes: if excludes.is_empty() {
54                None
55            } else {
56                let any_pattern = wax::any(excludes)
57                    .expect("exclude globs errors should be filtered and only emit a warning");
58                Some(any_pattern)
59            },
60            loaders,
61        }
62    }
63
64    fn parser(&self) -> &Parser {
65        &self.parser
66    }
67
68    fn modules_identifier(&self) -> &str {
69        &self.modules_identifier
70    }
71
72    fn is_excluded(&self, require: &Path) -> bool {
73        self.excludes
74            .as_ref()
75            .map(|any| any.is_match(require))
76            .unwrap_or(false)
77    }
78
79    fn loaders(&self) -> &LoaderConfiguration {
80        &self.loaders
81    }
82}
83
84/// A rule that inlines required modules
85#[derive(Debug)]
86pub(crate) struct Bundler {
87    metadata: RuleMetadata,
88    require_mode: BundleRequireMode,
89    options: BundleOptions,
90}
91
92impl Bundler {
93    pub(crate) fn new<'a>(
94        parser: Parser,
95        require_mode: BundleRequireMode,
96        excludes: impl Iterator<Item = &'a str>,
97        loaders: LoaderConfiguration,
98    ) -> Self {
99        Self {
100            metadata: RuleMetadata::default(),
101            require_mode,
102            options: BundleOptions::new(parser, DEFAULT_MODULE_IDENTIFIER, excludes, loaders),
103        }
104    }
105
106    pub(crate) fn with_modules_identifier(mut self, modules_identifier: impl Into<String>) -> Self {
107        self.options.modules_identifier = modules_identifier.into();
108        self
109    }
110}
111
112impl Rule for Bundler {
113    fn process(&self, block: &mut Block, context: &Context) -> RuleProcessResult {
114        self.require_mode
115            .process_block(block, context, &self.options)
116    }
117}
118
119impl RuleConfiguration for Bundler {
120    fn configure(&mut self, _properties: RuleProperties) -> Result<(), RuleConfigurationError> {
121        Err(RuleConfigurationError::InternalUsageOnly(
122            self.get_name().to_owned(),
123        ))
124    }
125
126    fn get_name(&self) -> &'static str {
127        BUNDLER_RULE_NAME
128    }
129
130    fn serialize_to_properties(&self) -> RuleProperties {
131        RuleProperties::new()
132    }
133
134    fn set_metadata(&mut self, metadata: RuleMetadata) {
135        self.metadata = metadata;
136    }
137
138    fn metadata(&self) -> &RuleMetadata {
139        &self.metadata
140    }
141}
142
143const DEFAULT_MODULE_IDENTIFIER: &str = "__DARKLUA_BUNDLE_MODULES";
144
145#[cfg(test)]
146mod test {
147    use super::*;
148    use crate::rules::{require::PathRequireMode, Rule};
149
150    use insta::assert_json_snapshot;
151
152    fn new_rule() -> Bundler {
153        Bundler::new(
154            Parser::default(),
155            BundleRequireMode::default(),
156            std::iter::empty(),
157            LoaderConfiguration::default(),
158        )
159    }
160
161    fn new_rule_with_require_mode(mode: impl Into<BundleRequireMode>) -> Bundler {
162        Bundler::new(
163            Parser::default(),
164            mode.into(),
165            std::iter::empty(),
166            LoaderConfiguration::default(),
167        )
168    }
169
170    // the bundler rule should only be used internally by darklua
171    // so there is no need for it to serialize properly. The
172    // implementation exist just make sure it does not panic
173
174    #[test]
175    fn serialize_default_rule() {
176        let rule: Box<dyn Rule> = Box::new(new_rule());
177
178        assert_json_snapshot!("default_bundler", rule);
179    }
180
181    #[test]
182    fn serialize_path_require_mode_with_custom_module_folder_name() {
183        let rule: Box<dyn Rule> =
184            Box::new(new_rule_with_require_mode(PathRequireMode::new("__init__")));
185
186        assert_json_snapshot!("default_bundler", rule);
187    }
188
189    #[test]
190    fn serialize_path_require_mode_with_custom_module_folder_name_and_modules_identifier() {
191        let rule: Box<dyn Rule> = Box::new(
192            new_rule_with_require_mode(PathRequireMode::new("__init__"))
193                .with_modules_identifier("_CUSTOM_VAR"),
194        );
195
196        assert_json_snapshot!("default_bundler", rule);
197    }
198
199    #[test]
200    fn serialize_with_custom_modules_identifier() {
201        let rule: Box<dyn Rule> = Box::new(new_rule().with_modules_identifier("_CUSTOM_VAR"));
202
203        assert_json_snapshot!("default_bundler", rule);
204    }
205}