1use crate::{Diagnostic, FileId, Lexer, Span, Token, TokenKind};
2
3#[derive(Clone, Copy, PartialEq)]
6enum BraceKind {
7 Block,
8 Value,
9 MemberList,
11}
12
13struct BraceFrame {
20 kind: BraceKind,
21 bracket_base: u32,
22}
23
24pub struct Asi<'a> {
26 lexer: Lexer<'a>,
27 buffered: Option<Token>,
28 lookahead: std::collections::VecDeque<Token>,
29 last_emitted: Option<TokenKind>,
30 bracket_depth: u32,
33 braces: Vec<BraceFrame>,
34 paren_is_header: Vec<bool>,
38 closed_header_paren: bool,
39 closed_block_brace: bool,
40 cast_angle_depth: u32,
45 closed_cast_angle: bool,
49 in_decl_header: bool,
53 last_is_member_name: bool,
58 in_case_label: bool,
61 after_case_label_colon: bool,
64 brace_opens_specifier_list: bool,
70}
71
72impl<'a> Asi<'a> {
73 pub fn new(source: &'a str, file: FileId) -> Self {
75 Self {
76 lexer: Lexer::new(source, file),
77 buffered: None,
78 lookahead: std::collections::VecDeque::new(),
79 last_emitted: None,
80 bracket_depth: 0,
81 braces: Vec::new(),
82 paren_is_header: Vec::new(),
83 closed_header_paren: false,
84 closed_block_brace: false,
85 last_is_member_name: false,
86 in_case_label: false,
87 after_case_label_colon: false,
88 in_decl_header: false,
89 brace_opens_specifier_list: false,
90 cast_angle_depth: 0,
91 closed_cast_angle: false,
92 }
93 }
94
95 pub fn next_token(&mut self) -> Token {
96 if let Some(tok) = self.buffered.take() {
97 self.track(&tok.kind);
98 return tok;
99 }
100
101 let mut pending_newline = false;
102 loop {
103 let tok = self
104 .lookahead
105 .pop_front()
106 .unwrap_or_else(|| self.lexer.next_token());
107 match tok.kind {
108 TokenKind::Newline => {
109 pending_newline = true;
110 }
111 TokenKind::Eof => {
112 if self.needs_semi() {
113 return self.insert_semicolon_before(tok);
114 }
115 self.track(&tok.kind);
116 return tok;
117 }
118 _ => {
119 let boundary = match self.brace_closed_by(&tok.kind) {
123 Some(BraceKind::Value | BraceKind::MemberList) => false,
124 Some(BraceKind::Block) => true,
125 None => pending_newline,
126 };
127 if boundary
128 && !self.inside_brackets()
129 && !self.can_continue_here(&tok.kind)
130 && self.needs_semi()
131 {
132 return self.insert_semicolon_before(tok);
133 }
134 self.track(&tok.kind);
135 return tok;
136 }
137 }
138 }
139 }
140
141 fn can_continue_here(&mut self, kind: &TokenKind) -> bool {
142 if matches!(
143 kind,
144 TokenKind::Else
145 | TokenKind::Catch
146 | TokenKind::Finally
147 | TokenKind::Extends
148 | TokenKind::Implements
149 ) {
150 loop {
153 let next = self.lexer.next_token();
154 let newline = matches!(next.kind, TokenKind::Newline);
155 let member = matches!(next.kind, TokenKind::Colon | TokenKind::Question);
156 self.lookahead.push_back(next);
157 if !newline {
158 return !member;
159 }
160 }
161 }
162 can_continue(kind)
163 }
164
165 pub fn finish(self) -> Result<Vec<Diagnostic>, crate::compiler_error::CompileError> {
166 self.lexer.finish()
167 }
168
169 pub fn into_diagnostics(self) -> Vec<Diagnostic> {
170 self.lexer.into_diagnostics()
171 }
172
173 fn insert_semicolon_before(&mut self, tok: Token) -> Token {
175 let pos = tok.span.start;
176 let file = tok.span.file;
177 self.buffered = Some(tok);
178 let semi = Token::new(
179 TokenKind::Semicolon,
180 Span {
181 file,
182 start: pos,
183 end: pos,
184 },
185 );
186 self.track(&semi.kind);
187 semi
188 }
189
190 fn needs_semi(&self) -> bool {
191 if self.closed_header_paren || self.in_decl_header {
194 return false;
195 }
196 match &self.last_emitted {
197 None => false,
198 Some(kind) => self.last_is_member_name || !cannot_end_statement(kind),
199 }
200 }
201
202 fn at_statement_start(&self) -> bool {
205 matches!(
206 self.last_emitted,
207 None | Some(
208 TokenKind::LeftBrace
209 | TokenKind::RightBrace
210 | TokenKind::Semicolon
211 | TokenKind::Colon
212 )
213 )
214 }
215
216 fn brace_closed_by(&self, next: &TokenKind) -> Option<BraceKind> {
220 if !matches!(next, TokenKind::RightBrace) {
221 return None;
222 }
223 self.braces.last().map(|frame| frame.kind)
224 }
225
226 fn inside_brackets(&self) -> bool {
228 self.bracket_depth > self.braces.last().map_or(0, |frame| frame.bracket_base)
229 }
230
231 fn classify_brace(&self) -> BraceKind {
236 if self.brace_opens_specifier_list {
237 return BraceKind::Value;
238 }
239 if self.after_case_label_colon {
240 return BraceKind::Block;
241 }
242 if self.closed_cast_angle {
243 return BraceKind::Value;
244 }
245 match &self.last_emitted {
246 Some(TokenKind::Colon) => BraceKind::MemberList,
249 Some(kind) if opens_value_brace(kind) => BraceKind::Value,
250 _ => BraceKind::Block,
251 }
252 }
253
254 fn push_brace(&mut self) {
255 let kind = self.classify_brace();
256 let bracket_base = match kind {
257 BraceKind::Block | BraceKind::MemberList => self.bracket_depth,
258 BraceKind::Value => self.braces.last().map_or(0, |frame| frame.bracket_base),
259 };
260 self.braces.push(BraceFrame { kind, bracket_base });
261 }
262
263 fn track(&mut self, kind: &TokenKind) {
271 self.correct_bang_context(kind);
272 let closed_block = self.brace_closed_by(kind) == Some(BraceKind::Block);
273 let closed_header = self.track_brackets(kind);
274 let closed_cast = self.track_cast_angles(kind);
275 self.after_case_label_colon = self.next_after_case_label_colon(kind);
276 self.in_case_label = self.next_in_case_label(kind);
277 self.in_decl_header = self.next_in_decl_header(kind);
278 self.brace_opens_specifier_list = self.next_brace_opens_specifier_list(kind);
279 self.last_is_member_name = self.next_last_is_member_name();
280 self.closed_header_paren = closed_header;
281 self.closed_block_brace = closed_block;
282 self.closed_cast_angle = closed_cast;
283 self.last_emitted = Some(kind.clone());
284 }
285
286 fn track_cast_angles(&mut self, kind: &TokenKind) -> bool {
288 match kind {
289 TokenKind::LessThan if self.cast_angle_depth > 0 => self.cast_angle_depth += 1,
290 TokenKind::LessThan
291 if self.expects_operand()
292 && !matches!(self.last_emitted, Some(TokenKind::LessThan)) =>
293 {
294 self.cast_angle_depth = 1;
295 }
296 TokenKind::GreaterThan if self.cast_angle_depth > 0 => {
297 self.cast_angle_depth -= 1;
298 return self.cast_angle_depth == 0;
299 }
300 _ => {}
301 }
302 false
303 }
304
305 fn expects_operand(&self) -> bool {
308 self.at_statement_start()
309 || self
310 .last_emitted
311 .as_ref()
312 .is_some_and(|kind| opens_value_brace(kind) || matches!(kind, TokenKind::Arrow))
315 }
316
317 fn correct_bang_context(&mut self, kind: &TokenKind) {
319 if !matches!(kind, TokenKind::Bang) {
320 return;
321 }
322 if self.last_is_member_name {
323 self.lexer.set_bang_is_postfix(true);
324 } else if self.closed_header_paren
325 || self.closed_block_brace
326 || matches!(self.last_emitted, Some(TokenKind::Semicolon))
327 {
328 self.lexer.set_bang_is_postfix(false);
329 }
330 }
331
332 fn track_brackets(&mut self, kind: &TokenKind) -> bool {
334 match kind {
335 TokenKind::LeftParen => {
336 self.bracket_depth += 1;
337 self.paren_is_header.push(self.opens_control_header());
338 }
339 TokenKind::LeftBracket | TokenKind::TemplateHead(_) => self.bracket_depth += 1,
342 TokenKind::RightParen => {
343 self.bracket_depth = self.bracket_depth.saturating_sub(1);
344 return self.paren_is_header.pop().unwrap_or(false);
345 }
346 TokenKind::RightBracket | TokenKind::TemplateTail(_) => {
347 self.bracket_depth = self.bracket_depth.saturating_sub(1);
348 }
349 TokenKind::LeftBrace => self.push_brace(),
350 TokenKind::RightBrace => {
351 self.braces.pop();
352 }
353 _ => {}
354 }
355 false
356 }
357
358 fn next_after_case_label_colon(&self, kind: &TokenKind) -> bool {
359 matches!(kind, TokenKind::Colon) && self.in_case_label
360 }
361
362 fn next_in_case_label(&self, kind: &TokenKind) -> bool {
363 match kind {
364 TokenKind::Case | TokenKind::Default => {
368 self.at_statement_start()
369 && self.braces.last().map(|frame| frame.kind) == Some(BraceKind::Block)
370 }
371 TokenKind::Colon
375 | TokenKind::Semicolon
376 | TokenKind::Comma
377 | TokenKind::LeftBrace
378 | TokenKind::RightBrace
379 | TokenKind::LeftParen
380 | TokenKind::RightParen => false,
381 _ => self.in_case_label,
382 }
383 }
384
385 fn next_in_decl_header(&self, kind: &TokenKind) -> bool {
386 match kind {
387 TokenKind::Class | TokenKind::Interface | TokenKind::Enum => {
390 self.at_statement_start() || matches!(self.last_emitted, Some(TokenKind::Export))
391 }
392 TokenKind::LeftBrace
393 | TokenKind::RightBrace
394 | TokenKind::Semicolon
395 | TokenKind::Colon
396 | TokenKind::Comma => false,
397 _ => self.in_decl_header,
398 }
399 }
400
401 fn next_brace_opens_specifier_list(&self, kind: &TokenKind) -> bool {
402 match kind {
403 TokenKind::Import | TokenKind::Export => true,
404 TokenKind::Identifier => self.brace_opens_specifier_list,
407 _ => false,
408 }
409 }
410
411 fn next_last_is_member_name(&self) -> bool {
412 matches!(
413 self.last_emitted,
414 Some(TokenKind::Dot | TokenKind::QuestionDot)
415 )
416 }
417
418 fn opens_control_header(&self) -> bool {
424 !self.last_is_member_name
425 && matches!(
426 self.last_emitted,
427 Some(
428 TokenKind::If
429 | TokenKind::While
430 | TokenKind::For
431 | TokenKind::Switch
432 | TokenKind::Catch
433 )
434 )
435 }
436}
437
438fn cannot_end_statement(kind: &TokenKind) -> bool {
449 matches!(
450 kind,
451 TokenKind::Semicolon
452 | TokenKind::LeftBrace
453 | TokenKind::LeftParen
454 | TokenKind::LeftBracket
455 | TokenKind::Comma
456 ) || awaits_operand(kind)
457 || awaits_body(kind)
458}
459
460fn cannot_end_expression(kind: &TokenKind) -> bool {
468 matches!(
469 kind,
470 TokenKind::Equals
471 | TokenKind::PlusEquals
472 | TokenKind::MinusEquals
473 | TokenKind::StarEquals
474 | TokenKind::SlashEquals
475 | TokenKind::PercentEquals
476 | TokenKind::StarStarEquals
477 | TokenKind::Plus
478 | TokenKind::Minus
479 | TokenKind::Star
480 | TokenKind::Slash
481 | TokenKind::Percent
482 | TokenKind::StarStar
483 | TokenKind::EqEqEq
484 | TokenKind::EqEq
485 | TokenKind::BangEqEq
486 | TokenKind::BangEq
487 | TokenKind::LessThan
488 | TokenKind::GreaterThan
489 | TokenKind::LessEquals
490 | TokenKind::GreaterEquals
491 | TokenKind::AmpAmp
492 | TokenKind::Amp
493 | TokenKind::AmpEquals
494 | TokenKind::PipeEquals
495 | TokenKind::Caret
496 | TokenKind::CaretEquals
497 | TokenKind::Pipe
498 | TokenKind::PipePipe
499 | TokenKind::Question
500 | TokenKind::QuestionQuestion
501 | TokenKind::Colon
502 )
503}
504
505fn awaits_operand(kind: &TokenKind) -> bool {
510 cannot_end_expression(kind)
511 || matches!(
512 kind,
513 TokenKind::Arrow
514 | TokenKind::Tilde
515 | TokenKind::Extends
518 | TokenKind::Implements
519 )
520}
521
522fn awaits_body(kind: &TokenKind) -> bool {
529 matches!(
530 kind,
531 TokenKind::Try | TokenKind::Else | TokenKind::Do | TokenKind::Finally | TokenKind::Catch
532 )
533}
534
535fn opens_value_brace(kind: &TokenKind) -> bool {
540 if matches!(kind, TokenKind::GreaterThan) {
544 return false;
545 }
546 cannot_end_expression(kind)
547 || matches!(
548 kind,
549 TokenKind::LeftParen
550 | TokenKind::Tilde
551 | TokenKind::LeftBracket
552 | TokenKind::Comma
553 | TokenKind::DotDotDot
555 | TokenKind::Return
556 | TokenKind::Throw
557 | TokenKind::Const
558 | TokenKind::Let
559 | TokenKind::TemplateHead(_)
560 | TokenKind::TemplateMiddle(_)
561 )
562}
563
564fn can_continue(kind: &TokenKind) -> bool {
572 if matches!(kind, TokenKind::Tilde) {
574 return false;
575 }
576 awaits_operand(kind)
577 || matches!(
578 kind,
579 TokenKind::Dot
580 | TokenKind::QuestionDot
582 | TokenKind::LeftParen
583 | TokenKind::LeftBracket
584 | TokenKind::Else
585 | TokenKind::Catch
588 | TokenKind::Finally
589 | TokenKind::Extends
592 | TokenKind::Implements
593 | TokenKind::TemplateMiddle(_)
597 | TokenKind::TemplateTail(_)
598 )
599}
600
601#[cfg(test)]
602mod tests {
603 use super::Asi;
604 use crate::TokenKind;
605
606 fn asi_kinds(source: &str) -> Vec<TokenKind> {
607 let mut asi = Asi::new(source, crate::FileId(0));
608 let mut kinds = Vec::new();
609 loop {
610 let tok = asi.next_token();
611 let is_eof = tok.kind == TokenKind::Eof;
612 kinds.push(tok.kind);
613 if is_eof {
614 break;
615 }
616 }
617 let diags = asi.into_diagnostics();
618 assert!(
619 diags.is_empty(),
620 "unexpected diagnostics for {source:?}: {diags:?}"
621 );
622 kinds
623 }
624
625 #[test]
626 fn strips_all_newlines() {
627 let kinds = asi_kinds("a\nb\n");
628 assert!(!kinds.iter().any(|k| matches!(k, TokenKind::Newline)));
629 }
630
631 #[test]
632 fn inserts_semicolon_between_statements() {
633 assert_eq!(
634 asi_kinds("a\nb"),
635 vec![
636 TokenKind::Identifier,
637 TokenKind::Semicolon,
638 TokenKind::Identifier,
639 TokenKind::Semicolon,
640 TokenKind::Eof,
641 ]
642 );
643 }
644
645 #[test]
646 fn newline_before_as_splits_statement() {
647 assert_eq!(
650 asi_kinds("a\nas(b)"),
651 vec![
652 TokenKind::Identifier,
653 TokenKind::Semicolon,
654 TokenKind::Identifier,
655 TokenKind::LeftParen,
656 TokenKind::Identifier,
657 TokenKind::RightParen,
658 TokenKind::Semicolon,
659 TokenKind::Eof,
660 ]
661 );
662 }
663
664 #[test]
665 fn inserts_semicolon_before_close_brace() {
666 assert_eq!(
667 asi_kinds("{ a\n}"),
668 vec![
669 TokenKind::LeftBrace,
670 TokenKind::Identifier,
671 TokenKind::Semicolon,
672 TokenKind::RightBrace,
673 TokenKind::Semicolon,
674 TokenKind::Eof,
675 ]
676 );
677 }
678
679 #[test]
680 fn empty_block_no_semicolon_inside() {
681 assert_eq!(
682 asi_kinds("{\n}"),
683 vec![
684 TokenKind::LeftBrace,
685 TokenKind::RightBrace,
686 TokenKind::Semicolon,
687 TokenKind::Eof,
688 ]
689 );
690 }
691
692 #[test]
693 fn does_not_double_semicolon() {
694 assert_eq!(
695 asi_kinds("a;\nb;\n"),
696 vec![
697 TokenKind::Identifier,
698 TokenKind::Semicolon,
699 TokenKind::Identifier,
700 TokenKind::Semicolon,
701 TokenKind::Eof,
702 ]
703 );
704 }
705
706 #[test]
707 fn continues_across_newline_with_infix_operator() {
708 assert_eq!(
709 asi_kinds("a = b\n+ c"),
710 vec![
711 TokenKind::Identifier,
712 TokenKind::Equals,
713 TokenKind::Identifier,
714 TokenKind::Plus,
715 TokenKind::Identifier,
716 TokenKind::Semicolon,
717 TokenKind::Eof,
718 ]
719 );
720 }
721
722 #[test]
723 fn continues_across_newline_with_dot() {
724 assert_eq!(
725 asi_kinds("foo\n.bar"),
726 vec![
727 TokenKind::Identifier,
728 TokenKind::Dot,
729 TokenKind::Identifier,
730 TokenKind::Semicolon,
731 TokenKind::Eof,
732 ]
733 );
734 }
735
736 #[test]
737 fn inserts_semicolon_at_eof() {
738 assert_eq!(
739 asi_kinds("a"),
740 vec![TokenKind::Identifier, TokenKind::Semicolon, TokenKind::Eof,]
741 );
742 }
743
744 #[test]
745 fn eof_with_trailing_semicolon_no_double() {
746 assert_eq!(
747 asi_kinds("a;"),
748 vec![TokenKind::Identifier, TokenKind::Semicolon, TokenKind::Eof,]
749 );
750 }
751
752 #[test]
753 fn no_semicolon_inside_parens() {
754 assert_eq!(
755 asi_kinds("foo(\n a,\n b\n)"),
756 vec![
757 TokenKind::Identifier,
758 TokenKind::LeftParen,
759 TokenKind::Identifier,
760 TokenKind::Comma,
761 TokenKind::Identifier,
762 TokenKind::RightParen,
763 TokenKind::Semicolon,
764 TokenKind::Eof,
765 ]
766 );
767 }
768
769 #[test]
770 fn no_semicolon_after_if_header_on_next_line() {
771 assert_eq!(
774 asi_kinds("if (a)\nb"),
775 vec![
776 TokenKind::If,
777 TokenKind::LeftParen,
778 TokenKind::Identifier,
779 TokenKind::RightParen,
780 TokenKind::Identifier,
781 TokenKind::Semicolon,
782 TokenKind::Eof,
783 ]
784 );
785 }
786
787 #[test]
788 fn no_semicolon_after_while_header_on_next_line() {
789 assert_eq!(
790 asi_kinds("while (a)\nb"),
791 vec![
792 TokenKind::While,
793 TokenKind::LeftParen,
794 TokenKind::Identifier,
795 TokenKind::RightParen,
796 TokenKind::Identifier,
797 TokenKind::Semicolon,
798 TokenKind::Eof,
799 ]
800 );
801 }
802
803 #[test]
804 fn header_paren_only_suppresses_its_own_close() {
805 assert_eq!(
807 asi_kinds("if ((a))\nb"),
808 vec![
809 TokenKind::If,
810 TokenKind::LeftParen,
811 TokenKind::LeftParen,
812 TokenKind::Identifier,
813 TokenKind::RightParen,
814 TokenKind::RightParen,
815 TokenKind::Identifier,
816 TokenKind::Semicolon,
817 TokenKind::Eof,
818 ]
819 );
820 }
821
822 #[test]
823 fn call_close_paren_still_inserts_semicolon() {
824 assert_eq!(
826 asi_kinds("f()\ng()"),
827 vec![
828 TokenKind::Identifier,
829 TokenKind::LeftParen,
830 TokenKind::RightParen,
831 TokenKind::Semicolon,
832 TokenKind::Identifier,
833 TokenKind::LeftParen,
834 TokenKind::RightParen,
835 TokenKind::Semicolon,
836 TokenKind::Eof,
837 ]
838 );
839 }
840
841 #[test]
842 fn no_semicolon_inside_brackets() {
843 assert_eq!(
844 asi_kinds("[\n1,\n2\n]"),
845 vec![
846 TokenKind::LeftBracket,
847 TokenKind::NumberLiteral(1.0),
848 TokenKind::Comma,
849 TokenKind::NumberLiteral(2.0),
850 TokenKind::RightBracket,
851 TokenKind::Semicolon,
852 TokenKind::Eof,
853 ]
854 );
855 }
856
857 #[test]
858 fn else_continues_if_statement() {
859 assert_eq!(
860 asi_kinds("if (c) { }\nelse { }"),
861 vec![
862 TokenKind::If,
863 TokenKind::LeftParen,
864 TokenKind::Identifier,
865 TokenKind::RightParen,
866 TokenKind::LeftBrace,
867 TokenKind::RightBrace,
868 TokenKind::Else,
869 TokenKind::LeftBrace,
870 TokenKind::RightBrace,
871 TokenKind::Semicolon,
872 TokenKind::Eof,
873 ]
874 );
875 }
876
877 #[test]
878 fn catch_continues_try_statement() {
879 assert_eq!(
880 asi_kinds("try { }\ncatch (e) { }"),
881 vec![
882 TokenKind::Try,
883 TokenKind::LeftBrace,
884 TokenKind::RightBrace,
885 TokenKind::Catch,
886 TokenKind::LeftParen,
887 TokenKind::Identifier,
888 TokenKind::RightParen,
889 TokenKind::LeftBrace,
890 TokenKind::RightBrace,
891 TokenKind::Semicolon,
892 TokenKind::Eof,
893 ]
894 );
895 }
896
897 #[test]
898 fn finally_continues_try_statement() {
899 assert_eq!(
900 asi_kinds("try { }\nfinally { }"),
901 vec![
902 TokenKind::Try,
903 TokenKind::LeftBrace,
904 TokenKind::RightBrace,
905 TokenKind::Finally,
906 TokenKind::LeftBrace,
907 TokenKind::RightBrace,
908 TokenKind::Semicolon,
909 TokenKind::Eof,
910 ]
911 );
912 }
913
914 #[test]
915 fn no_semicolon_after_trailing_arrow() {
916 assert_eq!(
919 asi_kinds("const f = (x: number): number =>\nx + 1"),
920 vec![
921 TokenKind::Const,
922 TokenKind::Identifier,
923 TokenKind::Equals,
924 TokenKind::LeftParen,
925 TokenKind::Identifier,
926 TokenKind::Colon,
927 TokenKind::Identifier,
928 TokenKind::RightParen,
929 TokenKind::Colon,
930 TokenKind::Identifier,
931 TokenKind::Arrow,
932 TokenKind::Identifier,
933 TokenKind::Plus,
934 TokenKind::NumberLiteral(1.0),
935 TokenKind::Semicolon,
936 TokenKind::Eof,
937 ]
938 );
939 }
940
941 #[test]
942 fn arrow_block_body_still_gets_terminator() {
943 assert_eq!(
946 asi_kinds("const f = () => {\na\n}"),
947 vec![
948 TokenKind::Const,
949 TokenKind::Identifier,
950 TokenKind::Equals,
951 TokenKind::LeftParen,
952 TokenKind::RightParen,
953 TokenKind::Arrow,
954 TokenKind::LeftBrace,
955 TokenKind::Identifier,
956 TokenKind::Semicolon,
957 TokenKind::RightBrace,
958 TokenKind::Semicolon,
959 TokenKind::Eof,
960 ]
961 );
962 }
963
964 #[test]
965 fn extends_continues_class_header() {
966 assert_eq!(
967 asi_kinds("class S\nextends B { }"),
968 vec![
969 TokenKind::Class,
970 TokenKind::Identifier,
971 TokenKind::Extends,
972 TokenKind::Identifier,
973 TokenKind::LeftBrace,
974 TokenKind::RightBrace,
975 TokenKind::Semicolon,
976 TokenKind::Eof,
977 ]
978 );
979 }
980
981 #[test]
982 fn no_semicolon_after_trailing_extends() {
983 assert_eq!(
984 asi_kinds("class S extends\nB { }"),
985 vec![
986 TokenKind::Class,
987 TokenKind::Identifier,
988 TokenKind::Extends,
989 TokenKind::Identifier,
990 TokenKind::LeftBrace,
991 TokenKind::RightBrace,
992 TokenKind::Semicolon,
993 TokenKind::Eof,
994 ]
995 );
996 }
997
998 #[test]
999 fn implements_continues_class_header() {
1000 assert_eq!(
1001 asi_kinds("class S\nimplements I { }"),
1002 vec![
1003 TokenKind::Class,
1004 TokenKind::Identifier,
1005 TokenKind::Implements,
1006 TokenKind::Identifier,
1007 TokenKind::LeftBrace,
1008 TokenKind::RightBrace,
1009 TokenKind::Semicolon,
1010 TokenKind::Eof,
1011 ]
1012 );
1013 }
1014
1015 #[test]
1016 fn no_semicolon_after_trailing_implements() {
1017 assert_eq!(
1018 asi_kinds("class S implements\nI { }"),
1019 vec![
1020 TokenKind::Class,
1021 TokenKind::Identifier,
1022 TokenKind::Implements,
1023 TokenKind::Identifier,
1024 TokenKind::LeftBrace,
1025 TokenKind::RightBrace,
1026 TokenKind::Semicolon,
1027 TokenKind::Eof,
1028 ]
1029 );
1030 }
1031
1032 #[test]
1033 fn optional_chain_continues_across_newline() {
1034 assert_eq!(
1036 asi_kinds("foo\n?.bar"),
1037 vec![
1038 TokenKind::Identifier,
1039 TokenKind::QuestionDot,
1040 TokenKind::Identifier,
1041 TokenKind::Semicolon,
1042 TokenKind::Eof,
1043 ]
1044 );
1045 }
1046
1047 #[test]
1048 fn empty_input() {
1049 assert_eq!(asi_kinds(""), vec![TokenKind::Eof]);
1050 }
1051
1052 #[test]
1053 fn leading_newlines_no_semicolon() {
1054 assert_eq!(
1055 asi_kinds("\n\na"),
1056 vec![TokenKind::Identifier, TokenKind::Semicolon, TokenKind::Eof,]
1057 );
1058 }
1059
1060 #[test]
1061 fn no_semicolon_in_multiline_object_literal() {
1062 assert_eq!(
1065 asi_kinds("x = {\na: 1,\nb: 2\n}"),
1066 vec![
1067 TokenKind::Identifier,
1068 TokenKind::Equals,
1069 TokenKind::LeftBrace,
1070 TokenKind::Identifier,
1071 TokenKind::Colon,
1072 TokenKind::NumberLiteral(1.0),
1073 TokenKind::Comma,
1074 TokenKind::Identifier,
1075 TokenKind::Colon,
1076 TokenKind::NumberLiteral(2.0),
1077 TokenKind::RightBrace,
1078 TokenKind::Semicolon,
1079 TokenKind::Eof,
1080 ]
1081 );
1082 }
1083
1084 #[test]
1085 fn no_semicolon_in_multiline_object_literal_trailing_comma() {
1086 assert_eq!(
1087 asi_kinds("x = {\na: 1,\n}"),
1088 vec![
1089 TokenKind::Identifier,
1090 TokenKind::Equals,
1091 TokenKind::LeftBrace,
1092 TokenKind::Identifier,
1093 TokenKind::Colon,
1094 TokenKind::NumberLiteral(1.0),
1095 TokenKind::Comma,
1096 TokenKind::RightBrace,
1097 TokenKind::Semicolon,
1098 TokenKind::Eof,
1099 ]
1100 );
1101 }
1102
1103 #[test]
1104 fn block_brace_still_gets_terminator() {
1105 assert_eq!(
1107 asi_kinds("{\na\n}"),
1108 vec![
1109 TokenKind::LeftBrace,
1110 TokenKind::Identifier,
1111 TokenKind::Semicolon,
1112 TokenKind::RightBrace,
1113 TokenKind::Semicolon,
1114 TokenKind::Eof,
1115 ]
1116 );
1117 }
1118
1119 #[test]
1120 fn type_literal_members_get_separators() {
1121 assert_eq!(
1125 asi_kinds("let p: {\na: number\nb: number\n}"),
1126 vec![
1127 TokenKind::Let,
1128 TokenKind::Identifier,
1129 TokenKind::Colon,
1130 TokenKind::LeftBrace,
1131 TokenKind::Identifier,
1132 TokenKind::Colon,
1133 TokenKind::Identifier,
1134 TokenKind::Semicolon,
1135 TokenKind::Identifier,
1136 TokenKind::Colon,
1137 TokenKind::Identifier,
1138 TokenKind::RightBrace,
1139 TokenKind::Semicolon,
1140 TokenKind::Eof,
1141 ]
1142 );
1143 }
1144
1145 #[test]
1146 fn nested_object_value_suppresses_inner_close() {
1147 assert_eq!(
1149 asi_kinds("x = {\nouter: {\ninner: 1\n}\n}"),
1150 vec![
1151 TokenKind::Identifier,
1152 TokenKind::Equals,
1153 TokenKind::LeftBrace,
1154 TokenKind::Identifier,
1155 TokenKind::Colon,
1156 TokenKind::LeftBrace,
1157 TokenKind::Identifier,
1158 TokenKind::Colon,
1159 TokenKind::NumberLiteral(1.0),
1160 TokenKind::RightBrace,
1161 TokenKind::RightBrace,
1162 TokenKind::Semicolon,
1163 TokenKind::Eof,
1164 ]
1165 );
1166 }
1167
1168 #[test]
1169 fn multiline_destructuring_pattern_no_semicolon() {
1170 assert_eq!(
1171 asi_kinds("const {\na,\nb\n} = p"),
1172 vec![
1173 TokenKind::Const,
1174 TokenKind::LeftBrace,
1175 TokenKind::Identifier,
1176 TokenKind::Comma,
1177 TokenKind::Identifier,
1178 TokenKind::RightBrace,
1179 TokenKind::Equals,
1180 TokenKind::Identifier,
1181 TokenKind::Semicolon,
1182 TokenKind::Eof,
1183 ]
1184 );
1185 }
1186
1187 #[test]
1188 fn no_semicolon_after_trailing_equals() {
1189 assert_eq!(
1192 asi_kinds("const x =\n5"),
1193 vec![
1194 TokenKind::Const,
1195 TokenKind::Identifier,
1196 TokenKind::Equals,
1197 TokenKind::NumberLiteral(5.0),
1198 TokenKind::Semicolon,
1199 TokenKind::Eof,
1200 ]
1201 );
1202 }
1203
1204 #[test]
1205 fn no_semicolon_after_trailing_binary_operator() {
1206 assert_eq!(
1207 asi_kinds("const x = 1 +\n2"),
1208 vec![
1209 TokenKind::Const,
1210 TokenKind::Identifier,
1211 TokenKind::Equals,
1212 TokenKind::NumberLiteral(1.0),
1213 TokenKind::Plus,
1214 TokenKind::NumberLiteral(2.0),
1215 TokenKind::Semicolon,
1216 TokenKind::Eof,
1217 ]
1218 );
1219 }
1220
1221 #[test]
1222 fn no_semicolon_after_trailing_assignment_reassign() {
1223 assert_eq!(
1224 asi_kinds("x =\n5"),
1225 vec![
1226 TokenKind::Identifier,
1227 TokenKind::Equals,
1228 TokenKind::NumberLiteral(5.0),
1229 TokenKind::Semicolon,
1230 TokenKind::Eof,
1231 ]
1232 );
1233 }
1234
1235 #[test]
1236 fn no_semicolon_after_trailing_compound_assignment() {
1237 assert_eq!(
1238 asi_kinds("x +=\n5"),
1239 vec![
1240 TokenKind::Identifier,
1241 TokenKind::PlusEquals,
1242 TokenKind::NumberLiteral(5.0),
1243 TokenKind::Semicolon,
1244 TokenKind::Eof,
1245 ]
1246 );
1247 }
1248
1249 #[test]
1250 fn no_semicolon_after_trailing_logical_operator() {
1251 assert_eq!(
1252 asi_kinds("true &&\nfalse"),
1253 vec![
1254 TokenKind::BooleanLiteral(true),
1255 TokenKind::AmpAmp,
1256 TokenKind::BooleanLiteral(false),
1257 TokenKind::Semicolon,
1258 TokenKind::Eof,
1259 ]
1260 );
1261 }
1262
1263 #[test]
1264 fn no_semicolon_after_trailing_nullish_coalescing() {
1265 assert_eq!(
1266 asi_kinds("a ??\nb"),
1267 vec![
1268 TokenKind::Identifier,
1269 TokenKind::QuestionQuestion,
1270 TokenKind::Identifier,
1271 TokenKind::Semicolon,
1272 TokenKind::Eof,
1273 ]
1274 );
1275 }
1276
1277 #[test]
1278 fn no_semicolon_before_leading_ternary() {
1279 assert_eq!(
1282 asi_kinds("cond\n? 1 : 2"),
1283 vec![
1284 TokenKind::Identifier,
1285 TokenKind::Question,
1286 TokenKind::NumberLiteral(1.0),
1287 TokenKind::Colon,
1288 TokenKind::NumberLiteral(2.0),
1289 TokenKind::Semicolon,
1290 TokenKind::Eof,
1291 ]
1292 );
1293 }
1294
1295 #[test]
1296 fn no_semicolon_after_trailing_ternary_question_and_colon() {
1297 assert_eq!(
1298 asi_kinds("cond ?\n1 :\n2"),
1299 vec![
1300 TokenKind::Identifier,
1301 TokenKind::Question,
1302 TokenKind::NumberLiteral(1.0),
1303 TokenKind::Colon,
1304 TokenKind::NumberLiteral(2.0),
1305 TokenKind::Semicolon,
1306 TokenKind::Eof,
1307 ]
1308 );
1309 }
1310
1311 #[test]
1312 fn no_semicolon_in_multiline_operator_chain() {
1313 assert_eq!(
1314 asi_kinds("const x = a +\nb +\nc"),
1315 vec![
1316 TokenKind::Const,
1317 TokenKind::Identifier,
1318 TokenKind::Equals,
1319 TokenKind::Identifier,
1320 TokenKind::Plus,
1321 TokenKind::Identifier,
1322 TokenKind::Plus,
1323 TokenKind::Identifier,
1324 TokenKind::Semicolon,
1325 TokenKind::Eof,
1326 ]
1327 );
1328 }
1329
1330 #[test]
1331 fn return_keeps_restricted_production() {
1332 assert_eq!(
1335 asi_kinds("return\n5"),
1336 vec![
1337 TokenKind::Return,
1338 TokenKind::Semicolon,
1339 TokenKind::NumberLiteral(5.0),
1340 TokenKind::Semicolon,
1341 TokenKind::Eof,
1342 ]
1343 );
1344 }
1345
1346 fn asi_marked(source: &str) -> String {
1353 let mut asi = Asi::new(source, crate::FileId(0));
1354 let mut inserted: Vec<usize> = Vec::new();
1355 loop {
1356 let tok = asi.next_token();
1357 if tok.kind == TokenKind::Eof {
1358 break;
1359 }
1360 if tok.kind == TokenKind::Semicolon && tok.span.start == tok.span.end {
1362 inserted.push(tok.span.start as usize);
1363 }
1364 }
1365 let mut out = String::new();
1366 let mut last = 0;
1367 for at in inserted {
1368 out.push_str(&source[last..at]);
1369 out.push_str("<;>");
1370 last = at;
1371 }
1372 out.push_str(&source[last..]);
1373 out
1374 }
1375
1376 #[test]
1377 fn clause_keyword_before_its_brace_gets_no_terminator() {
1378 assert_eq!(
1381 asi_marked("try\n{\na\n}\ncatch (e)\n{\n}"),
1382 "try\n{\na\n<;>}\ncatch (e)\n{\n}<;>"
1383 );
1384 assert_eq!(
1385 asi_marked("if (c)\n{\n}\nelse\n{\n}"),
1386 "if (c)\n{\n}\nelse\n{\n}<;>"
1387 );
1388 assert_eq!(asi_marked("do\n{\n}\nwhile (c)"), "do\n{\n}\n<;>while (c)");
1394 }
1395
1396 #[test]
1397 fn a_newline_before_a_clause_keyword_still_ends_the_statement() {
1398 assert_eq!(asi_marked("a\ntry\n{\n}"), "a\n<;>try\n{\n}<;>");
1401 assert_eq!(
1402 asi_marked("a\ndo\n{\n}\nwhile (c)"),
1403 "a\n<;>do\n{\n}\n<;>while (c)"
1404 );
1405 }
1406
1407 #[test]
1408 fn block_closing_brace_terminates_without_a_newline() {
1409 assert_eq!(
1410 asi_marked("function f(): number { return 1 }"),
1411 "function f(): number { return 1 <;>}<;>"
1412 );
1413 assert_eq!(
1414 asi_marked("class C { x: number = 1 }"),
1415 "class C { x: number = 1 <;>}<;>"
1416 );
1417 }
1418
1419 #[test]
1420 fn value_closing_brace_never_terminates() {
1421 assert_eq!(asi_marked("x = { a: 1 }"), "x = { a: 1 }<;>");
1422 assert_eq!(
1423 asi_marked("import { a } from \"m\""),
1424 "import { a } from \"m\"<;>"
1425 );
1426 assert_eq!(
1428 asi_marked("export type { A } from \"m\""),
1429 "export type { A } from \"m\"<;>"
1430 );
1431 }
1432
1433 #[test]
1434 fn only_a_after_case_label_colon_opens_a_block() {
1435 assert_eq!(
1438 asi_marked("switch (n) {\ncase 1: { a }\n}"),
1439 "switch (n) {\ncase 1: { a <;>}\n<;>}<;>"
1440 );
1441 assert_eq!(
1442 asi_marked("x = c ? { a: 1 } : { a: 2 }"),
1443 "x = c ? { a: 1 } : { a: 2 }<;>"
1444 );
1445 assert_eq!(
1446 asi_marked("x = { default: { a: 1 } }"),
1447 "x = { default: { a: 1 } }<;>"
1448 );
1449 }
1450
1451 #[test]
1452 fn a_keyword_property_name_is_not_a_keyword() {
1453 assert_eq!(asi_marked("a = o.do\nb = 1"), "a = o.do\n<;>b = 1<;>");
1456 assert_eq!(asi_marked("a = o.class\nb = 1"), "a = o.class\n<;>b = 1<;>");
1457 assert_eq!(
1458 asi_marked("a = o.default\nb = 1"),
1459 "a = o.default\n<;>b = 1<;>"
1460 );
1461 assert_eq!(asi_marked("a = o?.try\nb = 1"), "a = o?.try\n<;>b = 1<;>");
1462 assert_eq!(
1465 asi_marked("a = o.switch(1)\nb = 1"),
1466 "a = o.switch(1)\n<;>b = 1<;>"
1467 );
1468 assert_eq!(
1469 asi_marked("a = o.catch(1)\nb = 1"),
1470 "a = o.catch(1)\n<;>b = 1<;>"
1471 );
1472 }
1473
1474 #[test]
1475 fn a_generic_header_is_followed_by_a_block() {
1476 assert_eq!(
1478 asi_marked("function f(): Array<number> {\nreturn a\n}"),
1479 "function f(): Array<number> {\nreturn a\n<;>}<;>"
1480 );
1481 assert_eq!(
1482 asi_marked("class Box<T> {\nv: number = 1\n}"),
1483 "class Box<T> {\nv: number = 1\n<;>}<;>"
1484 );
1485 }
1486
1487 #[test]
1488 fn a_cast_is_followed_by_an_object_literal() {
1489 assert_eq!(
1491 asi_marked("x = <Foo>{ a: 1 }\ny()"),
1492 "x = <Foo>{ a: 1 }\n<;>y()<;>"
1493 );
1494 assert_eq!(asi_marked("<Foo>{ a: 1 }\ny()"), "<Foo>{ a: 1 }\n<;>y()<;>");
1495 assert_eq!(
1496 asi_marked("f = x => <Foo>{ a: x }\ny()"),
1497 "f = x => <Foo>{ a: x }\n<;>y()<;>"
1498 );
1499 assert_eq!(
1500 asi_marked("x = <Box<Array<number>>>{ v: [] }\ny()"),
1501 "x = <Box<Array<number>>>{ v: [] }\n<;>y()<;>"
1502 );
1503 assert_eq!(
1505 asi_marked("if (a < b) {\nreturn a\n}"),
1506 "if (a < b) {\nreturn a\n<;>}<;>"
1507 );
1508 }
1509
1510 #[test]
1511 fn a_spread_operand_is_an_object_literal() {
1512 assert_eq!(
1513 asi_marked("x = { ...{ a: 1 }, b: 2 }\ny()"),
1514 "x = { ...{ a: 1 }, b: 2 }\n<;>y()<;>"
1515 );
1516 }
1517
1518 #[test]
1519 fn template_substitution_suspends_insertion() {
1520 assert_eq!(
1521 asi_marked("const s = `a${\nn\n}`"),
1522 "const s = `a${\nn\n}`<;>"
1523 );
1524 assert_eq!(
1525 asi_marked("const s = `a${\nn\n}b${\nn\n}c`"),
1526 "const s = `a${\nn\n}b${\nn\n}c`<;>"
1527 );
1528 }
1529
1530 #[test]
1531 fn declaration_header_suspends_insertion() {
1532 assert_eq!(
1533 asi_marked("class S\nextends B\nimplements I\n{\n}"),
1534 "class S\nextends B\nimplements I\n{\n}<;>"
1535 );
1536 assert_eq!(asi_marked("interface I\n{\n}"), "interface I\n{\n}<;>");
1537 assert_eq!(asi_marked("enum E\n{\nA\n}"), "enum E\n{\nA\n<;>}<;>");
1538 }
1539
1540 #[test]
1541 fn block_inside_parens_is_statement_context_again() {
1542 assert_eq!(
1545 asi_marked("f((x: number): number => {\nreturn x\n})"),
1546 "f((x: number): number => {\nreturn x\n<;>})<;>"
1547 );
1548 assert_eq!(asi_marked("f(a,\nb)"), "f(a,\nb)<;>");
1549 }
1550
1551 #[test]
1552 fn mvp_fixture_snapshot() {
1553 let source = "function greet(name: string): string {\n return \"Hello, \" + name;\n}\n\nfunction main(): string {\n const msg = greet(\"world\");\n console.log(msg);\n assert(msg === \"Hello, world\", \"greeting should match\");\n return msg;\n}\n";
1554 let mut asi = Asi::new(source, crate::FileId(0));
1555 let mut tokens = Vec::new();
1556 loop {
1557 let tok = asi.next_token();
1558 let is_eof = tok.kind == TokenKind::Eof;
1559 tokens.push(tok);
1560 if is_eof {
1561 break;
1562 }
1563 }
1564 assert!(asi.into_diagnostics().is_empty());
1565 let kinds: Vec<String> = tokens.iter().map(|t| format!("{:?}", t.kind)).collect();
1566 insta::assert_debug_snapshot!(kinds);
1567 }
1568}