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#[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}