Skip to main content

darklua_core/rules/
remove_comments.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
9#[derive(Debug, Default)]
10pub(crate) struct RemoveCommentProcessor {}
11
12impl NodeProcessor for RemoveCommentProcessor {
13    fn process_block(&mut self, block: &mut Block) {
14        block.clear_comments();
15    }
16
17    fn process_function_call(&mut self, call: &mut FunctionCall) {
18        call.clear_comments();
19        call.mutate_arguments().clear_comments();
20    }
21
22    fn process_assign_statement(&mut self, assign: &mut AssignStatement) {
23        assign.clear_comments();
24    }
25
26    fn process_compound_assign_statement(&mut self, assign: &mut CompoundAssignStatement) {
27        assign.clear_comments();
28    }
29
30    fn process_do_statement(&mut self, statement: &mut DoStatement) {
31        statement.clear_comments();
32    }
33
34    fn process_function_statement(&mut self, function: &mut FunctionStatement) {
35        function.clear_comments();
36    }
37
38    fn process_generic_for_statement(&mut self, generic_for: &mut GenericForStatement) {
39        generic_for.clear_comments();
40    }
41
42    fn process_if_statement(&mut self, if_statement: &mut IfStatement) {
43        if_statement.clear_comments();
44    }
45
46    fn process_last_statement(&mut self, statement: &mut LastStatement) {
47        match statement {
48            LastStatement::Break(token) | LastStatement::Continue(token) => {
49                if let Some(token) = token {
50                    token.clear_comments();
51                }
52            }
53            LastStatement::Return(statement) => statement.clear_comments(),
54        }
55    }
56
57    fn process_local_assign_statement(&mut self, assign: &mut VariableAssignment) {
58        assign.clear_comments();
59    }
60
61    fn process_local_function_statement(&mut self, function: &mut FunctionAssignment) {
62        function.clear_comments();
63    }
64
65    fn process_numeric_for_statement(&mut self, numeric_for: &mut NumericForStatement) {
66        numeric_for.clear_comments();
67    }
68
69    fn process_repeat_statement(&mut self, repeat: &mut RepeatStatement) {
70        repeat.clear_comments();
71    }
72
73    fn process_while_statement(&mut self, statement: &mut WhileStatement) {
74        statement.clear_comments();
75    }
76
77    fn process_type_declaration(&mut self, type_declaration: &mut TypeDeclarationStatement) {
78        type_declaration.clear_comments();
79    }
80
81    fn process_type_function(&mut self, function: &mut TypeFunctionStatement) {
82        function.clear_comments();
83    }
84
85    fn process_attributes(&mut self, attributes: &mut Attributes) {
86        attributes.clear_comments();
87    }
88
89    fn process_literal_expression(&mut self, expression: &mut LiteralExpression) {
90        match expression {
91            LiteralExpression::True(token)
92            | LiteralExpression::False(token)
93            | LiteralExpression::Nil(token) => {
94                if let Some(token) = token {
95                    token.clear_comments()
96                }
97            }
98            LiteralExpression::Number(_)
99            | LiteralExpression::String(_)
100            | LiteralExpression::Table(_) => {}
101        }
102    }
103
104    fn process_literal_table(&mut self, table: &mut LiteralTable) {
105        table.clear_comments();
106    }
107
108    fn process_expression(&mut self, expression: &mut Expression) {
109        match expression {
110            Expression::False(token)
111            | Expression::Nil(token)
112            | Expression::True(token)
113            | Expression::VariableArguments(token) => {
114                if let Some(token) = token {
115                    token.clear_comments()
116                }
117            }
118            Expression::Binary(_)
119            | Expression::Call(_)
120            | Expression::Field(_)
121            | Expression::Function(_)
122            | Expression::Identifier(_)
123            | Expression::If(_)
124            | Expression::Index(_)
125            | Expression::Number(_)
126            | Expression::Parenthese(_)
127            | Expression::String(_)
128            | Expression::InterpolatedString(_)
129            | Expression::Table(_)
130            | Expression::Unary(_)
131            | Expression::TypeCast(_)
132            | Expression::TypeInstantiation(_) => {}
133        }
134    }
135
136    fn process_binary_expression(&mut self, binary: &mut BinaryExpression) {
137        binary.clear_comments();
138    }
139
140    fn process_field_expression(&mut self, field: &mut FieldExpression) {
141        field.clear_comments();
142    }
143
144    fn process_function_expression(&mut self, function: &mut FunctionExpression) {
145        function.clear_comments();
146    }
147
148    fn process_if_expression(&mut self, if_expression: &mut IfExpression) {
149        if_expression.clear_comments();
150    }
151
152    fn process_variable_expression(&mut self, identifier: &mut Identifier) {
153        identifier.clear_comments();
154    }
155
156    fn process_index_expression(&mut self, index: &mut IndexExpression) {
157        index.clear_comments();
158    }
159
160    fn process_number_expression(&mut self, number: &mut NumberExpression) {
161        number.clear_comments();
162    }
163
164    fn process_parenthese_expression(&mut self, expression: &mut ParentheseExpression) {
165        expression.clear_comments();
166    }
167
168    fn process_string_expression(&mut self, string: &mut StringExpression) {
169        string.clear_comments();
170    }
171
172    fn process_table_expression(&mut self, table: &mut TableExpression) {
173        table.clear_comments();
174    }
175
176    fn process_unary_expression(&mut self, unary: &mut UnaryExpression) {
177        unary.clear_comments();
178    }
179
180    fn process_interpolated_string_expression(
181        &mut self,
182        string: &mut InterpolatedStringExpression,
183    ) {
184        string.clear_comments();
185    }
186
187    fn process_type_cast_expression(&mut self, type_cast: &mut TypeCastExpression) {
188        type_cast.clear_comments();
189    }
190
191    fn process_type_instantiation(&mut self, type_instantiation: &mut TypeInstantiationExpression) {
192        type_instantiation.clear_comments();
193    }
194
195    fn process_prefix_expression(&mut self, _: &mut Prefix) {}
196
197    fn process_type(&mut self, r#type: &mut Type) {
198        match r#type {
199            Type::True(token) | Type::False(token) | Type::Nil(token) => {
200                if let Some(token) = token {
201                    token.clear_comments();
202                }
203            }
204            _ => {}
205        }
206    }
207
208    fn process_type_name(&mut self, type_name: &mut TypeName) {
209        type_name.clear_comments();
210    }
211
212    fn process_type_field(&mut self, type_field: &mut TypeField) {
213        type_field.clear_comments();
214    }
215
216    fn process_string_type(&mut self, string_type: &mut StringType) {
217        string_type.clear_comments();
218    }
219
220    fn process_array_type(&mut self, array: &mut ArrayType) {
221        array.clear_comments();
222    }
223
224    fn process_table_type(&mut self, table: &mut TableType) {
225        table.clear_comments();
226    }
227
228    fn process_expression_type(&mut self, expression_type: &mut ExpressionType) {
229        expression_type.clear_comments();
230    }
231
232    fn process_parenthese_type(&mut self, parenthese_type: &mut ParentheseType) {
233        parenthese_type.clear_comments();
234    }
235
236    fn process_function_type(&mut self, function_type: &mut FunctionType) {
237        function_type.clear_comments();
238    }
239
240    fn process_optional_type(&mut self, optional: &mut OptionalType) {
241        optional.clear_comments();
242    }
243
244    fn process_intersection_type(&mut self, intersection: &mut IntersectionType) {
245        intersection.clear_comments();
246    }
247
248    fn process_union_type(&mut self, union: &mut UnionType) {
249        union.clear_comments();
250    }
251
252    fn process_type_pack(&mut self, type_pack: &mut TypePack) {
253        type_pack.clear_comments();
254    }
255
256    fn process_generic_type_pack(&mut self, generic_type_pack: &mut GenericTypePack) {
257        generic_type_pack.clear_comments();
258    }
259
260    fn process_variadic_type_pack(&mut self, variadic_type_pack: &mut VariadicTypePack) {
261        variadic_type_pack.clear_comments();
262    }
263}
264
265#[derive(Debug)]
266pub(crate) struct FilterCommentProcessor<'a> {
267    original_code: &'a str,
268    except: &'a Vec<Regex>,
269}
270
271impl<'a> FilterCommentProcessor<'a> {
272    pub(crate) fn new(original_code: &'a str, except: &'a Vec<Regex>) -> Self {
273        Self {
274            original_code,
275            except,
276        }
277    }
278
279    fn ignore_trivia(&self, trivia: &Trivia) -> bool {
280        let content = trivia.read(self.original_code);
281        self.except.iter().any(|pattern| pattern.is_match(content))
282    }
283}
284
285impl NodeProcessor for FilterCommentProcessor<'_> {
286    fn process_block(&mut self, block: &mut Block) {
287        block.filter_comments(|trivia| self.ignore_trivia(trivia));
288    }
289
290    fn process_function_call(&mut self, call: &mut FunctionCall) {
291        call.filter_comments(|trivia| self.ignore_trivia(trivia));
292        call.mutate_arguments()
293            .filter_comments(|trivia| self.ignore_trivia(trivia));
294    }
295
296    fn process_assign_statement(&mut self, assign: &mut AssignStatement) {
297        assign.filter_comments(|trivia| self.ignore_trivia(trivia));
298    }
299
300    fn process_compound_assign_statement(&mut self, assign: &mut CompoundAssignStatement) {
301        assign.filter_comments(|trivia| self.ignore_trivia(trivia));
302    }
303
304    fn process_do_statement(&mut self, statement: &mut DoStatement) {
305        statement.filter_comments(|trivia| self.ignore_trivia(trivia));
306    }
307
308    fn process_function_statement(&mut self, function: &mut FunctionStatement) {
309        function.filter_comments(|trivia| self.ignore_trivia(trivia));
310    }
311
312    fn process_generic_for_statement(&mut self, generic_for: &mut GenericForStatement) {
313        generic_for.filter_comments(|trivia| self.ignore_trivia(trivia));
314    }
315
316    fn process_if_statement(&mut self, if_statement: &mut IfStatement) {
317        if_statement.filter_comments(|trivia| self.ignore_trivia(trivia));
318    }
319
320    fn process_last_statement(&mut self, statement: &mut LastStatement) {
321        match statement {
322            LastStatement::Break(token) | LastStatement::Continue(token) => {
323                if let Some(token) = token {
324                    token.filter_comments(|trivia| self.ignore_trivia(trivia));
325                }
326            }
327            LastStatement::Return(statement) => {
328                statement.filter_comments(|trivia| self.ignore_trivia(trivia))
329            }
330        }
331    }
332
333    fn process_local_assign_statement(&mut self, assign: &mut VariableAssignment) {
334        assign.filter_comments(|trivia| self.ignore_trivia(trivia));
335    }
336
337    fn process_local_function_statement(&mut self, function: &mut FunctionAssignment) {
338        function.filter_comments(|trivia| self.ignore_trivia(trivia));
339    }
340
341    fn process_numeric_for_statement(&mut self, numeric_for: &mut NumericForStatement) {
342        numeric_for.filter_comments(|trivia| self.ignore_trivia(trivia));
343    }
344
345    fn process_repeat_statement(&mut self, repeat: &mut RepeatStatement) {
346        repeat.filter_comments(|trivia| self.ignore_trivia(trivia));
347    }
348
349    fn process_while_statement(&mut self, statement: &mut WhileStatement) {
350        statement.filter_comments(|trivia| self.ignore_trivia(trivia));
351    }
352
353    fn process_type_declaration(&mut self, type_declaration: &mut TypeDeclarationStatement) {
354        type_declaration.filter_comments(|trivia| self.ignore_trivia(trivia));
355    }
356
357    fn process_type_function(&mut self, type_function: &mut TypeFunctionStatement) {
358        type_function.filter_comments(|trivia| self.ignore_trivia(trivia));
359    }
360
361    fn process_attributes(&mut self, attributes: &mut Attributes) {
362        attributes.filter_comments(|trivia| self.ignore_trivia(trivia));
363    }
364
365    fn process_literal_expression(&mut self, expression: &mut LiteralExpression) {
366        match expression {
367            LiteralExpression::True(token)
368            | LiteralExpression::False(token)
369            | LiteralExpression::Nil(token) => {
370                if let Some(token) = token {
371                    token.filter_comments(|trivia| self.ignore_trivia(trivia))
372                }
373            }
374            LiteralExpression::Number(_)
375            | LiteralExpression::String(_)
376            | LiteralExpression::Table(_) => {}
377        }
378    }
379
380    fn process_literal_table(&mut self, table: &mut LiteralTable) {
381        table.filter_comments(|trivia| self.ignore_trivia(trivia));
382    }
383
384    fn process_expression(&mut self, expression: &mut Expression) {
385        match expression {
386            Expression::False(token)
387            | Expression::Nil(token)
388            | Expression::True(token)
389            | Expression::VariableArguments(token) => {
390                if let Some(token) = token {
391                    token.filter_comments(|trivia| self.ignore_trivia(trivia))
392                }
393            }
394            Expression::Binary(_)
395            | Expression::Call(_)
396            | Expression::Field(_)
397            | Expression::Function(_)
398            | Expression::Identifier(_)
399            | Expression::If(_)
400            | Expression::Index(_)
401            | Expression::Number(_)
402            | Expression::Parenthese(_)
403            | Expression::String(_)
404            | Expression::InterpolatedString(_)
405            | Expression::Table(_)
406            | Expression::Unary(_)
407            | Expression::TypeCast(_)
408            | Expression::TypeInstantiation(_) => {}
409        }
410    }
411
412    fn process_binary_expression(&mut self, binary: &mut BinaryExpression) {
413        binary.filter_comments(|trivia| self.ignore_trivia(trivia));
414    }
415
416    fn process_field_expression(&mut self, field: &mut FieldExpression) {
417        field.filter_comments(|trivia| self.ignore_trivia(trivia));
418    }
419
420    fn process_function_expression(&mut self, function: &mut FunctionExpression) {
421        function.filter_comments(|trivia| self.ignore_trivia(trivia));
422    }
423
424    fn process_if_expression(&mut self, if_expression: &mut IfExpression) {
425        if_expression.filter_comments(|trivia| self.ignore_trivia(trivia));
426    }
427
428    fn process_variable_expression(&mut self, identifier: &mut Identifier) {
429        identifier.filter_comments(|trivia| self.ignore_trivia(trivia));
430    }
431
432    fn process_index_expression(&mut self, index: &mut IndexExpression) {
433        index.filter_comments(|trivia| self.ignore_trivia(trivia));
434    }
435
436    fn process_number_expression(&mut self, number: &mut NumberExpression) {
437        number.filter_comments(|trivia| self.ignore_trivia(trivia));
438    }
439
440    fn process_parenthese_expression(&mut self, expression: &mut ParentheseExpression) {
441        expression.filter_comments(|trivia| self.ignore_trivia(trivia));
442    }
443
444    fn process_string_expression(&mut self, string: &mut StringExpression) {
445        string.filter_comments(|trivia| self.ignore_trivia(trivia));
446    }
447
448    fn process_table_expression(&mut self, table: &mut TableExpression) {
449        table.filter_comments(|trivia| self.ignore_trivia(trivia));
450    }
451
452    fn process_unary_expression(&mut self, unary: &mut UnaryExpression) {
453        unary.filter_comments(|trivia| self.ignore_trivia(trivia));
454    }
455
456    fn process_interpolated_string_expression(
457        &mut self,
458        string: &mut InterpolatedStringExpression,
459    ) {
460        string.filter_comments(|trivia| self.ignore_trivia(trivia));
461    }
462
463    fn process_type_cast_expression(&mut self, type_cast: &mut TypeCastExpression) {
464        type_cast.filter_comments(|trivia| self.ignore_trivia(trivia));
465    }
466
467    fn process_type_instantiation(&mut self, type_instantiation: &mut TypeInstantiationExpression) {
468        type_instantiation.filter_comments(|trivia| self.ignore_trivia(trivia));
469    }
470
471    fn process_prefix_expression(&mut self, _: &mut Prefix) {}
472
473    fn process_type(&mut self, r#type: &mut Type) {
474        match r#type {
475            Type::True(token) | Type::False(token) | Type::Nil(token) => {
476                if let Some(token) = token {
477                    token.filter_comments(|trivia| self.ignore_trivia(trivia));
478                }
479            }
480            _ => {}
481        }
482    }
483
484    fn process_type_name(&mut self, type_name: &mut TypeName) {
485        type_name.filter_comments(|trivia| self.ignore_trivia(trivia));
486    }
487
488    fn process_type_field(&mut self, type_field: &mut TypeField) {
489        type_field.filter_comments(|trivia| self.ignore_trivia(trivia));
490    }
491
492    fn process_string_type(&mut self, string_type: &mut StringType) {
493        string_type.filter_comments(|trivia| self.ignore_trivia(trivia));
494    }
495
496    fn process_array_type(&mut self, array: &mut ArrayType) {
497        array.filter_comments(|trivia| self.ignore_trivia(trivia));
498    }
499
500    fn process_table_type(&mut self, table: &mut TableType) {
501        table.filter_comments(|trivia| self.ignore_trivia(trivia));
502    }
503
504    fn process_expression_type(&mut self, expression_type: &mut ExpressionType) {
505        expression_type.filter_comments(|trivia| self.ignore_trivia(trivia));
506    }
507
508    fn process_parenthese_type(&mut self, parenthese_type: &mut ParentheseType) {
509        parenthese_type.filter_comments(|trivia| self.ignore_trivia(trivia));
510    }
511
512    fn process_function_type(&mut self, function_type: &mut FunctionType) {
513        function_type.filter_comments(|trivia| self.ignore_trivia(trivia));
514    }
515
516    fn process_optional_type(&mut self, optional: &mut OptionalType) {
517        optional.filter_comments(|trivia| self.ignore_trivia(trivia));
518    }
519
520    fn process_intersection_type(&mut self, intersection: &mut IntersectionType) {
521        intersection.filter_comments(|trivia| self.ignore_trivia(trivia));
522    }
523
524    fn process_union_type(&mut self, union: &mut UnionType) {
525        union.filter_comments(|trivia| self.ignore_trivia(trivia));
526    }
527
528    fn process_type_pack(&mut self, type_pack: &mut TypePack) {
529        type_pack.filter_comments(|trivia| self.ignore_trivia(trivia));
530    }
531
532    fn process_generic_type_pack(&mut self, generic_type_pack: &mut GenericTypePack) {
533        generic_type_pack.filter_comments(|trivia| self.ignore_trivia(trivia));
534    }
535
536    fn process_variadic_type_pack(&mut self, variadic_type_pack: &mut VariadicTypePack) {
537        variadic_type_pack.filter_comments(|trivia| self.ignore_trivia(trivia));
538    }
539}
540
541pub const REMOVE_COMMENTS_RULE_NAME: &str = "remove_comments";
542
543/// A rule that removes comments associated with AST nodes.
544#[derive(Debug, Default)]
545pub struct RemoveComments {
546    metadata: RuleMetadata,
547    except: Vec<Regex>,
548}
549
550impl RemoveComments {
551    pub fn with_exception(mut self, exception_pattern: &str) -> Self {
552        match Regex::new(exception_pattern) {
553            Ok(regex_value) => {
554                self.except.push(regex_value);
555            }
556            Err(err) => {
557                log::warn!(
558                    "unable to compile regex pattern '{}': {}",
559                    exception_pattern,
560                    err
561                );
562            }
563        };
564
565        self
566    }
567}
568
569impl FlawlessRule for RemoveComments {
570    fn flawless_process(&self, block: &mut Block, context: &Context) {
571        if self.except.is_empty() {
572            let mut processor = RemoveCommentProcessor::default();
573            DefaultVisitor::visit_block(block, &mut processor);
574        } else {
575            let mut processor = FilterCommentProcessor::new(context.original_code(), &self.except);
576            DefaultVisitor::visit_block(block, &mut processor);
577        }
578    }
579}
580
581impl RuleConfiguration for RemoveComments {
582    fn configure(&mut self, properties: RuleProperties) -> Result<(), RuleConfigurationError> {
583        for (key, value) in properties {
584            match key.as_str() {
585                "except" => {
586                    self.except = value.expect_regex_list(&key)?;
587                }
588                _ => return Err(RuleConfigurationError::UnexpectedProperty(key)),
589            }
590        }
591
592        Ok(())
593    }
594
595    fn get_name(&self) -> &'static str {
596        REMOVE_COMMENTS_RULE_NAME
597    }
598
599    fn serialize_to_properties(&self) -> RuleProperties {
600        RuleProperties::new()
601    }
602
603    fn set_metadata(&mut self, metadata: RuleMetadata) {
604        self.metadata = metadata;
605    }
606
607    fn metadata(&self) -> &RuleMetadata {
608        &self.metadata
609    }
610}
611
612#[cfg(test)]
613mod test {
614    use super::*;
615    use crate::{
616        generator::{LuaGenerator, TokenBasedLuaGenerator},
617        rules::{ContextBuilder, Rule},
618        Parser, Resources,
619    };
620
621    use insta::assert_json_snapshot;
622
623    fn new_rule() -> RemoveComments {
624        RemoveComments::default()
625    }
626
627    #[test]
628    fn serialize_default_rule() {
629        let rule: Box<dyn Rule> = Box::new(new_rule());
630
631        assert_json_snapshot!(rule, @r###""remove_comments""###);
632    }
633
634    #[test]
635    fn configure_with_extra_field_error() {
636        let result = json5::from_str::<Box<dyn Rule>>(
637            r#"{
638            rule: 'remove_comments',
639            prop: "something",
640        }"#,
641        );
642        insta::assert_snapshot!(result.unwrap_err().to_string(), @"unexpected field 'prop' at line 1 column 1");
643    }
644
645    #[test]
646    fn configure_with_invalid_regex_error() {
647        let result = json5::from_str::<Box<dyn Rule>>(
648            r#"{
649            rule: 'remove_comments',
650            except: ["^[0-9"],
651        }"#,
652        );
653
654        insta::assert_snapshot!(
655            result.unwrap_err().to_string(),
656            @r###"
657        unexpected value for field 'except': invalid regex provided `^[0-9`
658          regex parse error:
659            ^[0-9
660             ^
661        error: unclosed character class at line 1 column 1
662        "###
663        );
664    }
665
666    #[test]
667    fn remove_comments_in_code() {
668        let code = include_str!("../../tests/test_cases/spaces_and_comments.lua");
669
670        let parser = Parser::default().preserve_tokens();
671
672        let mut block = parser.parse(code).expect("unable to parse code");
673
674        RemoveComments::default().flawless_process(
675            &mut block,
676            &ContextBuilder::new(".", &Resources::from_memory(), code).build(),
677        );
678
679        let mut generator = TokenBasedLuaGenerator::new(code);
680
681        generator.write_block(&block);
682
683        let code_output = &generator.into_string();
684
685        insta::assert_snapshot!("remove_comments_in_code", code_output);
686    }
687
688    #[test]
689    fn remove_comments_in_code_with_exception() {
690        let code = include_str!("../../tests/test_cases/spaces_and_comments.lua");
691
692        let parser = Parser::default().preserve_tokens();
693
694        let mut block = parser.parse(code).expect("unable to parse code");
695
696        RemoveComments::default()
697            .with_exception("this.*")
698            .flawless_process(
699                &mut block,
700                &ContextBuilder::new(".", &Resources::from_memory(), code).build(),
701            );
702
703        let mut generator = TokenBasedLuaGenerator::new(code);
704
705        generator.write_block(&block);
706
707        let code_output = &generator.into_string();
708
709        insta::assert_snapshot!("remove_comments_in_code_with_exception", code_output);
710    }
711}