darklua_core/rules/
remove_assertions.rs1use 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#[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}