darklua_core/rules/
remove_assertions.rs

1use std::collections::HashMap;
2use std::iter::{self, FromIterator};
3
4use crate::nodes::{Block, Expression, FunctionCall, Prefix, TupleArguments};
5use crate::process::{IdentifierTracker, NodeVisitor, ScopeVisitor};
6use crate::rules::{
7    Context, FlawlessRule, RuleConfiguration, RuleConfigurationError, RuleProperties,
8};
9
10use super::remove_call_match::{CallMatch, RemoveFunctionCallProcessor};
11
12const ASSERT_FUNCTION_NAME: &str = "assert";
13
14pub const REMOVE_ASSERTIONS_RULE_NAME: &str = "remove_assertions";
15
16/// A rule that removes `assert` calls.
17#[derive(Debug, PartialEq, Eq)]
18pub struct RemoveAssertions {
19    preserve_args_side_effects: bool,
20}
21
22impl Default for RemoveAssertions {
23    fn default() -> Self {
24        Self {
25            preserve_args_side_effects: true,
26        }
27    }
28}
29
30struct AssertMatcher;
31
32impl CallMatch<()> for AssertMatcher {
33    fn matches(&self, identifiers: &IdentifierTracker, prefix: &Prefix) -> bool {
34        if identifiers.is_identifier_used(ASSERT_FUNCTION_NAME) {
35            return false;
36        }
37
38        match prefix {
39            Prefix::Identifier(identifier) => identifier.get_name() == ASSERT_FUNCTION_NAME,
40            _ => false,
41        }
42    }
43
44    fn compute_result(
45        &self,
46        call: &FunctionCall,
47        mappings: &HashMap<&'static str, String>,
48    ) -> Option<Expression> {
49        let expressions = call.get_arguments().clone().to_expressions();
50
51        Some(match expressions.len() {
52            0 => Expression::nil(),
53            1 => expressions
54                .into_iter()
55                .next()
56                .expect("at least one expression is expected"),
57            _ => FunctionCall::from_name(
58                mappings
59                    .get("select")
60                    .cloned()
61                    .unwrap_or_else(|| "select".to_owned()),
62            )
63            .with_arguments(TupleArguments::from_iter(
64                iter::once(Expression::from(1)).chain(expressions),
65            ))
66            .into(),
67        })
68    }
69
70    fn reserve_globals(&self) -> impl Iterator<Item = &'static str> {
71        iter::once("select")
72    }
73}
74
75impl FlawlessRule for RemoveAssertions {
76    fn flawless_process(&self, block: &mut Block, _: &Context) {
77        let mut processor =
78            RemoveFunctionCallProcessor::new(self.preserve_args_side_effects, AssertMatcher);
79        ScopeVisitor::visit_block(block, &mut processor);
80
81        if let Some(statement) = processor.extract_reserved_globals() {
82            block.insert_statement(0, statement);
83        }
84    }
85}
86
87impl RuleConfiguration for RemoveAssertions {
88    fn configure(&mut self, properties: RuleProperties) -> Result<(), RuleConfigurationError> {
89        for (key, value) in properties {
90            match key.as_str() {
91                "preserve_arguments_side_effects" => {
92                    self.preserve_args_side_effects = value.expect_bool(&key)?;
93                }
94                _ => return Err(RuleConfigurationError::UnexpectedProperty(key)),
95            }
96        }
97
98        Ok(())
99    }
100
101    fn get_name(&self) -> &'static str {
102        REMOVE_ASSERTIONS_RULE_NAME
103    }
104
105    fn serialize_to_properties(&self) -> RuleProperties {
106        let mut properties = RuleProperties::new();
107
108        if !self.preserve_args_side_effects {
109            properties.insert("preserve_arguments_side_effects".to_owned(), false.into());
110        }
111
112        properties
113    }
114}
115
116#[cfg(test)]
117mod test {
118    use super::*;
119    use crate::rules::Rule;
120
121    use insta::assert_json_snapshot;
122
123    fn new_rule() -> RemoveAssertions {
124        RemoveAssertions::default()
125    }
126
127    #[test]
128    fn serialize_default_rule() {
129        let rule: Box<dyn Rule> = Box::new(new_rule());
130
131        assert_json_snapshot!("default_remove_assertions", rule);
132    }
133
134    #[test]
135    fn serialize_rule_without_side_effects() {
136        let rule: Box<dyn Rule> = Box::new(RemoveAssertions {
137            preserve_args_side_effects: false,
138        });
139
140        assert_json_snapshot!("remove_assertions_without_side_effects", rule);
141    }
142
143    #[test]
144    fn configure_with_extra_field_error() {
145        let result = json5::from_str::<Box<dyn Rule>>(
146            r#"{
147            rule: 'remove_assertions',
148            prop: "something",
149        }"#,
150        );
151        pretty_assertions::assert_eq!(result.unwrap_err().to_string(), "unexpected field 'prop'");
152    }
153}