Skip to main content

submilli_engine/
asi.rs

1use crate::{Diagnostic, FileId, Lexer, Span, Token, TokenKind};
2
3/// Statement blocks need a terminator before `}`. Value lists and type member
4/// lists do not; member lists still need separators between their entries.
5#[derive(Clone, Copy, PartialEq)]
6enum BraceKind {
7    Block,
8    Value,
9    /// Type members need separators even inside parentheses, but none before `}`.
10    MemberList,
11}
12
13/// An open `{` and the `(`/`[`/`${` nesting depth it was opened at. A statement block
14/// re-enters statement context even when it sits inside parentheses — the body of
15/// `arr.map(x => {` ⏎ `…` ⏎ `})` is statements, not a paren-wrapped expression — so
16/// "are we inside brackets?" is measured against the enclosing block's base, not zero.
17/// A value list carries its enclosing base forward. Member lists reset it so type
18/// members receive separators even inside a parameter list.
19struct BraceFrame {
20    kind: BraceKind,
21    bracket_base: u32,
22    /// Opened a class body, where a line break after `static` doesn't end a member.
23    class_body: bool,
24}
25
26/// Automatic Semicolon Insertion: strips `Newline` tokens, inserts `Semicolon` at statement boundaries.
27pub 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    /// Open `(`, `[`, and template substitutions (`${`), which all suspend ASI: a
34    /// newline inside one is a wrapped expression, never a statement end.
35    bracket_depth: u32,
36    braces: Vec<BraceFrame>,
37    // Tracks, per open `(`, whether it is a control-flow header (`if (…)`, `while (…)`,
38    // `for (…)`, `switch (…)`, `catch (…)`). The `)` closing such a header is followed by a
39    // body, not a statement end, so ASI must not insert a `;` after it.
40    paren_is_header: Vec<bool>,
41    closed_header_paren: bool,
42    closed_block_brace: bool,
43    /// `<`/`>` nesting inside the type of a `<T>expr` cast, or 0 outside one. A cast's
44    /// `<` stands where an operand is expected; every other `<` follows an operand (a
45    /// comparison) or a name (`Array<`, `f<`, `class Box<`), so the position alone
46    /// tells them apart.
47    cast_angle_depth: u32,
48    /// Did `last_emitted` close a cast's type? The `{` after it is the cast's operand —
49    /// an object literal — where after any other `>` it opens a body
50    /// (`function f(): Array<number> {`).
51    closed_cast_angle: bool,
52    /// Set between a `class` / `interface` / `enum` keyword and the `{` opening its body.
53    /// A declaration header is not a statement, so it never ends inside one — which a
54    /// token table cannot see, since the header's last token is an ordinary identifier.
55    in_decl_header: bool,
56    /// While a class header is open, how many braces enclose it. A type literal in
57    /// the header (`implements Box<{ a: number }>`) opens a brace of its own; the
58    /// class body is the first brace back at this depth after a name or `>`.
59    class_header_depth: Option<usize>,
60    /// Has every token since the start of the current class member been a modifier
61    /// word (`private`, `static`, …)?
62    in_member_modifiers: bool,
63    /// Is `last_emitted` the word `static` among a class member's modifiers? It stays
64    /// a modifier across a line break when a member name follows, as in TypeScript.
65    last_is_member_static: bool,
66    /// Did `last_emitted` follow a `.` / `?.`? Every clause and declaration keyword is
67    /// also a legal property name, so `o.default`, `o.do`, and `o.class` reach the token
68    /// tables spelled exactly like the statement forms. A member name ends an expression,
69    /// and therefore may end a statement, whatever the word is.
70    last_is_member_name: bool,
71    /// Set by `case` / `default` and held through the label's `:`, which is the only
72    /// `:` a statement block may follow. Every other `:` may introduce a member list.
73    in_case_label: bool,
74    /// True for exactly the token *after* a label's `:` — the state `classify_brace`
75    /// reads to open a block rather than a value list.
76    after_case_label_colon: bool,
77    /// Would a `{` here open an import/export specifier list? Set by the `import` /
78    /// `export` keyword and cleared by the first token that proves a declaration is
79    /// being exported instead. The `type` modifier of `export type { … }` is
80    /// contextual — lexed as an identifier — so the `{` is not always adjacent to the
81    /// keyword that identifies it.
82    brace_opens_specifier_list: bool,
83}
84
85impl<'a> Asi<'a> {
86    /// ASI over `source`, attributing every span to `file` (see [`Lexer::new`]).
87    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                    // A statement ends at the `}` closing its block, newline or not
137                    // (`function f() { return 1 }`); never at one closing a value list;
138                    // anywhere else, only at a newline.
139                    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            // Clause keywords can also name properties. Neither a clause nor a
169            // declaration header can put `:` or `?` immediately after its keyword.
170            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    /// Emit a zero-width `Semicolon` at `tok`'s start and hold `tok` for the next call.
192    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        // Two states no token table can see: the `)` closing a control-flow header is
210        // followed by a body, and a declaration header runs on to its `{`.
211        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    /// True where a statement may begin, so a keyword there is a keyword rather than a
221    /// property name or an enum member.
222    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    /// The kind of brace `next` would close, when it closes one. A `;` before a value-list
235    /// `}` corrupts the object literal / pattern / import it closes; before a block `}` it
236    /// is exactly the statement terminator JS's ASI supplies.
237    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    /// True inside a `(`, `[`, or `${` opened since the innermost block or member list.
245    fn inside_brackets(&self) -> bool {
246        self.bracket_depth > self.braces.last().map_or(0, |frame| frame.bracket_base)
247    }
248
249    /// Classify a `{` about to be emitted. Statement blocks follow a statement boundary;
250    /// everything in expression/value position is a value list. A `:` is ambiguous, and
251    /// `case`/`default` is the one label whose `:` introduces statements — an object
252    /// value, a ternary's else branch, and a type annotation all use a member-list base.
253    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            // A colon also introduces object values and ternary alternates. Their
265            // comma separators already suppress ASI, so the same base is safe.
266            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    /// [`Asi::track`], plus the state that needs the token's text.
294    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    /// Advance every piece of state past `kind`.
315    ///
316    /// Ordering is load-bearing throughout: **every `next_*` below reads `self` as the
317    /// state *before* `kind`**, which is why `last_emitted` is advanced last and why
318    /// `after_case_label_colon` is computed before `in_case_label` overwrites itself.
319    /// Bracket tracking runs first for the same reason — `push_brace` classifies the `{`
320    /// from the pre-token state.
321    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    /// Angle-bracket bookkeeping for `<T>expr` casts; returns whether `kind` closed one.
349    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    /// Is the next token an operand — the start of an expression rather than something
368    /// continuing one?
369    fn expects_operand(&self) -> bool {
370        self.at_statement_start()
371            || self
372                .last_emitted
373                .as_ref()
374                // `=>` stays out of `opens_value_brace` because `=> {` opens a body, but
375                // what follows it is still an operand: `x => <Foo>{ … }`.
376                .is_some_and(|kind| opens_value_brace(kind) || matches!(kind, TokenKind::Arrow))
377    }
378
379    /// Refine the lexer's prefix/postfix choice using grammatical boundary state.
380    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    /// Bracket and brace bookkeeping; returns whether `kind` closed a control-flow header.
395    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            // A `${` opens an expression the same way `(` does. `TemplateMiddle` closes one
402            // substitution and opens the next, so it nets out to no change.
403            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            // Both words are also legal property names, so a label needs both a statement
427            // position and a `switch` body around it: `{ default: … }` has the first and
428            // `o.default` neither.
429            TokenKind::Case | TokenKind::Default => {
430                self.at_statement_start()
431                    && self.braces.last().map(|frame| frame.kind) == Some(BraceKind::Block)
432            }
433            // A label's expression may be several tokens (`case Color.Red:`), so the flag
434            // survives to its `:`. Anything that ends a statement or a list ends the
435            // search — the word was a name, not a label.
436            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            // All three words are legal property names too, so a header needs a statement
450            // position: `{ class: … }` and `o.class` are names.
451            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            // Only the contextual `type` modifier may sit between the keyword and the
467            // specifier list; any other token means a declaration is being exported.
468            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    /// True when the `(` about to be tracked follows a control-flow keyword whose
481    /// parenthesized header is followed by a body rather than a statement terminator.
482    ///
483    /// All five words are legal property names, so `r.switch(1)` would otherwise mark its
484    /// *argument* list as a header and swallow the next statement's terminator.
485    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
500// The five tables below split along an axis worth stating once: `cannot_end_statement`
501// and the three it composes classify `last_emitted`, the token *behind* the newline;
502// `can_continue` classifies the token *ahead* of it. That is why `else` and `finally`
503// legitimately appear on both sides — they neither end the statement behind them nor
504// start a new one ahead.
505
506/// The words TypeScript reads as class member modifiers.
507const CLASS_MEMBER_MODIFIERS: &[&str] = &[
508    "public",
509    "private",
510    "protected",
511    "readonly",
512    "static",
513    "override",
514    "declare",
515    "abstract",
516    "accessor",
517];
518
519/// A token that may follow a modifier (TypeScript's `canFollowModifier`): a member
520/// name, or what opens a computed one or a static block.
521fn 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
534/// A statement cannot end on this token, so a following newline is mid-statement.
535/// `,` always sits inside a value list (object literal, pattern, import, argument or
536/// element list); an opening bracket has nothing before it to terminate; and an operator
537/// or clause keyword is still waiting for what comes next.
538fn 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
550/// Operators that require a following operand, so an expression — and therefore a
551/// statement — can never legally end on one, *and* whose operand is a value: the `{`
552/// after one of these opens an object literal, not a block.
553///
554/// The narrower of the two tables, and the only one `opens_value_brace` may consult.
555/// `needs_semi` and `can_continue` want [`awaits_operand`], the superset that also
556/// covers `=>` — whose operand can be a statement block.
557fn 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
595/// Every operator that awaits an operand, so a newline after one is mid-expression:
596/// [`cannot_end_expression`] plus `=>` — prettier's default wrap for a long arrow puts
597/// a newline right there. `=>` is kept out of the narrower table because its operand
598/// may be a statement block, which `opens_value_brace` must not read as a value.
599fn awaits_operand(kind: &TokenKind) -> bool {
600    cannot_end_expression(kind)
601        || matches!(
602            kind,
603            TokenKind::Arrow
604                | TokenKind::Tilde
605                // A class header is not a statement, so it never ends at one of its
606                // own clause keywords — prettier wraps long headers right after them.
607                | TokenKind::Extends
608                | TokenKind::Implements
609        )
610}
611
612/// Clause keywords whose body follows directly, so a statement can never end on one:
613/// `try`, `else`, `do`, and `finally` — the Allman-brace shapes (`try` ⏎ `{`), plus
614/// `else` ⏎ `if`.
615///
616/// Deliberately not folded into [`awaits_operand`]: `can_continue` reads that table, and
617/// a newline *before* `try` or `do` does end the preceding statement.
618fn awaits_body(kind: &TokenKind) -> bool {
619    matches!(
620        kind,
621        TokenKind::Try | TokenKind::Else | TokenKind::Do | TokenKind::Finally | TokenKind::Catch
622    )
623}
624
625/// Tokens after which a `{` opens a value list rather than a statement block:
626/// assignment/operator RHS, argument/element position, `return`/`throw` operands, a
627/// template substitution's expression, and the `const`/`let` keywords that introduce
628/// destructuring patterns. Import/export specifier lists are `brace_opens_specifier_list`'s job.
629fn opens_value_brace(kind: &TokenKind) -> bool {
630    // `>` is the one operand-awaiting token a `{` never follows as a value: `a > { … }`
631    // compares against an object literal and is meaningless, while `function f():
632    // Array<number> {` and `class Box<T> {` put a body there.
633    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                // A spread's operand: `{ ...{ a: 1 } }`, `[...{ … }]`.
644                | TokenKind::DotDotDot
645                | TokenKind::Return
646                | TokenKind::Throw
647                | TokenKind::Const
648                | TokenKind::Let
649                | TokenKind::TemplateHead(_)
650                | TokenKind::TemplateMiddle(_)
651        )
652}
653
654/// Tokens that continue the statement behind a newline, so no terminator is inserted
655/// before them.
656///
657/// Deliberately absent: `as` and `is`, contextual keywords lexed as identifiers, so a
658/// newline before them ends the statement — matching TypeScript, where `const v = x` ⏎
659/// `as (y)` is an initialized variable followed by a call of a function named `as`, not a
660/// cast. `typeof` starts a new expression, so it is not listed either.
661fn can_continue(kind: &TokenKind) -> bool {
662    // `~` awaits its operand, but cannot continue the preceding expression.
663    if matches!(kind, TokenKind::Tilde) {
664        return false;
665    }
666    awaits_operand(kind)
667        || matches!(
668            kind,
669            TokenKind::Dot
670                // Prettier breaks a long member chain before `?.` exactly as before `.`.
671                | TokenKind::QuestionDot
672                | TokenKind::LeftParen
673                | TokenKind::LeftBracket
674                | TokenKind::Else
675                // A `try`/`catch` block's `}` ends a clause, not the statement:
676                // the clause keywords continue it exactly as `else` does.
677                | TokenKind::Catch
678                | TokenKind::Finally
679                // Likewise a class header's own clause keywords, which prettier puts
680                // on their own lines when the header is long.
681                | TokenKind::Extends
682                | TokenKind::Implements
683                // `TemplateMiddle`/`TemplateTail` are always mid-template, never statement
684                // starters. `TemplateHead`/`TemplateNoSubstitution` start expressions, so
685                // are not listed.
686                | 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        // `as` is a contextual keyword (a plain identifier here), so ASI ends the
738        // statement — TS parses `a` ⏎ `as(b)` the same way.
739        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        // `if (a)` newline `b`: the `)` closes a control-flow header, so the body on the
862        // next line must not be severed by an inserted `;`.
863        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        // The inner `)` of `if ((a))` is an ordinary paren; only the header `)` suppresses ASI.
896        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        // A `)` closing a call expression ends a statement — ASI must still fire.
915        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        // Prettier's default wrap for a long arrow: `=>` awaits its body, so the
1007        // newline after it must not be turned into a statement end.
1008        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        // Regression guard for the `awaits_operand` split: `=>` must not make the
1034        // body brace a value list, or the last statement loses its inserted `;`.
1035        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    /// Whether ASI ends a statement right after the first `static` in `source`.
1089    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        // A type literal in the header doesn't take the place of the body.
1116        assert!(!semicolon_after_static(
1117            "class C implements Box<{ a: number }> {\n  static\n  b = 2\n}"
1118        ));
1119        // `static` not followed by a member name is itself the member's name.
1120        assert!(semicolon_after_static("class C {\n  static\n}"));
1121        // Outside a class body, or after a member name, the word is a name.
1122        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        // Prettier breaks a long member chain before `?.` exactly as before `.`.
1168        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        // `= {` opens a value brace; the comma rule covers inter-member newlines and the
1196        // value-brace rule covers the newline before `}`.
1197        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        // A statement-position `{` is a block; its last statement keeps its inserted `;`.
1239        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        // A `: {` type literal is a value list, so its closing `}` gets no terminator —
1255        // the member grammar makes the last separator optional. Newline-separated members
1256        // still receive an inserted `;` between them.
1257        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        // The inner `{` follows `:` inside a value brace, so it is itself a value brace.
1281        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        // `const x =` newline `5`: the `=` cannot end an expression, so the initializer
1323        // continues onto the next line rather than being severed by an inserted `;`.
1324        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        // `cond` newline `? 1 : 2`: a continuation line beginning with `?` must not be
1413        // severed from the condition.
1414        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        // Regression guard: `return` newline `5` is the restricted production — ASI inserts
1466        // `;` after `return`, leaving the `5` as its own statement.
1467        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    /// `source` with `<;>` marking every offset where ASI inserted a semicolon — always
1480    /// immediately before the token that triggered it, since an inserted terminator is
1481    /// zero-width at that token's start.
1482    ///
1483    /// Use this to pin *where* a terminator lands; use an `asi_kinds` token vector when
1484    /// the whole stream matters — that nothing else was dropped, reordered, or emitted.
1485    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            // An inserted semicolon is zero-width; one the author wrote spans its `;`.
1494            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        // Allman style: `try` / `catch (…)` / `else` / `do` are followed by a body, so
1512        // the newline before the `{` is not a statement end.
1513        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        // ECMAScript inserts nothing before this `while` — the DoWhileStatement production
1522        // allows it — but a token-stream pre-pass cannot see that, so it emits one here and
1523        // the parser drops it (`eat_asi_semicolon_before`). The rows below record where the
1524        // pre-pass lands the terminator; the behaviour that matters is pinned by
1525        // `asi_allman_braces.ts`.
1526        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        // `awaits_body` must not have leaked into `can_continue`: a newline *before*
1532        // `try` or `do` ends the preceding statement.
1533        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        // The contextual `type` modifier sits between `export` and its specifier list.
1560        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        // The `:` of a `case`/`default` label is the one colon a statement block follows;
1569        // a ternary's alternate and an object value after the same two words are not.
1570        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        // Every clause and declaration keyword is also a legal property name, so the
1587        // tables must not read `o.do` as a `do` loop or `{ class: … }` as a class header.
1588        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        // A call's argument list is not a control-flow header just because the method is
1596        // spelled `switch`.
1597        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        // `>` closes a type-argument list here, so the `{` opens a body, not a value.
1610        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        // A `<` in operand position opens a cast, so its `>` leaves a value next.
1623        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        // After a comparison the next `<` follows an operand, so no cast is tracked.
1637        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        // The body of an arrow passed as an argument gets its terminators, while the
1676        // argument list around it stays suspended.
1677        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}