Skip to main content

darklua_core/rules/
remove_compound_assign.rs

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/// A rule that converts compound assignment (like `+=`) into regular assignments.
307#[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}