tsv_ts 0.8.0

TypeScript/JS formatter, type stripper and acorn-compatible parser — part of tsv
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
// Statement printing for TypeScript — the per-kind DISPATCH plus the expression
// statement, which has no module of its own because its whole job is the value it
// wraps.
//
// The statement kinds with a shape of their own live in the submodules below:
// declarations in `variable` / `function` / `class` / `type_declarations`, modules in
// `modules`, control flow in `control_flow`. Two cross-cutting questions were split out
// because each is asked from several of those and answering it twice would let the
// answers drift: `head_parens` (which parens a statement's HEAD must keep or
// synthesize) and `restricted_production` (`return` / `throw` / `yield` and the
// ASI-safe parens their argument needs).

mod class;
mod control_flow;
pub(in crate::printer) mod function;
mod head_parens;
mod modules;
pub(in crate::printer) mod restricted_production;
mod type_declarations;
pub(in crate::printer) mod variable;

// Re-export for submodules to use `super::Printer` instead of `super::super::Printer`
pub(super) use super::{Printer, build_entity_name_doc, is_effectively_empty_body};

use super::LeadingGlue;
use super::ParenContext;
use super::RunLeadingBlank;
use super::class_expr_has_decorators;
use super::expressions::literals::format_directive;
use super::expressions::operators::SeqLayout;
use super::needs_parens::LeftmostText;
use crate::ast::{self, Expression, ExpressionKind, Statement, StatementKind};
use smallvec::smallvec;
use tsv_lang::Span;
use tsv_lang::doc::DocBuf;
use tsv_lang::doc::arena::DocId;

/// Where a statement sits — the two container facts its builders read.
///
/// `in_program_or_block` is Prettier's own-terms grandparent check for the
/// "avoid becoming a directive" rule ([`Printer::needs_avoid_directive_parens`]) —
/// `true` when the statement's immediate container is a `Program` or
/// `BlockStatement`, `false` for the containers that use a different AST node
/// (`SwitchCase`, `StaticBlock`, `TSModuleBlock`) and for every clause body.
///
/// `clause_tail_dedent` is the `;`-terminator gap's deferral axis — read through
/// [`StatementContext::terminator_gap`] by the kinds that can hand the gap to their list, and
/// through [`StatementContext::clause_tail`] by the kinds that never can. In a statement
/// LIST the joiner's own break immediately follows the statement doc, so the gap's
/// own-line run can take real breaks — the emission is closed on its own line before
/// anything else can queue a `line_suffix` (`None`). A non-block CLAUSE body's last
/// line instead stays open to the enclosing construct — an `else`, a do-while
/// `while`, or just the construct's end — where a later gap's deferred `//` would
/// flush onto a real-text `//` and weld irreversibly (`x; // c1 // c2` reparses as
/// ONE comment). There the run defers through the suffix machinery
/// ([`Printer::push_semicolon_with_gap_comments`]), and the value is how many indent
/// levels sit between the statement's doc position and the break that flushes its
/// tail: each body site adds one per `indent` wrap it emits, and a construct that
/// CONTINUES on the tail's flush line (`else`, `while`) restarts the count at its own
/// gap ([`StatementContext::clause_body`]).
#[derive(Clone, Copy)]
pub(in crate::printer) struct StatementContext {
    pub(in crate::printer) in_program_or_block: bool,
    clause_tail_dedent: Option<u8>,
    /// Whether this statement's own tail is also the tail of the outermost statement a
    /// LIST holds — so a comment freed from its `;`-terminator gap lands in that list's
    /// gap, where the ordinary own-line / same-line split places it.
    ///
    /// True for a list member, and inherited by a clause BODY whose construct emits
    /// nothing after it (`continues == false`): `if (cond) expr⏎/* c */;⏎fn();` is the
    /// list's gap read one construct deeper, and prettier reads it that way too. False
    /// once any enclosing construct continues past the tail — an `if`'s CONSEQUENT before
    /// an `else`, a do-while's body before its `while` — where the gap is interior to the
    /// construct and no list seam can reach it.
    tail_reaches_list: bool,
}

/// Who prints the comments in a `;`-terminated statement's content→`;` gap — the axis
/// `gap` alone was standing in for, and could not express.
///
/// Three answers, not two. `gap == None` used to mean "a statement in a list", which
/// is true of every statement `build_statement_doc` reaches from a list walk and false of the
/// embedded declaration entry points, whose `;` has no list around it at all. Reading the
/// absent dedent as list membership DROPPED the comment in `{let a /* c */;}` — the gap was
/// handed to a seam that does not exist there.
#[derive(Clone, Copy, PartialEq, Eq)]
pub(in crate::printer) enum TerminatorGap {
    /// The enclosing statement LIST. Prettier's `__contentEnd` ejects the gap from the
    /// statement ([`Printer::statement_comment_claim_end`]), and the list's own seam places
    /// the run by the ordinary own-line / same-line split.
    ListClaims,
    /// The statement itself, deferring an own-line run `dedent` levels through `line_suffix`:
    /// a non-block CLAUSE body, whose tail line stays open to the enclosing construct, where
    /// a later gap's deferred `//` would otherwise flush onto real text and weld.
    ClauseTail(u8),
}

impl TerminatorGap {
    /// The `line_suffix` dedent, for the emitters that only ever ask that.
    pub(in crate::printer) const fn clause_tail(self) -> Option<u8> {
        match self {
            Self::ClauseTail(dedent) => Some(dedent),
            Self::ListClaims => None,
        }
    }

    /// Whether the enclosing list claims the gap, so the statement emits a bare `;`.
    pub(in crate::printer) const fn list_claims(self) -> bool {
        matches!(self, Self::ListClaims)
    }
}

impl StatementContext {
    /// A statement joined into a `Program` or `BlockStatement` list.
    pub(in crate::printer) const PROGRAM_OR_BLOCK: Self = Self {
        in_program_or_block: true,
        clause_tail_dedent: None,
        tail_reaches_list: true,
    };
    /// A statement list whose container is not one of those AST nodes
    /// (`SwitchCase`, `StaticBlock`, `TSModuleBlock`).
    pub(in crate::printer) const OTHER_LIST: Self = Self {
        in_program_or_block: false,
        clause_tail_dedent: None,
        tail_reaches_list: true,
    };

    /// The context for a non-block clause BODY built from `self`'s position.
    ///
    /// `continues` — the construct emits more on the line the body's tail flushes at
    /// (an `if` with an `else`, a do-while's `while`), so the flush is that
    /// construct's own gap break; otherwise the tail falls through to whatever
    /// flushes `self`'s. `indented` — this body site wraps the body in one `indent`
    /// (prettier's `adjustClause`); an inline body (an `else`'s inline arm) adds
    /// none. The dedent must mirror the wraps exactly: a collapsed clause CHAIN
    /// stacks its indents even though no break renders them, and the freed comment
    /// settles at the flushing construct's level, not the innermost body's.
    pub(in crate::printer) fn clause_body(self, continues: bool, indented: bool) -> Self {
        let base = if continues {
            0
        } else {
            self.clause_tail_dedent.unwrap_or(0)
        };
        Self {
            in_program_or_block: false,
            clause_tail_dedent: Some(base.saturating_add(u8::from(indented))),
            tail_reaches_list: self.tail_reaches_list && !continues,
        }
    }

    /// The context for a labeled statement's BODY: a label adds no indent and ends
    /// with its body, so the body's tail is the label's tail — only the
    /// directive-prologue fact changes (a labeled string is never a directive).
    pub(in crate::printer) fn labeled_body(self) -> Self {
        Self {
            in_program_or_block: false,
            ..self
        }
    }

    /// The clause-tail deferral: `Some(dedent)` when the `;`-terminator gap's
    /// own-line run must ride in `line_suffix` docs, dedented this many levels.
    /// The `line_suffix` dedent alone — for the terminator emitters whose node ALWAYS keeps
    /// its own gap (a type alias, a `declare function`, a module declaration: kinds prettier's
    /// `nodeTypesWithContentEnd` does not list), so the list-claims answer cannot arise and
    /// the wider [`Self::terminator_gap`] would say nothing they can use.
    pub(in crate::printer) fn clause_tail(self) -> Option<u8> {
        self.clause_tail_dedent
    }

    /// This container's [`TerminatorGap`]. A statement `build_statement_doc` reaches from a
    /// list walk is always a list member; the embedded entry points do not go through a
    /// `StatementContext` at all, and pass no terminator at all — their host closes with a
    /// delimiter of its own and owns both the `;` and the gap before it
    /// (`Printer::build_embedded_variable_declaration_doc`).
    pub(in crate::printer) fn terminator_gap(self) -> TerminatorGap {
        match self.clause_tail_dedent {
            // A list member: the gap is the list's, as prettier's `__contentEnd` says.
            None => TerminatorGap::ListClaims,
            // A clause BODY whose tail is the enclosing list member's tail. The gap sits
            // between two list members like any other, so it takes the list's own seam —
            // which is what puts a block comment on the next statement's line
            // (`if (cond) expr⏎/* c */;⏎fn();` → `if (cond) expr;⏎/* c */ fn();`, the same
            // answer the plain statement list gives and the one prettier gives here). The
            // clause-tail deferral is for a tail that stays OPEN to its construct; this one
            // does not, and keeping the node-owned split gave one authoring two answers
            // depending on whether a clause bore it.
            //
            // ⚠️ [`Printer::statement_hands_off_terminator_gap`] is the other end of this
            // rule and walks to the SAME body. The two must agree exactly: both claiming is
            // a DOUBLE-PRINT, neither claiming a DROP.
            Some(_) if self.tail_reaches_list => TerminatorGap::ListClaims,
            Some(dedent) => TerminatorGap::ClauseTail(dedent),
        }
    }
}

impl<'a> Printer<'a> {
    /// Build a Doc for a statement. `ctx` is the container's two facts
    /// ([`StatementContext`]); only the arms whose statement ends in a
    /// `;`-terminator gap (and the clause-bearing control flow that must thread it)
    /// consult it.
    pub(super) fn build_statement_doc(
        &self,
        statement: &Statement<'_>,
        ctx: StatementContext,
    ) -> DocId {
        tsv_lang::fanout_probe::build(
            tsv_lang::fanout_probe::Build::Statement,
            &statement.kind,
            || (statement.span.start, statement.span.end),
        );
        // A statement ends its own line, so a run its value deferred out of a stripped
        // pair flushes there, never at an enclosing list's comma.
        let outer = self.enclosing_statement.replace(Some(statement.span));
        let built =
            self.with_line_ending_construct(|| self.build_statement_doc_uncounted(statement, ctx));
        self.enclosing_statement.set(outer);
        built
    }

    fn build_statement_doc_uncounted(
        &self,
        statement: &Statement<'_>,
        ctx: StatementContext,
    ) -> DocId {
        let d = self.d();
        match &statement.kind {
            StatementKind::ExpressionStatement(stmt) => {
                self.build_expression_statement_doc(stmt, statement.span, ctx)
            }
            StatementKind::VariableDeclaration(decl) => {
                self.build_variable_declaration_doc(decl, Some(ctx.terminator_gap()))
            }
            StatementKind::TSTypeAliasDeclaration(decl) => {
                self.build_type_alias_declaration_doc(decl, statement.span, ctx.clause_tail())
            }
            StatementKind::ReturnStatement(ret) => {
                self.build_return_statement_doc(ret, statement.span, ctx.terminator_gap())
            }
            // A statement-position block (bare `{ }`, a labeled block's body, or a
            // block nested directly in another block) expands its empty form to `{\n}`,
            // matching prettier. Only control-flow *bodies* (while/for/do/catch) and
            // function/class bodies collapse to `{}`, and those are built by their own
            // parents — never through this dispatch.
            StatementKind::BlockStatement(block) => {
                self.build_block_statement_expand_empty_doc(block)
            }
            StatementKind::FunctionDeclaration(decl) => self.build_function_declaration_doc(decl),
            StatementKind::ClassDeclaration(decl) => self.build_class_declaration_doc(decl),
            StatementKind::ExportNamedDeclaration(decl) => {
                self.build_export_named_declaration_doc(decl, statement.span)
            }
            StatementKind::ExportDefaultDeclaration(decl) => {
                self.build_export_default_declaration_doc(decl, statement.span)
            }
            StatementKind::ExportAllDeclaration(decl) => {
                self.build_export_all_declaration_doc(decl, statement.span)
            }
            StatementKind::TSExportAssignment(decl) => {
                self.build_export_assignment_doc(decl, statement.span)
            }
            StatementKind::TSNamespaceExportDeclaration(decl) => {
                self.build_namespace_export_declaration_doc(decl, statement.span)
            }
            StatementKind::ImportDeclaration(decl) => {
                self.build_import_declaration_doc(decl, statement.span)
            }
            StatementKind::TSImportEqualsDeclaration(decl) => {
                self.build_import_equals_declaration_doc(decl, statement.span)
            }
            // Control flow statements - use simple doc building
            StatementKind::IfStatement(stmt) => {
                self.build_if_statement_doc(stmt, statement.span, ctx)
            }
            StatementKind::ForStatement(stmt) => {
                self.build_for_statement_doc(stmt, statement.span, ctx)
            }
            StatementKind::ForInStatement(stmt) => {
                self.build_for_in_statement_doc(stmt, statement.span, ctx)
            }
            StatementKind::ForOfStatement(stmt) => {
                self.build_for_of_statement_doc(stmt, statement.span, ctx)
            }
            StatementKind::WhileStatement(stmt) => {
                self.build_while_statement_doc(stmt, statement.span, ctx)
            }
            StatementKind::DoWhileStatement(stmt) => {
                self.build_do_while_statement_doc(stmt, statement.span, ctx)
            }
            StatementKind::WithStatement(stmt) => {
                self.build_with_statement_doc(stmt, statement.span, ctx)
            }
            StatementKind::SwitchStatement(stmt) => {
                self.build_switch_statement_doc(stmt, statement.span)
            }
            StatementKind::TryStatement(stmt) => self.build_try_statement_doc(stmt, statement.span),
            StatementKind::ThrowStatement(stmt) => {
                self.build_throw_statement_doc(stmt, statement.span, ctx.terminator_gap())
            }
            StatementKind::BreakStatement(stmt) => {
                self.build_break_statement_doc(stmt, statement.span, ctx.terminator_gap())
            }
            StatementKind::ContinueStatement(stmt) => {
                self.build_continue_statement_doc(stmt, statement.span, ctx.terminator_gap())
            }
            StatementKind::LabeledStatement(stmt) => self.build_labeled_statement_doc(stmt, ctx),
            StatementKind::EmptyStatement(_) => d.text(";"),
            StatementKind::DebuggerStatement(_) => self.build_bare_keyword_terminator_doc(
                "debugger",
                statement.span,
                ctx.terminator_gap(),
            ),
            StatementKind::TSInterfaceDeclaration(decl) => {
                self.build_interface_declaration_doc(decl)
            }
            StatementKind::TSDeclareFunction(decl) => {
                self.build_declare_function_doc(decl, ctx.clause_tail())
            }
            StatementKind::TSEnumDeclaration(decl) => {
                self.build_enum_declaration_doc(decl, statement.span)
            }
            StatementKind::TSModuleDeclaration(decl) => {
                self.build_module_declaration_doc(decl, ctx.clause_tail())
            }
        }
    }

    /// Build a Doc for an expression statement: the value
    /// ([`Self::build_expression_statement_value_doc`], or the verbatim text of a
    /// directive-prologue entry) plus the `;` and the comments bound to it.
    fn build_expression_statement_doc(
        &self,
        stmt: &ast::ExpressionStatement<'_>,
        span: Span,
        ctx: StatementContext,
    ) -> DocId {
        let d = self.d();

        let expr_end = stmt.expression.span().end;
        // The value reports where the terminator gap opens — past a grouping `)` it retained
        // and emitted the gap up to, or back at the start of a trailing region it handed over
        // — because that region is then the VALUE's share, or the terminator's. Asking the
        // source a second time here instead would DROP whatever the value declined to claim,
        // or print twice what it handed over: the two must be one answer.
        let (value_doc, gap_start) = if stmt.is_directive {
            // The directive builder makes the claim itself (it is a reassembly), so it must
            // not also be wrapped below.
            (self.build_directive_doc(stmt), expr_end)
        } else {
            // A MULTI-LINE block the expression OWNS is claimed HERE rather than by the
            // innermost node its token begins, so it prints outside the expression's own
            // group and cannot force it broken
            // ([`Printer::build_value_with_outermost_owned_comment`]). The statement's
            // leading run belongs to the statement LIST, which emits it on the **to emit**
            // axis and so never sees the owned member — this seam owes only that one comment
            // (`syntax/comments/statement_leading_multiline_block_flat`).
            let mut gap_start = expr_end;
            let doc = self.build_value_with_outermost_owned_comment(stmt.expression, || {
                let (doc, start) =
                    self.build_expression_statement_value_doc(stmt, span, ctx.in_program_or_block);
                gap_start = start;
                doc
            });
            (doc, gap_start)
        };

        // Comments between the expression and the `;`, with the `;` bound to the
        // statement: a same-line block trails *after* it (`fn() /* c */;` → `fn(); /* c */`,
        // prettier 3.9), a same-line line trails after it via `line_suffix`
        // (`fn() // c` → `fn(); // c`), an own-line comment drops to its own line after it
        // (emitting a line comment before the `;` would swallow it). See
        // `push_semicolon_with_gap_comments`.
        if self.semicolon_gap_is_bare(gap_start, span.end) {
            return d.concat(&[value_doc, d.text(";")]);
        }
        let mut parts: DocBuf = smallvec![value_doc];
        if gap_start < expr_end
            && ctx.clause_tail().is_some()
            && self.push_handed_clause_semicolon(
                &mut parts,
                Span::new(gap_start, expr_end),
                span.end,
                ctx.terminator_gap(),
            )
        {
            return d.concat(&parts);
        }
        self.push_statement_semicolon(&mut parts, gap_start, span.end, ctx.terminator_gap());
        d.concat(&parts)
    }

    /// The `;` of a CLAUSE body whose value handed its trailing region to the terminator
    /// ([`Printer::with_statement_tail`]): the run the node claims rides in `line_suffix`
    /// docs behind the `;`, and `true` says so.
    ///
    /// The reparse reads that region as the terminator gap's, past the `;` — printed by the
    /// enclosing list after the whole clause-bearing statement, or by the construct's own
    /// gap after the body (an `if`'s `else` gap) — outside the clause's group either way.
    /// Printed inline here instead, a multi-line block or an own-line comment would force
    /// the clause's group open on this pass alone (`if (a)⏎\tx = b; /* m⏎n */` for
    /// `if (a) x = (b /* m⏎n */);`). Deferred, it lands where the reparse puts it. Only
    /// while no block prints inline behind
    /// the statement on its line: one would land ahead of a deferred run, so the gap keeps
    /// the inline split (`false`) and its order.
    fn push_handed_clause_semicolon(
        &self,
        parts: &mut DocBuf,
        handed: Span,
        span_end: u32,
        gap: TerminatorGap,
    ) -> bool {
        // Past the value's own end the gap holds the author's own spelling of the comments
        // after the `)`; the node's split answers those, so any of them keeps it.
        let claim_end = self.node_terminator_claim_end(handed.start, span_end, gap);
        if self.has_comments_to_emit_between(handed.end, claim_end) {
            return false;
        }
        // A block that prints inline behind the statement on its last line — the list's
        // share of the gap, or one the construct's own gap takes right after an ASI statement
        // (`if (c) y = (b /* c */) /* d */⏎else z();`). A `//` there defers too, behind this
        // run, so it keeps its order.
        let bytes_len = self.source.len() as u32;
        let next = self
            .next_significant_byte(span_end, bytes_len)
            .map_or(bytes_len, |pos| pos as u32);
        if self.comments_to_emit_between(claim_end, next).any(|c| {
            c.is_block
                && (c.span.start < span_end || !self.has_newline_between(span_end, c.span.start))
        }) {
            return false;
        }
        // The run is the value's same-line blocks alone ([`Printer::with_statement_tail`]),
        // each deferred behind the `;`.
        parts.push(self.d().text(";"));
        for comment in self.comments_to_emit_between(handed.start, handed.end) {
            parts.push(self.build_trailing_line_comment_doc(comment));
        }
        true
    }

    /// A directive-prologue entry's VALUE — the literal's own source bytes, plus the
    /// comment glued to them.
    ///
    /// Directives are exact code-unit sequences; `format_directive` mirrors Prettier's
    /// `printDirective` (swap the outer quote to single only when the content has no
    /// quote, else verbatim). Never parenthesized, and never routed through
    /// `build_expression_doc` — `format_string_literal` re-quotes and re-escapes, which
    /// is precisely what a directive must not do.
    ///
    /// Which makes this a **reassembly** in the sense of `docs/comments.md` hazard 1: the
    /// glued block the parser bound to the string token (`owned_by_node`) is skipped by
    /// every gap emitter, so nothing else can print it and the claim belongs here. A
    /// directive is a bare string literal by grammar, so the literal's span start is the
    /// statement's first printed byte — the left-edge fact
    /// [`Printer::prepend_owned_leading_comment_at`] asks each caller for.
    fn build_directive_doc(&self, stmt: &ast::ExpressionStatement<'_>) -> DocId {
        let d = self.d();
        let span = stmt.expression.span();
        let text = d.text_pooled(&format_directive(span.extract(self.source)));
        self.prepend_owned_leading_comment_at(span.start, text)
    }

    /// The expression statement's expression doc proper, under the statement's context
    /// flags: the nested-paren target and the chain's expression-statement merge rule.
    #[inline]
    fn build_expression_statement_expr_doc(
        &self,
        stmt: &ast::ExpressionStatement<'_>,
        nested_paren: Option<Span>,
    ) -> DocId {
        self.expr_stmt_paren_target.set(nested_paren);

        self.is_expression_statement.set(true);
        let doc = match &stmt.expression {
            // One of prettier's two `printSequenceExpression` parent arms (the `for`
            // head is the other): the operands after the first take a continuation
            // indent. Claimed here rather than in the expression dispatch because
            // that is where the parent is known — a sequence nested deeper inside
            // this statement is an ordinary operand and keeps the default layout.
            Expression {
                span,
                kind: ExpressionKind::SequenceExpression(seq),
            } => self.build_sequence_doc(seq, *span, SeqLayout::Indented),
            // Everything else takes the ordinary dispatch, which is where a
            // binary picks up the continuation indent an `ExpressionStatement`
            // parent gets from prettier's fall-through (a labeled statement's
            // body IS an expression statement, so it rides along).
            expr => self.build_expression_doc(expr),
        };
        self.is_expression_statement.set(false);
        self.expr_stmt_paren_target.set(None);
        doc
    }

    /// The non-directive expression statement's VALUE — the expression plus whatever parens
    /// this print gives it (required, directive-avoiding, or a source pair it keeps) and the
    /// comments only those parens can emit. The statement's `;` and the comments bound to it
    /// stay with the caller.
    ///
    /// `in_program_or_block` is threaded from [`Printer::build_statement_doc`] for the
    /// "avoid becoming a directive" rule (see [`Printer::needs_avoid_directive_parens`]).
    ///
    /// Returns the doc and where the caller's terminator gap opens: past the grouping `)`
    /// this print RETAINED and emitted the expression→`)` gap inside, or, for the plain
    /// statement, at the start of the trailing region the value handed to the terminator
    /// ([`Printer::with_statement_tail`]) — else the expression's end. Reported rather than
    /// re-derived, because only the branch that ran knows what it claimed; re-asking the
    /// source would drop what it declined to claim, or double what it handed over.
    fn build_expression_statement_value_doc(
        &self,
        stmt: &ast::ExpressionStatement<'_>,
        span: Span,
        in_program_or_block: bool,
    ) -> (DocId, u32) {
        let d = self.d();
        // A `//` the author wrote inside the value's own grouping parens keeps those
        // parens: the terminator gap would defer it past the `)` and the `;`, onto a line
        // that may already hold a `//`, where the two MERGE into one comment. The
        // statement's other value positions (declarator initializer, assignment RHS,
        // ternary branch) already answer this way through the shared shell builder.
        let expr_end = stmt.expression.span().end;
        let shell_close = self.value_paren_line_comment_close(expr_end, span.end);
        // A comment between a source `(` and the expression (`(// c⏎ expr)` /
        // `(/* c */⏎ expr)` — e.g. a bare parenthesized decorated class
        // expression) is preserved inside the parens, breaking them open; the flat
        // `(`/`)` wrap below would drop it, since there is nowhere on one line for
        // a comment the expression doesn't own to go. prettier hoists the comment
        // before `(` — a divergence (`expression_statement_paren_kept_comment`,
        // `decorated_expr_open_paren_comment`).
        let expr_start = stmt.expression.span().start;
        let source_paren = self.statement_opens_with_paren(span, expr_start);
        // An own-line directive in that `(`→expression gap freezes the expression
        // (Rule A). Only a SOURCE paren opens the gap: without one the directive
        // leads the statement, where the statement list's own rule already claims it.
        let frozen = source_paren
            .then(|| self.value_head_frozen_span(span.start + 1, stmt.expression.span()))
            .flatten();

        // Parens required for correctness (object expressions, object pattern
        // assignments) OR to avoid a bare string statement being read as a
        // directive (recomputed fresh, not preserved from source).
        let mut needs_parens = self
            .needs_parens(stmt.expression, ParenContext::ExpressionStatement)
            || self.needs_avoid_directive_parens(stmt, in_program_or_block);

        // When the whole expression isn't wrapped, a nested leftmost
        // object/function/class still needs parens around itself
        // (`(class {}).foo`, `({}).foo`, `(class {}) + 1`).
        let nested_paren = if needs_parens {
            None
        } else {
            let text = LeftmostText::of_value(frozen.is_some());
            self.expr_stmt_nested_paren_target(stmt.expression, text)
        };
        // A frozen slice is verbatim, so the printer has no interior left to wrap:
        // the nested target's parens go around the WHOLE slice instead. Without the
        // widening `({ a: 1 }.b)` would print as `{ a: 1 }.b;`, which reparses as a
        // block — prettier's own output there does exactly that.
        needs_parens |= frozen.is_some() && nested_paren.is_some();

        // The `(`→expression gap, resolved in one place so the gate below and the two
        // emitters that can claim it cannot read different ranges.
        let paren_gap = || self.comments_to_emit_between(span.start + 1, expr_start);
        // Deliberately **to emit**, not on-page: this branch also *prints* the comments it
        // finds. A block glued to the *expression* is owned by it, rides inside its doc and
        // is skipped here — which is what keeps `(/* c */ expr)` flat.
        let paren_open_comments = needs_parens
            && source_paren
            && self.has_comments_to_emit_between(span.start + 1, expr_start);

        // Build the expression once — or not at all, when the freeze replaces its doc
        // with the verbatim slice. `is_expression_statement` is the context flag for chain
        // handling: it allows short identifier names to merge with the first call. The
        // matching node's doc builder consumes the (non-consuming, span-matched) paren
        // target and wraps itself; clear it afterward so it can't leak into a sibling
        // statement.
        // The plain statement IS its expression, so the `;` follows the value directly and
        // its gap may take over the value's trailing region ([`Printer::with_statement_tail`]);
        // a statement that wraps its value in parens of its own keeps the region inside them.
        let plain = !needs_parens && !source_paren && shell_close.is_none();
        let mut tail_gap_start = expr_end;
        let expr_doc = match frozen {
            Some(span) => self.build_frozen_expression_doc(stmt.expression, span),
            None => {
                let (doc, gap_start) = self
                    .with_statement_tail(plain.then_some(stmt.expression), || {
                        self.build_expression_statement_expr_doc(stmt, nested_paren)
                    });
                tail_gap_start = gap_start.unwrap_or(expr_end);
                doc
            }
        };

        // A parenthesized *decorated* class expression breaks its parens open and
        // indents the content (prettier): `(⏎\t@dec⏎\tclass {}⏎)`. The decorators
        // force the break; an undecorated `(class {})` / `(function () {})` stays
        // inline (flat `else` below).
        let decorated_class_expr = needs_parens
            && matches!(
                &stmt.expression.kind,
                ExpressionKind::ClassExpression(c) if class_expr_has_decorators(c)
            );

        // Which grouping `)` this print retains and emits the expression→`)` gap inside.
        // The two paren-KEEPING branches below claim it; the decorated one does not (its
        // layout has no seam for a trailing run), so there the terminator gap keeps it.
        // The plain statement — no required, directive-avoiding, or source paren — IS its
        // expression: the branch below would push it alone and `concat` would hand it
        // back, so the buffer is skipped outright (the common case by a wide margin).
        if !needs_parens && !source_paren && shell_close.is_none() {
            return (expr_doc, tail_gap_start);
        }
        let mut parts = DocBuf::new();
        let mut consumed_close = None;
        if paren_open_comments {
            // The parens break open around the run. Only the first `hardline` is
            // the site's own — the run's internal separators come from the shared
            // leading-comment emitter, so a run the author glued onto one line
            // stays glued and a blank line between two comments survives.
            let mut inner: DocBuf = smallvec![d.hardline()];
            self.push_leading_comment_run(
                &mut inner,
                paren_gap(),
                expr_start,
                LeadingGlue::Adjacent,
            );
            inner.push(expr_doc);
            // The parens are already open, so a `//` before the `)` stays inside them
            // rather than deferring past the `;` — the same claim the retained-shell
            // branch below makes, at the branch that got here for the leading run.
            consumed_close = self.push_retained_gap_trailing_run(&mut inner, expr_end, shell_close);
            parts.push(d.text("("));
            parts.push(d.indent(d.concat(&inner)));
            parts.push(d.hardline());
            parts.push(d.text(")"));
        } else if decorated_class_expr {
            parts.push(self.build_break_open_parens(expr_doc));
        } else if let Some(close) = shell_close {
            // The value's authored parens are RETAINED around a `//` that would otherwise
            // escape them. This pair is the position's pair — a shell whose `(` leads the
            // statement already discharges what `needs_parens` wraps for, so adding the
            // clarity pair too would double it. The leading run is the plain branch's,
            // unchanged: a dropped source `(` is what strands it, and here the `(` is
            // kept, so only the expression's own owned comments ride inside `expr_doc`.
            let mut inner = DocBuf::new();
            if !needs_parens && source_paren {
                self.push_leading_comment_run(
                    &mut inner,
                    paren_gap(),
                    expr_start,
                    LeadingGlue::Adjacent,
                );
            }
            inner.push(expr_doc);
            consumed_close = self.push_retained_gap_trailing_run(&mut inner, expr_end, Some(close));
            parts.push(d.text("("));
            parts.push(d.indent_hardline(d.concat(&inner)));
            parts.push(d.hardline());
            parts.push(d.text(")"));
        } else {
            if needs_parens {
                parts.push(d.text("("));
            } else if source_paren {
                // The statement had a source `(` that this print DROPS as
                // redundant. A comment inside it then has no emitter left: the
                // statement-list gap ends at the `(`, and the expression's own doc
                // starts past the comment — so it must be emitted here or it is
                // lost. It leads the statement, which is where prettier hoists it
                // too. (An owned/glued comment rides inside `expr_doc` and is
                // skipped by the emit iterator; the paren-KEPT cases are the two
                // branches above.)
                self.push_leading_comment_run(
                    &mut parts,
                    paren_gap(),
                    expr_start,
                    LeadingGlue::Adjacent,
                );
            }
            parts.push(expr_doc);
            if needs_parens {
                parts.push(d.text(")"));
            }
        }
        let gap_start =
            consumed_close.map_or(expr_end, |close| Self::past_grouping_close(close, span.end));
        (d.concat(&parts), gap_start)
    }

    /// Emit the expression→`)` gap's trailing run inside a retained grouping pair, and
    /// report the `)` claimed — `None` when there is nothing to retain, which leaves the
    /// gap to the caller's terminator scan.
    ///
    /// One seam so the two paren-keeping branches of
    /// [`Self::build_expression_statement_value_doc`] cannot claim different ranges: a
    /// region claimed here and re-read there is a DOUBLE-PRINT, and one claimed at
    /// neither is a DROP.
    fn push_retained_gap_trailing_run(
        &self,
        inner: &mut DocBuf,
        expr_end: u32,
        close: Option<u32>,
    ) -> Option<u32> {
        let close = close?;
        self.push_anchored_trailing_run(inner, expr_end, close, RunLeadingBlank::Keep);
        Some(close)
    }

    /// Build a Doc for a return statement. `gap` reaches both terminator
    /// gaps — the bare form's keyword→`;` gap and the argument form's operand→`;`
    /// gap (threaded through the restricted production's own emitters).
    fn build_return_statement_doc(
        &self,
        ret: &ast::ReturnStatement<'_>,
        span: Span,
        gap: TerminatorGap,
    ) -> DocId {
        let Some(arg) = &ret.argument else {
            // No argument: a bare keyword closed by `;` (interior comments handled
            // there) — `return; /* c */` etc.
            return self.build_bare_keyword_terminator_doc("return", span, gap);
        };

        self.build_keyword_argument_doc("return", span.start, span.end, arg, gap)
    }

    /// Build a Doc for a "bare" keyword-terminator statement — a keyword that takes
    /// no operand and is closed by `;`: `debugger`, the no-arg `return`, and a
    /// label-less `break`/`continue`.
    ///
    /// None has a `[no LineTerminator]` issue at this point (the operand/label is
    /// absent), so when an explicit `;` follows on a later line the parser scans
    /// forward to it and the `;` becomes the statement's terminator — any comment
    /// between the keyword and that `;` sits *inside* the statement span (e.g.
    /// `debugger\n\n// c\n;` → span swallows `// c` and the `;`). Emitting just
    /// `keyword;` would drop them. Route the interior gap through
    /// `push_semicolon_with_gap_comments`: a same-line block trails after `;`
    /// (`debugger; /* c */`), a same-line line floats after `;` via `line_suffix`, an
    /// own-line comment drops to its own line (preceding blank line preserved). `span`
    /// is the full statement span — its end is the `;`, or the keyword end under ASI
    /// when there is no explicit `;` (then the interior range is empty).
    pub(in crate::printer::statements) fn build_bare_keyword_terminator_doc(
        &self,
        keyword: &'static str,
        span: Span,
        gap: TerminatorGap,
    ) -> DocId {
        let d = self.d();
        let keyword_end = span.start + keyword.len() as u32;
        let mut parts: DocBuf = smallvec![d.text(keyword)];
        self.push_statement_semicolon(&mut parts, keyword_end, span.end, gap);
        d.concat(&parts)
    }
}