darklua_core/rules/rename_variables/
mod.rs1mod 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#[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}