darklua_core/rules/
remove_attribute.rs1use 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#[derive(Debug, Default)]
82pub struct RemoveAttribute {
83 metadata: RuleMetadata,
84 r#match: Vec<Regex>,
85}
86
87impl RemoveAttribute {
88 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}