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