Skip to main content

darklua_core/rules/
remove_attribute.rs

1use regex::Regex;
2
3use crate::nodes::*;
4use crate::process::{DefaultVisitor, NodeProcessor, NodeVisitor};
5use crate::rules::{
6    Context, FlawlessRule, RuleConfiguration, RuleConfigurationError, RuleMetadata, RuleProperties,
7};
8
9pub const REMOVE_ATTRIBUTE_RULE_NAME: &str = "remove_attribute";
10
11#[derive(Debug, Default)]
12struct RemoveAttributeProcessor;
13
14impl NodeProcessor for RemoveAttributeProcessor {
15    fn process_function_statement(&mut self, function: &mut FunctionStatement) {
16        function.mutate_attributes().clear_attributes();
17    }
18
19    fn process_local_function_statement(&mut self, function: &mut LocalFunctionStatement) {
20        function.mutate_attributes().clear_attributes();
21    }
22
23    fn process_function_expression(&mut self, function: &mut FunctionExpression) {
24        function.mutate_attributes().clear_attributes();
25    }
26}
27
28struct FilterAttributeProcessor<'a> {
29    match_patterns: &'a Vec<Regex>,
30}
31
32impl<'a> FilterAttributeProcessor<'a> {
33    pub fn new(match_patterns: &'a Vec<Regex>) -> Self {
34        Self { match_patterns }
35    }
36
37    fn should_remove_name(&self, attribute_name: &str) -> bool {
38        self.match_patterns
39            .iter()
40            .any(|pattern| pattern.is_match(attribute_name))
41    }
42
43    fn should_remove(&self, attribute: &mut Attribute) -> bool {
44        match attribute {
45            Attribute::Name(named) => self.should_remove_name(named.get_identifier().get_name()),
46            Attribute::Group(group) => {
47                group.filter_attributes(|element| {
48                    self.should_remove_name(element.name().get_name())
49                });
50                !group.is_empty()
51            }
52        }
53    }
54}
55
56impl<'a> NodeProcessor for FilterAttributeProcessor<'a> {
57    fn process_function_statement(&mut self, function: &mut FunctionStatement) {
58        function
59            .mutate_attributes()
60            .filter_mut_attributes(|attribute| !self.should_remove(attribute));
61    }
62
63    fn process_local_function_statement(&mut self, function: &mut LocalFunctionStatement) {
64        function
65            .mutate_attributes()
66            .filter_mut_attributes(|attribute| !self.should_remove(attribute));
67    }
68
69    fn process_function_expression(&mut self, function: &mut FunctionExpression) {
70        function
71            .mutate_attributes()
72            .filter_mut_attributes(|attribute| !self.should_remove(attribute));
73    }
74}
75
76/// A rule that removes function attributes.
77///
78/// When configured with a `match` parameter containing regex patterns,
79/// only attributes whose names match the patterns are removed.
80/// When `match` is empty (default), all attributes are removed.
81#[derive(Debug, Default)]
82pub struct RemoveAttribute {
83    metadata: RuleMetadata,
84    r#match: Vec<Regex>,
85}
86
87impl RemoveAttribute {
88    /// Adds a regex pattern to match against attribute names.
89    pub fn with_match(mut self, match_pattern: &str) -> Self {
90        match Regex::new(match_pattern) {
91            Ok(regex) => {
92                self.r#match.push(regex);
93            }
94            Err(err) => {
95                log::warn!(
96                    "unable to compile regex pattern `{}`: {}",
97                    match_pattern,
98                    err
99                );
100            }
101        }
102        self
103    }
104}
105
106impl FlawlessRule for RemoveAttribute {
107    fn flawless_process(&self, block: &mut Block, _: &Context) {
108        if self.r#match.is_empty() {
109            let mut processor = RemoveAttributeProcessor;
110            DefaultVisitor::visit_block(block, &mut processor);
111        } else {
112            let mut processor = FilterAttributeProcessor::new(&self.r#match);
113            DefaultVisitor::visit_block(block, &mut processor);
114        }
115    }
116}
117
118impl RuleConfiguration for RemoveAttribute {
119    fn configure(&mut self, properties: RuleProperties) -> Result<(), RuleConfigurationError> {
120        for (key, value) in properties {
121            match key.as_str() {
122                "match" => {
123                    self.r#match = value.expect_regex_list(&key)?;
124                }
125                _ => return Err(RuleConfigurationError::UnexpectedProperty(key)),
126            }
127        }
128        Ok(())
129    }
130
131    fn get_name(&self) -> &'static str {
132        REMOVE_ATTRIBUTE_RULE_NAME
133    }
134
135    fn serialize_to_properties(&self) -> RuleProperties {
136        RuleProperties::new()
137    }
138
139    fn set_metadata(&mut self, metadata: RuleMetadata) {
140        self.metadata = metadata;
141    }
142
143    fn metadata(&self) -> &RuleMetadata {
144        &self.metadata
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use super::*;
151    use crate::rules::Rule;
152
153    fn new_rule() -> RemoveAttribute {
154        RemoveAttribute::default()
155    }
156
157    #[test]
158    fn test_serialize_default_rule() {
159        let rule: Box<dyn Rule> = Box::new(new_rule());
160
161        insta::assert_json_snapshot!(rule, @r#""remove_attribute""#);
162    }
163
164    #[test]
165    fn test_configure_with_extra_field_error() {
166        let result = json5::from_str::<Box<dyn Rule>>(
167            r#"{
168                rule: 'remove_attribute',
169                unexpected: true,
170            }"#,
171        );
172
173        insta::assert_snapshot!(result.unwrap_err(), @"unexpected field 'unexpected' at line 1 column 1");
174    }
175
176    #[test]
177    fn test_configure_with_invalid_regex_error() {
178        let result = json5::from_str::<Box<dyn Rule>>(
179            r#"{
180                rule: 'remove_attribute',
181                match: ['[invalid'],
182            }"#,
183        );
184
185        insta::assert_snapshot!(result.unwrap_err(), @r###"
186        unexpected value for field 'match': invalid regex provided `[invalid`
187          regex parse error:
188            [invalid
189            ^
190        error: unclosed character class at line 1 column 1
191        "###);
192    }
193}