darklua_core/rules/bundle/
mod.rs1pub(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#[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 #[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}