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 class_body: bool,
24}
25
26pub struct Asi<'a> {
28 source: &'a str,
29 lexer: Lexer<'a>,
30 buffered: Option<Token>,
31 lookahead: std::collections::VecDeque<Token>,
32 last_emitted: Option<TokenKind>,
33 bracket_depth: u32,
36 braces: Vec<BraceFrame>,
37 paren_is_header: Vec<bool>,
41 closed_header_paren: bool,
42 closed_block_brace: bool,
43 cast_angle_depth: u32,
48 closed_cast_angle: bool,
52 in_decl_header: bool,
56 class_header_depth: Option<usize>,
60 in_member_modifiers: bool,
63 last_is_member_static: bool,
66 last_is_member_name: bool,
71 in_case_label: bool,
74 after_case_label_colon: bool,
77 brace_opens_specifier_list: bool,
83}
84
85impl<'a> Asi<'a> {
86 pub fn new(source: &'a str, file: FileId) -> Self {
88 Self {
89 source,
90 lexer: Lexer::new(source, file),
91 buffered: None,
92 lookahead: std::collections::VecDeque::new(),
93 last_emitted: None,
94 bracket_depth: 0,
95 braces: Vec::new(),
96 paren_is_header: Vec::new(),
97 closed_header_paren: false,
98 closed_block_brace: false,
99 last_is_member_name: false,
100 in_case_label: false,
101 after_case_label_colon: false,
102 in_decl_header: false,
103 class_header_depth: None,
104 in_member_modifiers: false,
105 last_is_member_static: false,
106 brace_opens_specifier_list: false,
107 cast_angle_depth: 0,
108 closed_cast_angle: false,
109 }
110 }
111
112 pub fn next_token(&mut self) -> Token {
113 if let Some(tok) = self.buffered.take() {
114 self.track_token(&tok);
115 return tok;
116 }
117
118 let mut pending_newline = false;
119 loop {
120 let tok = self
121 .lookahead
122 .pop_front()
123 .unwrap_or_else(|| self.lexer.next_token());
124 match tok.kind {
125 TokenKind::Newline => {
126 pending_newline = true;
127 }
128 TokenKind::Eof => {
129 if self.needs_semi() {
130 return self.insert_semicolon_before(tok);
131 }
132 self.track_token(&tok);
133 return tok;
134 }
135 _ => {
136 let boundary = match self.brace_closed_by(&tok.kind) {
140 Some(BraceKind::Value | BraceKind::MemberList) => false,
141 Some(BraceKind::Block) => true,
142 None => pending_newline,
143 };
144 if boundary
145 && !self.inside_brackets()
146 && !self.can_continue_here(&tok.kind)
147 && !(self.last_is_member_static && can_follow_modifier(&tok.kind))
148 && self.needs_semi()
149 {
150 return self.insert_semicolon_before(tok);
151 }
152 self.track_token(&tok);
153 return tok;
154 }
155 }
156 }
157 }
158
159 fn can_continue_here(&mut self, kind: &TokenKind) -> bool {
160 if matches!(
161 kind,
162 TokenKind::Else
163 | TokenKind::Catch
164 | TokenKind::Finally
165 | TokenKind::Extends
166 | TokenKind::Implements
167 ) {
168 loop {
171 let next = self.lexer.next_token();
172 let newline = matches!(next.kind, TokenKind::Newline);
173 let member = matches!(next.kind, TokenKind::Colon | TokenKind::Question);
174 self.lookahead.push_back(next);
175 if !newline {
176 return !member;
177 }
178 }
179 }
180 can_continue(kind)
181 }
182
183 pub fn finish(self) -> Result<Vec<Diagnostic>, crate::compiler_error::CompileError> {
184 self.lexer.finish()
185 }
186
187 pub fn into_diagnostics(self) -> Vec<Diagnostic> {
188 self.lexer.into_diagnostics()
189 }
190
191 fn insert_semicolon_before(&mut self, tok: Token) -> Token {
193 let pos = tok.span.start;
194 let file = tok.span.file;
195 self.buffered = Some(tok);
196 let semi = Token::new(
197 TokenKind::Semicolon,
198 Span {
199 file,
200 start: pos,
201 end: pos,
202 },
203 );
204 self.track(&semi.kind);
205 semi
206 }
207
208 fn needs_semi(&self) -> bool {
209 if self.closed_header_paren || self.in_decl_header {
212 return false;
213 }
214 match &self.last_emitted {
215 None => false,
216 Some(kind) => self.last_is_member_name || !cannot_end_statement(kind),
217 }
218 }
219
220 fn at_statement_start(&self) -> bool {
223 matches!(
224 self.last_emitted,
225 None | Some(
226 TokenKind::LeftBrace
227 | TokenKind::RightBrace
228 | TokenKind::Semicolon
229 | TokenKind::Colon
230 )
231 )
232 }
233
234 fn brace_closed_by(&self, next: &TokenKind) -> Option<BraceKind> {
238 if !matches!(next, TokenKind::RightBrace) {
239 return None;
240 }
241 self.braces.last().map(|frame| frame.kind)
242 }
243
244 fn inside_brackets(&self) -> bool {
246 self.bracket_depth > self.braces.last().map_or(0, |frame| frame.bracket_base)
247 }
248
249 fn classify_brace(&self) -> BraceKind {
254 if self.brace_opens_specifier_list {
255 return BraceKind::Value;
256 }
257 if self.after_case_label_colon {
258 return BraceKind::Block;
259 }
260 if self.closed_cast_angle {
261 return BraceKind::Value;
262 }
263 match &self.last_emitted {
264 Some(TokenKind::Colon) => BraceKind::MemberList,
267 Some(kind) if opens_value_brace(kind) => BraceKind::Value,
268 _ => BraceKind::Block,
269 }
270 }
271
272 fn push_brace(&mut self) {
273 let kind = self.classify_brace();
274 let bracket_base = match kind {
275 BraceKind::Block | BraceKind::MemberList => self.bracket_depth,
276 BraceKind::Value => self.braces.last().map_or(0, |frame| frame.bracket_base),
277 };
278 let class_body = self.class_header_depth == Some(self.braces.len())
279 && matches!(
280 self.last_emitted,
281 Some(TokenKind::Identifier | TokenKind::GreaterThan)
282 );
283 if class_body {
284 self.class_header_depth = None;
285 }
286 self.braces.push(BraceFrame {
287 kind,
288 bracket_base,
289 class_body,
290 });
291 }
292
293 fn track_token(&mut self, tok: &Token) {
295 let at_member_start = self.braces.last().is_some_and(|frame| frame.class_body)
296 && !self.inside_brackets()
297 && matches!(
298 self.last_emitted,
299 Some(TokenKind::LeftBrace | TokenKind::RightBrace | TokenKind::Semicolon)
300 );
301 let word = match tok.kind {
302 TokenKind::Identifier => self
303 .source
304 .get(tok.span.start as usize..tok.span.end as usize),
305 _ => None,
306 };
307 let modifier = word.is_some_and(|word| CLASS_MEMBER_MODIFIERS.contains(&word));
308 let in_member_modifiers = modifier && (at_member_start || self.in_member_modifiers);
309 self.track(&tok.kind);
310 self.in_member_modifiers = in_member_modifiers;
311 self.last_is_member_static = in_member_modifiers && word == Some("static");
312 }
313
314 fn track(&mut self, kind: &TokenKind) {
322 self.correct_bang_context(kind);
323 let closed_block = self.brace_closed_by(kind) == Some(BraceKind::Block);
324 let closed_header = self.track_brackets(kind);
325 let closed_cast = self.track_cast_angles(kind);
326 self.after_case_label_colon = self.next_after_case_label_colon(kind);
327 self.in_case_label = self.next_in_case_label(kind);
328 self.in_decl_header = self.next_in_decl_header(kind);
329 match kind {
330 TokenKind::Class if self.in_decl_header => {
331 self.class_header_depth = Some(self.braces.len());
332 }
333 TokenKind::Semicolon if self.class_header_depth == Some(self.braces.len()) => {
334 self.class_header_depth = None;
335 }
336 _ => {}
337 }
338 self.in_member_modifiers = false;
339 self.last_is_member_static = false;
340 self.brace_opens_specifier_list = self.next_brace_opens_specifier_list(kind);
341 self.last_is_member_name = self.next_last_is_member_name();
342 self.closed_header_paren = closed_header;
343 self.closed_block_brace = closed_block;
344 self.closed_cast_angle = closed_cast;
345 self.last_emitted = Some(kind.clone());
346 }
347
348 fn track_cast_angles(&mut self, kind: &TokenKind) -> bool {
350 match kind {
351 TokenKind::LessThan if self.cast_angle_depth > 0 => self.cast_angle_depth += 1,
352 TokenKind::LessThan
353 if self.expects_operand()
354 && !matches!(self.last_emitted, Some(TokenKind::LessThan)) =>
355 {
356 self.cast_angle_depth = 1;
357 }
358 TokenKind::GreaterThan if self.cast_angle_depth > 0 => {
359 self.cast_angle_depth -= 1;
360 return self.cast_angle_depth == 0;
361 }
362 _ => {}
363 }
364 false
365 }
366
367 fn expects_operand(&self) -> bool {
370 self.at_statement_start()
371 || self
372 .last_emitted
373 .as_ref()
374 .is_some_and(|kind| opens_value_brace(kind) || matches!(kind, TokenKind::Arrow))
377 }
378
379 fn correct_bang_context(&mut self, kind: &TokenKind) {
381 if !matches!(kind, TokenKind::Bang) {
382 return;
383 }
384 if self.last_is_member_name {
385 self.lexer.set_bang_is_postfix(true);
386 } else if self.closed_header_paren
387 || self.closed_block_brace
388 || matches!(self.last_emitted, Some(TokenKind::Semicolon))
389 {
390 self.lexer.set_bang_is_postfix(false);
391 }
392 }
393
394 fn track_brackets(&mut self, kind: &TokenKind) -> bool {
396 match kind {
397 TokenKind::LeftParen => {
398 self.bracket_depth += 1;
399 self.paren_is_header.push(self.opens_control_header());
400 }
401 TokenKind::LeftBracket | TokenKind::TemplateHead(_) => self.bracket_depth += 1,
404 TokenKind::RightParen => {
405 self.bracket_depth = self.bracket_depth.saturating_sub(1);
406 return self.paren_is_header.pop().unwrap_or(false);
407 }
408 TokenKind::RightBracket | TokenKind::TemplateTail(_) => {
409 self.bracket_depth = self.bracket_depth.saturating_sub(1);
410 }
411 TokenKind::LeftBrace => self.push_brace(),
412 TokenKind::RightBrace => {
413 self.braces.pop();
414 }
415 _ => {}
416 }
417 false
418 }
419
420 fn next_after_case_label_colon(&self, kind: &TokenKind) -> bool {
421 matches!(kind, TokenKind::Colon) && self.in_case_label
422 }
423
424 fn next_in_case_label(&self, kind: &TokenKind) -> bool {
425 match kind {
426 TokenKind::Case | TokenKind::Default => {
430 self.at_statement_start()
431 && self.braces.last().map(|frame| frame.kind) == Some(BraceKind::Block)
432 }
433 TokenKind::Colon
437 | TokenKind::Semicolon
438 | TokenKind::Comma
439 | TokenKind::LeftBrace
440 | TokenKind::RightBrace
441 | TokenKind::LeftParen
442 | TokenKind::RightParen => false,
443 _ => self.in_case_label,
444 }
445 }
446
447 fn next_in_decl_header(&self, kind: &TokenKind) -> bool {
448 match kind {
449 TokenKind::Class | TokenKind::Interface | TokenKind::Enum => {
452 self.at_statement_start() || matches!(self.last_emitted, Some(TokenKind::Export))
453 }
454 TokenKind::LeftBrace
455 | TokenKind::RightBrace
456 | TokenKind::Semicolon
457 | TokenKind::Colon
458 | TokenKind::Comma => false,
459 _ => self.in_decl_header,
460 }
461 }
462
463 fn next_brace_opens_specifier_list(&self, kind: &TokenKind) -> bool {
464 match kind {
465 TokenKind::Import | TokenKind::Export => true,
466 TokenKind::Identifier => self.brace_opens_specifier_list,
469 _ => false,
470 }
471 }
472
473 fn next_last_is_member_name(&self) -> bool {
474 matches!(
475 self.last_emitted,
476 Some(TokenKind::Dot | TokenKind::QuestionDot)
477 )
478 }
479
480 fn opens_control_header(&self) -> bool {
486 !self.last_is_member_name
487 && matches!(
488 self.last_emitted,
489 Some(
490 TokenKind::If
491 | TokenKind::While
492 | TokenKind::For
493 | TokenKind::Switch
494 | TokenKind::Catch
495 )
496 )
497 }
498}
499
500const CLASS_MEMBER_MODIFIERS: &[&str] = &[
508 "public",
509 "private",
510 "protected",
511 "readonly",
512 "static",
513 "override",
514 "declare",
515 "abstract",
516 "accessor",
517];
518
519fn can_follow_modifier(kind: &TokenKind) -> bool {
522 crate::parser::is_property_name(kind)
523 || matches!(
524 kind,
525 TokenKind::StringLiteral(_)
526 | TokenKind::NumberLiteral(_)
527 | TokenKind::BigIntLiteral(_)
528 | TokenKind::LeftBracket
529 | TokenKind::LeftBrace
530 | TokenKind::Star
531 )
532}
533
534fn cannot_end_statement(kind: &TokenKind) -> bool {
539 matches!(
540 kind,
541 TokenKind::Semicolon
542 | TokenKind::LeftBrace
543 | TokenKind::LeftParen
544 | TokenKind::LeftBracket
545 | TokenKind::Comma
546 ) || awaits_operand(kind)
547 || awaits_body(kind)
548}
549
550fn cannot_end_expression(kind: &TokenKind) -> bool {
558 matches!(
559 kind,
560 TokenKind::Equals
561 | TokenKind::PlusEquals
562 | TokenKind::MinusEquals
563 | TokenKind::StarEquals
564 | TokenKind::SlashEquals
565 | TokenKind::PercentEquals
566 | TokenKind::StarStarEquals
567 | TokenKind::Plus
568 | TokenKind::Minus
569 | TokenKind::Star
570 | TokenKind::Slash
571 | TokenKind::Percent
572 | TokenKind::StarStar
573 | TokenKind::EqEqEq
574 | TokenKind::EqEq
575 | TokenKind::BangEqEq
576 | TokenKind::BangEq
577 | TokenKind::LessThan
578 | TokenKind::GreaterThan
579 | TokenKind::LessEquals
580 | TokenKind::GreaterEquals
581 | TokenKind::AmpAmp
582 | TokenKind::Amp
583 | TokenKind::AmpEquals
584 | TokenKind::PipeEquals
585 | TokenKind::Caret
586 | TokenKind::CaretEquals
587 | TokenKind::Pipe
588 | TokenKind::PipePipe
589 | TokenKind::Question
590 | TokenKind::QuestionQuestion
591 | TokenKind::Colon
592 )
593}
594
595fn awaits_operand(kind: &TokenKind) -> bool {
600 cannot_end_expression(kind)
601 || matches!(
602 kind,
603 TokenKind::Arrow
604 | TokenKind::Tilde
605 | TokenKind::Extends
608 | TokenKind::Implements
609 )
610}
611
612fn awaits_body(kind: &TokenKind) -> bool {
619 matches!(
620 kind,
621 TokenKind::Try | TokenKind::Else | TokenKind::Do | TokenKind::Finally | TokenKind::Catch
622 )
623}
624
625fn opens_value_brace(kind: &TokenKind) -> bool {
630 if matches!(kind, TokenKind::GreaterThan) {
634 return false;
635 }
636 cannot_end_expression(kind)
637 || matches!(
638 kind,
639 TokenKind::LeftParen
640 | TokenKind::Tilde
641 | TokenKind::LeftBracket
642 | TokenKind::Comma
643 | TokenKind::DotDotDot
645 | TokenKind::Return
646 | TokenKind::Throw
647 | TokenKind::Const
648 | TokenKind::Let
649 | TokenKind::TemplateHead(_)
650 | TokenKind::TemplateMiddle(_)
651 )
652}
653
654fn can_continue(kind: &TokenKind) -> bool {
662 if matches!(kind, TokenKind::Tilde) {
664 return false;
665 }
666 awaits_operand(kind)
667 || matches!(
668 kind,
669 TokenKind::Dot
670 | TokenKind::QuestionDot
672 | TokenKind::LeftParen
673 | TokenKind::LeftBracket
674 | TokenKind::Else
675 | TokenKind::Catch
678 | TokenKind::Finally
679 | TokenKind::Extends
682 | TokenKind::Implements
683 | TokenKind::TemplateMiddle(_)
687 | TokenKind::TemplateTail(_)
688 )
689}
690
691#[cfg(test)]
692mod tests {
693 use super::Asi;
694 use crate::TokenKind;
695
696 fn asi_kinds(source: &str) -> Vec<TokenKind> {
697 let mut asi = Asi::new(source, crate::FileId(0));
698 let mut kinds = Vec::new();
699 loop {
700 let tok = asi.next_token();
701 let is_eof = tok.kind == TokenKind::Eof;
702 kinds.push(tok.kind);
703 if is_eof {
704 break;
705 }
706 }
707 let diags = asi.into_diagnostics();
708 assert!(
709 diags.is_empty(),
710 "unexpected diagnostics for {source:?}: {diags:?}"
711 );
712 kinds
713 }
714
715 #[test]
716 fn strips_all_newlines() {
717 let kinds = asi_kinds("a\nb\n");
718 assert!(!kinds.iter().any(|k| matches!(k, TokenKind::Newline)));
719 }
720
721 #[test]
722 fn inserts_semicolon_between_statements() {
723 assert_eq!(
724 asi_kinds("a\nb"),
725 vec![
726 TokenKind::Identifier,
727 TokenKind::Semicolon,
728 TokenKind::Identifier,
729 TokenKind::Semicolon,
730 TokenKind::Eof,
731 ]
732 );
733 }
734
735 #[test]
736 fn newline_before_as_splits_statement() {
737 assert_eq!(
740 asi_kinds("a\nas(b)"),
741 vec![
742 TokenKind::Identifier,
743 TokenKind::Semicolon,
744 TokenKind::Identifier,
745 TokenKind::LeftParen,
746 TokenKind::Identifier,
747 TokenKind::RightParen,
748 TokenKind::Semicolon,
749 TokenKind::Eof,
750 ]
751 );
752 }
753
754 #[test]
755 fn inserts_semicolon_before_close_brace() {
756 assert_eq!(
757 asi_kinds("{ a\n}"),
758 vec![
759 TokenKind::LeftBrace,
760 TokenKind::Identifier,
761 TokenKind::Semicolon,
762 TokenKind::RightBrace,
763 TokenKind::Semicolon,
764 TokenKind::Eof,
765 ]
766 );
767 }
768
769 #[test]
770 fn empty_block_no_semicolon_inside() {
771 assert_eq!(
772 asi_kinds("{\n}"),
773 vec![
774 TokenKind::LeftBrace,
775 TokenKind::RightBrace,
776 TokenKind::Semicolon,
777 TokenKind::Eof,
778 ]
779 );
780 }
781
782 #[test]
783 fn does_not_double_semicolon() {
784 assert_eq!(
785 asi_kinds("a;\nb;\n"),
786 vec![
787 TokenKind::Identifier,
788 TokenKind::Semicolon,
789 TokenKind::Identifier,
790 TokenKind::Semicolon,
791 TokenKind::Eof,
792 ]
793 );
794 }
795
796 #[test]
797 fn continues_across_newline_with_infix_operator() {
798 assert_eq!(
799 asi_kinds("a = b\n+ c"),
800 vec![
801 TokenKind::Identifier,
802 TokenKind::Equals,
803 TokenKind::Identifier,
804 TokenKind::Plus,
805 TokenKind::Identifier,
806 TokenKind::Semicolon,
807 TokenKind::Eof,
808 ]
809 );
810 }
811
812 #[test]
813 fn continues_across_newline_with_dot() {
814 assert_eq!(
815 asi_kinds("foo\n.bar"),
816 vec![
817 TokenKind::Identifier,
818 TokenKind::Dot,
819 TokenKind::Identifier,
820 TokenKind::Semicolon,
821 TokenKind::Eof,
822 ]
823 );
824 }
825
826 #[test]
827 fn inserts_semicolon_at_eof() {
828 assert_eq!(
829 asi_kinds("a"),
830 vec![TokenKind::Identifier, TokenKind::Semicolon, TokenKind::Eof,]
831 );
832 }
833
834 #[test]
835 fn eof_with_trailing_semicolon_no_double() {
836 assert_eq!(
837 asi_kinds("a;"),
838 vec![TokenKind::Identifier, TokenKind::Semicolon, TokenKind::Eof,]
839 );
840 }
841
842 #[test]
843 fn no_semicolon_inside_parens() {
844 assert_eq!(
845 asi_kinds("foo(\n a,\n b\n)"),
846 vec![
847 TokenKind::Identifier,
848 TokenKind::LeftParen,
849 TokenKind::Identifier,
850 TokenKind::Comma,
851 TokenKind::Identifier,
852 TokenKind::RightParen,
853 TokenKind::Semicolon,
854 TokenKind::Eof,
855 ]
856 );
857 }
858
859 #[test]
860 fn no_semicolon_after_if_header_on_next_line() {
861 assert_eq!(
864 asi_kinds("if (a)\nb"),
865 vec![
866 TokenKind::If,
867 TokenKind::LeftParen,
868 TokenKind::Identifier,
869 TokenKind::RightParen,
870 TokenKind::Identifier,
871 TokenKind::Semicolon,
872 TokenKind::Eof,
873 ]
874 );
875 }
876
877 #[test]
878 fn no_semicolon_after_while_header_on_next_line() {
879 assert_eq!(
880 asi_kinds("while (a)\nb"),
881 vec![
882 TokenKind::While,
883 TokenKind::LeftParen,
884 TokenKind::Identifier,
885 TokenKind::RightParen,
886 TokenKind::Identifier,
887 TokenKind::Semicolon,
888 TokenKind::Eof,
889 ]
890 );
891 }
892
893 #[test]
894 fn header_paren_only_suppresses_its_own_close() {
895 assert_eq!(
897 asi_kinds("if ((a))\nb"),
898 vec![
899 TokenKind::If,
900 TokenKind::LeftParen,
901 TokenKind::LeftParen,
902 TokenKind::Identifier,
903 TokenKind::RightParen,
904 TokenKind::RightParen,
905 TokenKind::Identifier,
906 TokenKind::Semicolon,
907 TokenKind::Eof,
908 ]
909 );
910 }
911
912 #[test]
913 fn call_close_paren_still_inserts_semicolon() {
914 assert_eq!(
916 asi_kinds("f()\ng()"),
917 vec![
918 TokenKind::Identifier,
919 TokenKind::LeftParen,
920 TokenKind::RightParen,
921 TokenKind::Semicolon,
922 TokenKind::Identifier,
923 TokenKind::LeftParen,
924 TokenKind::RightParen,
925 TokenKind::Semicolon,
926 TokenKind::Eof,
927 ]
928 );
929 }
930
931 #[test]
932 fn no_semicolon_inside_brackets() {
933 assert_eq!(
934 asi_kinds("[\n1,\n2\n]"),
935 vec![
936 TokenKind::LeftBracket,
937 TokenKind::NumberLiteral(1.0),
938 TokenKind::Comma,
939 TokenKind::NumberLiteral(2.0),
940 TokenKind::RightBracket,
941 TokenKind::Semicolon,
942 TokenKind::Eof,
943 ]
944 );
945 }
946
947 #[test]
948 fn else_continues_if_statement() {
949 assert_eq!(
950 asi_kinds("if (c) { }\nelse { }"),
951 vec![
952 TokenKind::If,
953 TokenKind::LeftParen,
954 TokenKind::Identifier,
955 TokenKind::RightParen,
956 TokenKind::LeftBrace,
957 TokenKind::RightBrace,
958 TokenKind::Else,
959 TokenKind::LeftBrace,
960 TokenKind::RightBrace,
961 TokenKind::Semicolon,
962 TokenKind::Eof,
963 ]
964 );
965 }
966
967 #[test]
968 fn catch_continues_try_statement() {
969 assert_eq!(
970 asi_kinds("try { }\ncatch (e) { }"),
971 vec![
972 TokenKind::Try,
973 TokenKind::LeftBrace,
974 TokenKind::RightBrace,
975 TokenKind::Catch,
976 TokenKind::LeftParen,
977 TokenKind::Identifier,
978 TokenKind::RightParen,
979 TokenKind::LeftBrace,
980 TokenKind::RightBrace,
981 TokenKind::Semicolon,
982 TokenKind::Eof,
983 ]
984 );
985 }
986
987 #[test]
988 fn finally_continues_try_statement() {
989 assert_eq!(
990 asi_kinds("try { }\nfinally { }"),
991 vec![
992 TokenKind::Try,
993 TokenKind::LeftBrace,
994 TokenKind::RightBrace,
995 TokenKind::Finally,
996 TokenKind::LeftBrace,
997 TokenKind::RightBrace,
998 TokenKind::Semicolon,
999 TokenKind::Eof,
1000 ]
1001 );
1002 }
1003
1004 #[test]
1005 fn no_semicolon_after_trailing_arrow() {
1006 assert_eq!(
1009 asi_kinds("const f = (x: number): number =>\nx + 1"),
1010 vec![
1011 TokenKind::Const,
1012 TokenKind::Identifier,
1013 TokenKind::Equals,
1014 TokenKind::LeftParen,
1015 TokenKind::Identifier,
1016 TokenKind::Colon,
1017 TokenKind::Identifier,
1018 TokenKind::RightParen,
1019 TokenKind::Colon,
1020 TokenKind::Identifier,
1021 TokenKind::Arrow,
1022 TokenKind::Identifier,
1023 TokenKind::Plus,
1024 TokenKind::NumberLiteral(1.0),
1025 TokenKind::Semicolon,
1026 TokenKind::Eof,
1027 ]
1028 );
1029 }
1030
1031 #[test]
1032 fn arrow_block_body_still_gets_terminator() {
1033 assert_eq!(
1036 asi_kinds("const f = () => {\na\n}"),
1037 vec![
1038 TokenKind::Const,
1039 TokenKind::Identifier,
1040 TokenKind::Equals,
1041 TokenKind::LeftParen,
1042 TokenKind::RightParen,
1043 TokenKind::Arrow,
1044 TokenKind::LeftBrace,
1045 TokenKind::Identifier,
1046 TokenKind::Semicolon,
1047 TokenKind::RightBrace,
1048 TokenKind::Semicolon,
1049 TokenKind::Eof,
1050 ]
1051 );
1052 }
1053
1054 #[test]
1055 fn extends_continues_class_header() {
1056 assert_eq!(
1057 asi_kinds("class S\nextends B { }"),
1058 vec![
1059 TokenKind::Class,
1060 TokenKind::Identifier,
1061 TokenKind::Extends,
1062 TokenKind::Identifier,
1063 TokenKind::LeftBrace,
1064 TokenKind::RightBrace,
1065 TokenKind::Semicolon,
1066 TokenKind::Eof,
1067 ]
1068 );
1069 }
1070
1071 #[test]
1072 fn no_semicolon_after_trailing_extends() {
1073 assert_eq!(
1074 asi_kinds("class S extends\nB { }"),
1075 vec![
1076 TokenKind::Class,
1077 TokenKind::Identifier,
1078 TokenKind::Extends,
1079 TokenKind::Identifier,
1080 TokenKind::LeftBrace,
1081 TokenKind::RightBrace,
1082 TokenKind::Semicolon,
1083 TokenKind::Eof,
1084 ]
1085 );
1086 }
1087
1088 fn semicolon_after_static(source: &str) -> bool {
1090 let offset = source.find("static").expect("the source holds `static`") as u32;
1091 let mut asi = Asi::new(source, crate::FileId(0));
1092 loop {
1093 let tok = asi.next_token();
1094 if tok.span.start == offset && tok.kind == TokenKind::Identifier {
1095 return asi.next_token().kind == TokenKind::Semicolon;
1096 }
1097 assert_ne!(tok.kind, TokenKind::Eof);
1098 }
1099 }
1100
1101 #[test]
1102 fn static_keeps_a_class_member_across_a_line_break() {
1103 assert!(!semicolon_after_static(
1104 "class C {\n static\n b: number = 2;\n}"
1105 ));
1106 assert!(!semicolon_after_static(
1107 "class C {\n x = 1\n static\n m() {}\n}"
1108 ));
1109 assert!(!semicolon_after_static(
1110 "class C {\n private static\n b = 2\n}"
1111 ));
1112 assert!(!semicolon_after_static(
1113 "class C {\n static\n [k]: number = 2\n}"
1114 ));
1115 assert!(!semicolon_after_static(
1117 "class C implements Box<{ a: number }> {\n static\n b = 2\n}"
1118 ));
1119 assert!(semicolon_after_static("class C {\n static\n}"));
1121 assert!(semicolon_after_static("let y = 1\nstatic\nx = 2"));
1123 assert!(semicolon_after_static(
1124 "const o = {\n f() {\n static\n x\n }\n}"
1125 ));
1126 assert!(semicolon_after_static(
1127 "class C {\n x = static\n y = 2\n}"
1128 ));
1129 }
1130
1131 #[test]
1132 fn implements_continues_class_header() {
1133 assert_eq!(
1134 asi_kinds("class S\nimplements I { }"),
1135 vec![
1136 TokenKind::Class,
1137 TokenKind::Identifier,
1138 TokenKind::Implements,
1139 TokenKind::Identifier,
1140 TokenKind::LeftBrace,
1141 TokenKind::RightBrace,
1142 TokenKind::Semicolon,
1143 TokenKind::Eof,
1144 ]
1145 );
1146 }
1147
1148 #[test]
1149 fn no_semicolon_after_trailing_implements() {
1150 assert_eq!(
1151 asi_kinds("class S implements\nI { }"),
1152 vec![
1153 TokenKind::Class,
1154 TokenKind::Identifier,
1155 TokenKind::Implements,
1156 TokenKind::Identifier,
1157 TokenKind::LeftBrace,
1158 TokenKind::RightBrace,
1159 TokenKind::Semicolon,
1160 TokenKind::Eof,
1161 ]
1162 );
1163 }
1164
1165 #[test]
1166 fn optional_chain_continues_across_newline() {
1167 assert_eq!(
1169 asi_kinds("foo\n?.bar"),
1170 vec![
1171 TokenKind::Identifier,
1172 TokenKind::QuestionDot,
1173 TokenKind::Identifier,
1174 TokenKind::Semicolon,
1175 TokenKind::Eof,
1176 ]
1177 );
1178 }
1179
1180 #[test]
1181 fn empty_input() {
1182 assert_eq!(asi_kinds(""), vec![TokenKind::Eof]);
1183 }
1184
1185 #[test]
1186 fn leading_newlines_no_semicolon() {
1187 assert_eq!(
1188 asi_kinds("\n\na"),
1189 vec![TokenKind::Identifier, TokenKind::Semicolon, TokenKind::Eof,]
1190 );
1191 }
1192
1193 #[test]
1194 fn no_semicolon_in_multiline_object_literal() {
1195 assert_eq!(
1198 asi_kinds("x = {\na: 1,\nb: 2\n}"),
1199 vec![
1200 TokenKind::Identifier,
1201 TokenKind::Equals,
1202 TokenKind::LeftBrace,
1203 TokenKind::Identifier,
1204 TokenKind::Colon,
1205 TokenKind::NumberLiteral(1.0),
1206 TokenKind::Comma,
1207 TokenKind::Identifier,
1208 TokenKind::Colon,
1209 TokenKind::NumberLiteral(2.0),
1210 TokenKind::RightBrace,
1211 TokenKind::Semicolon,
1212 TokenKind::Eof,
1213 ]
1214 );
1215 }
1216
1217 #[test]
1218 fn no_semicolon_in_multiline_object_literal_trailing_comma() {
1219 assert_eq!(
1220 asi_kinds("x = {\na: 1,\n}"),
1221 vec![
1222 TokenKind::Identifier,
1223 TokenKind::Equals,
1224 TokenKind::LeftBrace,
1225 TokenKind::Identifier,
1226 TokenKind::Colon,
1227 TokenKind::NumberLiteral(1.0),
1228 TokenKind::Comma,
1229 TokenKind::RightBrace,
1230 TokenKind::Semicolon,
1231 TokenKind::Eof,
1232 ]
1233 );
1234 }
1235
1236 #[test]
1237 fn block_brace_still_gets_terminator() {
1238 assert_eq!(
1240 asi_kinds("{\na\n}"),
1241 vec![
1242 TokenKind::LeftBrace,
1243 TokenKind::Identifier,
1244 TokenKind::Semicolon,
1245 TokenKind::RightBrace,
1246 TokenKind::Semicolon,
1247 TokenKind::Eof,
1248 ]
1249 );
1250 }
1251
1252 #[test]
1253 fn type_literal_members_get_separators() {
1254 assert_eq!(
1258 asi_kinds("let p: {\na: number\nb: number\n}"),
1259 vec![
1260 TokenKind::Let,
1261 TokenKind::Identifier,
1262 TokenKind::Colon,
1263 TokenKind::LeftBrace,
1264 TokenKind::Identifier,
1265 TokenKind::Colon,
1266 TokenKind::Identifier,
1267 TokenKind::Semicolon,
1268 TokenKind::Identifier,
1269 TokenKind::Colon,
1270 TokenKind::Identifier,
1271 TokenKind::RightBrace,
1272 TokenKind::Semicolon,
1273 TokenKind::Eof,
1274 ]
1275 );
1276 }
1277
1278 #[test]
1279 fn nested_object_value_suppresses_inner_close() {
1280 assert_eq!(
1282 asi_kinds("x = {\nouter: {\ninner: 1\n}\n}"),
1283 vec![
1284 TokenKind::Identifier,
1285 TokenKind::Equals,
1286 TokenKind::LeftBrace,
1287 TokenKind::Identifier,
1288 TokenKind::Colon,
1289 TokenKind::LeftBrace,
1290 TokenKind::Identifier,
1291 TokenKind::Colon,
1292 TokenKind::NumberLiteral(1.0),
1293 TokenKind::RightBrace,
1294 TokenKind::RightBrace,
1295 TokenKind::Semicolon,
1296 TokenKind::Eof,
1297 ]
1298 );
1299 }
1300
1301 #[test]
1302 fn multiline_destructuring_pattern_no_semicolon() {
1303 assert_eq!(
1304 asi_kinds("const {\na,\nb\n} = p"),
1305 vec![
1306 TokenKind::Const,
1307 TokenKind::LeftBrace,
1308 TokenKind::Identifier,
1309 TokenKind::Comma,
1310 TokenKind::Identifier,
1311 TokenKind::RightBrace,
1312 TokenKind::Equals,
1313 TokenKind::Identifier,
1314 TokenKind::Semicolon,
1315 TokenKind::Eof,
1316 ]
1317 );
1318 }
1319
1320 #[test]
1321 fn no_semicolon_after_trailing_equals() {
1322 assert_eq!(
1325 asi_kinds("const x =\n5"),
1326 vec![
1327 TokenKind::Const,
1328 TokenKind::Identifier,
1329 TokenKind::Equals,
1330 TokenKind::NumberLiteral(5.0),
1331 TokenKind::Semicolon,
1332 TokenKind::Eof,
1333 ]
1334 );
1335 }
1336
1337 #[test]
1338 fn no_semicolon_after_trailing_binary_operator() {
1339 assert_eq!(
1340 asi_kinds("const x = 1 +\n2"),
1341 vec![
1342 TokenKind::Const,
1343 TokenKind::Identifier,
1344 TokenKind::Equals,
1345 TokenKind::NumberLiteral(1.0),
1346 TokenKind::Plus,
1347 TokenKind::NumberLiteral(2.0),
1348 TokenKind::Semicolon,
1349 TokenKind::Eof,
1350 ]
1351 );
1352 }
1353
1354 #[test]
1355 fn no_semicolon_after_trailing_assignment_reassign() {
1356 assert_eq!(
1357 asi_kinds("x =\n5"),
1358 vec![
1359 TokenKind::Identifier,
1360 TokenKind::Equals,
1361 TokenKind::NumberLiteral(5.0),
1362 TokenKind::Semicolon,
1363 TokenKind::Eof,
1364 ]
1365 );
1366 }
1367
1368 #[test]
1369 fn no_semicolon_after_trailing_compound_assignment() {
1370 assert_eq!(
1371 asi_kinds("x +=\n5"),
1372 vec![
1373 TokenKind::Identifier,
1374 TokenKind::PlusEquals,
1375 TokenKind::NumberLiteral(5.0),
1376 TokenKind::Semicolon,
1377 TokenKind::Eof,
1378 ]
1379 );
1380 }
1381
1382 #[test]
1383 fn no_semicolon_after_trailing_logical_operator() {
1384 assert_eq!(
1385 asi_kinds("true &&\nfalse"),
1386 vec![
1387 TokenKind::BooleanLiteral(true),
1388 TokenKind::AmpAmp,
1389 TokenKind::BooleanLiteral(false),
1390 TokenKind::Semicolon,
1391 TokenKind::Eof,
1392 ]
1393 );
1394 }
1395
1396 #[test]
1397 fn no_semicolon_after_trailing_nullish_coalescing() {
1398 assert_eq!(
1399 asi_kinds("a ??\nb"),
1400 vec![
1401 TokenKind::Identifier,
1402 TokenKind::QuestionQuestion,
1403 TokenKind::Identifier,
1404 TokenKind::Semicolon,
1405 TokenKind::Eof,
1406 ]
1407 );
1408 }
1409
1410 #[test]
1411 fn no_semicolon_before_leading_ternary() {
1412 assert_eq!(
1415 asi_kinds("cond\n? 1 : 2"),
1416 vec![
1417 TokenKind::Identifier,
1418 TokenKind::Question,
1419 TokenKind::NumberLiteral(1.0),
1420 TokenKind::Colon,
1421 TokenKind::NumberLiteral(2.0),
1422 TokenKind::Semicolon,
1423 TokenKind::Eof,
1424 ]
1425 );
1426 }
1427
1428 #[test]
1429 fn no_semicolon_after_trailing_ternary_question_and_colon() {
1430 assert_eq!(
1431 asi_kinds("cond ?\n1 :\n2"),
1432 vec![
1433 TokenKind::Identifier,
1434 TokenKind::Question,
1435 TokenKind::NumberLiteral(1.0),
1436 TokenKind::Colon,
1437 TokenKind::NumberLiteral(2.0),
1438 TokenKind::Semicolon,
1439 TokenKind::Eof,
1440 ]
1441 );
1442 }
1443
1444 #[test]
1445 fn no_semicolon_in_multiline_operator_chain() {
1446 assert_eq!(
1447 asi_kinds("const x = a +\nb +\nc"),
1448 vec![
1449 TokenKind::Const,
1450 TokenKind::Identifier,
1451 TokenKind::Equals,
1452 TokenKind::Identifier,
1453 TokenKind::Plus,
1454 TokenKind::Identifier,
1455 TokenKind::Plus,
1456 TokenKind::Identifier,
1457 TokenKind::Semicolon,
1458 TokenKind::Eof,
1459 ]
1460 );
1461 }
1462
1463 #[test]
1464 fn return_keeps_restricted_production() {
1465 assert_eq!(
1468 asi_kinds("return\n5"),
1469 vec![
1470 TokenKind::Return,
1471 TokenKind::Semicolon,
1472 TokenKind::NumberLiteral(5.0),
1473 TokenKind::Semicolon,
1474 TokenKind::Eof,
1475 ]
1476 );
1477 }
1478
1479 fn asi_marked(source: &str) -> String {
1486 let mut asi = Asi::new(source, crate::FileId(0));
1487 let mut inserted: Vec<usize> = Vec::new();
1488 loop {
1489 let tok = asi.next_token();
1490 if tok.kind == TokenKind::Eof {
1491 break;
1492 }
1493 if tok.kind == TokenKind::Semicolon && tok.span.start == tok.span.end {
1495 inserted.push(tok.span.start as usize);
1496 }
1497 }
1498 let mut out = String::new();
1499 let mut last = 0;
1500 for at in inserted {
1501 out.push_str(&source[last..at]);
1502 out.push_str("<;>");
1503 last = at;
1504 }
1505 out.push_str(&source[last..]);
1506 out
1507 }
1508
1509 #[test]
1510 fn clause_keyword_before_its_brace_gets_no_terminator() {
1511 assert_eq!(
1514 asi_marked("try\n{\na\n}\ncatch (e)\n{\n}"),
1515 "try\n{\na\n<;>}\ncatch (e)\n{\n}<;>"
1516 );
1517 assert_eq!(
1518 asi_marked("if (c)\n{\n}\nelse\n{\n}"),
1519 "if (c)\n{\n}\nelse\n{\n}<;>"
1520 );
1521 assert_eq!(asi_marked("do\n{\n}\nwhile (c)"), "do\n{\n}\n<;>while (c)");
1527 }
1528
1529 #[test]
1530 fn a_newline_before_a_clause_keyword_still_ends_the_statement() {
1531 assert_eq!(asi_marked("a\ntry\n{\n}"), "a\n<;>try\n{\n}<;>");
1534 assert_eq!(
1535 asi_marked("a\ndo\n{\n}\nwhile (c)"),
1536 "a\n<;>do\n{\n}\n<;>while (c)"
1537 );
1538 }
1539
1540 #[test]
1541 fn block_closing_brace_terminates_without_a_newline() {
1542 assert_eq!(
1543 asi_marked("function f(): number { return 1 }"),
1544 "function f(): number { return 1 <;>}<;>"
1545 );
1546 assert_eq!(
1547 asi_marked("class C { x: number = 1 }"),
1548 "class C { x: number = 1 <;>}<;>"
1549 );
1550 }
1551
1552 #[test]
1553 fn value_closing_brace_never_terminates() {
1554 assert_eq!(asi_marked("x = { a: 1 }"), "x = { a: 1 }<;>");
1555 assert_eq!(
1556 asi_marked("import { a } from \"m\""),
1557 "import { a } from \"m\"<;>"
1558 );
1559 assert_eq!(
1561 asi_marked("export type { A } from \"m\""),
1562 "export type { A } from \"m\"<;>"
1563 );
1564 }
1565
1566 #[test]
1567 fn only_a_after_case_label_colon_opens_a_block() {
1568 assert_eq!(
1571 asi_marked("switch (n) {\ncase 1: { a }\n}"),
1572 "switch (n) {\ncase 1: { a <;>}\n<;>}<;>"
1573 );
1574 assert_eq!(
1575 asi_marked("x = c ? { a: 1 } : { a: 2 }"),
1576 "x = c ? { a: 1 } : { a: 2 }<;>"
1577 );
1578 assert_eq!(
1579 asi_marked("x = { default: { a: 1 } }"),
1580 "x = { default: { a: 1 } }<;>"
1581 );
1582 }
1583
1584 #[test]
1585 fn a_keyword_property_name_is_not_a_keyword() {
1586 assert_eq!(asi_marked("a = o.do\nb = 1"), "a = o.do\n<;>b = 1<;>");
1589 assert_eq!(asi_marked("a = o.class\nb = 1"), "a = o.class\n<;>b = 1<;>");
1590 assert_eq!(
1591 asi_marked("a = o.default\nb = 1"),
1592 "a = o.default\n<;>b = 1<;>"
1593 );
1594 assert_eq!(asi_marked("a = o?.try\nb = 1"), "a = o?.try\n<;>b = 1<;>");
1595 assert_eq!(
1598 asi_marked("a = o.switch(1)\nb = 1"),
1599 "a = o.switch(1)\n<;>b = 1<;>"
1600 );
1601 assert_eq!(
1602 asi_marked("a = o.catch(1)\nb = 1"),
1603 "a = o.catch(1)\n<;>b = 1<;>"
1604 );
1605 }
1606
1607 #[test]
1608 fn a_generic_header_is_followed_by_a_block() {
1609 assert_eq!(
1611 asi_marked("function f(): Array<number> {\nreturn a\n}"),
1612 "function f(): Array<number> {\nreturn a\n<;>}<;>"
1613 );
1614 assert_eq!(
1615 asi_marked("class Box<T> {\nv: number = 1\n}"),
1616 "class Box<T> {\nv: number = 1\n<;>}<;>"
1617 );
1618 }
1619
1620 #[test]
1621 fn a_cast_is_followed_by_an_object_literal() {
1622 assert_eq!(
1624 asi_marked("x = <Foo>{ a: 1 }\ny()"),
1625 "x = <Foo>{ a: 1 }\n<;>y()<;>"
1626 );
1627 assert_eq!(asi_marked("<Foo>{ a: 1 }\ny()"), "<Foo>{ a: 1 }\n<;>y()<;>");
1628 assert_eq!(
1629 asi_marked("f = x => <Foo>{ a: x }\ny()"),
1630 "f = x => <Foo>{ a: x }\n<;>y()<;>"
1631 );
1632 assert_eq!(
1633 asi_marked("x = <Box<Array<number>>>{ v: [] }\ny()"),
1634 "x = <Box<Array<number>>>{ v: [] }\n<;>y()<;>"
1635 );
1636 assert_eq!(
1638 asi_marked("if (a < b) {\nreturn a\n}"),
1639 "if (a < b) {\nreturn a\n<;>}<;>"
1640 );
1641 }
1642
1643 #[test]
1644 fn a_spread_operand_is_an_object_literal() {
1645 assert_eq!(
1646 asi_marked("x = { ...{ a: 1 }, b: 2 }\ny()"),
1647 "x = { ...{ a: 1 }, b: 2 }\n<;>y()<;>"
1648 );
1649 }
1650
1651 #[test]
1652 fn template_substitution_suspends_insertion() {
1653 assert_eq!(
1654 asi_marked("const s = `a${\nn\n}`"),
1655 "const s = `a${\nn\n}`<;>"
1656 );
1657 assert_eq!(
1658 asi_marked("const s = `a${\nn\n}b${\nn\n}c`"),
1659 "const s = `a${\nn\n}b${\nn\n}c`<;>"
1660 );
1661 }
1662
1663 #[test]
1664 fn declaration_header_suspends_insertion() {
1665 assert_eq!(
1666 asi_marked("class S\nextends B\nimplements I\n{\n}"),
1667 "class S\nextends B\nimplements I\n{\n}<;>"
1668 );
1669 assert_eq!(asi_marked("interface I\n{\n}"), "interface I\n{\n}<;>");
1670 assert_eq!(asi_marked("enum E\n{\nA\n}"), "enum E\n{\nA\n<;>}<;>");
1671 }
1672
1673 #[test]
1674 fn block_inside_parens_is_statement_context_again() {
1675 assert_eq!(
1678 asi_marked("f((x: number): number => {\nreturn x\n})"),
1679 "f((x: number): number => {\nreturn x\n<;>})<;>"
1680 );
1681 assert_eq!(asi_marked("f(a,\nb)"), "f(a,\nb)<;>");
1682 }
1683
1684 #[test]
1685 fn mvp_fixture_snapshot() {
1686 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";
1687 let mut asi = Asi::new(source, crate::FileId(0));
1688 let mut tokens = Vec::new();
1689 loop {
1690 let tok = asi.next_token();
1691 let is_eof = tok.kind == TokenKind::Eof;
1692 tokens.push(tok);
1693 if is_eof {
1694 break;
1695 }
1696 }
1697 assert!(asi.into_diagnostics().is_empty());
1698 let kinds: Vec<String> = tokens.iter().map(|t| format!("{:?}", t.kind)).collect();
1699 insta::assert_debug_snapshot!(kinds);
1700 }
1701}