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}
23
24/// Automatic Semicolon Insertion: strips `Newline` tokens, inserts `Semicolon` at statement boundaries.
25pub struct Asi<'a> {
26    lexer: Lexer<'a>,
27    buffered: Option<Token>,
28    lookahead: std::collections::VecDeque<Token>,
29    last_emitted: Option<TokenKind>,
30    /// Open `(`, `[`, and template substitutions (`${`), which all suspend ASI: a
31    /// newline inside one is a wrapped expression, never a statement end.
32    bracket_depth: u32,
33    braces: Vec<BraceFrame>,
34    // Tracks, per open `(`, whether it is a control-flow header (`if (…)`, `while (…)`,
35    // `for (…)`, `switch (…)`, `catch (…)`). The `)` closing such a header is followed by a
36    // body, not a statement end, so ASI must not insert a `;` after it.
37    paren_is_header: Vec<bool>,
38    closed_header_paren: bool,
39    closed_block_brace: bool,
40    /// `<`/`>` nesting inside the type of a `<T>expr` cast, or 0 outside one. A cast's
41    /// `<` stands where an operand is expected; every other `<` follows an operand (a
42    /// comparison) or a name (`Array<`, `f<`, `class Box<`), so the position alone
43    /// tells them apart.
44    cast_angle_depth: u32,
45    /// Did `last_emitted` close a cast's type? The `{` after it is the cast's operand —
46    /// an object literal — where after any other `>` it opens a body
47    /// (`function f(): Array<number> {`).
48    closed_cast_angle: bool,
49    /// Set between a `class` / `interface` / `enum` keyword and the `{` opening its body.
50    /// A declaration header is not a statement, so it never ends inside one — which a
51    /// token table cannot see, since the header's last token is an ordinary identifier.
52    in_decl_header: bool,
53    /// Did `last_emitted` follow a `.` / `?.`? Every clause and declaration keyword is
54    /// also a legal property name, so `o.default`, `o.do`, and `o.class` reach the token
55    /// tables spelled exactly like the statement forms. A member name ends an expression,
56    /// and therefore may end a statement, whatever the word is.
57    last_is_member_name: bool,
58    /// Set by `case` / `default` and held through the label's `:`, which is the only
59    /// `:` a statement block may follow. Every other `:` may introduce a member list.
60    in_case_label: bool,
61    /// True for exactly the token *after* a label's `:` — the state `classify_brace`
62    /// reads to open a block rather than a value list.
63    after_case_label_colon: bool,
64    /// Would a `{` here open an import/export specifier list? Set by the `import` /
65    /// `export` keyword and cleared by the first token that proves a declaration is
66    /// being exported instead. The `type` modifier of `export type { … }` is
67    /// contextual — lexed as an identifier — so the `{` is not always adjacent to the
68    /// keyword that identifies it.
69    brace_opens_specifier_list: bool,
70}
71
72impl<'a> Asi<'a> {
73    /// ASI over `source`, attributing every span to `file` (see [`Lexer::new`]).
74    pub fn new(source: &'a str, file: FileId) -> Self {
75        Self {
76            lexer: Lexer::new(source, file),
77            buffered: None,
78            lookahead: std::collections::VecDeque::new(),
79            last_emitted: None,
80            bracket_depth: 0,
81            braces: Vec::new(),
82            paren_is_header: Vec::new(),
83            closed_header_paren: false,
84            closed_block_brace: false,
85            last_is_member_name: false,
86            in_case_label: false,
87            after_case_label_colon: false,
88            in_decl_header: false,
89            brace_opens_specifier_list: false,
90            cast_angle_depth: 0,
91            closed_cast_angle: false,
92        }
93    }
94
95    pub fn next_token(&mut self) -> Token {
96        if let Some(tok) = self.buffered.take() {
97            self.track(&tok.kind);
98            return tok;
99        }
100
101        let mut pending_newline = false;
102        loop {
103            let tok = self
104                .lookahead
105                .pop_front()
106                .unwrap_or_else(|| self.lexer.next_token());
107            match tok.kind {
108                TokenKind::Newline => {
109                    pending_newline = true;
110                }
111                TokenKind::Eof => {
112                    if self.needs_semi() {
113                        return self.insert_semicolon_before(tok);
114                    }
115                    self.track(&tok.kind);
116                    return tok;
117                }
118                _ => {
119                    // A statement ends at the `}` closing its block, newline or not
120                    // (`function f() { return 1 }`); never at one closing a value list;
121                    // anywhere else, only at a newline.
122                    let boundary = match self.brace_closed_by(&tok.kind) {
123                        Some(BraceKind::Value | BraceKind::MemberList) => false,
124                        Some(BraceKind::Block) => true,
125                        None => pending_newline,
126                    };
127                    if boundary
128                        && !self.inside_brackets()
129                        && !self.can_continue_here(&tok.kind)
130                        && self.needs_semi()
131                    {
132                        return self.insert_semicolon_before(tok);
133                    }
134                    self.track(&tok.kind);
135                    return tok;
136                }
137            }
138        }
139    }
140
141    fn can_continue_here(&mut self, kind: &TokenKind) -> bool {
142        if matches!(
143            kind,
144            TokenKind::Else
145                | TokenKind::Catch
146                | TokenKind::Finally
147                | TokenKind::Extends
148                | TokenKind::Implements
149        ) {
150            // Clause keywords can also name properties. Neither a clause nor a
151            // declaration header can put `:` or `?` immediately after its keyword.
152            loop {
153                let next = self.lexer.next_token();
154                let newline = matches!(next.kind, TokenKind::Newline);
155                let member = matches!(next.kind, TokenKind::Colon | TokenKind::Question);
156                self.lookahead.push_back(next);
157                if !newline {
158                    return !member;
159                }
160            }
161        }
162        can_continue(kind)
163    }
164
165    pub fn finish(self) -> Result<Vec<Diagnostic>, crate::compiler_error::CompileError> {
166        self.lexer.finish()
167    }
168
169    pub fn into_diagnostics(self) -> Vec<Diagnostic> {
170        self.lexer.into_diagnostics()
171    }
172
173    /// Emit a zero-width `Semicolon` at `tok`'s start and hold `tok` for the next call.
174    fn insert_semicolon_before(&mut self, tok: Token) -> Token {
175        let pos = tok.span.start;
176        let file = tok.span.file;
177        self.buffered = Some(tok);
178        let semi = Token::new(
179            TokenKind::Semicolon,
180            Span {
181                file,
182                start: pos,
183                end: pos,
184            },
185        );
186        self.track(&semi.kind);
187        semi
188    }
189
190    fn needs_semi(&self) -> bool {
191        // Two states no token table can see: the `)` closing a control-flow header is
192        // followed by a body, and a declaration header runs on to its `{`.
193        if self.closed_header_paren || self.in_decl_header {
194            return false;
195        }
196        match &self.last_emitted {
197            None => false,
198            Some(kind) => self.last_is_member_name || !cannot_end_statement(kind),
199        }
200    }
201
202    /// True where a statement may begin, so a keyword there is a keyword rather than a
203    /// property name or an enum member.
204    fn at_statement_start(&self) -> bool {
205        matches!(
206            self.last_emitted,
207            None | Some(
208                TokenKind::LeftBrace
209                    | TokenKind::RightBrace
210                    | TokenKind::Semicolon
211                    | TokenKind::Colon
212            )
213        )
214    }
215
216    /// The kind of brace `next` would close, when it closes one. A `;` before a value-list
217    /// `}` corrupts the object literal / pattern / import it closes; before a block `}` it
218    /// is exactly the statement terminator JS's ASI supplies.
219    fn brace_closed_by(&self, next: &TokenKind) -> Option<BraceKind> {
220        if !matches!(next, TokenKind::RightBrace) {
221            return None;
222        }
223        self.braces.last().map(|frame| frame.kind)
224    }
225
226    /// True inside a `(`, `[`, or `${` opened since the innermost block or member list.
227    fn inside_brackets(&self) -> bool {
228        self.bracket_depth > self.braces.last().map_or(0, |frame| frame.bracket_base)
229    }
230
231    /// Classify a `{` about to be emitted. Statement blocks follow a statement boundary;
232    /// everything in expression/value position is a value list. A `:` is ambiguous, and
233    /// `case`/`default` is the one label whose `:` introduces statements — an object
234    /// value, a ternary's else branch, and a type annotation all use a member-list base.
235    fn classify_brace(&self) -> BraceKind {
236        if self.brace_opens_specifier_list {
237            return BraceKind::Value;
238        }
239        if self.after_case_label_colon {
240            return BraceKind::Block;
241        }
242        if self.closed_cast_angle {
243            return BraceKind::Value;
244        }
245        match &self.last_emitted {
246            // A colon also introduces object values and ternary alternates. Their
247            // comma separators already suppress ASI, so the same base is safe.
248            Some(TokenKind::Colon) => BraceKind::MemberList,
249            Some(kind) if opens_value_brace(kind) => BraceKind::Value,
250            _ => BraceKind::Block,
251        }
252    }
253
254    fn push_brace(&mut self) {
255        let kind = self.classify_brace();
256        let bracket_base = match kind {
257            BraceKind::Block | BraceKind::MemberList => self.bracket_depth,
258            BraceKind::Value => self.braces.last().map_or(0, |frame| frame.bracket_base),
259        };
260        self.braces.push(BraceFrame { kind, bracket_base });
261    }
262
263    /// Advance every piece of state past `kind`.
264    ///
265    /// Ordering is load-bearing throughout: **every `next_*` below reads `self` as the
266    /// state *before* `kind`**, which is why `last_emitted` is advanced last and why
267    /// `after_case_label_colon` is computed before `in_case_label` overwrites itself.
268    /// Bracket tracking runs first for the same reason — `push_brace` classifies the `{`
269    /// from the pre-token state.
270    fn track(&mut self, kind: &TokenKind) {
271        self.correct_bang_context(kind);
272        let closed_block = self.brace_closed_by(kind) == Some(BraceKind::Block);
273        let closed_header = self.track_brackets(kind);
274        let closed_cast = self.track_cast_angles(kind);
275        self.after_case_label_colon = self.next_after_case_label_colon(kind);
276        self.in_case_label = self.next_in_case_label(kind);
277        self.in_decl_header = self.next_in_decl_header(kind);
278        self.brace_opens_specifier_list = self.next_brace_opens_specifier_list(kind);
279        self.last_is_member_name = self.next_last_is_member_name();
280        self.closed_header_paren = closed_header;
281        self.closed_block_brace = closed_block;
282        self.closed_cast_angle = closed_cast;
283        self.last_emitted = Some(kind.clone());
284    }
285
286    /// Angle-bracket bookkeeping for `<T>expr` casts; returns whether `kind` closed one.
287    fn track_cast_angles(&mut self, kind: &TokenKind) -> bool {
288        match kind {
289            TokenKind::LessThan if self.cast_angle_depth > 0 => self.cast_angle_depth += 1,
290            TokenKind::LessThan
291                if self.expects_operand()
292                    && !matches!(self.last_emitted, Some(TokenKind::LessThan)) =>
293            {
294                self.cast_angle_depth = 1;
295            }
296            TokenKind::GreaterThan if self.cast_angle_depth > 0 => {
297                self.cast_angle_depth -= 1;
298                return self.cast_angle_depth == 0;
299            }
300            _ => {}
301        }
302        false
303    }
304
305    /// Is the next token an operand — the start of an expression rather than something
306    /// continuing one?
307    fn expects_operand(&self) -> bool {
308        self.at_statement_start()
309            || self
310                .last_emitted
311                .as_ref()
312                // `=>` stays out of `opens_value_brace` because `=> {` opens a body, but
313                // what follows it is still an operand: `x => <Foo>{ … }`.
314                .is_some_and(|kind| opens_value_brace(kind) || matches!(kind, TokenKind::Arrow))
315    }
316
317    /// Refine the lexer's prefix/postfix choice using grammatical boundary state.
318    fn correct_bang_context(&mut self, kind: &TokenKind) {
319        if !matches!(kind, TokenKind::Bang) {
320            return;
321        }
322        if self.last_is_member_name {
323            self.lexer.set_bang_is_postfix(true);
324        } else if self.closed_header_paren
325            || self.closed_block_brace
326            || matches!(self.last_emitted, Some(TokenKind::Semicolon))
327        {
328            self.lexer.set_bang_is_postfix(false);
329        }
330    }
331
332    /// Bracket and brace bookkeeping; returns whether `kind` closed a control-flow header.
333    fn track_brackets(&mut self, kind: &TokenKind) -> bool {
334        match kind {
335            TokenKind::LeftParen => {
336                self.bracket_depth += 1;
337                self.paren_is_header.push(self.opens_control_header());
338            }
339            // A `${` opens an expression the same way `(` does. `TemplateMiddle` closes one
340            // substitution and opens the next, so it nets out to no change.
341            TokenKind::LeftBracket | TokenKind::TemplateHead(_) => self.bracket_depth += 1,
342            TokenKind::RightParen => {
343                self.bracket_depth = self.bracket_depth.saturating_sub(1);
344                return self.paren_is_header.pop().unwrap_or(false);
345            }
346            TokenKind::RightBracket | TokenKind::TemplateTail(_) => {
347                self.bracket_depth = self.bracket_depth.saturating_sub(1);
348            }
349            TokenKind::LeftBrace => self.push_brace(),
350            TokenKind::RightBrace => {
351                self.braces.pop();
352            }
353            _ => {}
354        }
355        false
356    }
357
358    fn next_after_case_label_colon(&self, kind: &TokenKind) -> bool {
359        matches!(kind, TokenKind::Colon) && self.in_case_label
360    }
361
362    fn next_in_case_label(&self, kind: &TokenKind) -> bool {
363        match kind {
364            // Both words are also legal property names, so a label needs both a statement
365            // position and a `switch` body around it: `{ default: … }` has the first and
366            // `o.default` neither.
367            TokenKind::Case | TokenKind::Default => {
368                self.at_statement_start()
369                    && self.braces.last().map(|frame| frame.kind) == Some(BraceKind::Block)
370            }
371            // A label's expression may be several tokens (`case Color.Red:`), so the flag
372            // survives to its `:`. Anything that ends a statement or a list ends the
373            // search — the word was a name, not a label.
374            TokenKind::Colon
375            | TokenKind::Semicolon
376            | TokenKind::Comma
377            | TokenKind::LeftBrace
378            | TokenKind::RightBrace
379            | TokenKind::LeftParen
380            | TokenKind::RightParen => false,
381            _ => self.in_case_label,
382        }
383    }
384
385    fn next_in_decl_header(&self, kind: &TokenKind) -> bool {
386        match kind {
387            // All three words are legal property names too, so a header needs a statement
388            // position: `{ class: … }` and `o.class` are names.
389            TokenKind::Class | TokenKind::Interface | TokenKind::Enum => {
390                self.at_statement_start() || matches!(self.last_emitted, Some(TokenKind::Export))
391            }
392            TokenKind::LeftBrace
393            | TokenKind::RightBrace
394            | TokenKind::Semicolon
395            | TokenKind::Colon
396            | TokenKind::Comma => false,
397            _ => self.in_decl_header,
398        }
399    }
400
401    fn next_brace_opens_specifier_list(&self, kind: &TokenKind) -> bool {
402        match kind {
403            TokenKind::Import | TokenKind::Export => true,
404            // Only the contextual `type` modifier may sit between the keyword and the
405            // specifier list; any other token means a declaration is being exported.
406            TokenKind::Identifier => self.brace_opens_specifier_list,
407            _ => false,
408        }
409    }
410
411    fn next_last_is_member_name(&self) -> bool {
412        matches!(
413            self.last_emitted,
414            Some(TokenKind::Dot | TokenKind::QuestionDot)
415        )
416    }
417
418    /// True when the `(` about to be tracked follows a control-flow keyword whose
419    /// parenthesized header is followed by a body rather than a statement terminator.
420    ///
421    /// All five words are legal property names, so `r.switch(1)` would otherwise mark its
422    /// *argument* list as a header and swallow the next statement's terminator.
423    fn opens_control_header(&self) -> bool {
424        !self.last_is_member_name
425            && matches!(
426                self.last_emitted,
427                Some(
428                    TokenKind::If
429                        | TokenKind::While
430                        | TokenKind::For
431                        | TokenKind::Switch
432                        | TokenKind::Catch
433                )
434            )
435    }
436}
437
438// The five tables below split along an axis worth stating once: `cannot_end_statement`
439// and the three it composes classify `last_emitted`, the token *behind* the newline;
440// `can_continue` classifies the token *ahead* of it. That is why `else` and `finally`
441// legitimately appear on both sides — they neither end the statement behind them nor
442// start a new one ahead.
443
444/// A statement cannot end on this token, so a following newline is mid-statement.
445/// `,` always sits inside a value list (object literal, pattern, import, argument or
446/// element list); an opening bracket has nothing before it to terminate; and an operator
447/// or clause keyword is still waiting for what comes next.
448fn cannot_end_statement(kind: &TokenKind) -> bool {
449    matches!(
450        kind,
451        TokenKind::Semicolon
452            | TokenKind::LeftBrace
453            | TokenKind::LeftParen
454            | TokenKind::LeftBracket
455            | TokenKind::Comma
456    ) || awaits_operand(kind)
457        || awaits_body(kind)
458}
459
460/// Operators that require a following operand, so an expression — and therefore a
461/// statement — can never legally end on one, *and* whose operand is a value: the `{`
462/// after one of these opens an object literal, not a block.
463///
464/// The narrower of the two tables, and the only one `opens_value_brace` may consult.
465/// `needs_semi` and `can_continue` want [`awaits_operand`], the superset that also
466/// covers `=>` — whose operand can be a statement block.
467fn cannot_end_expression(kind: &TokenKind) -> bool {
468    matches!(
469        kind,
470        TokenKind::Equals
471            | TokenKind::PlusEquals
472            | TokenKind::MinusEquals
473            | TokenKind::StarEquals
474            | TokenKind::SlashEquals
475            | TokenKind::PercentEquals
476            | TokenKind::StarStarEquals
477            | TokenKind::Plus
478            | TokenKind::Minus
479            | TokenKind::Star
480            | TokenKind::Slash
481            | TokenKind::Percent
482            | TokenKind::StarStar
483            | TokenKind::EqEqEq
484            | TokenKind::EqEq
485            | TokenKind::BangEqEq
486            | TokenKind::BangEq
487            | TokenKind::LessThan
488            | TokenKind::GreaterThan
489            | TokenKind::LessEquals
490            | TokenKind::GreaterEquals
491            | TokenKind::AmpAmp
492            | TokenKind::Amp
493            | TokenKind::AmpEquals
494            | TokenKind::PipeEquals
495            | TokenKind::Caret
496            | TokenKind::CaretEquals
497            | TokenKind::Pipe
498            | TokenKind::PipePipe
499            | TokenKind::Question
500            | TokenKind::QuestionQuestion
501            | TokenKind::Colon
502    )
503}
504
505/// Every operator that awaits an operand, so a newline after one is mid-expression:
506/// [`cannot_end_expression`] plus `=>` — prettier's default wrap for a long arrow puts
507/// a newline right there. `=>` is kept out of the narrower table because its operand
508/// may be a statement block, which `opens_value_brace` must not read as a value.
509fn awaits_operand(kind: &TokenKind) -> bool {
510    cannot_end_expression(kind)
511        || matches!(
512            kind,
513            TokenKind::Arrow
514                | TokenKind::Tilde
515                // A class header is not a statement, so it never ends at one of its
516                // own clause keywords — prettier wraps long headers right after them.
517                | TokenKind::Extends
518                | TokenKind::Implements
519        )
520}
521
522/// Clause keywords whose body follows directly, so a statement can never end on one:
523/// `try`, `else`, `do`, and `finally` — the Allman-brace shapes (`try` ⏎ `{`), plus
524/// `else` ⏎ `if`.
525///
526/// Deliberately not folded into [`awaits_operand`]: `can_continue` reads that table, and
527/// a newline *before* `try` or `do` does end the preceding statement.
528fn awaits_body(kind: &TokenKind) -> bool {
529    matches!(
530        kind,
531        TokenKind::Try | TokenKind::Else | TokenKind::Do | TokenKind::Finally | TokenKind::Catch
532    )
533}
534
535/// Tokens after which a `{` opens a value list rather than a statement block:
536/// assignment/operator RHS, argument/element position, `return`/`throw` operands, a
537/// template substitution's expression, and the `const`/`let` keywords that introduce
538/// destructuring patterns. Import/export specifier lists are `brace_opens_specifier_list`'s job.
539fn opens_value_brace(kind: &TokenKind) -> bool {
540    // `>` is the one operand-awaiting token a `{` never follows as a value: `a > { … }`
541    // compares against an object literal and is meaningless, while `function f():
542    // Array<number> {` and `class Box<T> {` put a body there.
543    if matches!(kind, TokenKind::GreaterThan) {
544        return false;
545    }
546    cannot_end_expression(kind)
547        || matches!(
548            kind,
549            TokenKind::LeftParen
550                | TokenKind::Tilde
551                | TokenKind::LeftBracket
552                | TokenKind::Comma
553                // A spread's operand: `{ ...{ a: 1 } }`, `[...{ … }]`.
554                | TokenKind::DotDotDot
555                | TokenKind::Return
556                | TokenKind::Throw
557                | TokenKind::Const
558                | TokenKind::Let
559                | TokenKind::TemplateHead(_)
560                | TokenKind::TemplateMiddle(_)
561        )
562}
563
564/// Tokens that continue the statement behind a newline, so no terminator is inserted
565/// before them.
566///
567/// Deliberately absent: `as` and `is`, contextual keywords lexed as identifiers, so a
568/// newline before them ends the statement — matching TypeScript, where `const v = x` ⏎
569/// `as (y)` is an initialized variable followed by a call of a function named `as`, not a
570/// cast. `typeof` starts a new expression, so it is not listed either.
571fn can_continue(kind: &TokenKind) -> bool {
572    // `~` awaits its operand, but cannot continue the preceding expression.
573    if matches!(kind, TokenKind::Tilde) {
574        return false;
575    }
576    awaits_operand(kind)
577        || matches!(
578            kind,
579            TokenKind::Dot
580                // Prettier breaks a long member chain before `?.` exactly as before `.`.
581                | TokenKind::QuestionDot
582                | TokenKind::LeftParen
583                | TokenKind::LeftBracket
584                | TokenKind::Else
585                // A `try`/`catch` block's `}` ends a clause, not the statement:
586                // the clause keywords continue it exactly as `else` does.
587                | TokenKind::Catch
588                | TokenKind::Finally
589                // Likewise a class header's own clause keywords, which prettier puts
590                // on their own lines when the header is long.
591                | TokenKind::Extends
592                | TokenKind::Implements
593                // `TemplateMiddle`/`TemplateTail` are always mid-template, never statement
594                // starters. `TemplateHead`/`TemplateNoSubstitution` start expressions, so
595                // are not listed.
596                | TokenKind::TemplateMiddle(_)
597                | TokenKind::TemplateTail(_)
598        )
599}
600
601#[cfg(test)]
602mod tests {
603    use super::Asi;
604    use crate::TokenKind;
605
606    fn asi_kinds(source: &str) -> Vec<TokenKind> {
607        let mut asi = Asi::new(source, crate::FileId(0));
608        let mut kinds = Vec::new();
609        loop {
610            let tok = asi.next_token();
611            let is_eof = tok.kind == TokenKind::Eof;
612            kinds.push(tok.kind);
613            if is_eof {
614                break;
615            }
616        }
617        let diags = asi.into_diagnostics();
618        assert!(
619            diags.is_empty(),
620            "unexpected diagnostics for {source:?}: {diags:?}"
621        );
622        kinds
623    }
624
625    #[test]
626    fn strips_all_newlines() {
627        let kinds = asi_kinds("a\nb\n");
628        assert!(!kinds.iter().any(|k| matches!(k, TokenKind::Newline)));
629    }
630
631    #[test]
632    fn inserts_semicolon_between_statements() {
633        assert_eq!(
634            asi_kinds("a\nb"),
635            vec![
636                TokenKind::Identifier,
637                TokenKind::Semicolon,
638                TokenKind::Identifier,
639                TokenKind::Semicolon,
640                TokenKind::Eof,
641            ]
642        );
643    }
644
645    #[test]
646    fn newline_before_as_splits_statement() {
647        // `as` is a contextual keyword (a plain identifier here), so ASI ends the
648        // statement — TS parses `a` ⏎ `as(b)` the same way.
649        assert_eq!(
650            asi_kinds("a\nas(b)"),
651            vec![
652                TokenKind::Identifier,
653                TokenKind::Semicolon,
654                TokenKind::Identifier,
655                TokenKind::LeftParen,
656                TokenKind::Identifier,
657                TokenKind::RightParen,
658                TokenKind::Semicolon,
659                TokenKind::Eof,
660            ]
661        );
662    }
663
664    #[test]
665    fn inserts_semicolon_before_close_brace() {
666        assert_eq!(
667            asi_kinds("{ a\n}"),
668            vec![
669                TokenKind::LeftBrace,
670                TokenKind::Identifier,
671                TokenKind::Semicolon,
672                TokenKind::RightBrace,
673                TokenKind::Semicolon,
674                TokenKind::Eof,
675            ]
676        );
677    }
678
679    #[test]
680    fn empty_block_no_semicolon_inside() {
681        assert_eq!(
682            asi_kinds("{\n}"),
683            vec![
684                TokenKind::LeftBrace,
685                TokenKind::RightBrace,
686                TokenKind::Semicolon,
687                TokenKind::Eof,
688            ]
689        );
690    }
691
692    #[test]
693    fn does_not_double_semicolon() {
694        assert_eq!(
695            asi_kinds("a;\nb;\n"),
696            vec![
697                TokenKind::Identifier,
698                TokenKind::Semicolon,
699                TokenKind::Identifier,
700                TokenKind::Semicolon,
701                TokenKind::Eof,
702            ]
703        );
704    }
705
706    #[test]
707    fn continues_across_newline_with_infix_operator() {
708        assert_eq!(
709            asi_kinds("a = b\n+ c"),
710            vec![
711                TokenKind::Identifier,
712                TokenKind::Equals,
713                TokenKind::Identifier,
714                TokenKind::Plus,
715                TokenKind::Identifier,
716                TokenKind::Semicolon,
717                TokenKind::Eof,
718            ]
719        );
720    }
721
722    #[test]
723    fn continues_across_newline_with_dot() {
724        assert_eq!(
725            asi_kinds("foo\n.bar"),
726            vec![
727                TokenKind::Identifier,
728                TokenKind::Dot,
729                TokenKind::Identifier,
730                TokenKind::Semicolon,
731                TokenKind::Eof,
732            ]
733        );
734    }
735
736    #[test]
737    fn inserts_semicolon_at_eof() {
738        assert_eq!(
739            asi_kinds("a"),
740            vec![TokenKind::Identifier, TokenKind::Semicolon, TokenKind::Eof,]
741        );
742    }
743
744    #[test]
745    fn eof_with_trailing_semicolon_no_double() {
746        assert_eq!(
747            asi_kinds("a;"),
748            vec![TokenKind::Identifier, TokenKind::Semicolon, TokenKind::Eof,]
749        );
750    }
751
752    #[test]
753    fn no_semicolon_inside_parens() {
754        assert_eq!(
755            asi_kinds("foo(\n  a,\n  b\n)"),
756            vec![
757                TokenKind::Identifier,
758                TokenKind::LeftParen,
759                TokenKind::Identifier,
760                TokenKind::Comma,
761                TokenKind::Identifier,
762                TokenKind::RightParen,
763                TokenKind::Semicolon,
764                TokenKind::Eof,
765            ]
766        );
767    }
768
769    #[test]
770    fn no_semicolon_after_if_header_on_next_line() {
771        // `if (a)` newline `b`: the `)` closes a control-flow header, so the body on the
772        // next line must not be severed by an inserted `;`.
773        assert_eq!(
774            asi_kinds("if (a)\nb"),
775            vec![
776                TokenKind::If,
777                TokenKind::LeftParen,
778                TokenKind::Identifier,
779                TokenKind::RightParen,
780                TokenKind::Identifier,
781                TokenKind::Semicolon,
782                TokenKind::Eof,
783            ]
784        );
785    }
786
787    #[test]
788    fn no_semicolon_after_while_header_on_next_line() {
789        assert_eq!(
790            asi_kinds("while (a)\nb"),
791            vec![
792                TokenKind::While,
793                TokenKind::LeftParen,
794                TokenKind::Identifier,
795                TokenKind::RightParen,
796                TokenKind::Identifier,
797                TokenKind::Semicolon,
798                TokenKind::Eof,
799            ]
800        );
801    }
802
803    #[test]
804    fn header_paren_only_suppresses_its_own_close() {
805        // The inner `)` of `if ((a))` is an ordinary paren; only the header `)` suppresses ASI.
806        assert_eq!(
807            asi_kinds("if ((a))\nb"),
808            vec![
809                TokenKind::If,
810                TokenKind::LeftParen,
811                TokenKind::LeftParen,
812                TokenKind::Identifier,
813                TokenKind::RightParen,
814                TokenKind::RightParen,
815                TokenKind::Identifier,
816                TokenKind::Semicolon,
817                TokenKind::Eof,
818            ]
819        );
820    }
821
822    #[test]
823    fn call_close_paren_still_inserts_semicolon() {
824        // A `)` closing a call expression ends a statement — ASI must still fire.
825        assert_eq!(
826            asi_kinds("f()\ng()"),
827            vec![
828                TokenKind::Identifier,
829                TokenKind::LeftParen,
830                TokenKind::RightParen,
831                TokenKind::Semicolon,
832                TokenKind::Identifier,
833                TokenKind::LeftParen,
834                TokenKind::RightParen,
835                TokenKind::Semicolon,
836                TokenKind::Eof,
837            ]
838        );
839    }
840
841    #[test]
842    fn no_semicolon_inside_brackets() {
843        assert_eq!(
844            asi_kinds("[\n1,\n2\n]"),
845            vec![
846                TokenKind::LeftBracket,
847                TokenKind::NumberLiteral(1.0),
848                TokenKind::Comma,
849                TokenKind::NumberLiteral(2.0),
850                TokenKind::RightBracket,
851                TokenKind::Semicolon,
852                TokenKind::Eof,
853            ]
854        );
855    }
856
857    #[test]
858    fn else_continues_if_statement() {
859        assert_eq!(
860            asi_kinds("if (c) { }\nelse { }"),
861            vec![
862                TokenKind::If,
863                TokenKind::LeftParen,
864                TokenKind::Identifier,
865                TokenKind::RightParen,
866                TokenKind::LeftBrace,
867                TokenKind::RightBrace,
868                TokenKind::Else,
869                TokenKind::LeftBrace,
870                TokenKind::RightBrace,
871                TokenKind::Semicolon,
872                TokenKind::Eof,
873            ]
874        );
875    }
876
877    #[test]
878    fn catch_continues_try_statement() {
879        assert_eq!(
880            asi_kinds("try { }\ncatch (e) { }"),
881            vec![
882                TokenKind::Try,
883                TokenKind::LeftBrace,
884                TokenKind::RightBrace,
885                TokenKind::Catch,
886                TokenKind::LeftParen,
887                TokenKind::Identifier,
888                TokenKind::RightParen,
889                TokenKind::LeftBrace,
890                TokenKind::RightBrace,
891                TokenKind::Semicolon,
892                TokenKind::Eof,
893            ]
894        );
895    }
896
897    #[test]
898    fn finally_continues_try_statement() {
899        assert_eq!(
900            asi_kinds("try { }\nfinally { }"),
901            vec![
902                TokenKind::Try,
903                TokenKind::LeftBrace,
904                TokenKind::RightBrace,
905                TokenKind::Finally,
906                TokenKind::LeftBrace,
907                TokenKind::RightBrace,
908                TokenKind::Semicolon,
909                TokenKind::Eof,
910            ]
911        );
912    }
913
914    #[test]
915    fn no_semicolon_after_trailing_arrow() {
916        // Prettier's default wrap for a long arrow: `=>` awaits its body, so the
917        // newline after it must not be turned into a statement end.
918        assert_eq!(
919            asi_kinds("const f = (x: number): number =>\nx + 1"),
920            vec![
921                TokenKind::Const,
922                TokenKind::Identifier,
923                TokenKind::Equals,
924                TokenKind::LeftParen,
925                TokenKind::Identifier,
926                TokenKind::Colon,
927                TokenKind::Identifier,
928                TokenKind::RightParen,
929                TokenKind::Colon,
930                TokenKind::Identifier,
931                TokenKind::Arrow,
932                TokenKind::Identifier,
933                TokenKind::Plus,
934                TokenKind::NumberLiteral(1.0),
935                TokenKind::Semicolon,
936                TokenKind::Eof,
937            ]
938        );
939    }
940
941    #[test]
942    fn arrow_block_body_still_gets_terminator() {
943        // Regression guard for the `awaits_operand` split: `=>` must not make the
944        // body brace a value list, or the last statement loses its inserted `;`.
945        assert_eq!(
946            asi_kinds("const f = () => {\na\n}"),
947            vec![
948                TokenKind::Const,
949                TokenKind::Identifier,
950                TokenKind::Equals,
951                TokenKind::LeftParen,
952                TokenKind::RightParen,
953                TokenKind::Arrow,
954                TokenKind::LeftBrace,
955                TokenKind::Identifier,
956                TokenKind::Semicolon,
957                TokenKind::RightBrace,
958                TokenKind::Semicolon,
959                TokenKind::Eof,
960            ]
961        );
962    }
963
964    #[test]
965    fn extends_continues_class_header() {
966        assert_eq!(
967            asi_kinds("class S\nextends B { }"),
968            vec![
969                TokenKind::Class,
970                TokenKind::Identifier,
971                TokenKind::Extends,
972                TokenKind::Identifier,
973                TokenKind::LeftBrace,
974                TokenKind::RightBrace,
975                TokenKind::Semicolon,
976                TokenKind::Eof,
977            ]
978        );
979    }
980
981    #[test]
982    fn no_semicolon_after_trailing_extends() {
983        assert_eq!(
984            asi_kinds("class S extends\nB { }"),
985            vec![
986                TokenKind::Class,
987                TokenKind::Identifier,
988                TokenKind::Extends,
989                TokenKind::Identifier,
990                TokenKind::LeftBrace,
991                TokenKind::RightBrace,
992                TokenKind::Semicolon,
993                TokenKind::Eof,
994            ]
995        );
996    }
997
998    #[test]
999    fn implements_continues_class_header() {
1000        assert_eq!(
1001            asi_kinds("class S\nimplements I { }"),
1002            vec![
1003                TokenKind::Class,
1004                TokenKind::Identifier,
1005                TokenKind::Implements,
1006                TokenKind::Identifier,
1007                TokenKind::LeftBrace,
1008                TokenKind::RightBrace,
1009                TokenKind::Semicolon,
1010                TokenKind::Eof,
1011            ]
1012        );
1013    }
1014
1015    #[test]
1016    fn no_semicolon_after_trailing_implements() {
1017        assert_eq!(
1018            asi_kinds("class S implements\nI { }"),
1019            vec![
1020                TokenKind::Class,
1021                TokenKind::Identifier,
1022                TokenKind::Implements,
1023                TokenKind::Identifier,
1024                TokenKind::LeftBrace,
1025                TokenKind::RightBrace,
1026                TokenKind::Semicolon,
1027                TokenKind::Eof,
1028            ]
1029        );
1030    }
1031
1032    #[test]
1033    fn optional_chain_continues_across_newline() {
1034        // Prettier breaks a long member chain before `?.` exactly as before `.`.
1035        assert_eq!(
1036            asi_kinds("foo\n?.bar"),
1037            vec![
1038                TokenKind::Identifier,
1039                TokenKind::QuestionDot,
1040                TokenKind::Identifier,
1041                TokenKind::Semicolon,
1042                TokenKind::Eof,
1043            ]
1044        );
1045    }
1046
1047    #[test]
1048    fn empty_input() {
1049        assert_eq!(asi_kinds(""), vec![TokenKind::Eof]);
1050    }
1051
1052    #[test]
1053    fn leading_newlines_no_semicolon() {
1054        assert_eq!(
1055            asi_kinds("\n\na"),
1056            vec![TokenKind::Identifier, TokenKind::Semicolon, TokenKind::Eof,]
1057        );
1058    }
1059
1060    #[test]
1061    fn no_semicolon_in_multiline_object_literal() {
1062        // `= {` opens a value brace; the comma rule covers inter-member newlines and the
1063        // value-brace rule covers the newline before `}`.
1064        assert_eq!(
1065            asi_kinds("x = {\na: 1,\nb: 2\n}"),
1066            vec![
1067                TokenKind::Identifier,
1068                TokenKind::Equals,
1069                TokenKind::LeftBrace,
1070                TokenKind::Identifier,
1071                TokenKind::Colon,
1072                TokenKind::NumberLiteral(1.0),
1073                TokenKind::Comma,
1074                TokenKind::Identifier,
1075                TokenKind::Colon,
1076                TokenKind::NumberLiteral(2.0),
1077                TokenKind::RightBrace,
1078                TokenKind::Semicolon,
1079                TokenKind::Eof,
1080            ]
1081        );
1082    }
1083
1084    #[test]
1085    fn no_semicolon_in_multiline_object_literal_trailing_comma() {
1086        assert_eq!(
1087            asi_kinds("x = {\na: 1,\n}"),
1088            vec![
1089                TokenKind::Identifier,
1090                TokenKind::Equals,
1091                TokenKind::LeftBrace,
1092                TokenKind::Identifier,
1093                TokenKind::Colon,
1094                TokenKind::NumberLiteral(1.0),
1095                TokenKind::Comma,
1096                TokenKind::RightBrace,
1097                TokenKind::Semicolon,
1098                TokenKind::Eof,
1099            ]
1100        );
1101    }
1102
1103    #[test]
1104    fn block_brace_still_gets_terminator() {
1105        // A statement-position `{` is a block; its last statement keeps its inserted `;`.
1106        assert_eq!(
1107            asi_kinds("{\na\n}"),
1108            vec![
1109                TokenKind::LeftBrace,
1110                TokenKind::Identifier,
1111                TokenKind::Semicolon,
1112                TokenKind::RightBrace,
1113                TokenKind::Semicolon,
1114                TokenKind::Eof,
1115            ]
1116        );
1117    }
1118
1119    #[test]
1120    fn type_literal_members_get_separators() {
1121        // A `: {` type literal is a value list, so its closing `}` gets no terminator —
1122        // the member grammar makes the last separator optional. Newline-separated members
1123        // still receive an inserted `;` between them.
1124        assert_eq!(
1125            asi_kinds("let p: {\na: number\nb: number\n}"),
1126            vec![
1127                TokenKind::Let,
1128                TokenKind::Identifier,
1129                TokenKind::Colon,
1130                TokenKind::LeftBrace,
1131                TokenKind::Identifier,
1132                TokenKind::Colon,
1133                TokenKind::Identifier,
1134                TokenKind::Semicolon,
1135                TokenKind::Identifier,
1136                TokenKind::Colon,
1137                TokenKind::Identifier,
1138                TokenKind::RightBrace,
1139                TokenKind::Semicolon,
1140                TokenKind::Eof,
1141            ]
1142        );
1143    }
1144
1145    #[test]
1146    fn nested_object_value_suppresses_inner_close() {
1147        // The inner `{` follows `:` inside a value brace, so it is itself a value brace.
1148        assert_eq!(
1149            asi_kinds("x = {\nouter: {\ninner: 1\n}\n}"),
1150            vec![
1151                TokenKind::Identifier,
1152                TokenKind::Equals,
1153                TokenKind::LeftBrace,
1154                TokenKind::Identifier,
1155                TokenKind::Colon,
1156                TokenKind::LeftBrace,
1157                TokenKind::Identifier,
1158                TokenKind::Colon,
1159                TokenKind::NumberLiteral(1.0),
1160                TokenKind::RightBrace,
1161                TokenKind::RightBrace,
1162                TokenKind::Semicolon,
1163                TokenKind::Eof,
1164            ]
1165        );
1166    }
1167
1168    #[test]
1169    fn multiline_destructuring_pattern_no_semicolon() {
1170        assert_eq!(
1171            asi_kinds("const {\na,\nb\n} = p"),
1172            vec![
1173                TokenKind::Const,
1174                TokenKind::LeftBrace,
1175                TokenKind::Identifier,
1176                TokenKind::Comma,
1177                TokenKind::Identifier,
1178                TokenKind::RightBrace,
1179                TokenKind::Equals,
1180                TokenKind::Identifier,
1181                TokenKind::Semicolon,
1182                TokenKind::Eof,
1183            ]
1184        );
1185    }
1186
1187    #[test]
1188    fn no_semicolon_after_trailing_equals() {
1189        // `const x =` newline `5`: the `=` cannot end an expression, so the initializer
1190        // continues onto the next line rather than being severed by an inserted `;`.
1191        assert_eq!(
1192            asi_kinds("const x =\n5"),
1193            vec![
1194                TokenKind::Const,
1195                TokenKind::Identifier,
1196                TokenKind::Equals,
1197                TokenKind::NumberLiteral(5.0),
1198                TokenKind::Semicolon,
1199                TokenKind::Eof,
1200            ]
1201        );
1202    }
1203
1204    #[test]
1205    fn no_semicolon_after_trailing_binary_operator() {
1206        assert_eq!(
1207            asi_kinds("const x = 1 +\n2"),
1208            vec![
1209                TokenKind::Const,
1210                TokenKind::Identifier,
1211                TokenKind::Equals,
1212                TokenKind::NumberLiteral(1.0),
1213                TokenKind::Plus,
1214                TokenKind::NumberLiteral(2.0),
1215                TokenKind::Semicolon,
1216                TokenKind::Eof,
1217            ]
1218        );
1219    }
1220
1221    #[test]
1222    fn no_semicolon_after_trailing_assignment_reassign() {
1223        assert_eq!(
1224            asi_kinds("x =\n5"),
1225            vec![
1226                TokenKind::Identifier,
1227                TokenKind::Equals,
1228                TokenKind::NumberLiteral(5.0),
1229                TokenKind::Semicolon,
1230                TokenKind::Eof,
1231            ]
1232        );
1233    }
1234
1235    #[test]
1236    fn no_semicolon_after_trailing_compound_assignment() {
1237        assert_eq!(
1238            asi_kinds("x +=\n5"),
1239            vec![
1240                TokenKind::Identifier,
1241                TokenKind::PlusEquals,
1242                TokenKind::NumberLiteral(5.0),
1243                TokenKind::Semicolon,
1244                TokenKind::Eof,
1245            ]
1246        );
1247    }
1248
1249    #[test]
1250    fn no_semicolon_after_trailing_logical_operator() {
1251        assert_eq!(
1252            asi_kinds("true &&\nfalse"),
1253            vec![
1254                TokenKind::BooleanLiteral(true),
1255                TokenKind::AmpAmp,
1256                TokenKind::BooleanLiteral(false),
1257                TokenKind::Semicolon,
1258                TokenKind::Eof,
1259            ]
1260        );
1261    }
1262
1263    #[test]
1264    fn no_semicolon_after_trailing_nullish_coalescing() {
1265        assert_eq!(
1266            asi_kinds("a ??\nb"),
1267            vec![
1268                TokenKind::Identifier,
1269                TokenKind::QuestionQuestion,
1270                TokenKind::Identifier,
1271                TokenKind::Semicolon,
1272                TokenKind::Eof,
1273            ]
1274        );
1275    }
1276
1277    #[test]
1278    fn no_semicolon_before_leading_ternary() {
1279        // `cond` newline `? 1 : 2`: a continuation line beginning with `?` must not be
1280        // severed from the condition.
1281        assert_eq!(
1282            asi_kinds("cond\n? 1 : 2"),
1283            vec![
1284                TokenKind::Identifier,
1285                TokenKind::Question,
1286                TokenKind::NumberLiteral(1.0),
1287                TokenKind::Colon,
1288                TokenKind::NumberLiteral(2.0),
1289                TokenKind::Semicolon,
1290                TokenKind::Eof,
1291            ]
1292        );
1293    }
1294
1295    #[test]
1296    fn no_semicolon_after_trailing_ternary_question_and_colon() {
1297        assert_eq!(
1298            asi_kinds("cond ?\n1 :\n2"),
1299            vec![
1300                TokenKind::Identifier,
1301                TokenKind::Question,
1302                TokenKind::NumberLiteral(1.0),
1303                TokenKind::Colon,
1304                TokenKind::NumberLiteral(2.0),
1305                TokenKind::Semicolon,
1306                TokenKind::Eof,
1307            ]
1308        );
1309    }
1310
1311    #[test]
1312    fn no_semicolon_in_multiline_operator_chain() {
1313        assert_eq!(
1314            asi_kinds("const x = a +\nb +\nc"),
1315            vec![
1316                TokenKind::Const,
1317                TokenKind::Identifier,
1318                TokenKind::Equals,
1319                TokenKind::Identifier,
1320                TokenKind::Plus,
1321                TokenKind::Identifier,
1322                TokenKind::Plus,
1323                TokenKind::Identifier,
1324                TokenKind::Semicolon,
1325                TokenKind::Eof,
1326            ]
1327        );
1328    }
1329
1330    #[test]
1331    fn return_keeps_restricted_production() {
1332        // Regression guard: `return` newline `5` is the restricted production — ASI inserts
1333        // `;` after `return`, leaving the `5` as its own statement.
1334        assert_eq!(
1335            asi_kinds("return\n5"),
1336            vec![
1337                TokenKind::Return,
1338                TokenKind::Semicolon,
1339                TokenKind::NumberLiteral(5.0),
1340                TokenKind::Semicolon,
1341                TokenKind::Eof,
1342            ]
1343        );
1344    }
1345
1346    /// `source` with `<;>` marking every offset where ASI inserted a semicolon — always
1347    /// immediately before the token that triggered it, since an inserted terminator is
1348    /// zero-width at that token's start.
1349    ///
1350    /// Use this to pin *where* a terminator lands; use an `asi_kinds` token vector when
1351    /// the whole stream matters — that nothing else was dropped, reordered, or emitted.
1352    fn asi_marked(source: &str) -> String {
1353        let mut asi = Asi::new(source, crate::FileId(0));
1354        let mut inserted: Vec<usize> = Vec::new();
1355        loop {
1356            let tok = asi.next_token();
1357            if tok.kind == TokenKind::Eof {
1358                break;
1359            }
1360            // An inserted semicolon is zero-width; one the author wrote spans its `;`.
1361            if tok.kind == TokenKind::Semicolon && tok.span.start == tok.span.end {
1362                inserted.push(tok.span.start as usize);
1363            }
1364        }
1365        let mut out = String::new();
1366        let mut last = 0;
1367        for at in inserted {
1368            out.push_str(&source[last..at]);
1369            out.push_str("<;>");
1370            last = at;
1371        }
1372        out.push_str(&source[last..]);
1373        out
1374    }
1375
1376    #[test]
1377    fn clause_keyword_before_its_brace_gets_no_terminator() {
1378        // Allman style: `try` / `catch (…)` / `else` / `do` are followed by a body, so
1379        // the newline before the `{` is not a statement end.
1380        assert_eq!(
1381            asi_marked("try\n{\na\n}\ncatch (e)\n{\n}"),
1382            "try\n{\na\n<;>}\ncatch (e)\n{\n}<;>"
1383        );
1384        assert_eq!(
1385            asi_marked("if (c)\n{\n}\nelse\n{\n}"),
1386            "if (c)\n{\n}\nelse\n{\n}<;>"
1387        );
1388        // ECMAScript inserts nothing before this `while` — the DoWhileStatement production
1389        // allows it — but a token-stream pre-pass cannot see that, so it emits one here and
1390        // the parser drops it (`eat_asi_semicolon_before`). The rows below record where the
1391        // pre-pass lands the terminator; the behaviour that matters is pinned by
1392        // `asi_allman_braces.ts`.
1393        assert_eq!(asi_marked("do\n{\n}\nwhile (c)"), "do\n{\n}\n<;>while (c)");
1394    }
1395
1396    #[test]
1397    fn a_newline_before_a_clause_keyword_still_ends_the_statement() {
1398        // `awaits_body` must not have leaked into `can_continue`: a newline *before*
1399        // `try` or `do` ends the preceding statement.
1400        assert_eq!(asi_marked("a\ntry\n{\n}"), "a\n<;>try\n{\n}<;>");
1401        assert_eq!(
1402            asi_marked("a\ndo\n{\n}\nwhile (c)"),
1403            "a\n<;>do\n{\n}\n<;>while (c)"
1404        );
1405    }
1406
1407    #[test]
1408    fn block_closing_brace_terminates_without_a_newline() {
1409        assert_eq!(
1410            asi_marked("function f(): number { return 1 }"),
1411            "function f(): number { return 1 <;>}<;>"
1412        );
1413        assert_eq!(
1414            asi_marked("class C { x: number = 1 }"),
1415            "class C { x: number = 1 <;>}<;>"
1416        );
1417    }
1418
1419    #[test]
1420    fn value_closing_brace_never_terminates() {
1421        assert_eq!(asi_marked("x = { a: 1 }"), "x = { a: 1 }<;>");
1422        assert_eq!(
1423            asi_marked("import { a } from \"m\""),
1424            "import { a } from \"m\"<;>"
1425        );
1426        // The contextual `type` modifier sits between `export` and its specifier list.
1427        assert_eq!(
1428            asi_marked("export type { A } from \"m\""),
1429            "export type { A } from \"m\"<;>"
1430        );
1431    }
1432
1433    #[test]
1434    fn only_a_after_case_label_colon_opens_a_block() {
1435        // The `:` of a `case`/`default` label is the one colon a statement block follows;
1436        // a ternary's alternate and an object value after the same two words are not.
1437        assert_eq!(
1438            asi_marked("switch (n) {\ncase 1: { a }\n}"),
1439            "switch (n) {\ncase 1: { a <;>}\n<;>}<;>"
1440        );
1441        assert_eq!(
1442            asi_marked("x = c ? { a: 1 } : { a: 2 }"),
1443            "x = c ? { a: 1 } : { a: 2 }<;>"
1444        );
1445        assert_eq!(
1446            asi_marked("x = { default: { a: 1 } }"),
1447            "x = { default: { a: 1 } }<;>"
1448        );
1449    }
1450
1451    #[test]
1452    fn a_keyword_property_name_is_not_a_keyword() {
1453        // Every clause and declaration keyword is also a legal property name, so the
1454        // tables must not read `o.do` as a `do` loop or `{ class: … }` as a class header.
1455        assert_eq!(asi_marked("a = o.do\nb = 1"), "a = o.do\n<;>b = 1<;>");
1456        assert_eq!(asi_marked("a = o.class\nb = 1"), "a = o.class\n<;>b = 1<;>");
1457        assert_eq!(
1458            asi_marked("a = o.default\nb = 1"),
1459            "a = o.default\n<;>b = 1<;>"
1460        );
1461        assert_eq!(asi_marked("a = o?.try\nb = 1"), "a = o?.try\n<;>b = 1<;>");
1462        // A call's argument list is not a control-flow header just because the method is
1463        // spelled `switch`.
1464        assert_eq!(
1465            asi_marked("a = o.switch(1)\nb = 1"),
1466            "a = o.switch(1)\n<;>b = 1<;>"
1467        );
1468        assert_eq!(
1469            asi_marked("a = o.catch(1)\nb = 1"),
1470            "a = o.catch(1)\n<;>b = 1<;>"
1471        );
1472    }
1473
1474    #[test]
1475    fn a_generic_header_is_followed_by_a_block() {
1476        // `>` closes a type-argument list here, so the `{` opens a body, not a value.
1477        assert_eq!(
1478            asi_marked("function f(): Array<number> {\nreturn a\n}"),
1479            "function f(): Array<number> {\nreturn a\n<;>}<;>"
1480        );
1481        assert_eq!(
1482            asi_marked("class Box<T> {\nv: number = 1\n}"),
1483            "class Box<T> {\nv: number = 1\n<;>}<;>"
1484        );
1485    }
1486
1487    #[test]
1488    fn a_cast_is_followed_by_an_object_literal() {
1489        // A `<` in operand position opens a cast, so its `>` leaves a value next.
1490        assert_eq!(
1491            asi_marked("x = <Foo>{ a: 1 }\ny()"),
1492            "x = <Foo>{ a: 1 }\n<;>y()<;>"
1493        );
1494        assert_eq!(asi_marked("<Foo>{ a: 1 }\ny()"), "<Foo>{ a: 1 }\n<;>y()<;>");
1495        assert_eq!(
1496            asi_marked("f = x => <Foo>{ a: x }\ny()"),
1497            "f = x => <Foo>{ a: x }\n<;>y()<;>"
1498        );
1499        assert_eq!(
1500            asi_marked("x = <Box<Array<number>>>{ v: [] }\ny()"),
1501            "x = <Box<Array<number>>>{ v: [] }\n<;>y()<;>"
1502        );
1503        // After a comparison the next `<` follows an operand, so no cast is tracked.
1504        assert_eq!(
1505            asi_marked("if (a < b) {\nreturn a\n}"),
1506            "if (a < b) {\nreturn a\n<;>}<;>"
1507        );
1508    }
1509
1510    #[test]
1511    fn a_spread_operand_is_an_object_literal() {
1512        assert_eq!(
1513            asi_marked("x = { ...{ a: 1 }, b: 2 }\ny()"),
1514            "x = { ...{ a: 1 }, b: 2 }\n<;>y()<;>"
1515        );
1516    }
1517
1518    #[test]
1519    fn template_substitution_suspends_insertion() {
1520        assert_eq!(
1521            asi_marked("const s = `a${\nn\n}`"),
1522            "const s = `a${\nn\n}`<;>"
1523        );
1524        assert_eq!(
1525            asi_marked("const s = `a${\nn\n}b${\nn\n}c`"),
1526            "const s = `a${\nn\n}b${\nn\n}c`<;>"
1527        );
1528    }
1529
1530    #[test]
1531    fn declaration_header_suspends_insertion() {
1532        assert_eq!(
1533            asi_marked("class S\nextends B\nimplements I\n{\n}"),
1534            "class S\nextends B\nimplements I\n{\n}<;>"
1535        );
1536        assert_eq!(asi_marked("interface I\n{\n}"), "interface I\n{\n}<;>");
1537        assert_eq!(asi_marked("enum E\n{\nA\n}"), "enum E\n{\nA\n<;>}<;>");
1538    }
1539
1540    #[test]
1541    fn block_inside_parens_is_statement_context_again() {
1542        // The body of an arrow passed as an argument gets its terminators, while the
1543        // argument list around it stays suspended.
1544        assert_eq!(
1545            asi_marked("f((x: number): number => {\nreturn x\n})"),
1546            "f((x: number): number => {\nreturn x\n<;>})<;>"
1547        );
1548        assert_eq!(asi_marked("f(a,\nb)"), "f(a,\nb)<;>");
1549    }
1550
1551    #[test]
1552    fn mvp_fixture_snapshot() {
1553        let source = "function greet(name: string): string {\n  return \"Hello, \" + name;\n}\n\nfunction main(): string {\n  const msg = greet(\"world\");\n  console.log(msg);\n  assert(msg === \"Hello, world\", \"greeting should match\");\n  return msg;\n}\n";
1554        let mut asi = Asi::new(source, crate::FileId(0));
1555        let mut tokens = Vec::new();
1556        loop {
1557            let tok = asi.next_token();
1558            let is_eof = tok.kind == TokenKind::Eof;
1559            tokens.push(tok);
1560            if is_eof {
1561                break;
1562            }
1563        }
1564        assert!(asi.into_diagnostics().is_empty());
1565        let kinds: Vec<String> = tokens.iter().map(|t| format!("{:?}", t.kind)).collect();
1566        insta::assert_debug_snapshot!(kinds);
1567    }
1568}