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