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 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#[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 #[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}