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