darklua_core/rules/
unused_if_branch.rs

1use crate::nodes::{Block, DoStatement, Expression, IfExpression, IfStatement, Statement};
2use crate::process::{DefaultVisitor, Evaluator, NodeProcessor, NodeVisitor};
3use crate::rules::{
4    Context, FlawlessRule, RuleConfiguration, RuleConfigurationError, RuleProperties,
5};
6
7use super::verify_no_rule_properties;
8
9enum FilterResult {
10    Keep,
11    Remove,
12    Replace(Box<Statement>),
13}
14
15#[derive(Debug, Clone, Default)]
16struct IfFilter {
17    evaluator: Evaluator,
18}
19
20impl IfFilter {
21    fn simplify_if_statement(&self, if_statement: &mut IfStatement) -> FilterResult {
22        if let Some(else_block) = if_statement.get_else_block() {
23            if else_block.is_empty() {
24                if_statement.take_else_block();
25            }
26        }
27
28        let mut keep_next_branches = true;
29        let mut replace_else_with = None;
30
31        let is_empty = if_statement.retain_branches_mut(|branch| {
32            keep_next_branches && {
33                let branch_condition_value = self.evaluator.evaluate(branch.get_condition());
34                match branch_condition_value.is_truthy() {
35                    Some(true) => {
36                        keep_next_branches = false;
37
38                        if self.evaluator.has_side_effects(branch.get_condition()) {
39                            true
40                        } else {
41                            replace_else_with = Some(branch.take_block());
42                            false
43                        }
44                    }
45                    Some(false) => {
46                        if self.evaluator.has_side_effects(branch.get_condition()) {
47                            branch.take_block();
48                            true
49                        } else {
50                            false
51                        }
52                    }
53                    None => true,
54                }
55            }
56        });
57
58        if is_empty {
59            if let Some(block_replacer) = replace_else_with {
60                if block_replacer.is_empty() {
61                    FilterResult::Remove
62                } else {
63                    FilterResult::Replace(Box::new(DoStatement::new(block_replacer).into()))
64                }
65            } else if let Some(else_block) = if_statement.take_else_block() {
66                if else_block.is_empty() {
67                    FilterResult::Remove
68                } else {
69                    FilterResult::Replace(Box::new(DoStatement::new(else_block).into()))
70                }
71            } else {
72                FilterResult::Remove
73            }
74        } else {
75            if !keep_next_branches {
76                if let Some(block_replacer) = replace_else_with {
77                    if_statement.set_else_block(block_replacer);
78                } else {
79                    if_statement.take_else_block();
80                }
81            }
82            FilterResult::Keep
83        }
84    }
85
86    fn simplify_if(&self, if_expression: &mut IfExpression) -> Option<Expression> {
87        let condition_value = self.evaluator.evaluate(if_expression.get_condition());
88        match condition_value.is_truthy() {
89            Some(true) => {
90                if self
91                    .evaluator
92                    .has_side_effects(if_expression.get_condition())
93                {
94                    if_expression.clear_elseif_branches();
95                    *if_expression.mutate_else_result() = Self::result_placeholder();
96                    None
97                } else {
98                    let result = if_expression.get_result().clone();
99
100                    Some(if self.evaluator.can_return_multiple_values(&result) {
101                        result.in_parentheses()
102                    } else {
103                        result
104                    })
105                }
106            }
107            Some(false) => {
108                if self
109                    .evaluator
110                    .has_side_effects(if_expression.get_condition())
111                {
112                    *if_expression.mutate_result() = Self::result_placeholder();
113                    None
114                } else if let Some(branch) = if_expression.remove_branch(0) {
115                    let (new_condition, new_result) = branch.into_expressions();
116                    *if_expression.mutate_condition() = new_condition;
117                    *if_expression.mutate_result() = new_result;
118                    self.simplify_if(if_expression)
119                } else {
120                    let result = if_expression.get_else_result().clone();
121
122                    Some(if self.evaluator.can_return_multiple_values(&result) {
123                        result.in_parentheses()
124                    } else {
125                        result
126                    })
127                }
128            }
129            None => {
130                let mut keep_next_branches = true;
131                let mut replace_else_with = None;
132
133                if_expression.retain_elseif_branches_mut(|branch| {
134                    keep_next_branches && {
135                        let branch_condition_value =
136                            self.evaluator.evaluate(branch.get_condition());
137                        match branch_condition_value.is_truthy() {
138                            Some(true) => {
139                                keep_next_branches = false;
140
141                                if self.evaluator.has_side_effects(branch.get_condition()) {
142                                    true
143                                } else {
144                                    replace_else_with = Some(branch.get_result().clone());
145                                    false
146                                }
147                            }
148                            Some(false) => {
149                                if self.evaluator.has_side_effects(branch.get_condition()) {
150                                    *branch.mutate_result() = Self::result_placeholder();
151                                    true
152                                } else {
153                                    false
154                                }
155                            }
156                            None => true,
157                        }
158                    }
159                });
160
161                if !keep_next_branches {
162                    *if_expression.mutate_else_result() =
163                        replace_else_with.unwrap_or_else(Self::result_placeholder);
164                }
165                None
166            }
167        }
168    }
169
170    fn result_placeholder() -> Expression {
171        Expression::nil()
172    }
173}
174
175impl NodeProcessor for IfFilter {
176    fn process_block(&mut self, block: &mut Block) {
177        block.filter_mut_statements(|statement| {
178            if let Statement::If(if_statement) = statement {
179                match self.simplify_if_statement(if_statement) {
180                    FilterResult::Keep => true,
181                    FilterResult::Remove => false,
182                    FilterResult::Replace(new_statement) => {
183                        *statement = *new_statement;
184                        true
185                    }
186                }
187            } else {
188                true
189            }
190        });
191    }
192
193    fn process_expression(&mut self, expression: &mut Expression) {
194        if let Expression::If(if_expression) = expression {
195            if let Some(replace_with) = self.simplify_if(if_expression) {
196                *expression = replace_with;
197            }
198        }
199    }
200}
201
202pub const REMOVE_UNUSED_IF_BRANCH_RULE_NAME: &str = "remove_unused_if_branch";
203
204/// A rule that removes unused if branches. It can also turn a if statement into a do block
205/// statement.
206#[derive(Debug, Default, PartialEq, Eq)]
207pub struct RemoveUnusedIfBranch {}
208
209impl FlawlessRule for RemoveUnusedIfBranch {
210    fn flawless_process(&self, block: &mut Block, _: &Context) {
211        let mut processor = IfFilter::default();
212        DefaultVisitor::visit_block(block, &mut processor);
213    }
214}
215
216impl RuleConfiguration for RemoveUnusedIfBranch {
217    fn configure(&mut self, properties: RuleProperties) -> Result<(), RuleConfigurationError> {
218        verify_no_rule_properties(&properties)?;
219
220        Ok(())
221    }
222
223    fn get_name(&self) -> &'static str {
224        REMOVE_UNUSED_IF_BRANCH_RULE_NAME
225    }
226
227    fn serialize_to_properties(&self) -> RuleProperties {
228        RuleProperties::new()
229    }
230}
231
232#[cfg(test)]
233mod test {
234    use super::*;
235    use crate::rules::Rule;
236
237    use insta::assert_json_snapshot;
238
239    fn new_rule() -> RemoveUnusedIfBranch {
240        RemoveUnusedIfBranch::default()
241    }
242
243    #[test]
244    fn serialize_default_rule() {
245        let rule: Box<dyn Rule> = Box::new(new_rule());
246
247        assert_json_snapshot!("default_remove_unused_if_branch", rule);
248    }
249
250    #[test]
251    fn configure_with_extra_field_error() {
252        let result = json5::from_str::<Box<dyn Rule>>(
253            r#"{
254            rule: 'remove_unused_if_branch',
255            prop: "something",
256        }"#,
257        );
258        pretty_assertions::assert_eq!(result.unwrap_err().to_string(), "unexpected field 'prop'");
259    }
260}