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