darklua_core/rules/bundle/
mod.rs

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