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