Skip to main content

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