darklua_core/nodes/
block.rs

1use crate::nodes::{LastStatement, ReturnStatement, Statement, Token, Trivia, TriviaKind};
2
3/// Represents the tokens associated with a Lua code block, maintaining
4/// syntax information like semicolons that separate statements.
5///
6/// Fields:
7/// - `semicolons`: Optional tokens for semicolons between statements
8/// - `last_semicolon`: Optional semicolon after the last statement
9/// - `final_token`: Optional token at the end of the block (e.g., `end` or `until`)
10///
11/// Typically created by the parser to preserve source formatting for roundtrip transformations.
12#[derive(Clone, Debug, PartialEq, Eq)]
13pub struct BlockTokens {
14    pub semicolons: Vec<Option<Token>>,
15    pub last_semicolon: Option<Token>,
16    pub final_token: Option<Token>,
17}
18
19impl BlockTokens {
20    super::impl_token_fns!(
21        iter = [last_semicolon, final_token]
22        iter_flatten = [semicolons]
23    );
24}
25
26/// Represents a block, a collection of [`Statement`]s that can end with a [`LastStatement`].
27#[derive(Clone, Debug, PartialEq, Eq)]
28pub struct Block {
29    statements: Vec<Statement>,
30    last_statement: Option<LastStatement>,
31    tokens: Option<Box<BlockTokens>>,
32}
33
34impl Block {
35    /// Creates a new Block with the given statements and optional last statement.
36    pub fn new(statements: Vec<Statement>, last_statement: Option<LastStatement>) -> Self {
37        Self {
38            statements,
39            last_statement,
40            tokens: None,
41        }
42    }
43
44    /// Attaches token information to this block and returns the updated block.
45    pub fn with_tokens(mut self, tokens: BlockTokens) -> Self {
46        self.tokens = Some(tokens.into());
47        self
48    }
49
50    /// Attaches token information to this block.
51    #[inline]
52    pub fn set_tokens(&mut self, tokens: BlockTokens) {
53        self.tokens = Some(tokens.into());
54    }
55
56    /// Returns a reference to the token information attached to this block, if any.
57    #[inline]
58    pub fn get_tokens(&self) -> Option<&BlockTokens> {
59        self.tokens.as_ref().map(|tokens| tokens.as_ref())
60    }
61
62    /// Returns a mutable reference to the token information attached to this block, if any.
63    #[inline]
64    pub fn mutate_tokens(&mut self) -> Option<&mut BlockTokens> {
65        self.tokens.as_mut().map(|tokens| tokens.as_mut())
66    }
67
68    /// Adds a statement to the end of the block. Updates token information if present.
69    ///
70    /// This method maintains consistency between the statements and their token information.
71    pub fn push_statement<T: Into<Statement>>(&mut self, statement: T) {
72        if let Some(tokens) = &mut self.tokens {
73            if self.statements.len() == tokens.semicolons.len() {
74                tokens.semicolons.push(None);
75            }
76        }
77        self.statements.push(statement.into());
78    }
79
80    /// Removes a statement at the specified index. Updates token information if present.
81    ///
82    /// Does nothing if the index is out of bounds.
83    pub fn remove_statement(&mut self, index: usize) {
84        let statements_len = self.statements.len();
85        if index < statements_len {
86            let mut removed = self.statements.remove(index);
87
88            fn filter_trivia(trivia: Trivia) -> Option<Trivia> {
89                if trivia.kind() == TriviaKind::Whitespace {
90                    None
91                } else {
92                    Some(trivia)
93                }
94            }
95
96            let mut trivia: Vec<_> = removed
97                .mutate_first_token()
98                .drain_leading_trivia()
99                .filter_map(filter_trivia)
100                .collect();
101
102            let mut drain_trailing_token = true;
103
104            if let Some(tokens) = &mut self.tokens {
105                if tokens.semicolons.len() == statements_len {
106                    let removed_semicolon = tokens.semicolons.remove(index);
107
108                    if let Some(mut semicolon) = removed_semicolon {
109                        trivia.extend(semicolon.drain_trailing_trivia().filter_map(filter_trivia));
110                        drain_trailing_token = false;
111                    }
112                }
113            }
114
115            if drain_trailing_token {
116                trivia.extend(
117                    removed
118                        .mutate_last_token()
119                        .drain_trailing_trivia()
120                        .filter_map(filter_trivia),
121                );
122            }
123
124            if !trivia.is_empty() {
125                let next_statement = self.statements.get_mut(index);
126
127                let token = if let Some(next_statement) = next_statement {
128                    next_statement.mutate_first_token()
129                } else if let Some(last) = self.last_statement.as_mut() {
130                    last.mutate_first_token()
131                } else {
132                    self.set_default_tokens();
133
134                    self.tokens.as_mut().unwrap().final_token.as_mut().unwrap()
135                };
136
137                let line_numbers: Vec<_> = trivia.iter().map(|t| t.get_line_number()).collect();
138
139                let mut previous = None;
140                let mut offset = 0;
141
142                for (index, (trivia, line_number)) in
143                    trivia.into_iter().zip(line_numbers).enumerate()
144                {
145                    if let (Some(previous), Some(next_line)) = (previous, line_number) {
146                        let gap = next_line.saturating_sub(previous);
147                        if gap != 0 {
148                            token.insert_leading_trivia(
149                                index + offset,
150                                TriviaKind::Whitespace.with_content("\n".repeat(gap)),
151                            );
152                            offset += gap;
153                        }
154                    }
155
156                    token.insert_leading_trivia(index + offset, trivia.clone());
157
158                    if line_number.is_some() {
159                        previous = line_number;
160                    }
161                }
162            }
163        }
164    }
165
166    /// Adds a statement to the end of the block and returns the updated block.
167    pub fn with_statement<T: Into<Statement>>(mut self, statement: T) -> Self {
168        self.statements.push(statement.into());
169        self
170    }
171
172    /// Inserts a statement at the specified index, appending if the index is beyond the end.
173    /// Updates token information if present.
174    ///
175    /// This method maintains consistency between the statements and their token information.
176    pub fn insert_statement(&mut self, index: usize, statement: impl Into<Statement>) {
177        if index > self.statements.len() {
178            self.push_statement(statement.into());
179        } else {
180            self.statements.insert(index, statement.into());
181
182            if let Some(tokens) = &mut self.tokens {
183                if index <= tokens.semicolons.len() {
184                    tokens.semicolons.insert(index, None);
185                }
186            }
187        }
188    }
189
190    /// Sets the last statement of the block.
191    #[inline]
192    pub fn set_last_statement(&mut self, last_statement: impl Into<LastStatement>) {
193        self.last_statement = Some(last_statement.into());
194    }
195
196    /// Sets the last statement of the block and returns the updated block.
197    pub fn with_last_statement(mut self, last_statement: impl Into<LastStatement>) -> Self {
198        self.last_statement = Some(last_statement.into());
199        self
200    }
201
202    /// Checks if the block is empty (contains no statements or last statement).
203    #[inline]
204    pub fn is_empty(&self) -> bool {
205        self.last_statement.is_none() && self.statements.is_empty()
206    }
207
208    /// Returns the number of statements in the block.
209    #[inline]
210    pub fn statements_len(&self) -> usize {
211        self.statements.len()
212    }
213
214    /// Returns an iterator over references to the statements in the block.
215    #[inline]
216    pub fn iter_statements(&self) -> impl Iterator<Item = &Statement> {
217        self.statements.iter()
218    }
219
220    /// Returns an iterator over references to the statements in the block in reverse order.
221    #[inline]
222    pub fn reverse_iter_statements(&self) -> impl Iterator<Item = &Statement> {
223        self.statements.iter().rev()
224    }
225
226    /// Returns a reference to the last statement of the block, if any.
227    #[inline]
228    pub fn get_last_statement(&self) -> Option<&LastStatement> {
229        self.last_statement.as_ref()
230    }
231
232    /// Filters statements in the block, removing those for which the predicate returns `false`.
233    /// Updates token information if present.
234    ///
235    /// This method ensures token information stays consistent with the statements collection.
236    pub fn filter_statements<F>(&mut self, mut f: F)
237    where
238        F: FnMut(&Statement) -> bool,
239    {
240        let mut i = 0;
241
242        while i != self.statements.len() {
243            if f(&self.statements[i]) {
244                i += 1;
245            } else {
246                self.remove_statement(i);
247            }
248        }
249    }
250
251    /// Filters statements with mutable access, removing those for which the predicate returns `false`.
252    /// Updates token information if present.
253    ///
254    /// This method gives mutable access to statements during filtering.
255    pub fn filter_mut_statements<F>(&mut self, mut f: F)
256    where
257        F: FnMut(&mut Statement) -> bool,
258    {
259        let mut i = 0;
260
261        while i != self.statements.len() {
262            if f(&mut self.statements[i]) {
263                i += 1;
264            } else {
265                self.remove_statement(i);
266            }
267        }
268    }
269
270    /// Truncates the statements to the specified length.
271    ///
272    /// Updates token information if present.
273    pub fn truncate(&mut self, length: usize) {
274        self.statements.truncate(length);
275        if let Some(tokens) = &mut self.tokens {
276            tokens.semicolons.truncate(length);
277        }
278    }
279
280    /// Returns a mutable iterator over the statements in the block.
281    #[inline]
282    pub fn iter_mut_statements(&mut self) -> impl Iterator<Item = &mut Statement> {
283        self.statements.iter_mut()
284    }
285
286    /// Returns a reference to the first statement in the block, if any.
287    #[inline]
288    pub fn first_statement(&self) -> Option<&Statement> {
289        self.statements.first()
290    }
291
292    /// Returns a mutable reference to the first statement in the block, if any.
293    #[inline]
294    pub fn first_mut_statement(&mut self) -> Option<&mut Statement> {
295        self.statements.first_mut()
296    }
297
298    /// Removes all statements from the block and returns them.
299    ///
300    /// Clears token information if present.
301    pub fn take_statements(&mut self) -> Vec<Statement> {
302        if let Some(tokens) = &mut self.tokens {
303            tokens.semicolons.clear();
304        }
305        self.statements.drain(..).collect()
306    }
307
308    /// Removes and returns the last statement of the block, if any.
309    ///
310    /// Clears the last semicolon token if present.
311    pub fn take_last_statement(&mut self) -> Option<LastStatement> {
312        if let Some(tokens) = &mut self.tokens {
313            tokens.last_semicolon.take();
314        }
315        self.last_statement.take()
316    }
317
318    /// Sets the statements of the block.
319    ///
320    /// Clears any existing semicolon tokens.
321    pub fn set_statements(&mut self, statements: Vec<Statement>) {
322        self.statements = statements;
323
324        if let Some(tokens) = &mut self.tokens {
325            tokens.semicolons.clear();
326        }
327    }
328
329    /// Returns a mutable reference to the last statement of the block, if any.
330    #[inline]
331    pub fn mutate_last_statement(&mut self) -> Option<&mut LastStatement> {
332        self.last_statement.as_mut()
333    }
334
335    /// Replaces the last statement with the given statement and returns the old one, if any.
336    #[inline]
337    pub fn replace_last_statement<S: Into<LastStatement>>(
338        &mut self,
339        statement: S,
340    ) -> Option<LastStatement> {
341        self.last_statement.replace(statement.into())
342    }
343
344    /// Clears all statements and the last statement from the block.
345    ///
346    /// Also clears all token information if present.
347    pub fn clear(&mut self) {
348        self.statements.clear();
349        self.last_statement.take();
350
351        if let Some(tokens) = &mut self.tokens {
352            tokens.semicolons.clear();
353            tokens.last_semicolon = None;
354        }
355    }
356
357    /// Returns a mutable reference to the first token in this block, creating
358    /// it if it doesn't exist.
359    pub fn mutate_first_token(&mut self) -> &mut Token {
360        if self.is_empty() {
361            self.set_default_tokens();
362            return self.tokens.as_mut().unwrap().final_token.as_mut().unwrap();
363        }
364
365        if !self.statements.is_empty() {
366            return self
367                .first_mut_statement()
368                .expect("first statement should exist")
369                .mutate_first_token();
370        }
371
372        match self
373            .mutate_last_statement()
374            .expect("non-empty block should have a last statement")
375        {
376            LastStatement::Break(token) => {
377                if token.is_none() {
378                    *token = Some(Token::from_content("break"));
379                }
380                token.as_mut().unwrap()
381            }
382            LastStatement::Continue(token) => {
383                if token.is_none() {
384                    *token = Some(Token::from_content("continue"));
385                }
386                token.as_mut().unwrap()
387            }
388            LastStatement::Return(return_stmt) => return_stmt.mutate_first_token(),
389        }
390    }
391
392    /// Returns a mutable reference to the last token for this block,
393    /// creating it if missing.
394    pub fn mutate_last_token(&mut self) -> &mut Token {
395        if self.is_empty() {
396            self.set_default_tokens();
397            return self.tokens.as_mut().unwrap().final_token.as_mut().unwrap();
398        }
399
400        if let Some(last_stmt) = self.last_statement.as_mut() {
401            return last_stmt.mutate_last_token();
402        }
403
404        self.statements.last_mut().unwrap().mutate_last_token()
405    }
406
407    fn set_default_tokens(&mut self) {
408        if self.get_tokens().is_none() {
409            self.set_tokens(BlockTokens {
410                semicolons: Vec::new(),
411                last_semicolon: None,
412                final_token: Some(Token::from_content("")),
413            });
414        } else {
415            let tokens = self.tokens.as_mut().unwrap();
416            if tokens.final_token.is_none() {
417                tokens.final_token = Some(Token::from_content(""));
418            }
419        }
420    }
421
422    super::impl_token_fns!(iter = [tokens]);
423}
424
425impl Default for Block {
426    fn default() -> Self {
427        Self::new(Vec::new(), None)
428    }
429}
430
431impl<IntoStatement: Into<Statement>> From<IntoStatement> for Block {
432    fn from(statement: IntoStatement) -> Block {
433        Block::new(vec![statement.into()], None)
434    }
435}
436
437impl From<LastStatement> for Block {
438    fn from(statement: LastStatement) -> Block {
439        Block::new(Vec::new(), Some(statement))
440    }
441}
442
443impl From<ReturnStatement> for Block {
444    fn from(statement: ReturnStatement) -> Block {
445        Block::new(Vec::new(), Some(statement.into()))
446    }
447}
448
449#[cfg(test)]
450mod test {
451    use super::*;
452    use crate::{
453        generator::{LuaGenerator, TokenBasedLuaGenerator},
454        nodes::{DoStatement, RepeatStatement},
455        Parser,
456    };
457
458    fn parse_block_with_tokens(lua: &str) -> Block {
459        let parser = Parser::default().preserve_tokens();
460        parser.parse(lua).expect("code should parse")
461    }
462
463    fn parse_statement_with_tokens(lua: &str) -> Statement {
464        let mut block = parse_block_with_tokens(lua);
465        assert!(block.get_last_statement().is_none());
466        let statements = block.take_statements();
467        assert_eq!(statements.len(), 1);
468        statements.into_iter().next().unwrap()
469    }
470
471    #[test]
472    fn default_block_is_empty() {
473        let block = Block::default();
474
475        assert!(block.is_empty());
476    }
477
478    #[test]
479    fn is_empty_is_true_when_block_has_no_statements_or_last_statement() {
480        let block = Block::new(Vec::new(), None);
481
482        assert!(block.is_empty());
483    }
484
485    #[test]
486    fn is_empty_is_false_when_block_has_a_last_statement() {
487        let block = Block::default().with_last_statement(LastStatement::new_break());
488
489        assert!(!block.is_empty());
490    }
491
492    #[test]
493    fn is_empty_is_false_when_block_a_statement() {
494        let block = Block::default().with_statement(DoStatement::default());
495
496        assert!(!block.is_empty());
497    }
498
499    #[test]
500    fn clear_removes_statements() {
501        let mut block = Block::default().with_statement(DoStatement::default());
502        block.clear();
503
504        assert!(block.is_empty());
505    }
506
507    #[test]
508    fn clear_removes_last_statement() {
509        let mut block = Block::default().with_last_statement(LastStatement::new_break());
510        block.clear();
511
512        assert!(block.is_empty());
513        assert_eq!(block.get_last_statement(), None);
514    }
515
516    #[test]
517    fn set_last_statement() {
518        let mut block = Block::default();
519        let continue_statement = LastStatement::new_continue();
520        block.set_last_statement(continue_statement.clone());
521
522        assert_eq!(block.get_last_statement(), Some(&continue_statement));
523    }
524
525    #[test]
526    fn insert_statement_at_index_0() {
527        let mut block = Block::default().with_statement(DoStatement::default());
528
529        let new_statement = RepeatStatement::new(Block::default(), false);
530        block.insert_statement(0, new_statement.clone());
531
532        assert_eq!(
533            block,
534            Block::default()
535                .with_statement(new_statement)
536                .with_statement(DoStatement::default())
537        );
538    }
539
540    #[test]
541    fn insert_statement_at_index_0_with_tokens() {
542        let mut block = parse_block_with_tokens("do end;");
543
544        block.insert_statement(0, RepeatStatement::new(Block::default(), false));
545
546        insta::assert_debug_snapshot!("insert_statement_at_index_0_with_tokens", block);
547    }
548
549    #[test]
550    fn insert_statement_at_upper_bound() {
551        let mut block = Block::default().with_statement(DoStatement::default());
552
553        let new_statement = RepeatStatement::new(Block::default(), false);
554        block.insert_statement(1, new_statement.clone());
555
556        assert_eq!(
557            block,
558            Block::default()
559                .with_statement(DoStatement::default())
560                .with_statement(new_statement)
561        );
562    }
563
564    #[test]
565    fn insert_statement_after_statement_upper_bound() {
566        let mut block = Block::default().with_statement(DoStatement::default());
567
568        let new_statement = RepeatStatement::new(Block::default(), false);
569        block.insert_statement(4, new_statement.clone());
570
571        assert_eq!(
572            block,
573            Block::default()
574                .with_statement(DoStatement::default())
575                .with_statement(new_statement)
576        );
577    }
578
579    #[test]
580    fn insert_statement_after_statement_upper_bound_with_tokens() {
581        let mut block = parse_block_with_tokens("do end;");
582
583        block.insert_statement(4, RepeatStatement::new(Block::default(), false));
584
585        insta::assert_debug_snapshot!(
586            "insert_statement_after_statement_upper_bound_with_tokens",
587            block
588        );
589    }
590
591    #[test]
592    fn push_statement_with_tokens() {
593        let mut block = parse_block_with_tokens("");
594
595        let new_statement = parse_statement_with_tokens("while true do end");
596        block.push_statement(new_statement);
597
598        pretty_assertions::assert_eq!(
599            block.get_tokens(),
600            Some(&BlockTokens {
601                semicolons: vec![None],
602                last_semicolon: None,
603                final_token: None,
604            })
605        );
606    }
607
608    #[test]
609    fn attempt_to_remove_statement_from_empty_block() {
610        let mut block = parse_block_with_tokens("");
611
612        block.remove_statement(0);
613
614        assert!(block.is_empty());
615    }
616
617    #[test]
618    fn remove_first_and_only_statement() {
619        let mut block = parse_block_with_tokens("while true do end");
620
621        block.remove_statement(0);
622
623        assert!(block.is_empty());
624    }
625
626    #[test]
627    fn remove_first_statement() {
628        let mut block = parse_block_with_tokens("while true do end ; do end");
629
630        block.remove_statement(0);
631
632        insta::assert_debug_snapshot!("remove_first_statement", block);
633    }
634
635    #[test]
636    fn attempt_to_remove_statement_out_of_bounds() {
637        let mut block = parse_block_with_tokens("while true do end");
638        let original = block.clone();
639
640        block.remove_statement(1);
641        block.remove_statement(2);
642
643        assert_eq!(block, original);
644    }
645
646    #[test]
647    fn clear_removes_semicolon_tokens() {
648        let mut block = Block::default()
649            .with_statement(DoStatement::default())
650            .with_tokens(BlockTokens {
651                semicolons: vec![Some(Token::from_content(";"))],
652                last_semicolon: None,
653                final_token: None,
654            });
655        block.clear();
656
657        assert!(block.get_tokens().unwrap().semicolons.is_empty());
658    }
659
660    #[test]
661    fn clear_removes_last_semicolon_token() {
662        let mut block = Block::default()
663            .with_last_statement(LastStatement::new_break())
664            .with_tokens(BlockTokens {
665                semicolons: Vec::new(),
666                last_semicolon: Some(Token::from_content(";")),
667                final_token: None,
668            });
669        block.clear();
670
671        assert!(block.get_tokens().unwrap().last_semicolon.is_none());
672    }
673
674    #[test]
675    fn set_statements_clear_semicolon_tokens() {
676        let mut block = Block::default()
677            .with_statement(DoStatement::default())
678            .with_tokens(BlockTokens {
679                semicolons: vec![Some(Token::from_content(";"))],
680                last_semicolon: None,
681                final_token: None,
682            });
683        block.set_statements(Vec::new());
684
685        assert!(block.get_tokens().unwrap().semicolons.is_empty());
686    }
687
688    #[test]
689    fn take_last_statement_clear_semicolon_token() {
690        let mut block = Block::default()
691            .with_last_statement(LastStatement::new_break())
692            .with_tokens(BlockTokens {
693                semicolons: Vec::new(),
694                last_semicolon: Some(Token::from_content(";")),
695                final_token: None,
696            });
697
698        assert_eq!(
699            block.take_last_statement(),
700            Some(LastStatement::new_break())
701        );
702
703        assert!(block.get_tokens().unwrap().last_semicolon.is_none());
704    }
705
706    #[test]
707    fn filter_statements_does_not_panic_when_semicolons_do_not_match() {
708        let mut block = Block::default()
709            .with_statement(DoStatement::default())
710            .with_statement(DoStatement::default())
711            .with_tokens(BlockTokens {
712                semicolons: vec![Some(Token::from_content(";"))],
713                last_semicolon: None,
714                final_token: None,
715            });
716
717        block.filter_statements(|_statement| false);
718
719        pretty_assertions::assert_eq!(
720            block,
721            Block::default().with_tokens(BlockTokens {
722                semicolons: Vec::new(),
723                last_semicolon: None,
724                final_token: None,
725            })
726        );
727    }
728
729    #[test]
730    fn filter_mut_statements_does_not_panic_when_semicolons_do_not_match() {
731        let mut block = Block::default()
732            .with_statement(DoStatement::default())
733            .with_statement(DoStatement::default())
734            .with_tokens(BlockTokens {
735                semicolons: vec![Some(Token::from_content(";"))],
736                last_semicolon: None,
737                final_token: None,
738            });
739
740        block.filter_mut_statements(|_statement| false);
741
742        pretty_assertions::assert_eq!(
743            block,
744            Block::default().with_tokens(BlockTokens {
745                semicolons: Vec::new(),
746                last_semicolon: None,
747                final_token: None,
748            })
749        );
750    }
751
752    mod statement_removal {
753        use super::*;
754
755        fn remove_statement_test(index: usize, code: &str) -> String {
756            let mut block = parse_block_with_tokens(code);
757
758            block.remove_statement(index);
759
760            let mut generator = TokenBasedLuaGenerator::new(code);
761
762            generator.write_block(&block);
763
764            generator.into_string()
765        }
766
767        #[test]
768        fn remove_single_statement_preserves_leading_comments() {
769            let lua_code = remove_statement_test(0, "-- comment\nlocal a = 1");
770
771            insta::assert_snapshot!(lua_code, @"-- comment");
772        }
773
774        #[test]
775        fn remove_single_statement_preserves_trailing_comments() {
776            let lua_code = remove_statement_test(0, "local a = 1 -- comment");
777
778            insta::assert_snapshot!(lua_code, @"-- comment");
779        }
780
781        #[test]
782        fn remove_statement_preserves_comments() {
783            let lua_code = remove_statement_test(0, "local a = 1 -- comment\nlocal b = 2");
784
785            insta::assert_snapshot!(lua_code, @r###"
786            -- comment
787            local b = 2
788            "###);
789        }
790
791        #[test]
792        fn remove_statement_preserves_comments_before_return() {
793            let lua_code = remove_statement_test(0, "local a = 1 -- comment\nreturn");
794
795            insta::assert_snapshot!(lua_code, @r###"
796            -- comment
797            return
798            "###);
799        }
800
801        #[test]
802        fn remove_statement_preserves_comments_before_break() {
803            let lua_code = remove_statement_test(0, "--first\nlocal a = 1 -- comment\nbreak");
804
805            insta::assert_snapshot!(lua_code, @r###"
806            --first
807            -- comment
808            break
809            "###);
810        }
811
812        #[test]
813        fn remove_statement_preserves_comments_before_continue() {
814            let lua_code = remove_statement_test(0, "local a = 1 -- comment\ncontinue");
815
816            insta::assert_snapshot!(lua_code, @r###"
817            -- comment
818            continue
819            "###);
820        }
821
822        #[test]
823        fn remove_statement_preserves_comments_after_semicolon() {
824            let lua_code = remove_statement_test(0, "local a = 1; -- comment\nlocal b = 2;");
825
826            insta::assert_snapshot!(lua_code, @r###"
827            -- comment
828            local b = 2;
829            "###);
830        }
831    }
832}