darklua_core/rules/
remove_redeclared_keys.rs1use crate::nodes::{
2 AssignStatement, Block, Expression, FunctionCall, FunctionExpression, Identifier,
3 IndexExpression, LocalAssignStatement, ParentheseExpression, ReturnStatement, Statement,
4 StringExpression, TableEntry, TableExpression,
5};
6use crate::process::{DefaultVisitor, Evaluator, LuaValue, NodeProcessor, NodeVisitor};
7use crate::rules::{Context, RuleConfiguration, RuleConfigurationError, RuleProperties};
8
9use super::runtime_identifier::RuntimeIdentifierBuilder;
10use super::{Rule, RuleProcessResult};
11
12#[derive(Default)]
13struct Processor {
14 evaluator: Evaluator,
15 table_identifier: String,
16 skip_next_table_exp: bool,
17}
18
19impl Processor {
20 fn skip(&mut self, active: bool) {
21 self.skip_next_table_exp = active;
22 }
23}
24
25use std::collections::HashMap;
26use std::fmt::Debug;
27
28impl NodeProcessor for Processor {
29 fn process_expression(&mut self, exp: &mut Expression) {
30 if let Expression::Table(table_exp) = exp {
31 if self.skip_next_table_exp {
32 self.skip(false);
33 return;
34 }
35 let entries = table_exp.mutate_entries();
36 let mut numeral_table = HashMap::new();
37 let mut str_table = HashMap::new();
38 let mut num_index: usize = 0;
39 let mut side_effect_stmts: Vec<Statement> = Vec::new();
40
41 for (i, entry) in entries.iter().enumerate() {
42 match entry {
43 TableEntry::Index(index_entry) => {
44 let value = self.evaluator.evaluate(index_entry.get_key());
45 match value {
46 LuaValue::Number(lua_index) => {
47 if lua_index.fract() == 0.0 && lua_index > 0.0 {
48 let key = (lua_index as usize) - 1;
49 if side_effect_stmts.is_empty() {
50 numeral_table.insert(key, i);
51 } else {
52 let assignment = AssignStatement::from_variable(
53 IndexExpression::new(
54 Identifier::new(self.table_identifier.as_str()),
55 key + 1,
56 ),
57 index_entry.get_value().clone(),
58 );
59 side_effect_stmts.push(assignment.into());
60 }
61 }
62 }
63 LuaValue::String(key) => {
64 if side_effect_stmts.is_empty() {
65 str_table.insert(key, i);
66 } else {
67 let assignment = AssignStatement::from_variable(
68 IndexExpression::new(
69 Identifier::new(self.table_identifier.as_str()),
70 StringExpression::from_value(key),
71 ),
72 index_entry.get_value().clone(),
73 );
74 side_effect_stmts.push(assignment.into());
75 }
76 }
77 LuaValue::Unknown => {
78 let assignment = AssignStatement::from_variable(
79 IndexExpression::new(
80 Identifier::new(self.table_identifier.as_str()),
81 index_entry.get_key().clone(),
82 ),
83 index_entry.get_value().clone(),
84 );
85 side_effect_stmts.push(assignment.into());
86 }
87 _ => (),
88 }
89 }
90 TableEntry::Value(_) => {
91 numeral_table.insert(num_index, i);
92 num_index += 1;
93 }
94 TableEntry::Field(field_entry) => {
95 let key = field_entry.get_field().get_name();
96 str_table.insert(key.to_owned(), i);
97 }
98 }
99 }
100
101 let mut keys: Vec<_> = numeral_table.keys().collect();
102 keys.sort();
103 let mut new_entries: Vec<TableEntry> = Vec::new();
104
105 for i in keys {
106 let v = numeral_table[i];
107 let entry = &entries[v];
108 let new_entry = match entry {
109 TableEntry::Index(index_entry) => {
110 if *i <= num_index {
111 Some(TableEntry::Value(index_entry.get_value().clone()))
112 } else {
113 Some(TableEntry::Index(index_entry.clone()))
114 }
115 }
116 TableEntry::Value(exp) => Some(TableEntry::Value(exp.clone())),
117 _ => None,
118 };
119 if let Some(new_entry) = new_entry {
120 new_entries.push(new_entry);
121 }
122 }
123
124 for (_, v) in str_table {
125 let entry = &entries[v];
126 new_entries.push(entry.clone());
127 }
128
129 entries.clear();
130 for ent in new_entries {
131 entries.push(ent);
132 }
133
134 if !side_effect_stmts.is_empty() {
135 let var = Identifier::new(self.table_identifier.as_str());
136 let table_stmt = TableExpression::new(entries.clone());
137 self.skip(true);
138 let local_assign_stmt =
139 LocalAssignStatement::new(vec![var.clone().into()], vec![table_stmt.into()]);
140 side_effect_stmts.insert(0, local_assign_stmt.into());
141 let return_stmt = ReturnStatement::one(var);
142 let func_block = Block::new(side_effect_stmts, Some(return_stmt.into()));
143 let func = Expression::Function(FunctionExpression::from_block(func_block));
144 let parenthese_func = ParentheseExpression::new(func);
145 let func_call = FunctionCall::from_prefix(parenthese_func);
146 let call_exp = Expression::Call(Box::new(func_call));
147 *exp = call_exp;
148 }
149 }
150 }
151}
152
153pub const REMOVE_REDECLARED_KEYS_RULE_NAME: &str = "remove_redeclared_keys";
154
155#[derive(Debug, PartialEq, Eq)]
157pub struct RemoveRedeclaredKeys {
158 runtime_identifier_format: String,
159}
160
161impl Default for RemoveRedeclaredKeys {
162 fn default() -> Self {
163 Self {
164 runtime_identifier_format: "_DARKLUA_REMOVE_REDECLARED_KEYS_{name}{hash}".to_string(),
165 }
166 }
167}
168
169impl Rule for RemoveRedeclaredKeys {
170 fn process(&self, block: &mut Block, _: &Context) -> RuleProcessResult {
171 let var_builder = RuntimeIdentifierBuilder::new(
172 self.runtime_identifier_format.as_str(),
173 format!("{block:?}").as_bytes(),
174 None,
175 )?;
176 let mut processor = Processor {
177 evaluator: Evaluator::default(),
178 table_identifier: var_builder.build("tbl")?,
179 skip_next_table_exp: false,
180 };
181 DefaultVisitor::visit_block(block, &mut processor);
182 Ok(())
183 }
184}
185
186impl RuleConfiguration for RemoveRedeclaredKeys {
187 fn configure(&mut self, properties: RuleProperties) -> Result<(), RuleConfigurationError> {
188 for (key, value) in properties {
189 match key.as_str() {
190 "runtime_identifier_format" => {
191 self.runtime_identifier_format = value.expect_string(&key)?;
192 }
193 _ => return Err(RuleConfigurationError::UnexpectedProperty(key)),
194 }
195 }
196
197 Ok(())
198 }
199
200 fn get_name(&self) -> &'static str {
201 REMOVE_REDECLARED_KEYS_RULE_NAME
202 }
203
204 fn serialize_to_properties(&self) -> RuleProperties {
205 RuleProperties::new()
206 }
207}
208
209#[cfg(test)]
210mod test {
211 use super::*;
212 use crate::rules::Rule;
213
214 use insta::assert_json_snapshot;
215
216 fn new_rule() -> RemoveRedeclaredKeys {
217 RemoveRedeclaredKeys::default()
218 }
219
220 #[test]
221 fn serialize_default_rule() {
222 let rule: Box<dyn Rule> = Box::new(new_rule());
223
224 assert_json_snapshot!("default_remove_redeclared_keys", rule);
225 }
226
227 #[test]
228 fn configure_with_extra_field_error() {
229 let result = json5::from_str::<Box<dyn Rule>>(
230 r#"{
231 rule: 'remove_redeclared_keys',
232 runtime_identifier_format: '{name}',
233 prop: "something",
234 }"#,
235 );
236 pretty_assertions::assert_eq!(result.unwrap_err().to_string(), "unexpected field 'prop'");
237 }
238}