Skip to main content

darklua_core/rules/rename_variables/
mod.rs

1mod function_names;
2mod globals;
3mod rename_processor;
4
5use rename_processor::RenameProcessor;
6
7use crate::nodes::Block;
8use crate::process::processors::CollectGlobalsProcessor;
9use crate::process::utils::is_valid_identifier;
10use crate::process::{DefaultVisitor, NodeVisitor, ScopeVisitor};
11use crate::rules::{
12    Context, FlawlessRule, RuleConfiguration, RuleConfigurationError, RuleMetadata, RuleProperties,
13    RulePropertyValue,
14};
15
16use std::collections::HashSet;
17use std::iter::FromIterator;
18
19pub const RENAME_VARIABLES_RULE_NAME: &str = "rename_variables";
20
21/// Rename all identifiers to small and meaningless names.
22#[derive(Debug, PartialEq, Eq)]
23pub struct RenameVariables {
24    metadata: RuleMetadata,
25    globals: Vec<String>,
26    include_functions: bool,
27    detect_globals: bool,
28}
29
30impl RenameVariables {
31    pub fn new<I: IntoIterator<Item = String>>(iter: I) -> Self {
32        Self {
33            metadata: RuleMetadata::default(),
34            globals: Vec::from_iter(iter),
35            include_functions: false,
36            detect_globals: true,
37        }
38    }
39
40    pub fn with_function_names(mut self) -> Self {
41        self.include_functions = true;
42        self
43    }
44
45    pub fn enable_global_detection(mut self) -> Self {
46        self.detect_globals = true;
47        self
48    }
49
50    pub fn disable_global_detection(mut self) -> Self {
51        self.detect_globals = false;
52        self
53    }
54
55    fn set_globals(&mut self, list: Vec<String>) -> Result<(), RuleConfigurationError> {
56        for value in list {
57            match value.as_str() {
58                "$default" => self
59                    .globals
60                    .extend(globals::DEFAULT.iter().map(ToString::to_string)),
61                "$roblox" => self
62                    .globals
63                    .extend(globals::ROBLOX.iter().map(ToString::to_string)),
64                identifier if !is_valid_identifier(identifier) => {
65                    return Err(RuleConfigurationError::StringExpected("".to_owned()))
66                }
67                _ => self.globals.push(value),
68            }
69        }
70
71        Ok(())
72    }
73
74    fn normalize_globals(&self) -> Vec<String> {
75        let mut globals_set: HashSet<String> = self.globals.iter().cloned().collect();
76
77        let mut result = Vec::new();
78
79        if globals::DEFAULT
80            .iter()
81            .all(|identifier| globals_set.contains(*identifier))
82        {
83            globals::DEFAULT.iter().for_each(|identifier| {
84                globals_set.remove(*identifier);
85            });
86            result.push("$default".to_owned());
87        }
88
89        if globals::ROBLOX
90            .iter()
91            .all(|identifier| globals_set.contains(*identifier))
92        {
93            globals::ROBLOX.iter().for_each(|identifier| {
94                globals_set.remove(*identifier);
95            });
96            result.push("$roblox".to_owned());
97        }
98
99        result.extend(globals_set);
100        result.sort();
101        result
102    }
103}
104
105impl Default for RenameVariables {
106    fn default() -> Self {
107        Self::new(globals::DEFAULT.iter().map(|string| (*string).to_owned()))
108    }
109}
110
111impl FlawlessRule for RenameVariables {
112    fn flawless_process(&self, block: &mut Block, _: &Context) {
113        let avoid_identifiers = if self.include_functions {
114            Vec::new()
115        } else {
116            let mut collect_functions = function_names::CollectFunctionNames::default();
117            DefaultVisitor::visit_block(block, &mut collect_functions);
118            collect_functions.into()
119        };
120
121        let mut find_globals = CollectGlobalsProcessor::default();
122        if self.detect_globals {
123            ScopeVisitor::visit_block(block, &mut find_globals);
124        };
125
126        let mut processor = RenameProcessor::new(
127            self.globals
128                .clone()
129                .into_iter()
130                .chain(avoid_identifiers)
131                .chain(find_globals.into_globals()),
132            self.include_functions,
133        );
134        ScopeVisitor::visit_block(block, &mut processor);
135    }
136}
137
138impl RuleConfiguration for RenameVariables {
139    fn configure(&mut self, properties: RuleProperties) -> Result<(), RuleConfigurationError> {
140        for (key, value) in properties {
141            match key.as_str() {
142                "globals" => {
143                    self.set_globals(value.expect_string_list(&key)?)?;
144                }
145                "include_functions" => {
146                    self.include_functions = value.expect_bool(&key)?;
147                }
148                "detect_globals" => {
149                    self.detect_globals = value.expect_bool(&key)?;
150                }
151                _ => return Err(RuleConfigurationError::UnexpectedProperty(key)),
152            }
153        }
154
155        Ok(())
156    }
157
158    fn get_name(&self) -> &'static str {
159        RENAME_VARIABLES_RULE_NAME
160    }
161
162    fn serialize_to_properties(&self) -> RuleProperties {
163        let mut properties = RuleProperties::new();
164
165        let globals = self.normalize_globals();
166        if !(globals.len() == 1 && globals.contains(&"$default".to_owned())) {
167            properties.insert(
168                "globals".to_owned(),
169                RulePropertyValue::StringList(self.normalize_globals()),
170            );
171        }
172
173        if self.include_functions {
174            properties.insert(
175                "include_functions".to_owned(),
176                RulePropertyValue::Boolean(self.include_functions),
177            );
178        }
179
180        if !self.detect_globals {
181            properties.insert(
182                "detect_globals".to_owned(),
183                RulePropertyValue::Boolean(self.detect_globals),
184            );
185        }
186
187        properties
188    }
189
190    fn set_metadata(&mut self, metadata: RuleMetadata) {
191        self.metadata = metadata;
192    }
193
194    fn metadata(&self) -> &RuleMetadata {
195        &self.metadata
196    }
197}
198
199#[cfg(test)]
200mod test {
201    use super::*;
202    use crate::rules::Rule;
203
204    use insta::assert_json_snapshot;
205    use std::iter::empty;
206
207    fn new_rule() -> Box<dyn Rule> {
208        Box::<RenameVariables>::default()
209    }
210
211    #[test]
212    fn serialize_default_rule() {
213        assert_json_snapshot!("default_rename_variables", new_rule());
214    }
215
216    #[test]
217    fn serialize_no_globals_rule() {
218        assert_json_snapshot!(
219            "no_globals_rename_variables",
220            Box::new(RenameVariables::new(empty())) as Box<dyn Rule>
221        );
222    }
223
224    #[test]
225    fn serialize_roblox_globals_rule() {
226        let rule = Box::new(RenameVariables::new(
227            globals::ROBLOX.iter().map(ToString::to_string),
228        ));
229
230        assert_json_snapshot!("roblox_globals_rename_variables", rule as Box<dyn Rule>);
231    }
232
233    #[test]
234    fn serialize_with_function_names() {
235        let rule = Box::new(
236            RenameVariables::new(globals::DEFAULT.iter().map(ToString::to_string))
237                .with_function_names(),
238        );
239
240        assert_json_snapshot!(
241            "rename_variables_with_function_names",
242            rule as Box<dyn Rule>
243        );
244    }
245
246    #[test]
247    fn serialize_skip_functions() {
248        let rule = Box::new(RenameVariables::new(
249            globals::ROBLOX.iter().map(ToString::to_string),
250        ));
251
252        assert_json_snapshot!("roblox_globals_rename_variables", rule as Box<dyn Rule>);
253    }
254}