1use std::ops::{Deref, DerefMut};
2
3use crate::nodes::{
4 AssignStatement, BinaryExpression, Block, CompoundAssignStatement, DoStatement, Expression,
5 FieldExpression, IndexExpression, Prefix, Statement, Variable, VariableAssignment,
6};
7use crate::process::{DefaultVisitor, IdentifierTracker, NodeProcessor, NodeVisitor, ScopeVisitor};
8use crate::rules::{
9 Context, FlawlessRule, RuleConfiguration, RuleConfigurationError, RuleMetadata, RuleProperties,
10};
11
12use super::{verify_no_rule_properties, RemoveCommentProcessor, RemoveWhitespacesProcessor};
13
14struct Processor {
15 identifier_tracker: IdentifierTracker,
16 remove_comments: RemoveCommentProcessor,
17 remove_spaces: RemoveWhitespacesProcessor,
18}
19
20impl Processor {
21 #[inline]
22 fn generate_variable(&mut self) -> String {
23 self.identifier_tracker
24 .generate_identifier_with_prefix("__DARKLUA_VAR")
25 }
26
27 fn simplify_prefix(&self, prefix: &Prefix) -> Option<Prefix> {
28 match prefix {
29 Prefix::Parenthese(parenthese) => {
30 if let Expression::Identifier(identifier) = parenthese.inner_expression() {
31 Some(Prefix::from(identifier.clone()))
32 } else {
33 None
34 }
35 }
36 Prefix::Identifier(_)
37 | Prefix::Call(_)
38 | Prefix::Field(_)
39 | Prefix::Index(_)
40 | Prefix::TypeInstantiation(_) => None,
41 }
42 }
43
44 fn remove_parentheses(&self, expression: impl Into<Expression>) -> Expression {
45 let expression = expression.into();
46 if let Expression::Parenthese(parenthese) = expression {
47 parenthese.into_inner_expression()
48 } else {
49 expression
50 }
51 }
52
53 fn replace_with(&mut self, assignment: &CompoundAssignStatement) -> Option<Statement> {
54 match assignment.get_variable() {
55 Variable::Index(index) => {
56 let prefix_assignment = match index.get_prefix() {
57 Prefix::Identifier(_) => None,
58 Prefix::Parenthese(parenthese)
59 if matches!(
60 parenthese.inner_expression(),
61 Expression::False(_)
62 | Expression::Identifier(_)
63 | Expression::Number(_)
64 | Expression::Nil(_)
65 | Expression::String(_)
66 | Expression::True(_)
67 | Expression::VariableArguments(_)
68 ) =>
69 {
70 None
71 }
72 Prefix::Call(_)
73 | Prefix::Field(_)
74 | Prefix::Index(_)
75 | Prefix::Parenthese(_)
76 | Prefix::TypeInstantiation(_) => Some(self.generate_variable()),
77 };
78 let index_assignment = match index.get_index() {
79 Expression::False(_)
80 | Expression::Identifier(_)
81 | Expression::Number(_)
82 | Expression::Nil(_)
83 | Expression::InterpolatedString(_)
84 | Expression::String(_)
85 | Expression::True(_)
86 | Expression::VariableArguments(_) => None,
87 Expression::Parenthese(parenthese)
88 if matches!(
89 parenthese.inner_expression(),
90 Expression::False(_)
91 | Expression::Identifier(_)
92 | Expression::Number(_)
93 | Expression::Nil(_)
94 | Expression::String(_)
95 | Expression::True(_)
96 | Expression::VariableArguments(_)
97 ) =>
98 {
99 None
100 }
101 Expression::Binary(_)
102 | Expression::Call(_)
103 | Expression::Field(_)
104 | Expression::Function(_)
105 | Expression::If(_)
106 | Expression::Index(_)
107 | Expression::Parenthese(_)
108 | Expression::Table(_)
109 | Expression::TypeCast(_)
110 | Expression::TypeInstantiation(_)
111 | Expression::Unary(_) => Some(self.generate_variable()),
112 };
113
114 match (prefix_assignment, index_assignment) {
115 (None, None) => {
116 let variable = IndexExpression::new(
117 self.simplify_prefix(index.get_prefix())
118 .unwrap_or_else(|| index.get_prefix().clone()),
119 self.remove_parentheses(index.get_index().clone()),
120 );
121
122 Some(self.create_new_assignment_with_variable(
123 assignment,
124 variable.clone().into(),
125 Some(variable.into()),
126 ))
127 }
128 (None, Some(index_variable)) => {
129 let assign = VariableAssignment::from_variable(index_variable.clone())
130 .with_value(self.remove_parentheses(index.get_index().clone()));
131 let variable = IndexExpression::new(
132 self.simplify_prefix(index.get_prefix())
133 .unwrap_or_else(|| index.get_prefix().clone()),
134 Expression::identifier(index_variable),
135 );
136 Some(self.create_do_assignment(assignment, assign, variable))
137 }
138 (Some(prefix_variable), None) => {
139 let assign = VariableAssignment::from_variable(prefix_variable.clone())
140 .with_value(self.remove_parentheses(index.get_prefix().clone()));
141 let variable = IndexExpression::new(
142 Prefix::from_name(prefix_variable),
143 index.get_index().clone(),
144 );
145
146 Some(self.create_do_assignment(assignment, assign, variable))
147 }
148 (Some(prefix_variable), Some(index_variable)) => {
149 let assign = VariableAssignment::from_variable(prefix_variable.clone())
150 .with_value(self.remove_parentheses(index.get_prefix().clone()))
151 .with_variable(index_variable.clone())
152 .with_value(self.remove_parentheses(index.get_index().clone()));
153 let variable = IndexExpression::new(
154 Prefix::from_name(prefix_variable),
155 Expression::identifier(index_variable),
156 );
157 Some(self.create_do_assignment(assignment, assign, variable))
158 }
159 }
160 }
161 Variable::Field(field) => match field.get_prefix() {
162 Prefix::Identifier(_) => None,
163 Prefix::Parenthese(parenthese)
164 if matches!(
165 parenthese.inner_expression(),
166 Expression::False(_)
167 | Expression::Identifier(_)
168 | Expression::Number(_)
169 | Expression::Nil(_)
170 | Expression::String(_)
171 | Expression::True(_)
172 | Expression::VariableArguments(_)
173 ) =>
174 {
175 let new_prefix =
176 if let Expression::Identifier(identifier) = parenthese.inner_expression() {
177 Prefix::from(identifier.clone())
178 } else {
179 (*parenthese.clone()).into()
180 };
181 let new_variable = FieldExpression::new(new_prefix, field.get_field().clone());
182
183 Some(self.create_new_assignment_with_variable(
184 assignment,
185 new_variable.clone().into(),
186 Some(new_variable.into()),
187 ))
188 }
189 Prefix::Call(_)
190 | Prefix::Field(_)
191 | Prefix::Index(_)
192 | Prefix::Parenthese(_)
193 | Prefix::TypeInstantiation(_) => {
194 let identifier = self.generate_variable();
195 let new_variable = FieldExpression::new(
196 Prefix::from_name(&identifier),
197 field.get_field().clone(),
198 );
199
200 let assign = VariableAssignment::from_variable(identifier).with_value(
201 match field.get_prefix().clone() {
202 Prefix::Parenthese(parenthese) => parenthese.into_inner_expression(),
203 prefix => prefix.into(),
204 },
205 );
206
207 Some(self.create_do_assignment(assignment, assign, new_variable))
208 }
209 },
210 Variable::Identifier(_) => None,
211 }
212 }
213
214 fn create_do_assignment(
215 &mut self,
216 compound_assignment: &CompoundAssignStatement,
217 assign: impl Into<Statement>,
218 variable: impl Into<Variable>,
219 ) -> Statement {
220 let variable = variable.into();
221 DoStatement::new(
222 Block::default()
223 .with_statement(assign.into())
224 .with_statement(self.create_new_assignment_with_variable(
225 compound_assignment,
226 variable.clone().into(),
227 Some(variable),
228 )),
229 )
230 .into()
231 }
232
233 fn create_new_assignment(
234 &mut self,
235 assignment: &CompoundAssignStatement,
236 variable: impl Into<Expression>,
237 ) -> Statement {
238 self.create_new_assignment_with_variable(assignment, variable.into(), None)
239 }
240
241 fn create_new_assignment_with_variable(
242 &mut self,
243 assignment: &CompoundAssignStatement,
244 mut value: Expression,
245 variable: Option<Variable>,
246 ) -> Statement {
247 let operator = assignment.get_operator().to_binary_operator();
248
249 DefaultVisitor::visit_expression(&mut value, &mut self.remove_comments);
250 DefaultVisitor::visit_expression(&mut value, &mut self.remove_spaces);
251
252 let mut expression = BinaryExpression::new(operator, value, assignment.get_value().clone());
253 if let Some(token) = assignment.get_tokens().map(|tokens| {
254 let mut new_token = tokens.operator.clone();
255 new_token.replace_with_content(operator.to_str());
256 new_token
257 }) {
258 expression.set_token(token);
259 }
260
261 AssignStatement::from_variable(
262 variable.unwrap_or_else(|| assignment.get_variable().clone()),
263 expression,
264 )
265 .into()
266 }
267}
268
269impl Default for Processor {
270 fn default() -> Self {
271 Self {
272 identifier_tracker: IdentifierTracker::new(),
273 remove_comments: RemoveCommentProcessor::default(),
274 remove_spaces: RemoveWhitespacesProcessor::default(),
275 }
276 }
277}
278
279impl Deref for Processor {
280 type Target = IdentifierTracker;
281
282 fn deref(&self) -> &Self::Target {
283 &self.identifier_tracker
284 }
285}
286
287impl DerefMut for Processor {
288 fn deref_mut(&mut self) -> &mut Self::Target {
289 &mut self.identifier_tracker
290 }
291}
292
293impl NodeProcessor for Processor {
294 fn process_statement(&mut self, statement: &mut Statement) {
295 if let Statement::CompoundAssign(assignment) = statement {
296 let variable = assignment.get_variable();
297 *statement = self
298 .replace_with(assignment)
299 .unwrap_or_else(|| self.create_new_assignment(assignment, variable.clone()));
300 }
301 }
302}
303
304pub const REMOVE_COMPOUND_ASSIGNMENT_RULE_NAME: &str = "remove_compound_assignment";
305
306#[derive(Debug, Default, PartialEq, Eq)]
308pub struct RemoveCompoundAssignment {
309 metadata: RuleMetadata,
310}
311
312impl RemoveCompoundAssignment {
313 pub(crate) fn replace_compound_assignment(&self, statement: &mut Statement) {
314 let mut processor = Processor::default();
315 ScopeVisitor::visit_statement(statement, &mut processor);
316 }
317}
318
319impl FlawlessRule for RemoveCompoundAssignment {
320 fn flawless_process(&self, block: &mut Block, _: &Context) {
321 let mut processor = Processor::default();
322 ScopeVisitor::visit_block(block, &mut processor);
323 }
324}
325
326impl RuleConfiguration for RemoveCompoundAssignment {
327 fn configure(&mut self, properties: RuleProperties) -> Result<(), RuleConfigurationError> {
328 verify_no_rule_properties(&properties)?;
329
330 Ok(())
331 }
332
333 fn get_name(&self) -> &'static str {
334 REMOVE_COMPOUND_ASSIGNMENT_RULE_NAME
335 }
336
337 fn serialize_to_properties(&self) -> RuleProperties {
338 RuleProperties::new()
339 }
340
341 fn set_metadata(&mut self, metadata: RuleMetadata) {
342 self.metadata = metadata;
343 }
344
345 fn metadata(&self) -> &RuleMetadata {
346 &self.metadata
347 }
348}
349
350#[cfg(test)]
351mod test {
352 use super::*;
353 use crate::rules::Rule;
354
355 use insta::assert_json_snapshot;
356
357 fn new_rule() -> RemoveCompoundAssignment {
358 RemoveCompoundAssignment::default()
359 }
360
361 #[test]
362 fn serialize_default_rule() {
363 let rule: Box<dyn Rule> = Box::new(new_rule());
364
365 assert_json_snapshot!(rule, @r###""remove_compound_assignment""###);
366 }
367
368 #[test]
369 fn configure_with_extra_field_error() {
370 let result = json5::from_str::<Box<dyn Rule>>(
371 r#"{
372 rule: 'remove_compound_assignment',
373 prop: "something",
374 }"#,
375 );
376 insta::assert_snapshot!(result.unwrap_err().to_string(), @"unexpected field 'prop' at line 1 column 1");
377 }
378}